diff --git a/src/macro/MacroBison.yy b/src/macro/MacroBison.yy
index a2b245836d51027e10872d9f6889cb846484228a..41359488ef6d5bbb2a672f5e92efeb9c2dfe6b25 100644
--- a/src/macro/MacroBison.yy
+++ b/src/macro/MacroBison.yy
@@ -84,7 +84,7 @@ class MacroDriver;
 
 %type <vector<string>> func_args
 %type <MacroValuePtr> expr
-%type <vector<MacroValuePtr>> comma_expr
+%type <vector<MacroValuePtr>> comma_expr tuple_comma_expr
 %%
 
 %start statement_list_or_nothing;
@@ -136,6 +136,8 @@ expr : INTEGER
        { $$ = make_shared<IntMV>($1); }
      | STRING
        { $$ = make_shared<StringMV>(driver.replace_vars_in_str($1)); }
+     | LPAREN tuple_comma_expr RPAREN
+       { $$ = make_shared<TupleMV>($2); }
      | NAME
        {
          try
@@ -213,6 +215,16 @@ comma_expr : %empty
              { $1.push_back($3); $$ = $1; }
            ;
 
+tuple_comma_expr : %empty
+                   { $$ = vector<MacroValuePtr>{}; } // Empty array
+                 | expr COMMA
+                   { $$ = vector<MacroValuePtr>{$1}; }
+                 | expr COMMA expr
+                   { $$ = vector<MacroValuePtr>{$1, $3}; }
+                 | tuple_comma_expr COMMA expr
+                   { $1.push_back($3); $$ = $1; }
+                 ;
+
 %%
 
 void
diff --git a/src/macro/MacroValue.cc b/src/macro/MacroValue.cc
index 4d1f0ca05f146ea96f3483bffc9c36cf36ee1ca3..739ca7954dd80913431c4276b50116e671520481 100644
--- a/src/macro/MacroValue.cc
+++ b/src/macro/MacroValue.cc
@@ -531,3 +531,110 @@ ArrayMV::range(const MacroValuePtr &mv1, const MacroValuePtr &mv2) noexcept(fals
     result.push_back(make_shared<IntMV>(v1));
   return make_shared<ArrayMV>(result);
 }
+
+TupleMV::TupleMV(vector<MacroValuePtr> values_arg) : values{move(values_arg)}
+{
+}
+
+shared_ptr<IntMV>
+TupleMV::is_equal(const MacroValuePtr &mv)
+{
+  auto mv2 = dynamic_pointer_cast<TupleMV>(mv);
+  if (!mv2 || values.size() != mv2->values.size())
+    return make_shared<IntMV>(0);
+
+  auto it = values.cbegin();
+  auto it2 = mv2->values.cbegin();
+  while (it != values.cend())
+    {
+      if ((*it)->is_different(*it2)->value)
+        return make_shared<IntMV>(0);
+      ++it;
+      ++it2;
+    }
+  return make_shared<IntMV>(1);
+}
+
+MacroValuePtr
+TupleMV::subscript(const MacroValuePtr &mv) noexcept(false)
+{
+  vector<MacroValuePtr> result;
+
+  auto copy_element = [&](int i) {
+    if (i < 1 || i > static_cast<int>(values.size()))
+      throw OutOfBoundsError();
+    result.push_back(values[i - 1]);
+  };
+
+  auto mv2 = dynamic_pointer_cast<IntMV>(mv);
+  auto mv3 = dynamic_pointer_cast<TupleMV>(mv);
+
+  if (mv2)
+    copy_element(mv2->value);
+  else if (mv3)
+    for (auto &v : mv3->values)
+      {
+        auto v2 = dynamic_pointer_cast<IntMV>(v);
+        if (!v2)
+          throw TypeError("Expression inside [] must be an integer or an integer array");
+        copy_element(v2->value);
+      }
+  else
+    throw TypeError("Expression inside [] must be an integer or an integer array");
+
+  if (result.size() > 1 || result.size() == 0)
+    return make_shared<TupleMV>(result);
+  else
+    return result[0];
+}
+
+string
+TupleMV::toString()
+{
+  ostringstream ss;
+  bool print_comma = false;
+  ss << "(";
+  for (auto &v : values)
+    {
+      if (print_comma)
+        ss << ", ";
+      else
+        print_comma = true;
+      ss << v->toString();
+    }
+  ss << ")";
+  return ss.str();
+}
+
+shared_ptr<IntMV>
+TupleMV::length() noexcept(false)
+{
+  return make_shared<IntMV>(values.size());
+}
+
+string
+TupleMV::print()
+{
+  ostringstream ss;
+  ss << "(";
+  for (auto it = values.begin();
+       it != values.end(); it++)
+    {
+      if (it != values.begin())
+        ss << ", ";
+
+      ss << (*it)->print();
+    }
+  ss << ")";
+  return ss.str();
+}
+
+shared_ptr<IntMV>
+TupleMV::in(const MacroValuePtr &mv) noexcept(false)
+{
+  for (auto &v : values)
+    if (v->is_equal(mv)->value)
+      return make_shared<IntMV>(1);
+
+  return make_shared<IntMV>(0);
+}
diff --git a/src/macro/MacroValue.hh b/src/macro/MacroValue.hh
index 35c9c934d956bc75fb18db07f0ee01bb237b90cb..051ec6143b44c15c369d5ef1162594b62e04a0fe 100644
--- a/src/macro/MacroValue.hh
+++ b/src/macro/MacroValue.hh
@@ -212,4 +212,24 @@ public:
   static shared_ptr<ArrayMV> range(const MacroValuePtr &mv1, const MacroValuePtr &mv2) noexcept(false);
 };
 
+//! Represents a tuple value in macro language
+class TupleMV : public MacroValue
+{
+public:
+  TupleMV(vector<MacroValuePtr> values_arg);
+
+  //! Underlying vector
+  const vector<MacroValuePtr> values;
+
+  shared_ptr<IntMV> is_equal(const MacroValuePtr &mv) override;
+  //! Subscripting operator
+  /*! Argument must be an ArrayMV<int>. Indexes begin at 1. Returns a StringMV. */
+  MacroValuePtr subscript(const MacroValuePtr &mv) noexcept(false) override;
+  //! Returns underlying string value
+  string toString() override;
+  string print() override;
+  shared_ptr<IntMV> length() noexcept(false) override;
+  shared_ptr<IntMV> in(const MacroValuePtr &mv) noexcept(false) override;
+};
+
 #endif