diff --git a/modcc/expression.cpp b/modcc/expression.cpp index 9ba6c58ea1c1a3716080163456688f8792db1981..51e8217b5147109af2dfff553a71889f25b11ef4 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 1da38c35247e7cb3fc0f2cf5f2c49cfa714e7baa..4d131b44c81c5f2aeddcb2ddab4abe8f8d4ae74f 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 a6b443306f53fc411d0b952a54ac67b586e151b8..a17329d9dc6e9a63ce0ddc7152526486779a0a8d 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 b1ea95a2f3777d7c45869728406883074af02862..cfb04b7f4f47866af0072aac6a58a0ea446c8eda 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 f238a684529c53ae9fcbe2e1d0da86d17ac9f0a6..8a9e63113f48eb1878d1fabbbbf98733b8511e6c 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 c302e36f2be73b71f2e9b1fe29b8d27bcb78c54a..34e37a5c038dea86f8e92a7356bc3a4002f32810 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 6cf466361032b5eaad20d8099321fd65b4d169fb..ae1db14dfa38aeadd54e00882e1540279b6bf6e6 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 279fc357452e905693f940cd8c2f3b112ebf8869..9938a8636448262e0d2dfc16669ea27362f3fcd2 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 bb59db9ebcd1fb3b01801cc56d96c56a076ab0bb..ed776735c544ecd1092b1575ff0a7ce75d550509 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 cd792ab092812af40ca33b2961d398611f61c302..a636c9eef246b61c2b9d2cb900a28458048048d7 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 70b8431997f8fc9c8c3d9deb6a5d24ec022e1c50..e94889985683a998076a3c577026d52002a44927 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 c2c68a61ab5bef75341bbd856e0818a11d51e3b7..4e86bf3c6e60f742d7d2d2ff33e2001fcd665959 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 bee760b7e01cc4868e52367ae7bf2cedebcbfc5b..de50a5f3423cff7f374111e14b951b498d06118c 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);