diff --git a/src/macro/MacroBison.yy b/src/macro/MacroBison.yy
index 369157e0d8ea85285c5550add89255fadb4f3017..aa07d7509ee551d5e2868da04f95e836cecfec1a 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<string *> *vector_string_p_val;
 };
 
 %code {
@@ -89,6 +90,7 @@ class MacroDriver;
 %left UMINUS UPLUS EXCLAMATION
 %left LBRACKET
 
+%type <vector_string_p_val> func_args
 %type <mv> 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<string *>(); $$->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 cbdb2533ca32ee5c5a1fd340025dddaddb29e7e7..70d92f1ca41483de9a8f160cd4852e20328b25c6 100644
--- a/src/macro/MacroDriver.cc
+++ b/src/macro/MacroDriver.cc
@@ -19,6 +19,8 @@
 
 #include <cstdlib>
 #include <iostream>
+#include <string>
+#include <regex>
 #include <fstream>
 #include <sstream>
 
@@ -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<string *> &args, const MacroValue *value)
+{
+  auto *smv = dynamic_cast<const StringMV *>(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<StringMV *>(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<const FuncMV *>(env[name]);
+  if (!fmv)
+    throw MacroValue::TypeError("You are using " + name + " as if it were a macro function");
+
+  vector<string *> func_args = fmv->get_args();
+  if (func_args.size() != dynamic_cast<const IntMV *>(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<string *> &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<const FuncMV *>(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 f54524707916ab657681aa00cb3a85d063d257bf..4c285be72e65145a66675a417424d592c403a8f1 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<const MacroValue *> values;
 
+  // The map defining an environment
+  typedef map<string, const MacroValue *> env_t;
+
   //! Environment: maps macro variables to their values
-  map<string, const MacroValue *> env;
+  env_t env;
+
+  //! Environment for function currently being evaluated
+  vector<env_t> 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<string *> &args, const MacroValue *value);
+
+  //! Push function arguments onto func_env stack setting equal to NULL
+  void push_args_into_func_env(const vector<string *> &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 9cacf8f137164b59de544f11920a7800dde6eb5c..4aef91e29714a700522d24dd552da67ded1dc79d 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<const StringMV *>(&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<const FuncMV *>(&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<string>::print() const
   return ss.str();
 }
 
+FuncMV::FuncMV(MacroDriver &driver, vector<string *> &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<const FuncMV *>(&mv);
+  if (mv2 != nullptr)
+    return value + mv2->value;
+
+  const auto *mv3 = dynamic_cast<const StringMV *>(&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<const FuncMV *>(&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<const IntMV *>(*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<string> v;
+  v.push_back(value.toString());
+  return new ArrayMV<string>(driver, v);
+}
diff --git a/src/macro/MacroValue.hh b/src/macro/MacroValue.hh
index 6c997d31d7efba7bcf613e71adeefb5c6410de0a..79e932507694cf95bc46db70940595b68a545fb5 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<string *> args;
+  const StringMV &value;
+public:
+  FuncMV(MacroDriver &driver, vector<string *> &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<string *> &
+  get_args() const
+  {
+    return args;
+  }
+};
+
 //! Represents an array in macro language
 template<typename T>
 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<typename T>
@@ -353,4 +383,11 @@ ArrayMV<T>::length() const noexcept(false)
   return new IntMV(driver, values.size());
 }
 
+template<typename T>
+const MacroValue *
+ArrayMV<T>::at(int i) const noexcept(false)
+{
+  return new_base_value(driver, values[i]);
+}
+
 #endif