diff --git a/src/macro/MacroValue.cc b/src/macro/MacroValue.cc
index 28484663a9de0ec0602897302aaec9d6e187af3a..73695d9493b0fdfd283702438b6292b577aab708 100644
--- a/src/macro/MacroValue.cc
+++ b/src/macro/MacroValue.cc
@@ -428,6 +428,67 @@ ArrayMV::minus(const MacroValuePtr &mv) noexcept(false)
   return make_shared<ArrayMV>(new_values);
 }
 
+MacroValuePtr
+ArrayMV::times(const MacroValuePtr &mv) noexcept(false)
+{
+  auto mv2 = dynamic_pointer_cast<ArrayMV>(mv);
+  if (!mv2)
+    throw TypeError("Type mismatch for operands of * operator");
+
+  vector<MacroValuePtr> new_values;
+  for (auto &itl : values)
+    for (auto &itr : mv2->values)
+      {
+        vector<MacroValuePtr> new_tuple;
+        auto lmvi = dynamic_pointer_cast<IntMV>(itl);
+        if (lmvi)
+          new_tuple.push_back(lmvi);
+        else
+          {
+            auto lmvs = dynamic_pointer_cast<StringMV>(itl);
+            if (lmvs)
+              new_tuple.push_back(lmvs);
+            else
+              {
+                auto lmvt = dynamic_pointer_cast<TupleMV>(itl);
+                if (lmvt)
+                  new_tuple = lmvt->values;
+                else
+                  {
+                    cerr << "ArrayMV::times: unsupported type on lhs" << endl;
+                    exit(EXIT_FAILURE);
+                  }
+              }
+          }
+
+        auto rmvi = dynamic_pointer_cast<IntMV>(itr);
+        if (rmvi)
+          new_tuple.push_back(rmvi);
+        else
+          {
+            auto rmvs = dynamic_pointer_cast<StringMV>(itr);
+            if (rmvs)
+              new_tuple.push_back(rmvs);
+            else
+              {
+                auto rmvt = dynamic_pointer_cast<TupleMV>(itr);
+                if (rmvt)
+                  for (auto &tit : rmvt->values)
+                    new_tuple.push_back(tit);
+                 else
+                  {
+                    cerr << "ArrayMV::times: unsupported type on rhs" << endl;
+                    exit(EXIT_FAILURE);
+                  }
+              }
+          }
+
+        new_values.push_back(make_shared<TupleMV>(new_tuple));
+      }
+
+  return make_shared<ArrayMV>(new_values);
+}
+
 shared_ptr<IntMV>
 ArrayMV::is_equal(const MacroValuePtr &mv)
 {
diff --git a/src/macro/MacroValue.hh b/src/macro/MacroValue.hh
index 810d48d08c29efa88ea3f943cab0490a2dcca0c8..5e35292f66657569761e11ac35253d43c5fd3c00 100644
--- a/src/macro/MacroValue.hh
+++ b/src/macro/MacroValue.hh
@@ -216,6 +216,8 @@ public:
   static shared_ptr<ArrayMV> range(const MacroValuePtr &mv1, const MacroValuePtr &mv2) noexcept(false);
   shared_ptr<ArrayMV> set_union(const MacroValuePtr &mvp) noexcept(false) override;
   shared_ptr<ArrayMV> set_intersection(const MacroValuePtr &mvp) noexcept(false) override;
+  // Computes the Cartesian product of two sets
+  MacroValuePtr times(const MacroValuePtr &mv) noexcept(false) override;
 };
 
 //! Represents a tuple value in macro language