diff --git a/.travis.yml b/.travis.yml
index 03a0186c2d..758a9ff39a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -62,6 +62,7 @@ addons:
- boost
- flex
- bison
+ - llvm
- openmpi
- python@3
update: true
@@ -105,7 +106,11 @@ install:
script:
- echo "------- Build, Test and Install -------"
- mkdir build && pushd build
- - cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+ cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -DNMODL_ENABLE_LLVM=ON -DLLVM_DIR=`brew --prefix llvm`/lib/cmake/llvm;
+ else
+ cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -DNMODL_ENABLE_LLVM=OFF;
+ fi
- make -j 2;
if [ $? -ne 0 ]; then
make VERBOSE=1;
@@ -121,11 +126,11 @@ 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;
+ echo "------- Skip 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
#=============================================================================
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b79743b1e4..29055452d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -147,6 +147,7 @@ find_python_module(yaml 3.12 REQUIRED)
# =============================================================================
if(NMODL_ENABLE_LLVM)
include(LLVMHelper)
+ include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(-DNMODL_LLVM_BACKEND)
endif()
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 3a2618fcdd..0e7b9b70ed 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -113,8 +113,7 @@ jobs:
- checkout: self
submodules: True
- script: |
- brew install flex cmake python@3
- brew install bison llvm
+ brew install flex bison cmake python@3 llvm
python3 -m pip install -U pip setuptools
python3 -m pip install --user 'Jinja2>=2.9.3' 'PyYAML>=3.13' pytest 'sympy>=1.3,<1.6'
displayName: 'Install Dependencies'
@@ -132,6 +131,7 @@ jobs:
displayName: 'Build and Run Tests'
- job: 'manylinux_wheels'
timeoutInMinutes: 45
+ condition: eq(1,2)
pool:
vmImage: 'ubuntu-18.04'
strategy:
@@ -177,6 +177,7 @@ jobs:
- template: ci/upload-wheels.yml
- job: 'macos_wheels'
timeoutInMinutes: 45
+ condition: eq(1,2)
pool:
vmImage: 'macOS-10.15'
strategy:
diff --git a/cmake/LLVMHelper.cmake b/cmake/LLVMHelper.cmake
index dbd29c92b6..982af48660 100644
--- a/cmake/LLVMHelper.cmake
+++ b/cmake/LLVMHelper.cmake
@@ -1,15 +1,17 @@
# =============================================================================
# LLVM/Clang needs to be linked with either libc++ or libstdc++
# =============================================================================
+
+find_package(LLVM REQUIRED CONFIG)
+
+# include LLVM header and core library
+llvm_map_components_to_libnames(LLVM_LIBS_TO_LINK core)
+set(CMAKE_REQUIRED_INCLUDES ${LLVM_INCLUDE_DIRS})
+set(CMAKE_REQUIRED_LIBRARIES ${LLVM_LIBS_TO_LINK})
+
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NMODL_ENABLE_LLVM)
- find_package(LLVM REQUIRED CONFIG)
include(CheckCXXSourceCompiles)
- # test by including LLVM header and core library
- llvm_map_components_to_libnames(LLVM_LIBS_TO_LINK core)
- set(CMAKE_REQUIRED_INCLUDES ${LLVM_INCLUDE_DIRS})
- set(CMAKE_REQUIRED_LIBRARIES ${LLVM_LIBS_TO_LINK})
-
# simple code to test LLVM library linking
set(CODE_TO_TEST
"
diff --git a/setup.py b/setup.py
index 281192bb0e..0a622593ba 100644
--- a/setup.py
+++ b/setup.py
@@ -94,7 +94,7 @@ def _config_exe(exe_name):
]
-cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable, "-DNMODL_ENABLE_LLVM=OFF"]
+cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable, "-DNMODL_ENABLE_LLVM=OFF", "-DNMODL_ENABLE_PYTHON_BINDINGS=ON"]
if "bdist_wheel" in sys.argv:
cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE")
diff --git a/src/codegen/llvm/codegen_llvm_visitor.cpp b/src/codegen/llvm/codegen_llvm_visitor.cpp
index 494d5fd1f3..b8b3778e86 100644
--- a/src/codegen/llvm/codegen_llvm_visitor.cpp
+++ b/src/codegen/llvm/codegen_llvm_visitor.cpp
@@ -10,46 +10,153 @@
#include "visitors/visitor_utils.hpp"
#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
-#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
-#include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
+#include "llvm/IR/ValueSymbolTable.h"
namespace nmodl {
namespace codegen {
-// LLVM code generator objects
-using namespace llvm;
-static std::unique_ptr TheContext;
-static std::unique_ptr TheModule;
-static std::unique_ptr> Builder;
-static std::map NamedValues;
+/****************************************************************************************/
+/* Overloaded visitor routines */
+/****************************************************************************************/
-void CodegenLLVMVisitor::visit_statement_block(const ast::StatementBlock& node) {
- logger->info("CodegenLLVMVisitor : visiting statement block");
- node.visit_children(*this);
- // TODO : code generation for new block scope
+void CodegenLLVMVisitor::visit_binary_expression(const ast::BinaryExpression& node) {
+ const auto& op = node.get_op().get_value();
+
+ // Process rhs first, since lhs is handled differently for assignment and binary
+ // operators.
+ node.get_rhs()->accept(*this);
+ llvm::Value* rhs = values.back();
+ values.pop_back();
+ if (op == ast::BinaryOp::BOP_ASSIGN) {
+ auto var = dynamic_cast(node.get_lhs().get());
+ if (!var) {
+ throw std::runtime_error("Error: only VarName assignment is currently supported.\n");
+ }
+ llvm::Value* alloca = named_values[var->get_node_name()];
+ builder.CreateStore(rhs, alloca);
+ return;
+ }
+
+ node.get_lhs()->accept(*this);
+ llvm::Value* lhs = values.back();
+ values.pop_back();
+ llvm::Value* result;
+
+ // \todo: Support other binary operators
+ switch (op) {
+#define DISPATCH(binary_op, llvm_op) \
+ case binary_op: \
+ result = llvm_op(lhs, rhs); \
+ values.push_back(result); \
+ break;
+
+ DISPATCH(ast::BinaryOp::BOP_ADDITION, builder.CreateFAdd);
+ DISPATCH(ast::BinaryOp::BOP_DIVISION, builder.CreateFDiv);
+ DISPATCH(ast::BinaryOp::BOP_MULTIPLICATION, builder.CreateFMul);
+ DISPATCH(ast::BinaryOp::BOP_SUBTRACTION, builder.CreateFSub);
+
+#undef DISPATCH
+ }
}
-void CodegenLLVMVisitor::visit_procedure_block(const ast::ProcedureBlock& node) {
- logger->info("CodegenLLVMVisitor : visiting {} procedure", node.get_node_name());
-
- // print position, nmodl and json form as
- /*
- logger->info("Location {} \n NMODL {} \n JSON : {} \n",
- node.get_token()->position(),
- to_nmodl(node),
- to_json(node));
- */
- node.visit_children(*this);
- // TODO : code generation for procedure block
+void CodegenLLVMVisitor::visit_boolean(const ast::Boolean& node) {
+ const auto& constant = llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context),
+ node.get_value());
+ values.push_back(constant);
+}
+
+void CodegenLLVMVisitor::visit_double(const ast::Double& node) {
+ const auto& constant = llvm::ConstantFP::get(llvm::Type::getDoubleTy(*context),
+ node.get_value());
+ values.push_back(constant);
+}
+
+void CodegenLLVMVisitor::visit_integer(const ast::Integer& node) {
+ const auto& constant = llvm::ConstantInt::get(llvm::Type::getInt32Ty(*context),
+ node.get_value());
+ values.push_back(constant);
+}
+
+void CodegenLLVMVisitor::visit_local_list_statement(const ast::LocalListStatement& node) {
+ for (const auto& variable: node.get_variables()) {
+ // LocalVar always stores a Name.
+ auto name = variable->get_node_name();
+ llvm::Type* var_type = llvm::Type::getDoubleTy(*context);
+ llvm::Value* alloca = builder.CreateAlloca(var_type, /*ArraySize=*/nullptr, name);
+ named_values[name] = alloca;
+ }
}
void CodegenLLVMVisitor::visit_program(const ast::Program& node) {
node.visit_children(*this);
- result_code = "Hello World";
+ // Keep this for easier development (maybe move to debug mode later).
+ std::cout << print_module();
+}
+
+void CodegenLLVMVisitor::visit_procedure_block(const ast::ProcedureBlock& node) {
+ const auto& name = node.get_node_name();
+ const auto& parameters = node.get_parameters();
+
+ // The procedure parameters are doubles by default.
+ std::vector arg_types;
+ for (size_t i = 0, e = parameters.size(); i < e; ++i)
+ arg_types.push_back(llvm::Type::getDoubleTy(*context));
+ llvm::Type* return_type = llvm::Type::getVoidTy(*context);
+
+ llvm::Function* proc =
+ llvm::Function::Create(llvm::FunctionType::get(return_type, arg_types, /*isVarArg=*/false),
+ llvm::Function::ExternalLinkage,
+ name,
+ *module);
+
+ llvm::BasicBlock* body = llvm::BasicBlock::Create(*context, /*Name=*/"", proc);
+ builder.SetInsertPoint(body);
+
+ // First, allocate parameters on the stack and add them to the symbol table.
+ unsigned i = 0;
+ for (auto& arg: proc->args()) {
+ std::string arg_name = parameters[i++].get()->get_node_name();
+ llvm::Value* alloca = builder.CreateAlloca(arg.getType(), /*ArraySize=*/nullptr, arg_name);
+ arg.setName(arg_name);
+ builder.CreateStore(&arg, alloca);
+ named_values[arg_name] = alloca;
+ }
+
+ const auto& statements = node.get_statement_block()->get_statements();
+ for (const auto& statement: statements) {
+ // \todo: Support other statement types.
+ if (statement->is_local_list_statement() || statement->is_expression_statement())
+ statement->accept(*this);
+ }
+
+ values.clear();
+ // \todo: Add proper support for the symbol table.
+ named_values.clear();
+}
+
+void CodegenLLVMVisitor::visit_unary_expression(const ast::UnaryExpression& node) {
+ ast::UnaryOp op = node.get_op().get_value();
+ node.get_expression()->accept(*this);
+ llvm::Value* value = values.back();
+ values.pop_back();
+ if (op == ast::UOP_NEGATION) {
+ llvm::Value* result = builder.CreateFNeg(value);
+ values.push_back(result);
+ } else {
+ // Support only `double` operators for now.
+ throw std::runtime_error("Error: unsupported unary operator\n");
+ }
+}
+
+void CodegenLLVMVisitor::visit_var_name(const ast::VarName& node) {
+ llvm::Value* var = builder.CreateLoad(named_values[node.get_node_name()]);
+ values.push_back(var);
}
} // namespace codegen
diff --git a/src/codegen/llvm/codegen_llvm_visitor.hpp b/src/codegen/llvm/codegen_llvm_visitor.hpp
index 5b0ad3a968..5a288d9836 100644
--- a/src/codegen/llvm/codegen_llvm_visitor.hpp
+++ b/src/codegen/llvm/codegen_llvm_visitor.hpp
@@ -21,6 +21,10 @@
#include "utils/logger.hpp"
#include "visitors/ast_visitor.hpp"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+
namespace nmodl {
namespace codegen {
@@ -45,8 +49,18 @@ class CodegenLLVMVisitor: public visitor::ConstAstVisitor {
// Output directory for code generation
std::string output_dir;
- // result string for demo
- std::string result_code;
+ private:
+ std::unique_ptr context = std::make_unique();
+
+ std::unique_ptr module = std::make_unique(mod_filename, *context);
+
+ llvm::IRBuilder<> builder;
+
+ // Stack to hold visited values
+ std::vector values;
+
+ // Mappings for named values for lookups
+ std::map named_values;
public:
/**
@@ -57,15 +71,27 @@ class CodegenLLVMVisitor: public visitor::ConstAstVisitor {
*/
CodegenLLVMVisitor(const std::string& mod_filename, const std::string& output_dir)
: mod_filename(mod_filename)
- , output_dir(output_dir) {}
+ , output_dir(output_dir)
+ , builder(*context) {}
- void visit_statement_block(const ast::StatementBlock& node) override;
+ // Visitors
+ void visit_binary_expression(const ast::BinaryExpression& node) override;
+ void visit_boolean(const ast::Boolean& node) override;
+ void visit_double(const ast::Double& node) override;
+ void visit_integer(const ast::Integer& node) override;
+ void visit_local_list_statement(const ast::LocalListStatement& node) override;
void visit_procedure_block(const ast::ProcedureBlock& node) override;
void visit_program(const ast::Program& node) override;
+ void visit_unary_expression(const ast::UnaryExpression& node) override;
+ void visit_var_name(const ast::VarName& node) override;
- // demo method
- std::string get_code() const {
- return result_code;
+ // TODO: use custom printer here
+ std::string print_module() const {
+ std::string str;
+ llvm::raw_string_ostream os(str);
+ os << *module;
+ os.flush();
+ return str;
}
};
diff --git a/test/integration/mod/procedure.mod b/test/integration/mod/procedure.mod
index ebbc39f15a..4017b6a505 100644
--- a/test/integration/mod/procedure.mod
+++ b/test/integration/mod/procedure.mod
@@ -1,5 +1,10 @@
+NEURON {
+ SUFFIX procedure_test
+ THREADSAFE
+}
+
PROCEDURE hello_world() {
- print("Hello World")
+ printf("Hello World")
}
PROCEDURE simple_sum(x, y) {
@@ -20,7 +25,7 @@ PROCEDURE loop_function(v) {
LOCAL i
i = 0
WHILE(i < 10) {
- print("Hello World")
+ printf("Hello World")
i = i + 1
}
}
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 3ef32e39b5..02ab230ce0 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -94,9 +94,10 @@ target_link_libraries(
${NMODL_WRAPPER_LIBS})
if(NMODL_ENABLE_LLVM)
- add_executable(testcodegen visitor/main.cpp codegen/llvm.cpp)
+ include_directories(${LLVM_INCLUDE_DIRS})
+ add_executable(testllvm visitor/main.cpp codegen/llvm.cpp)
target_link_libraries(
- testcodegen
+ testllvm
visitor
symtab
lexer
@@ -106,7 +107,7 @@ if(NMODL_ENABLE_LLVM)
llvm_codegen
${NMODL_WRAPPER_LIBS}
${LLVM_LIBS_TO_LINK})
- set(CODEGEN_TEST testcodegen)
+ set(CODEGEN_TEST testllvm)
endif()
# =============================================================================
diff --git a/test/unit/codegen/llvm.cpp b/test/unit/codegen/llvm.cpp
index b6efe2f9ca..270ce97ec0 100644
--- a/test/unit/codegen/llvm.cpp
+++ b/test/unit/codegen/llvm.cpp
@@ -6,20 +6,21 @@
*************************************************************************/
#include
+#include
#include "ast/program.hpp"
+#include "codegen/llvm/codegen_llvm_visitor.hpp"
#include "parser/nmodl_driver.hpp"
#include "visitors/checkparent_visitor.hpp"
#include "visitors/inline_visitor.hpp"
#include "visitors/symtab_visitor.hpp"
-#include "codegen/llvm/codegen_llvm_visitor.hpp"
using namespace nmodl;
using namespace visitor;
using nmodl::parser::NmodlDriver;
//=============================================================================
-// Sample LLVM codegen test
+// Utility to get LLVM module as a string
//=============================================================================
std::string run_llvm_visitor(const std::string& text) {
@@ -31,21 +32,184 @@ std::string run_llvm_visitor(const std::string& text) {
codegen::CodegenLLVMVisitor llvm_visitor("unknown", ".");
llvm_visitor.visit_program(*ast);
- return llvm_visitor.get_code();
+ return llvm_visitor.print_module();
}
-SCENARIO("Running LLVM Codegen", "[visitor][llvm]") {
- GIVEN("Simple procedure with hello world message") {
+//=============================================================================
+// BinaryExpression and Double
+//=============================================================================
+
+SCENARIO("Binary expression", "[visitor][llvm]") {
+ GIVEN("Procedure with addition of its arguments") {
+ std::string nmodl_text = R"(
+ PROCEDURE add(a, b) {
+ LOCAL i
+ i = a + b
+ }
+ )";
+
+ THEN("variables are loaded and add instruction is created") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check the values are loaded correctly and added
+ std::regex rhs(R"(%1 = load double, double\* %b)");
+ std::regex lhs(R"(%2 = load double, double\* %a)");
+ std::regex res(R"(%3 = fadd double %2, %1)");
+ REQUIRE(std::regex_search(module_string, m, rhs));
+ REQUIRE(std::regex_search(module_string, m, lhs));
+ REQUIRE(std::regex_search(module_string, m, res));
+ }
+ }
+
+ GIVEN("Procedure with multiple binary operators") {
std::string nmodl_text = R"(
- PROCEDURE say_hello() {
- print("Hello World")
+ PROCEDURE multiple(a, b) {
+ LOCAL i
+ i = (a - b) / (a + b)
}
)";
- THEN("Hello world message is printed") {
- std::string expected = "Hello World";
- auto result = run_llvm_visitor(nmodl_text);
- REQUIRE(result == expected);
+ THEN("variables are processed from rhs first") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check rhs
+ std::regex rr(R"(%1 = load double, double\* %b)");
+ std::regex rl(R"(%2 = load double, double\* %a)");
+ std::regex x(R"(%3 = fadd double %2, %1)");
+ REQUIRE(std::regex_search(module_string, m, rr));
+ REQUIRE(std::regex_search(module_string, m, rl));
+ REQUIRE(std::regex_search(module_string, m, x));
+
+ // Check lhs
+ std::regex lr(R"(%4 = load double, double\* %b)");
+ std::regex ll(R"(%5 = load double, double\* %a)");
+ std::regex y(R"(%6 = fsub double %5, %4)");
+ REQUIRE(std::regex_search(module_string, m, lr));
+ REQUIRE(std::regex_search(module_string, m, ll));
+ REQUIRE(std::regex_search(module_string, m, y));
+
+ // Check result
+ std::regex res(R"(%7 = fdiv double %6, %3)");
+ REQUIRE(std::regex_search(module_string, m, res));
}
}
-}
\ No newline at end of file
+
+ GIVEN("Procedure with assignment") {
+ std::string nmodl_text = R"(
+ PROCEDURE assignment() {
+ LOCAL i
+ i = 2
+ }
+ )";
+
+ THEN("double constant is stored into i") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check store immediate is created
+ std::regex allocation(R"(%i = alloca double)");
+ std::regex assignment(R"(store double 2.0*e\+00, double\* %i)");
+ REQUIRE(std::regex_search(module_string, m, allocation));
+ REQUIRE(std::regex_search(module_string, m, assignment));
+ }
+ }
+}
+
+//=============================================================================
+// LocalList and LocalVar
+//=============================================================================
+
+SCENARIO("Local variable", "[visitor][llvm]") {
+ GIVEN("Procedure with some local variables") {
+ std::string nmodl_text = R"(
+ PROCEDURE local() {
+ LOCAL i, j
+ }
+ )";
+
+ THEN("local variables are allocated on the stack") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check stack allocations for i and j
+ std::regex i(R"(%i = alloca double)");
+ std::regex j(R"(%j = alloca double)");
+ REQUIRE(std::regex_search(module_string, m, i));
+ REQUIRE(std::regex_search(module_string, m, j));
+ }
+ }
+}
+
+//=============================================================================
+// ProcedureBlock
+//=============================================================================
+
+SCENARIO("Procedure", "[visitor][llvm]") {
+ GIVEN("Empty procedure with no arguments") {
+ std::string nmodl_text = R"(
+ PROCEDURE empty() {}
+ )";
+
+ THEN("empty void function is produced") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check procedure has empty body
+ std::regex procedure(R"(define void @empty\(\) \{\n\})");
+ REQUIRE(std::regex_search(module_string, m, procedure));
+ }
+ }
+
+ GIVEN("Empty procedure with arguments") {
+ std::string nmodl_text = R"(
+ PROCEDURE with_argument(x) {}
+ )";
+
+ THEN("void function is produced with arguments allocated on stack") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ // Check procedure signature
+ std::regex function_signature(R"(define void @with_argument\(double %x1\) \{)");
+ REQUIRE(std::regex_search(module_string, m, function_signature));
+
+ // Check that procedure arguments are allocated on the local stack
+ std::regex alloca_instr(R"(%x = alloca double)");
+ std::regex store_instr(R"(store double %x1, double\* %x)");
+ REQUIRE(std::regex_search(module_string, m, alloca_instr));
+ REQUIRE(std::regex_search(module_string, m, store_instr));
+ }
+ }
+}
+
+//=============================================================================
+// UnaryExpression
+//=============================================================================
+
+SCENARIO("Unary expression", "[visitor][llvm]") {
+ GIVEN("Procedure with negation") {
+ std::string nmodl_text = R"(
+ PROCEDURE negation(a) {
+ LOCAL i
+ i = -a
+ }
+ )";
+
+ THEN("fneg instruction is created") {
+ std::string module_string = run_llvm_visitor(nmodl_text);
+ std::smatch m;
+
+ std::regex allocation(R"(%1 = load double, double\* %a)");
+ REQUIRE(std::regex_search(module_string, m, allocation));
+
+ // llvm v9 and llvm v11 implementation for negation
+ std::regex negation_v9(R"(%2 = fsub double -0.000000e\+00, %1)");
+ std::regex negation_v11(R"(fneg double %1)");
+ bool result = std::regex_search(module_string, m, negation_v9) ||
+ std::regex_search(module_string, m, negation_v11);
+ REQUIRE(result == true);
+ }
+ }
+}