From 244552393baec541e0d02a2c378a21a6c9409989 Mon Sep 17 00:00:00 2001 From: Houtan Bastani Date: Fri, 29 Jun 2018 16:32:05 +0200 Subject: [PATCH] macroprocessor: add support for functions that return strings. #3 --- src/macro/MacroBison.yy | 21 ++++++- src/macro/MacroDriver.cc | 118 ++++++++++++++++++++++++++++++++++++++- src/macro/MacroDriver.hh | 26 ++++++++- src/macro/MacroValue.cc | 97 ++++++++++++++++++++++++++++++-- src/macro/MacroValue.hh | 39 ++++++++++++- 5 files changed, 291 insertions(+), 10 deletions(-) diff --git a/src/macro/MacroBison.yy b/src/macro/MacroBison.yy index 369157e0..aa07d750 100644 --- a/src/macro/MacroBison.yy +++ b/src/macro/MacroBison.yy @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017 Dynare Team + * Copyright (C) 2008-2018 Dynare Team * * This file is part of Dynare. * @@ -50,6 +50,7 @@ class MacroDriver; string *string_val; int int_val; const MacroValue *mv; + vector *vector_string_p_val; }; %code { @@ -89,6 +90,7 @@ class MacroDriver; %left UMINUS UPLUS EXCLAMATION %left LBRACKET +%type func_args %type expr array_expr %% @@ -124,12 +126,25 @@ statement : expr { driver.printvars(@$, true); } | ECHOMACROVARS LPAREN SAVE RPAREN { out << driver.printvars(@$, false); } + | DEFINE NAME LPAREN func_args { driver.push_args_into_func_env(*$4); } RPAREN EQUAL expr + { + TYPERR_CATCH(driver.set_string_function(*$2, *$4, $8), @$); + driver.pop_func_env(); + delete $2; + delete $4; + } + ; + +func_args : NAME + { $$ = new vector(); $$->push_back($1); } + | func_args COMMA NAME + { $$->push_back($3); } ; expr : INTEGER { $$ = new IntMV(driver, $1); } | STRING - { $$ = new StringMV(driver, *$1); delete $1; } + { $$ = new StringMV(driver, driver.replace_vars_in_str(*$1)); delete $1; } | NAME { try @@ -142,6 +157,8 @@ expr : INTEGER } delete $1; } + | NAME LPAREN array_expr RPAREN + { TYPERR_CATCH($$ = driver.eval_string_function(*$1, $3), @$); delete $1; } | LENGTH LPAREN array_expr RPAREN { TYPERR_CATCH($$ = $3->length(), @$); } | LPAREN expr RPAREN diff --git a/src/macro/MacroDriver.cc b/src/macro/MacroDriver.cc index cbdb2533..70d92f1c 100644 --- a/src/macro/MacroDriver.cc +++ b/src/macro/MacroDriver.cc @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include @@ -87,6 +89,113 @@ MacroDriver::error(const Macro::parser::location_type &l, const string &m) const exit(EXIT_FAILURE); } +string +MacroDriver::replace_vars_in_str(const string &s) const +{ + if (s.find("@") == string::npos) + return string(s); + + string retval(s); + smatch name; + string name_str ("[A-Za-z_][A-Za-z0-9_]*"); + regex name_regex (name_str); // Matches NAME + regex macro_regex ("@\\s*\\{\\s*" + name_str + "\\s*\\}"); // Matches @{NAME} with potential whitespace + for(sregex_iterator it = sregex_iterator(s.begin(), s.end(), macro_regex); + it != std::sregex_iterator(); ++it) + { + string macro(it->str()); + regex_search(macro, name, name_regex); + try + { + const MacroValue *mv; + bool found_in_func_env = false; + for (unsigned i = func_env.size(); i-- > 0;) + { + auto it = func_env[i].find(name.str()); + if (it != func_env[i].end()) + { + found_in_func_env = true; + mv = it->second; + } + } + + if (!found_in_func_env) + mv = get_variable(name.str()); + + if (mv != nullptr) + { + // mv will equal nullptr if we have + // @#define y = 1 + // @#define func(y) = @{y} + // In this case we don't want @{y} to be replaced by its value in the environment + size_t index = retval.find(macro); + retval.replace(index, macro.length(), mv->toString()); + } + } + catch (UnknownVariable &) + { + // don't replace if name not defined + } + } + return retval; +} + +void +MacroDriver::set_string_function(const string &name, vector &args, const MacroValue *value) +{ + auto *smv = dynamic_cast(value); + if (!smv) + throw MacroValue::TypeError("The definition of a macro function must evaluate to a string"); + + env[name] = new FuncMV(*this, args, *(const_cast(smv))); +} + +const StringMV * +MacroDriver::eval_string_function(const string &name, const MacroValue *args) +{ + auto it = env.find(name); + if (it == env.end()) + throw UnknownVariable(name); + + const auto *fmv = dynamic_cast(env[name]); + if (!fmv) + throw MacroValue::TypeError("You are using " + name + " as if it were a macro function"); + + vector func_args = fmv->get_args(); + if (func_args.size() != dynamic_cast(args->length())->get_int_value()) + { + cerr << "Macroprocessor: The evaluation of: " << name << " could not be completed" << endl + << "because the number of arguments provided is different than the number of" << endl + << "arguments used in its definition" << endl; + exit(EXIT_FAILURE); + } + + int i = 0; + env_t func_env_map; + for (const auto it : func_args) + func_env_map[*it] = args->at(i++); + + func_env.push_back(func_env_map); + StringMV *smv = new StringMV(*this, replace_vars_in_str(fmv->toString())); + pop_func_env(); + return smv; +} + +void +MacroDriver::push_args_into_func_env(const vector &args) +{ + env_t func_env_map; + for (const auto it : args) + func_env_map[*it] = NULL; + func_env.push_back(func_env_map); +} + +void +MacroDriver::pop_func_env() +{ + func_env.pop_back(); +} + void MacroDriver::set_variable(const string &name, const MacroValue *value) { @@ -217,7 +326,14 @@ MacroDriver::printvars(const Macro::parser::location_type &l, const bool tostdou cout << "Macroprocessor: Printing macro variable values from " << file << " at line " << l.begin.line << endl; for (const auto & it : env) - cout << " " << it.first << " = " << it.second->print() << endl; + { + cout << " "; + const auto *fmv = dynamic_cast(it.second); + if (!fmv) + cout << it.first << " = " << it.second->print() << endl; + else + cout << it.first << it.second->print() << endl; + } cout << endl; return ""; } diff --git a/src/macro/MacroDriver.hh b/src/macro/MacroDriver.hh index f5452470..4c285be7 100644 --- a/src/macro/MacroDriver.hh +++ b/src/macro/MacroDriver.hh @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017 Dynare Team + * Copyright (C) 2008-2018 Dynare Team * * This file is part of Dynare. * @@ -158,8 +158,14 @@ private: //! Stores all created macro values set values; + // The map defining an environment + typedef map env_t; + //! Environment: maps macro variables to their values - map env; + env_t env; + + //! Environment for function currently being evaluated + vector func_env; //! Stack used to keep track of (possibly nested) loops //! First element is loop variable name, second is the array over which iteration is done, and third is subscript to be used by next call of iter_loop() (beginning with 0) */ @@ -210,6 +216,22 @@ public: //! Set a variable void set_variable(const string &name, const MacroValue *value); + //! Replace "@{x}" with the value of x (if it exists in the environment) in a string + //! Check for variable existence first in func_env before checking in env + string replace_vars_in_str(const string &s) const; + + //! Set a function with arguments + void set_string_function(const string &name, vector &args, const MacroValue *value); + + //! Push function arguments onto func_env stack setting equal to NULL + void push_args_into_func_env(const vector &args); + + //! Remove last entry in func_env vector + void pop_func_env(); + + //! Evaluate a function with arguments + const StringMV *eval_string_function(const string &name, const MacroValue *args); + //! Get a variable /*! Returns a newly allocated value (clone of the value stored in environment). */ const MacroValue *get_variable(const string &name) const noexcept(false); diff --git a/src/macro/MacroValue.cc b/src/macro/MacroValue.cc index 9cacf8f1..4aef91e2 100644 --- a/src/macro/MacroValue.cc +++ b/src/macro/MacroValue.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017 Dynare Team + * Copyright (C) 2008-2018 Dynare Team * * This file is part of Dynare. * @@ -113,6 +113,12 @@ MacroValue::length() const noexcept(false) throw TypeError("Length not supported for this type"); } +const MacroValue * +MacroValue::at(int i) const noexcept(false) +{ + throw TypeError("Length not supported for this type"); +} + const MacroValue * MacroValue::append(const MacroValue *mv) const noexcept(false) { @@ -351,9 +357,14 @@ const MacroValue * StringMV::operator+(const MacroValue &mv) const noexcept(false) { const auto *mv2 = dynamic_cast(&mv); - if (mv2 == nullptr) - throw TypeError("Type mismatch for operands of + operator"); - return new StringMV(driver, value + mv2->value); + if (mv2 != nullptr) + return new StringMV(driver, value + mv2->value); + + const auto *mv3 = dynamic_cast(&mv); + if (mv3 != nullptr) + return new StringMV(driver, value + mv3->toString()); + + throw TypeError("Type mismatch for operands of + operator"); } const MacroValue * @@ -479,3 +490,81 @@ ArrayMV::print() const return ss.str(); } +FuncMV::FuncMV(MacroDriver &driver, vector &args_arg, StringMV &value_arg) : + MacroValue(driver), args(args_arg), value(value_arg) +{ +} + +FuncMV::~FuncMV() += default; + +const MacroValue * +FuncMV::operator+(const MacroValue &mv) const noexcept(false) +{ + const auto *mv2 = dynamic_cast(&mv); + if (mv2 != nullptr) + return value + mv2->value; + + const auto *mv3 = dynamic_cast(&mv); + if (mv3 != nullptr) + return value + *mv3; + + throw TypeError("Type mismatch for operands of + operator"); +} + +const MacroValue * +FuncMV::operator==(const MacroValue &mv) const noexcept(false) +{ + const auto *mv2 = dynamic_cast(&mv); + if (mv2 == nullptr) + return new IntMV(driver, 0); + + if (value != mv2->value) + return new IntMV(driver, 0); + + if (args.size() == mv2->args.size()) + for (int i = 0; i < args.size(); i++) + if (args[i] != mv2->args[i]) + return new IntMV(driver, 0); + + return new IntMV(driver, 1); +} + +const MacroValue * +FuncMV::operator!=(const MacroValue &mv) const noexcept(false) +{ + if (dynamic_cast(*this == mv)->value == 1) + return new IntMV(driver, 0); + return new IntMV(driver, 1); +} + +string +FuncMV::toString() const +{ + return value.toString(); +} + +string +FuncMV::print() const +{ + bool comma_flag = false; + string retval = "("; + for (const auto it : args) + { + if (comma_flag) + retval += ", "; + retval += *it; + comma_flag = true; + } + retval += ")"; + return retval + " = '" + value.toString() + "'"; +} + +const MacroValue * +FuncMV::toArray() const +{ + // COMEBACK + vector v; + v.push_back(value.toString()); + return new ArrayMV(driver, v); +} diff --git a/src/macro/MacroValue.hh b/src/macro/MacroValue.hh index 6c997d31..79e93250 100644 --- a/src/macro/MacroValue.hh +++ b/src/macro/MacroValue.hh @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017 Dynare Team + * Copyright (C) 2008-2018 Dynare Team * * This file is part of Dynare. * @@ -98,6 +98,8 @@ public: virtual const MacroValue *toArray() const = 0; //! Gets length virtual const MacroValue *length() const noexcept(false); + //! Returns element at location i + virtual const MacroValue *at(int i) const noexcept(false); //! Appends value at the end of an array /*! The argument must be an array. */ virtual const MacroValue *append(const MacroValue *array) const noexcept(false); @@ -116,6 +118,7 @@ public: class IntMV : public MacroValue { friend class StringMV; + friend class FuncMV; friend class MacroDriver; private: //! Underlying integer value @@ -201,6 +204,32 @@ public: const MacroValue *in(const MacroValue *array) const noexcept(false) override; }; +class FuncMV : public MacroValue +{ + friend class MacroDriver; +private: + //! Function args & body + const vector args; + const StringMV &value; +public: + FuncMV(MacroDriver &driver, vector &args, StringMV &value_arg); + + ~FuncMV() override; + + //! Computes string concatenation + const MacroValue *operator+(const MacroValue &mv) const noexcept(false) override; + const MacroValue *operator==(const MacroValue &mv) const noexcept(false) override; + const MacroValue *operator!=(const MacroValue &mv) const noexcept(false) override; + string toString() const override; + string print() const override; + const MacroValue *toArray() const override; + inline const vector & + get_args() const + { + return args; + } +}; + //! Represents an array in macro language template class ArrayMV : public MacroValue @@ -236,6 +265,7 @@ public: const MacroValue *toArray() const override; //! Gets length const MacroValue *length() const noexcept(false) override; + const MacroValue *at(int i) const noexcept(false) override; }; template @@ -353,4 +383,11 @@ ArrayMV::length() const noexcept(false) return new IntMV(driver, values.size()); } +template +const MacroValue * +ArrayMV::at(int i) const noexcept(false) +{ + return new_base_value(driver, values[i]); +} + #endif