From 0549d2eb66cf4badbdfcb3e57311229a1f60b94e Mon Sep 17 00:00:00 2001 From: Nora Abi Akar <nora.abiakar@gmail.com> Date: Fri, 20 Dec 2019 14:29:52 +0100 Subject: [PATCH] Modcc: refactor function expander and inliner (#912) * Rewrite the function expander which lowers function calls and function arguments, such that both steps are done in the same visitor, and both visitors are applied to the entire procedure. * Rewrite the function inliner such that it is also applied to an entire procedure, after all functions have been expanded. The function inliner iterates over the body of the procedure, inlining one function call at a time, until all function calls have been inlined. * Conductivity and current accumulations are also modified to be done in one step at the end of `nrn_current` --- modcc/expression.cpp | 7 +- modcc/expression.hpp | 12 + modcc/functionexpander.cpp | 222 ++++++++++++------- modcc/functionexpander.hpp | 87 ++------ modcc/functioninliner.cpp | 332 +++++++++++++++------------- modcc/functioninliner.hpp | 119 ++++------ modcc/identifier.hpp | 3 +- modcc/module.cpp | 287 +++++++++++------------- modcc/parser.cpp | 5 +- modcc/solvers.cpp | 4 +- test/unit-modcc/mod_files/test6.mod | 5 +- test/unit-modcc/test_parser.cpp | 6 +- test/unit-modcc/test_printers.cpp | 69 +++--- 13 files changed, 578 insertions(+), 580 deletions(-) diff --git a/modcc/expression.cpp b/modcc/expression.cpp index 9ba6c58e..51e8217b 100644 --- a/modcc/expression.cpp +++ b/modcc/expression.cpp @@ -866,8 +866,7 @@ void IfExpression::semantic(scope_ptr scp) { condition_->semantic(scp); - auto cond = condition_->is_conditional(); - if(!cond) { + if(!condition_->is_conditional()) { error("not a valid conditional expression"); } @@ -878,6 +877,10 @@ void IfExpression::semantic(scope_ptr scp) { } } +void IfExpression::replace_condition(expression_ptr&& other) { + std::swap(condition_, other); +} + expression_ptr IfExpression::clone() const { return make_expression<IfExpression>( location_, diff --git a/modcc/expression.hpp b/modcc/expression.hpp index 1da38c35..4d131b44 100644 --- a/modcc/expression.hpp +++ b/modcc/expression.hpp @@ -797,6 +797,8 @@ public: expression_ptr clone() const override; std::string to_string() const override; + + void replace_condition(expression_ptr&& other); void semantic(scope_ptr scp) override; void accept(Visitor* v) override; @@ -1142,6 +1144,16 @@ public: BlockExpression* body() { return body_->is_block(); } + void body(expression_ptr&& new_body) { + if(!new_body->is_block()) { + Location loc = new_body? new_body->location(): Location{}; + throw compiler_exception( + " attempt to set FunctionExpression body with non-block expression, i.e.\n" + + new_body->to_string(), + loc); + } + body_ = std::move(new_body); + } FunctionExpression* is_function() override {return this;} void semantic(scope_type::symbol_map&) override; diff --git a/modcc/functionexpander.cpp b/modcc/functionexpander.cpp index a6b44330..a17329d9 100644 --- a/modcc/functionexpander.cpp +++ b/modcc/functionexpander.cpp @@ -12,77 +12,112 @@ expression_ptr insert_unique_local_assignment(expr_list_type& stmts, Expression* return std::move(exprs.id); } -/////////////////////////////////////////////////////////////////////////////// -// function call site lowering -/////////////////////////////////////////////////////////////////////////////// -expr_list_type lower_function_calls(Expression* e) -{ - auto v = std::make_unique<FunctionCallLowerer>(e->scope()); - - if(auto a=e->is_assignment()) { -#ifdef LOGGING - std::cout << "lower_function_calls inspect expression " << e->to_string() << "\n"; -#endif - // recursively inspect and replace function calls with identifiers - a->rhs()->accept(v.get()); - - } - - // return the list of statements that assign function call return values - // to identifiers, e.g. - // LOCAL ll1_ - // ll1_ = mInf(v) - return v->move_calls(); -} - -void FunctionCallLowerer::visit(Expression *e) { - throw compiler_exception( - "function lowering for expressions of the type " + e->to_string() - + " has not been defined", e->location() - ); +///////////////////////////////////////////////////////////////////// +// lower function call sites so that all function calls are of +// the form : variable = call(<args>) +// then lower function arguments that are not identifiers or literals +// e.g. +// a = 2 + foo(2+x, y, 1) +// becomes +// ll0_ = foo(2+x, y, 1) +// a = 2 + ll0_ +// becomes +// ll1_ = 2+x +// ll0_ = foo(ll1_, y, 1) +// a = 2 + ll0_ +///////////////////////////////////////////////////////////////////// +expression_ptr lower_functions(BlockExpression* block) { + auto v = std::make_unique<FunctionCallLowerer>(); + block->accept(v.get()); + return v->as_block(false); } +// We only need to lower function arguments when visiting a Call expression +// Function arguments are checked for other Call expressions, which recurse. +// When all Call arguments are handled, other arguments are checked, and +// lowered if needed +// e.g. foo(bar(x + 2), y - 1) +// First, the visitor recurses for bar(x + 2) which gets its arguments lowered: +// ll0_ = x + 2; +// bar(ll0_); +// Then, bar(x + 2) gets expanded into +// ll1_ = bar(ll0_); +// foo(ll1_, y - 1); +// Finally, foo(ll1_, y - 1) gets its arguments lowered into +// ll2_ = y - 1; +// foo(ll1_, ll2_); +// which turns: +// foo(bar(x + 2), y - 1) +// into: +// ll0_ = x + 2; +// ll1_ = bar(ll0_); +// ll2_ = y - 1; +// foo(ll1_, ll2_); void FunctionCallLowerer::visit(CallExpression *e) { + // Lower function calls for(auto& arg : e->args()) { if(auto func = arg->is_function_call()) { + // Recurse on the Call Expression func->accept(this); -#ifdef LOGGING - std::cout << " lowering : " << func->to_string() << "\n"; -#endif - expand_call( - func, [&arg](expression_ptr&& p){arg = std::move(p);} - ); - arg->semantic(scope_); + expand_call(func, [&arg](expression_ptr&& p){arg = std::move(p);}); + arg->semantic(block_scope_); } else { arg->accept(this); } } -} + // Lower function arguments + for(auto& arg : e->args()) { + if(arg->is_number() || arg->is_identifier()) { + continue; + } + auto id = insert_unique_local_assignment(statements_, arg.get()); + std::swap(arg, id); + } -void FunctionCallLowerer::visit(UnaryExpression *e) { - if(auto func = e->expression()->is_function_call()) { - func->accept(this); -#ifdef LOGGING - std::cout << " lowering : " << func->to_string() << "\n"; -#endif - expand_call(func, [&e](expression_ptr&& p){e->replace_expression(std::move(p));}); - e->semantic(scope_); + // Procedure Expressions need to be printed stand-alone + // Function Expressions are always part of a bigger expression + if (e->is_procedure_call()) { + statements_.push_back(e->clone()); } - else { - e->expression()->accept(this); +} + +void FunctionCallLowerer::visit(AssignmentExpression *e) { + e->rhs()->accept(this); + if (auto func = e->rhs()->is_function_call()) { + for (auto& arg: func->args()) { + if (auto id = arg->is_identifier()) { + if (id->name() == e->lhs()->is_identifier()->name()) { + expand_call(func, [&e](expression_ptr&& p){e->replace_rhs(std::move(p));}); + e->semantic(block_scope_); + break; + } + } + } } + statements_.push_back(e->clone()); } +void FunctionCallLowerer::visit(ConserveExpression *e) { + statements_.push_back(e->clone()); +} + +void FunctionCallLowerer::visit(CompartmentExpression *e) { + statements_.push_back(e->clone()); +} + +void FunctionCallLowerer::visit(LinearExpression *e) { + statements_.push_back(e->clone()); +} + +// Binary Expressions need to handle function calls if they contain them +// Functions calls have to be visited and expanded out of the expression void FunctionCallLowerer::visit(BinaryExpression *e) { if(auto func = e->lhs()->is_function_call()) { func->accept(this); -#ifdef LOGGING - std::cout << " lowering : " << func->to_string() << "\n"; -#endif expand_call(func, [&e](expression_ptr&& p){e->replace_lhs(std::move(p));}); - e->semantic(scope_); + e->semantic(block_scope_); } else { e->lhs()->accept(this); @@ -90,50 +125,67 @@ void FunctionCallLowerer::visit(BinaryExpression *e) { if(auto func = e->rhs()->is_function_call()) { func->accept(this); -#ifdef LOGGING - std::cout << " lowering : " << func->to_string() << "\n"; -#endif expand_call(func, [&e](expression_ptr&& p){e->replace_rhs(std::move(p));}); - e->semantic(scope_); + e->semantic(block_scope_); } else { e->rhs()->accept(this); } } -/////////////////////////////////////////////////////////////////////////////// -// function argument lowering -/////////////////////////////////////////////////////////////////////////////// - -expr_list_type -lower_function_arguments(std::vector<expression_ptr>& args) -{ - expr_list_type new_statements; - for(auto it=args.begin(); it!=args.end(); ++it) { - // get reference to the unique_ptr with the expression - auto& e = *it; -#ifdef LOGGING - std::cout << "inspecting argument @ " << e->location() << " : " << e->to_string() << std::endl; -#endif - - if(e->is_number() || e->is_identifier()) { - // do nothing, because identifiers and literals are in the correct form - // for lowering - continue; - } +// Unary Expressions need to handle function calls if they contain them +// Functions calls have to be visited and expanded out of the expression +void FunctionCallLowerer::visit(UnaryExpression *e) { + if(auto func = e->expression()->is_function_call()) { + func->accept(this); + expand_call(func, [&e](expression_ptr&& p){e->replace_expression(std::move(p));}); + e->semantic(block_scope_); + } + else { + e->expression()->accept(this); + } +} + +// If expressions need to handle the condition before the true and false branches +// The condition should be handled by the Binary Expression visitor which will +// expand any contained function calls and lower their arguments +void FunctionCallLowerer::visit(IfExpression *e) { + expr_list_type outer; + + e->condition()->accept(this); + + if(auto func = e->condition()->is_function_call()) { + expand_call(func, [&e](expression_ptr&& p){ + auto zero_exp = make_expression<NumberExpression>(Location{}, 0.); + p = make_expression<ConditionalExpression>(p->location(), tok::ne, p->clone(), std::move(zero_exp)); + e->replace_condition(std::move(p)); + }); + e->semantic(block_scope_); + } - auto id = insert_unique_local_assignment(new_statements, e.get()); -#ifdef LOGGING - std::cout << " lowering to " << new_statements.back()->to_string() << "\n"; -#endif - // replace the function call in the original expression with the local - // variable which holds the pre-computed value - std::swap(e, id); + std::swap(outer, statements_); + + e->true_branch()->accept(this); + auto true_branch = make_expression<BlockExpression>( + e->true_branch()->location(), + std::move(statements_), + true); + + statements_.clear(); + expression_ptr false_branch; + if (e->false_branch()) { + e->false_branch()->accept(this); + false_branch = make_expression<BlockExpression>( + e->false_branch()->location(), + std::move(statements_), + true); } -#ifdef LOGGING - std::cout << "\n"; -#endif - return new_statements; + statements_ = std::move(outer); + statements_.push_back(make_expression<IfExpression>( + e->location(), + e->condition()->clone(), + std::move(true_branch), + std::move(false_branch))); } diff --git a/modcc/functionexpander.hpp b/modcc/functionexpander.hpp index b1ea95a2..cfb04b7f 100644 --- a/modcc/functionexpander.hpp +++ b/modcc/functionexpander.hpp @@ -11,83 +11,34 @@ // Return the new unique local identifier. expression_ptr insert_unique_local_assignment(expr_list_type& stmts, Expression* e); -// prototype for lowering function calls -expr_list_type lower_function_calls(Expression* e); +// prototype for lowering function calls and arguments +expression_ptr lower_functions(BlockExpression* block); -/////////////////////////////////////////////////////////////////////////////// -// visitor that takes function call sites and lowers them to inline assignments -// -// e.g. if called on the following statement -// -// a = 3 + foo(x, y) -// -// the calls_ member will be -// -// LOCAL ll0_ -// ll0_ = foo(x,y) -// -// and the original statment is modified to be -// -// a = 3 + ll0_ -// -// If the calls_ data is spliced directly before the original statement -// the function call will have been fully lowered -/////////////////////////////////////////////////////////////////////////////// -class FunctionCallLowerer : public Visitor { +class FunctionCallLowerer : public BlockRewriterBase { public: - FunctionCallLowerer(scope_ptr s) - : scope_(s) - {} - - void visit(CallExpression *e) override; - void visit(Expression *e) override; - void visit(UnaryExpression *e) override; - void visit(BinaryExpression *e) override; - void visit(NumberExpression *e) override {}; - void visit(IdentifierExpression *e) override {}; - - expr_list_type& calls() { - return calls_; - } - - expr_list_type move_calls() { - return std::move(calls_); - } - - ~FunctionCallLowerer() {} + using BlockRewriterBase::visit; + + FunctionCallLowerer(): BlockRewriterBase() {} + FunctionCallLowerer(scope_ptr s): BlockRewriterBase(s) {} + + virtual void visit(CallExpression *e) override; + virtual void visit(ConserveExpression *e) override; + virtual void visit(CompartmentExpression *e) override; + virtual void visit(LinearExpression *e) override; + virtual void visit(AssignmentExpression *e) override; + virtual void visit(BinaryExpression *e) override; + virtual void visit(UnaryExpression *e) override; + virtual void visit(IfExpression *e) override; + virtual void visit(NumberExpression *e) override {}; + virtual void visit(IdentifierExpression *e) override {}; private: template< typename F> void expand_call(CallExpression* func, F replacer) { - auto id = insert_unique_local_assignment(calls_, func); + auto id = insert_unique_local_assignment(statements_, func); // replace the function call in the original expression with the local // variable which holds the pre-computed value replacer(std::move(id)); } - - expr_list_type calls_; - scope_ptr scope_; }; -/////////////////////////////////////////////////////////////////////////////// -// visitor that takes function arguments that are not literals of identifiers -// and lowers them to inline assignments -// -// e.g. if called on the following statement -// -// a = foo(2+x, y) -// -// the calls_ member will be -// -// LOCAL ll0_ -// ll0_ = 2+x -// -// and the original statment is modified to be -// -// a = foo(ll0_, y) -// -// If the calls_ data is spliced directly before the original statement -// the function arguments will have been fully lowered -/////////////////////////////////////////////////////////////////////////////// -expr_list_type lower_function_arguments(std::vector<expression_ptr>& args); - diff --git a/modcc/functioninliner.cpp b/modcc/functioninliner.cpp index f238a684..8a9e6311 100644 --- a/modcc/functioninliner.cpp +++ b/modcc/functioninliner.cpp @@ -4,128 +4,200 @@ #include "error.hpp" #include "functioninliner.hpp" #include "errorvisitor.hpp" +#include "symdiff.hpp" -expression_ptr inline_function_call(const expression_ptr& e) -{ - auto assign_to_func = e->is_assignment(); - auto ret_identifier = assign_to_func->lhs()->is_identifier(); +expression_ptr inline_function_calls(std::string calling_func, BlockExpression* block) { + auto inline_block = block->clone(); - if(auto f = assign_to_func->rhs()->is_function_call()) { - auto body = f->function()->body()->clone(); + // The function inliner will inline one function at a time + // Once all functions in a block have been inlined, the + // while loop will be broken + while(true) { + inline_block->semantic(block->scope()); - for (auto&s: body->is_block()->statements()) { - s->semantic(e->scope()); - } + auto func_inliner = std::make_unique<FunctionInliner>(calling_func); + inline_block->accept(func_inliner.get()); - FunctionInliner func_inliner(f->name(), ret_identifier, f->function()->args(), f->args(), e->scope()); + if (!func_inliner->return_val_set()) { + throw compiler_exception("return variable of function not set", block->location()); + } - body->accept(&func_inliner); - if (!func_inliner.return_val_set()) { - throw compiler_exception(pprintf("return variable of function % not set", f->name()), e->location()); + if (func_inliner->finished_inlining()) { + return func_inliner->as_block(false); } - return body; + + inline_block = func_inliner->as_block(false); } - return {}; } + /////////////////////////////////////////////////////////////////////////////// // function inliner /////////////////////////////////////////////////////////////////////////////// -// Takes a Binary or Unary Expression and replaces its variables that match any -// function argument in fargs_ with the corresponding call argument in cargs_ -void FunctionInliner::replace_args(Expression* e) { - for(auto i=0u; i<fargs_.size(); ++i) { - if(auto id = cargs_[i]->is_identifier()) { - VariableReplacer v(fargs_[i], id->spelling()); - e->accept(&v); - } - else if(auto value = cargs_[i]->is_number()) { - ValueInliner v(fargs_[i], value->value()); - e->accept(&v); - } - else { - throw compiler_exception("can't inline functions with expressions as arguments", e->location()); - } - } - e->semantic(scope_); - - ErrorVisitor v(""); - e->accept(&v); - if(v.num_errors()) { - throw compiler_exception("something went wrong with inlined function call ", e->location()); - } -} - +// The Function inliner works on inlining one function at a time. +// If no function is being inlined when an expression is being visited, +// the expression remains the same. void FunctionInliner::visit(Expression* e) { + if (!inlining_in_progress_) { + statements_.push_back(e->clone()); + return; + } throw compiler_exception( "I don't know how to do function inlining for this statement : " + e->to_string(), e->location()); } +// Only in procedures, always stays the same +void FunctionInliner::visit(ConserveExpression *e) { + statements_.push_back(e->clone()); +} + +// Only in procedures, always stays the same +void FunctionInliner::visit(CompartmentExpression *e) { + statements_.push_back(e->clone()); +} + +// Only in procedures, always stays the same +void FunctionInliner::visit(LinearExpression *e) { + statements_.push_back(e->clone()); +} + void FunctionInliner::visit(LocalDeclaration* e) { - auto loc = e->location(); + if (!inlining_in_progress_) { + statements_.push_back(e->clone()); + return; + } std::map<std::string, Token> new_vars; for (auto& var: e->variables()) { - auto unique_decl = make_unique_local_decl(scope_, loc, "r_"); + auto unique_decl = make_unique_local_decl(scope_, e->location(), "r_"); auto unique_name = unique_decl.id->is_identifier()->spelling(); // Local variables must be renamed to avoid collisions with the calling function. - // They are considered part of the function arguments `fargs_` and the renamed - // variable is considered part of the call arguments `cargs_` - fargs_.push_back(var.first); - cargs_.push_back(unique_decl.id->clone()); + // The mappings are stored in local_arg_map + local_arg_map_.emplace(std::make_pair(var.first, std::move(unique_decl.id))); auto e_tok = var.second; e_tok.spelling = unique_name; new_vars[unique_name] = e_tok; } e->variables().swap(new_vars); -} + statements_.push_back(e->clone()); -void FunctionInliner::visit(BlockExpression* e) { - for (auto& expr: e->statements()) { - expr->accept(this); - } } void FunctionInliner::visit(UnaryExpression* e) { - replace_args(e); + if (!inlining_in_progress_) { + return; + } + + auto sub = substitute(e->expression(), local_arg_map_); + sub = substitute(sub, call_arg_map_); + e->replace_expression(std::move(sub)); + + e->semantic(scope_); + + ErrorVisitor v(""); + e->accept(&v); + if(v.num_errors()) { + throw compiler_exception("something went wrong with inlined function call ", e->location()); + } } void FunctionInliner::visit(BinaryExpression* e) { - replace_args(e); + if (!inlining_in_progress_) { + return; + } + auto sub_lhs = substitute(e->lhs(), local_arg_map_); + sub_lhs = substitute(sub_lhs, call_arg_map_); + + auto sub_rhs = substitute(e->rhs(), local_arg_map_); + sub_rhs = substitute(sub_rhs, call_arg_map_); + + e->replace_lhs(std::move(sub_lhs)); + e->replace_rhs(std::move(sub_rhs)); + + e->semantic(scope_); + + ErrorVisitor v(""); + e->accept(&v); + if(v.num_errors()) { + throw compiler_exception("something went wrong with inlined function call ", e->location()); + } } void FunctionInliner::visit(AssignmentExpression* e) { + // At this point, after function lowering, all function calls should be on the rhs of + // an Assignment Expression. + // If we find a new function to inline, we can do so, provided we aren't already inlining + // another function and we haven't inlined a function already. + if (!inlining_in_progress_ && !inlining_executed_ && e->rhs()->is_function_call()) { + auto f = e->rhs()->is_function_call(); + auto& fargs = f->function()->args(); + auto& cargs = f->args(); + + inlining_in_progress_ = true; + inlining_func_ = f->name(); + lhs_ = e->lhs()->is_identifier()->clone(); + return_set_ = false; + scope_ = e->scope(); + + for (unsigned i = 0; i < fargs.size(); ++i) { + call_arg_map_.emplace(std::make_pair(fargs[i]->is_argument()->spelling(), cargs[i]->clone())); + } + + auto body = f->function()->body()->clone(); + for (auto&s: body->is_block()->statements()) { + s->semantic(e->scope()); + } + + body->accept(this); + inlining_in_progress_ = false; + inlining_executed_ = true; + return; + } + + // If we're not inlining a function call, don't change anything in the expression + if (!inlining_in_progress_) { + statements_.push_back(e->clone()); + return; + } + + // If we're inlining a function call, take care of variable renaming if (auto lhs = e->lhs()->is_identifier()) { - if (lhs->spelling() == func_name_) { + std::string iden_name = lhs->spelling(); + + // if the identifier name matches the function name, then we are setting the return value + if (iden_name == inlining_func_) { e->replace_lhs(lhs_->clone()); return_set_ = true; } else { - for (unsigned i = 0; i < fargs_.size(); i++) { - if (fargs_[i] == lhs->spelling()) { - e->replace_lhs(cargs_[i]->clone()); - break; - } + if (local_arg_map_.count(iden_name)) { + e->replace_lhs(local_arg_map_.at(iden_name)->clone()); } } } if (auto rhs = e->rhs()->is_identifier()) { - for (unsigned i = 0; i < fargs_.size(); i++) { - if (fargs_[i] == rhs->spelling()) { - e->replace_rhs(cargs_[i]->clone()); - break; - } + if (local_arg_map_.count(rhs->spelling())) { + e->replace_rhs(local_arg_map_.at(rhs->spelling())->clone()); + } + if (call_arg_map_.count(rhs->spelling())) { + e->replace_rhs(call_arg_map_.at(rhs->spelling())->clone()); } } else { e->rhs()->accept(this); } + statements_.push_back(e->clone()); } void FunctionInliner::visit(IfExpression* e) { + expr_list_type outer; + std::swap(outer, statements_); + + // Make sure if Expressions set the return value if not already set + // return_set_ will always be true unless we are inlining a function bool if_ret; bool save_ret = return_set_; @@ -133,117 +205,65 @@ void FunctionInliner::visit(IfExpression* e) { e->condition()->accept(this); e->true_branch()->accept(this); + auto true_branch = make_expression<BlockExpression>( + e->true_branch()->location(), + std::move(statements_), + true); + statements_.clear(); if_ret = return_set_; return_set_ = false; + expression_ptr false_branch; if (e->false_branch()) { e->false_branch()->accept(this); + false_branch = make_expression<BlockExpression>( + e->false_branch()->location(), + std::move(statements_), + true); } + statements_.clear(); if_ret &= return_set_; - return_set_ = save_ret? save_ret: if_ret; + + statements_ = std::move(outer); + statements_.push_back(make_expression<IfExpression>( + e->location(), + e->condition()->clone(), + std::move(true_branch), + std::move(false_branch))); } void FunctionInliner::visit(CallExpression* e) { - for (auto& a: e->is_function_call()->args()) { - for (unsigned i = 0; i < fargs_.size(); i++) { - if (auto id = a->is_identifier()) { - if (fargs_[i] == id->spelling()) { - a = cargs_[i]->clone(); - } - } else { - a->accept(this); - } + if (!inlining_in_progress_) { + if (e->is_procedure_call()) { + statements_.push_back(e->clone()); } + return; } -} - -/////////////////////////////////////////////////////////////////////////////// -// variable replacer -/////////////////////////////////////////////////////////////////////////////// - -void VariableReplacer::visit(Expression *e) { - throw compiler_exception( - "I don't know how to variable inlining for this statement : " - + e->to_string(), e->location()); -} - -void VariableReplacer::visit(UnaryExpression *e) { - auto exp = e->expression()->is_identifier(); - if(exp && exp->spelling()==source_) { - e->replace_expression( - make_expression<IdentifierExpression>(exp->location(), target_) - ); - } - else if(!exp) { - e->expression()->accept(this); - } -} - -void VariableReplacer::visit(BinaryExpression *e) { - auto lhs = e->lhs()->is_identifier(); - if(lhs && lhs->spelling()==source_) { - e->replace_lhs( - make_expression<IdentifierExpression>(lhs->location(), target_) - ); - } - else if(!lhs){ // only inspect subexpressions that are not themselves identifiers - e->lhs()->accept(this); - } - - auto rhs = e->rhs()->is_identifier(); - if(rhs && rhs->spelling()==source_) { - e->replace_rhs( - make_expression<IdentifierExpression>(rhs->location(), target_) - ); - } - else if(!rhs){ // only inspect subexpressions that are not themselves identifiers - e->rhs()->accept(this); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// value inliner -/////////////////////////////////////////////////////////////////////////////// -void ValueInliner::visit(Expression *e) { - throw compiler_exception( - "I don't know how to value inlining for this statement : " - + e->to_string(), e->location()); -} - -void ValueInliner::visit(UnaryExpression *e) { - auto exp = e->expression()->is_identifier(); - if(exp && exp->spelling()==source_) { - e->replace_expression( - make_expression<NumberExpression>(exp->location(), value_) - ); + if (e->is_function_call()->name() == inlining_func_ || e->is_function_call()->name() == calling_func_) { + throw compiler_exception("recursive functions not allowed", e->location()); } - else if(!exp){ - e->expression()->accept(this); - } -} -void ValueInliner::visit(BinaryExpression *e) { - auto lhs = e->lhs()->is_identifier(); - if(lhs && lhs->spelling()==source_) { - e->replace_lhs( - make_expression<NumberExpression>(lhs->location(), value_) - ); - } - else if(!lhs) { - e->lhs()->accept(this); - } + auto& args = e->is_function_call() ? e->is_function_call()->args() : e->is_procedure_call()->args(); - auto rhs = e->rhs()->is_identifier(); - if(rhs && rhs->spelling()==source_) { - e->replace_rhs( - make_expression<NumberExpression>(rhs->location(), value_) - ); + for (auto& a: args) { + if (auto id = a->is_identifier()) { + std::string iden_name = id->spelling(); + if (local_arg_map_.count(iden_name)) { + a = local_arg_map_.at(iden_name)->clone(); + } + if (call_arg_map_.count(iden_name)) { + a = call_arg_map_.at(iden_name)->clone(); + } + } else { + a->accept(this); + } } - else if(!rhs){ - e->rhs()->accept(this); + if (e->is_procedure_call()) { + statements_.push_back(e->clone()); + return; } } diff --git a/modcc/functioninliner.hpp b/modcc/functioninliner.hpp index c302e36f..34e37a5c 100644 --- a/modcc/functioninliner.hpp +++ b/modcc/functioninliner.hpp @@ -5,94 +5,61 @@ #include "scope.hpp" #include "visitor.hpp" -// Takes an assignment to a function call, returns an inlined -// version without modifying the original expression's contents -expression_ptr inline_function_call(const expression_ptr& e); - -class FunctionInliner : public Visitor { +expression_ptr inline_function_calls(std::string calling_func, BlockExpression* block); +class FunctionInliner : public BlockRewriterBase { public: - - FunctionInliner(std::string func_name, - Expression* lhs, - const std::vector<expression_ptr>& fargs, - const std::vector<expression_ptr>& cargs, - const scope_ptr& scope) : - func_name_(func_name), lhs_(lhs->clone()), scope_(scope) { - for (auto& f: fargs) { - fargs_.push_back(f->is_argument()->spelling()); - } - for (auto& c: cargs) { - cargs_.push_back(c->clone()); - } - } - - void visit(Expression* e) override; - void visit(UnaryExpression* e) override; - void visit(BinaryExpression* e) override; - void visit(BlockExpression *e) override; - void visit(AssignmentExpression* e) override; - void visit(IfExpression* e) override; - void visit(LocalDeclaration* e) override; - void visit(CallExpression* e) override; - void visit(NumberExpression* e) override {}; + using BlockRewriterBase::visit; + + FunctionInliner(std::string calling_func) : BlockRewriterBase(), calling_func_(calling_func) {}; + FunctionInliner(scope_ptr s): BlockRewriterBase(s) {} + + virtual void visit(Expression *e) override; + virtual void visit(CallExpression *e) override; + virtual void visit(ConserveExpression *e) override; + virtual void visit(CompartmentExpression *e) override; + virtual void visit(LinearExpression *e) override; + virtual void visit(AssignmentExpression* e) override; + virtual void visit(BinaryExpression* e) override; + virtual void visit(UnaryExpression* e) override; + virtual void visit(IfExpression* e) override; + virtual void visit(LocalDeclaration* e) override; + virtual void visit(NumberExpression* e) override {}; + virtual void visit(IdentifierExpression* e) override {}; bool return_val_set() {return return_set_;}; + bool finished_inlining() {return !inlining_executed_;}; ~FunctionInliner() {} private: - std::string func_name_; + std::string inlining_func_, calling_func_; expression_ptr lhs_; - std::vector<std::string> fargs_; - std::vector<expression_ptr> cargs_; + std::map<std::string, expression_ptr> call_arg_map_; + std::map<std::string, expression_ptr> local_arg_map_; scope_ptr scope_; - bool return_set_ = false; - - void replace_args(Expression* e); - -}; - -class VariableReplacer : public Visitor { - -public: - - VariableReplacer(std::string const& source, std::string const& target) - : source_(source), - target_(target) - {} - void visit(Expression *e) override; - void visit(UnaryExpression *e) override; - void visit(BinaryExpression *e) override; - void visit(NumberExpression *e) override {}; + // Tracks whether the return value of a function has been set + bool return_set_ = true; - ~VariableReplacer() {} + // Tracks whether a function is being inlined + bool inlining_in_progress_ = false; -private: - - std::string source_; - std::string target_; -}; - -class ValueInliner : public Visitor { - -public: - - ValueInliner(std::string const& source, long double value) - : source_(source), - value_(value) - {} + // Tracks whether a function has been inlined + bool inlining_executed_ = false; - void visit(Expression *e) override; - void visit(UnaryExpression *e) override; - void visit(BinaryExpression *e) override; - void visit(NumberExpression *e) override {}; - - ~ValueInliner() {} - -private: + void replace_args(Expression* e); - std::string source_; - long double value_; -}; +protected: + virtual void reset() override { + inlining_func_.clear(); + lhs_ = nullptr; + call_arg_map_.clear(); + local_arg_map_.clear(); + scope_.reset(); + return_set_ = true; + inlining_in_progress_ = false; + inlining_executed_ = false; + BlockRewriterBase::reset(); + } +}; \ No newline at end of file diff --git a/modcc/identifier.hpp b/modcc/identifier.hpp index 6cf46636..ae1db14d 100644 --- a/modcc/identifier.hpp +++ b/modcc/identifier.hpp @@ -16,7 +16,8 @@ enum class moduleKind { enum class accessKind { read, write, - readwrite + readwrite, + noaccess }; /// describes the scope of a variable diff --git a/modcc/module.cpp b/modcc/module.cpp index 279fc357..9938a863 100644 --- a/modcc/module.cpp +++ b/modcc/module.cpp @@ -48,20 +48,40 @@ class NrnCurrentRewriter: public BlockRewriterBase { } bool has_current_update_ = false; - std::set<std::string> ion_current_vars_; + std::set<std::string> current_vars_; + std::set<expression_ptr> conductivity_exps_; public: using BlockRewriterBase::visit; virtual void finalize() override { if (has_current_update_) { - // Initialize conductivity_ as first statement. - statements_.push_front(make_expression<AssignmentExpression>(loc_, - id("conductivity_"), - make_expression<NumberExpression>(loc_, 0.0))); - statements_.push_front(make_expression<AssignmentExpression>(loc_, - id("current_"), - make_expression<NumberExpression>(loc_, 0.0))); + expression_ptr current_sum, conductivity_sum; + for (auto& curr: current_vars_) { + auto curr_id = make_expression<IdentifierExpression>(Location{}, curr); + if (!current_sum) { + current_sum = std::move(curr_id); + } else { + current_sum = make_expression<AddBinaryExpression>( + Location{}, std::move(current_sum), std::move(curr_id)); + } + } + for (auto& cond: conductivity_exps_) { + if (!conductivity_sum) { + conductivity_sum = cond->clone(); + } else { + conductivity_sum = make_expression<AddBinaryExpression>( + Location{}, std::move(conductivity_sum), cond->clone()); + } + } + if (current_sum) { + statements_.push_back(make_expression<AssignmentExpression>(loc_, + id("current_"), std::move(current_sum))); + } + if (conductivity_sum) { + statements_.push_back(make_expression<AssignmentExpression>(loc_, + id("conductivity_"), std::move(conductivity_sum))); + } } } @@ -69,22 +89,13 @@ public: virtual void visit(ConductanceExpression *e) override {} virtual void visit(AssignmentExpression *e) override { statements_.push_back(e->clone()); - auto loc = e->location(); sourceKind current_source = current_update(e); if (current_source != sourceKind::no_source) { has_current_update_ = true; - if (current_source==sourceKind::ion_current_density || current_source==sourceKind::ion_current) { - ion_current_vars_.insert(e->lhs()->is_identifier()->name()); - } - else { - // A 'nonspecific' current contribution. - // Remove data source; currents accumulated into `current_` instead. - - e->lhs()->is_identifier()->symbol()->is_local_variable() - ->external_variable()->data_source(sourceKind::no_source); - } + auto visited_current = current_vars_.count(e->lhs()->is_identifier()->name()); + current_vars_.insert(e->lhs()->is_identifier()->name()); linear_test_result L = linear_test(e->rhs(), {"v"}); if (!L.is_linear) { @@ -93,17 +104,8 @@ public: return; } else { - statements_.push_back(make_expression<AssignmentExpression>(loc, - id("current_", loc), - make_expression<AddBinaryExpression>(loc, - id("current_", loc), - e->lhs()->clone()))); - if (L.coef.count("v")) { - statements_.push_back(make_expression<AssignmentExpression>(loc, - id("conductivity_", loc), - make_expression<AddBinaryExpression>(loc, - id("conductivity_", loc), - L.coef.at("v")->clone()))); + if (L.coef.count("v") && !visited_current) { + conductivity_exps_.insert(L.coef.at("v")->clone()); } } } @@ -280,6 +282,9 @@ bool Module::semantic() { auto& init_body = api_init->body()->statements(); + api_init->semantic(symbols_); + scope_ptr nrn_init_scope = api_init->scope(); + for(auto& e : *proc_init->body()) { auto solve_expression = e->is_solve_statement(); if (solve_expression) { @@ -292,11 +297,17 @@ bool Module::semantic() { if (solve_proc->kind() == procedureKind::linear) { solver = std::make_unique<LinearSolverVisitor>(state_vars); - linear_rewrite(solve_proc->body(), state_vars)->accept(solver.get()); + auto rewrite_body = linear_rewrite(solve_proc->body(), state_vars); + + rewrite_body->semantic(nrn_init_scope); + rewrite_body->accept(solver.get()); } else if (solve_proc->kind() == procedureKind::kinetic && solve_expression->variant() == solverVariant::steadystate) { solver = std::make_unique<SparseSolverVisitor>(solverVariant::steadystate); - kinetic_rewrite(solve_proc->body())->accept(solver.get()); + auto rewrite_body = kinetic_rewrite(solve_proc->body()); + + rewrite_body->semantic(nrn_init_scope); + rewrite_body->accept(solver.get()); } else { error("A SOLVE expression in an INITIAL block can only be used to solve a " "LINEAR block or a KINETIC block at steadystate and " + @@ -352,6 +363,10 @@ bool Module::semantic() { for(auto& e: (breakpoint->body()->statements())) { SolveExpression* solve_expression = e->is_solve_statement(); + LocalDeclaration* local_expression = e->is_local_declaration(); + if(local_expression) { + continue; + } if(!solve_expression) { found_non_solve = true; continue; @@ -398,11 +413,15 @@ bool Module::semantic() { solver = std::make_unique<SparseNonlinearSolverVisitor>(); } + rewrite_body->semantic(nrn_state_scope); rewrite_body->accept(solver.get()); } else if (deriv->kind()==procedureKind::linear) { solver = std::make_unique<LinearSolverVisitor>(state_vars); - linear_rewrite(deriv->body(), state_vars)->accept(solver.get()); + auto rewrite_body = linear_rewrite(deriv->body(), state_vars); + + rewrite_body->semantic(nrn_state_scope); + rewrite_body->accept(solver.get()); } else { deriv->body()->accept(solver.get()); @@ -667,12 +686,11 @@ void Module::add_variables_to_symbols() { // Nonspecific current variables are represented by an indexed variable // with a 'current' data source. Assignments in the NrnCurrent block will // later be rewritten so that these contributions are accumulated in `current_` - // (potentially saving some weight multiplications); at that point the - // data source for the nonspecific current variable will be reset to 'no_source'. + // (potentially saving some weight multiplications); if( neuron_block_.has_nonspecific_current() ) { auto const& i = neuron_block_.nonspecific_current; - create_indexed_variable(i.spelling, sourceKind::current, accessKind::write, "", i.location); + create_indexed_variable(i.spelling, current_kind, accessKind::noaccess, "", i.location); } for(auto const& ion : neuron_block_.ions) { @@ -732,9 +750,6 @@ void Module::add_variables_to_symbols() { } int Module::semantic_func_proc() { - bool keep_inlining = true; - int errors = 0; - //////////////////////////////////////////////////////////////////////////// // now iterate over the functions and procedures and perform semantic // analysis on each. This includes @@ -742,128 +757,90 @@ int Module::semantic_func_proc() { // - generate local variable table for each function/procedure // - inlining function calls //////////////////////////////////////////////////////////////////////////// - - while (keep_inlining) { - #ifdef LOGGING - std::cout << white("===================================\n"); +#ifdef LOGGING + std::cout << white("===================================\n"); std::cout << cyan(" Function Inlining\n"); std::cout << white("===================================\n"); - #endif - keep_inlining = false; - - for (auto& e: symbols_) { - auto& s = e.second; - if(s->kind() == symbolKind::procedure || s->kind() == symbolKind::function) { - - #ifdef LOGGING - std::cout << "\nfunction inlining for " << s->location() << "\n" - << s->to_string() << "\n" - << green("\n-call site lowering-\n\n"); - #endif - - // perform semantic analysis - s->semantic(symbols_); - - // then use an error visitor to print out all the semantic errors - ErrorVisitor v(source_name()); - s->accept(&v); - errors += v.num_errors(); - - // inline function calls - // this requires that the symbol table has already been built - if (v.num_errors() == 0) { - auto &b = s->kind() == symbolKind::function ? - s->is_function()->body()->statements() : - s->is_procedure()->body()->statements(); - - // lower function call sites so that all function calls are of - // the form : variable = call(<args>) - // e.g. - // a = 2 + foo(2+x, y, 1) - // becomes - // ll0_ = foo(2+x, y, 1) - // a = 2 + ll0_ - for (auto e = b.begin(); e != b.end(); ++e) { - b.splice(e, lower_function_calls((*e).get())); - } - #ifdef LOGGING - std::cout << "body after call site lowering\n"; - for(auto& l : b) std::cout << " " << l->to_string() << " @ " << l->location() << "\n"; - std::cout << green("\n-argument lowering-\n\n"); - #endif - // lower function arguments that are not identifiers or literals - // e.g. - // ll0_ = foo(2+x, y, 1) - // a = 2 + ll0_ - // becomes - // ll1_ = 2+x - // ll0_ = foo(ll1_, y, 1) - // a = 2 + ll0_ - for (auto e = b.begin(); e != b.end(); ++e) { - if (auto be = (*e)->is_binary()) { - // only apply to assignment expressions where rhs is a - // function call because the function call lowering step - // above ensures that all function calls are of this form - if (auto rhs = be->rhs()->is_function_call()) { - b.splice(e, lower_function_arguments(rhs->args())); - } - } - } - - #ifdef LOGGING - std::cout << "body after argument lowering\n"; - for(auto& l : b) std::cout << " " << l->to_string() << " @ " << l->location() << "\n"; - std::cout << green("\n-inlining-\n\n"); - #endif - } +#endif + for (auto& e: symbols_) { + auto& s = e.second; + if(s->kind() == symbolKind::procedure || s->kind() == symbolKind::function) { + // perform semantic analysis + s->semantic(symbols_); +#ifdef LOGGING + std::cout << "\nfunction lowering for " << s->location() << "\n" + << s->to_string() << "\n\n"; +#endif + + if (s->kind() == symbolKind::function) { + auto rewritten = lower_functions(s->is_function()->body()); + s->is_function()->body(std::move(rewritten)); + } else { + auto rewritten = lower_functions(s->is_procedure()->body()); + s->is_procedure()->body(std::move(rewritten)); } +#ifdef LOGGING + std::cout << "body after function lowering\n" + << s->to_string() << "\n\n"; +#endif } + } - for(auto& e : symbols_) { - auto& s = e.second; - - if(s->kind() == symbolKind::procedure) - { - if(errors==0) { - auto &b = s->kind()==symbolKind::function ? - s->is_function()->body()->statements() : - s->is_procedure()->body()->statements(); - - // Do the inlining: supports multiline functions and if/else statements - // e.g. if the function foo in the examples above is defined as follows - // - // function foo(a, b, c) { - // Local t = b + c - // foo = a*t - // } - // - // the full inlined example is - // ll1_ = 2+x - // r_0_ = y+1 - // ll0_ = ll1_*r_0_ - // a = 2 + ll0_ - - for (auto &e: b) { - if (auto ass = e->is_assignment()) { - if (ass->rhs()->is_function_call()) { - e = inline_function_call(e); - keep_inlining = true; - } - } - } + auto inline_and_simplify = [&](auto&& caller) { + auto rewritten = inline_function_calls(caller->name(), caller->body()); + caller->body(std::move(rewritten)); + caller->body(constant_simplify(caller->body())); + }; + // First, inline all function calls inside the bodies of each function + // This catches recursions + for(auto& e : symbols_) { + auto& s = e.second; + + if (s->kind() == symbolKind::function) { + // perform semantic analysis + s->semantic(symbols_); +#ifdef LOGGING + std::cout << "function inlining for " << s->location() << "\n" + << s->to_string() << "\n\n"; +#endif + inline_and_simplify(s->is_function()); + s->semantic(symbols_); +#ifdef LOGGING + std::cout << "body after inlining\n" + << s->to_string() << "\n\n"; +#endif + } + } - #ifdef LOGGING - std::cout << "body after inlining\n"; - for(auto& l : b) std::cout << " " << l->to_string() << " @ " << l->location() << "\n"; - #endif - // Finally, run a constant simplification pass. - if (auto proc = s->is_procedure()) { - proc->body(constant_simplify(proc->body())); - s->semantic(symbols_); - } - } - } + // Once all functions are inlined internally; we can inline + // function calls in the bodies of procedures + for(auto& e : symbols_) { + auto& s = e.second; + + if(s->kind() == symbolKind::procedure) { + // perform semantic analysis + s->semantic(symbols_); +#ifdef LOGGING + std::cout << "function inlining for " << s->location() << "\n" + << s->to_string() << "\n\n"; +#endif + inline_and_simplify(s->is_procedure()); + s->semantic(symbols_); +#ifdef LOGGING + std::cout << "body after inlining\n" + << s->to_string() << "\n\n"; +#endif + } + } + + int errors = 0; + for(auto& e : symbols_) { + auto& s = e.second; + if(s->kind() == symbolKind::procedure) { + ErrorVisitor v(source_name()); + s->accept(&v); + errors += v.num_errors(); } } return errors; @@ -907,5 +884,3 @@ void Module::check_revpot_mechanism() { kind_ = moduleKind::revpot; } - - diff --git a/modcc/parser.cpp b/modcc/parser.cpp index bb59db9e..ed776735 100644 --- a/modcc/parser.cpp +++ b/modcc/parser.cpp @@ -1643,7 +1643,10 @@ expression_ptr Parser::parse_if() { // handle 'else if {}' case recursively if(token_.type == tok::if_stmt) { - false_branch = parse_if(); + expr_list_type if_block; + auto exp = parse_if(); + if_block.push_back(std::move(exp)); + false_branch = make_expression<BlockExpression>(Location(), std::move(if_block), true); } // we have a closing 'else {}' else if(token_.type == tok::lbrace) { diff --git a/modcc/solvers.cpp b/modcc/solvers.cpp index cd792ab0..a636c9ee 100644 --- a/modcc/solvers.cpp +++ b/modcc/solvers.cpp @@ -876,7 +876,9 @@ public: virtual void visit(IfExpression* e) override { e->condition()->accept(this); e->true_branch()->accept(this); - e->false_branch()->accept(this); + if (e->false_branch()) { + e->false_branch()->accept(this); + } } virtual void visit(IdentifierExpression* e) override { diff --git a/test/unit-modcc/mod_files/test6.mod b/test/unit-modcc/mod_files/test6.mod index 70b84319..e9488998 100644 --- a/test/unit-modcc/mod_files/test6.mod +++ b/test/unit-modcc/mod_files/test6.mod @@ -25,13 +25,16 @@ BREAKPOINT { rates(delta) s0 = s1 * s2 } +FUNCTION iden(x) { + iden = x +} FUNCTION foo(x, y) { LOCAL temp if (x == 3) { foo = 2 * y } else if (x == 4) { - foo = y + foo = y * iden(6 + x) } else { temp = exp(y) foo = temp * x diff --git a/test/unit-modcc/test_parser.cpp b/test/unit-modcc/test_parser.cpp index c2c68a61..4e86bf3c 100644 --- a/test/unit-modcc/test_parser.cpp +++ b/test/unit-modcc/test_parser.cpp @@ -265,8 +265,10 @@ TEST(Parser, parse_if) { EXPECT_NE(s->condition()->is_unary(), nullptr); EXPECT_NE(s->true_branch()->is_block(), nullptr); ASSERT_NE(s->false_branch(), nullptr); - ASSERT_NE(s->false_branch()->is_if(), nullptr); - EXPECT_EQ(s->false_branch()->is_if()->false_branch(), nullptr); + ASSERT_NE(s->false_branch()->is_block(), nullptr); + + auto false_if_branch = s->false_branch()->is_block()->statements().front()->clone(); + EXPECT_EQ(false_if_branch->is_if()->false_branch(), nullptr); } } diff --git a/test/unit-modcc/test_printers.cpp b/test/unit-modcc/test_printers.cpp index bee760b7..de50a5f3 100644 --- a/test/unit-modcc/test_printers.cpp +++ b/test/unit-modcc/test_printers.cpp @@ -200,37 +200,44 @@ TEST(CPrinter, proc_body_const) { TEST(CPrinter, proc_body_inlined) { const char* expected = - " r_0_ = s2[i_]/ 3;\n" - " r_3_ = s1[i_]+ 2;\n" - " if (s1[i_]== 3) {\n" - " r_2_ = 2*r_3_;\n" - " }\n" - " else if (s1[i_]== 4) {\n" - " r_2_ = r_3_;\n" - " }\n" - " else {\n" - " r_5_ = exp(r_3_);\n" - " r_2_ = r_5_*s1[i_];\n" - " }\n" - "\n" - " r_7_ = r_0_/s2[i_];\n" - " r_8_ = log(r_7_);\n" - " r_6_ = 42*r_8_;\n" - " r_1_ = r_0_*r_6_;\n" - " t0 = r_2_*r_1_;\n" - " t1 = exprelr(t0);\n" - " ll0_ = t1+ 2;\n" - " if (ll0_== 3) {\n" - " t2 = 10;\n" - " }\n" - " else if (ll0_== 4) {\n" - " t2 = 5;\n" - " }\n" - " else {\n" - " r_4_ = 148.4131591025766;\n" - " t2 = r_4_*ll0_;\n" - " }\n" - " s2[i_] = t2+ 4;"; + "r_9_=s2[i_]/3;\n" + "r_8_=s1[i_]+2;\n" + "if(s1[i_]==3){\n" + " r_7_=2*r_8_;\n" + "}\n" + "else{\n" + " if(s1[i_]==4){\n" + " r_12_=6+s1[i_];\n" + " r_11_=r_12_;\n" + " r_7_=r_8_*r_11_;\n" + " }\n" + " else{\n" + " r_10_=exp(r_8_);\n" + " r_7_=r_10_*s1[i_];\n" + " }\n" + "}\n" + "r_14_=r_9_/s2[i_];\n" + "r_15_=log(r_14_);\n" + "r_13_=42*r_15_;\n" + "r_6_=r_9_*r_13_;\n" + "t0=r_7_*r_6_;\n" + "t1=exprelr(t0);\n" + "ll0_=t1+2;\n" + "if(ll0_==3){\n" + " t2=10;\n" + "}\n" + "else{\n" + " if(ll0_==4){\n" + " r_18_=6+ll0_;\n" + " r_17_=r_18_;\n" + " t2=5*r_17_;\n" + " }\n" + " else{\n" + " r_16_=148.4131591025766;\n" + " t2=r_16_*ll0_;\n" + " }\n" + "}\n" + "s2[i_]=t2+4;\n"; Module m(io::read_all(DATADIR "/mod_files/test6.mod"), "test6.mod"); Parser p(m, false); -- GitLab