From d56a2792ec35f9599296be6c95c18bc2e64bbed2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Sat, 21 May 2022 12:11:12 +0200
Subject: [PATCH] Change behavior of string concatenation binary operator

For (language) consistency, now concatenation binary operators allow
the string to be the right hand side.
For instance
  B + string -> string
it was not accepted before and produced a compilation error
---
 .../ConcatExpressionProcessor.hpp             |  25 +-
 .../utils/BinaryOperatorRegisterForString.cpp |   8 +-
 .../ConcatExpressionProcessorBuilder.hpp      |   9 +-
 ...ASTNodeBinaryOperatorExpressionBuilder.cpp | 341 +++++++++++++++++-
 tests/test_ASTNodeDataTypeBuilder.cpp         |  13 -
 tests/test_ConcatExpressionProcessor.cpp      |  96 +++++
 6 files changed, 456 insertions(+), 36 deletions(-)

diff --git a/src/language/node_processor/ConcatExpressionProcessor.hpp b/src/language/node_processor/ConcatExpressionProcessor.hpp
index 718325641..43b785979 100644
--- a/src/language/node_processor/ConcatExpressionProcessor.hpp
+++ b/src/language/node_processor/ConcatExpressionProcessor.hpp
@@ -4,7 +4,7 @@
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
 
-template <typename B_DataT>
+template <typename A_DataT, typename B_DataT>
 class ConcatExpressionProcessor final : public INodeProcessor
 {
  private:
@@ -25,12 +25,31 @@ class ConcatExpressionProcessor final : public INodeProcessor
     }
   }
 
+  PUGS_INLINE
+  DataVariant
+  _eval(const DataVariant& a, const std::string& b)
+  {
+    static_assert(not std::is_same_v<A_DataT, std::string>, "this case is treated by the other eval function");
+    if constexpr ((std::is_arithmetic_v<A_DataT>)and(not std::is_same_v<A_DataT, bool>)) {
+      return std::to_string(std::get<A_DataT>(a)) + b;
+    } else {
+      std::ostringstream os;
+      os << std::boolalpha << a << b;
+      return os.str();
+    }
+  }
+
  public:
   DataVariant
   execute(ExecutionPolicy& exec_policy)
   {
-    return this->_eval(std::get<std::string>(m_node.children[0]->execute(exec_policy)),
-                       m_node.children[1]->execute(exec_policy));
+    if constexpr (std::is_same_v<std::string, A_DataT>) {
+      return this->_eval(std::get<std::string>(m_node.children[0]->execute(exec_policy)),
+                         m_node.children[1]->execute(exec_policy));
+    } else {
+      return this->_eval(m_node.children[0]->execute(exec_policy),
+                         std::get<std::string>(m_node.children[1]->execute(exec_policy)));
+    }
   }
 
   ConcatExpressionProcessor(ASTNode& node) : m_node{node} {}
diff --git a/src/language/utils/BinaryOperatorRegisterForString.cpp b/src/language/utils/BinaryOperatorRegisterForString.cpp
index cae341bf9..e16c884ed 100644
--- a/src/language/utils/BinaryOperatorRegisterForString.cpp
+++ b/src/language/utils/BinaryOperatorRegisterForString.cpp
@@ -32,13 +32,17 @@ BinaryOperatorRegisterForString::_register_comparisons()
     std::make_shared<BinaryOperatorProcessorBuilder<language::not_eq_op, bool, std::string, std::string>>());
 }
 
-template <typename RHS_T>
+template <typename T>
 void
 BinaryOperatorRegisterForString::_register_concat()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  repository.addBinaryOperator<language::plus_op>(std::make_shared<ConcatExpressionProcessorBuilder<RHS_T>>());
+  repository.addBinaryOperator<language::plus_op>(std::make_shared<ConcatExpressionProcessorBuilder<std::string, T>>());
+  if constexpr (not std::is_same_v<T, std::string>) {
+    repository.addBinaryOperator<language::plus_op>(
+      std::make_shared<ConcatExpressionProcessorBuilder<T, std::string>>());
+  }
 }
 
 BinaryOperatorRegisterForString::BinaryOperatorRegisterForString()
diff --git a/src/language/utils/ConcatExpressionProcessorBuilder.hpp b/src/language/utils/ConcatExpressionProcessorBuilder.hpp
index 8fed35962..e38fec5f4 100644
--- a/src/language/utils/ConcatExpressionProcessorBuilder.hpp
+++ b/src/language/utils/ConcatExpressionProcessorBuilder.hpp
@@ -8,16 +8,19 @@
 
 #include <type_traits>
 
-template <typename B_DataT>
+template <typename A_DataT, typename B_DataT>
 class ConcatExpressionProcessorBuilder final : public IBinaryOperatorProcessorBuilder
 {
+  static_assert(std::is_same_v<A_DataT, std::string> or std::is_same_v<B_DataT, std::string>,
+                "one of the operand types must be an std::string");
+
  public:
   ConcatExpressionProcessorBuilder() = default;
 
   ASTNodeDataType
   getDataTypeOfA() const
   {
-    return ast_node_data_type_from<std::string>;
+    return ast_node_data_type_from<A_DataT>;
   }
 
   ASTNodeDataType
@@ -35,7 +38,7 @@ class ConcatExpressionProcessorBuilder final : public IBinaryOperatorProcessorBu
   std::unique_ptr<INodeProcessor>
   getNodeProcessor(ASTNode& node) const
   {
-    return std::make_unique<ConcatExpressionProcessor<B_DataT>>(node);
+    return std::make_unique<ConcatExpressionProcessor<A_DataT, B_DataT>>(node);
   }
 };
 
diff --git a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
index 175093d61..48a4af3d2 100644
--- a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
+++ b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
@@ -463,15 +463,17 @@ x+y;
       CHECK_AST(data, result);
     }
 
-    SECTION("string concatenate bool")
+    SECTION("string concatenate B")
     {
       std::string_view data = R"(
 "foo"+true;
 )";
 
-      std::string_view result = R"(
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
 (root:ASTNodeListProcessor)
- `-(language::plus_op:ConcatExpressionProcessor<bool>)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, bool>)
      +-(language::literal:"foo":ValueProcessor)
      `-(language::true_kw:ValueProcessor)
 )";
@@ -481,17 +483,18 @@ x+y;
 
     SECTION("string concatenate N")
     {
-      std::string_view data = R"(
+      std::string_view data   = R"(
 let n : N, n=0;
 "foo"+n;
 )";
-
-      std::string_view result = R"(
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
 (root:ASTNodeListProcessor)
  +-(language::eq_op:AffectationProcessor<language::eq_op, unsigned long, long>)
  |   +-(language::name:n:NameProcessor)
  |   `-(language::integer:0:ValueProcessor)
- `-(language::plus_op:ConcatExpressionProcessor<unsigned long>)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, unsigned long>)
      +-(language::literal:"foo":ValueProcessor)
      `-(language::name:n:NameProcessor)
 )";
@@ -505,9 +508,11 @@ let n : N, n=0;
 "foo"+1;
 )";
 
-      std::string_view result = R"(
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
 (root:ASTNodeListProcessor)
- `-(language::plus_op:ConcatExpressionProcessor<long>)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, long>)
      +-(language::literal:"foo":ValueProcessor)
      `-(language::integer:1:ValueProcessor)
 )";
@@ -521,9 +526,11 @@ let n : N, n=0;
 "foo"+1.2;
 )";
 
-      std::string_view result = R"(
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
 (root:ASTNodeListProcessor)
- `-(language::plus_op:ConcatExpressionProcessor<double>)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, double>)
      +-(language::literal:"foo":ValueProcessor)
      `-(language::real:1.2:ValueProcessor)
 )";
@@ -531,6 +538,309 @@ let n : N, n=0;
       CHECK_AST(data, result);
     }
 
+    SECTION("string concatenate R^1")
+    {
+      std::string_view data = R"(
+let x:R^1;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyVector<1ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^2")
+    {
+      std::string_view data = R"(
+let x:R^2;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyVector<2ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^3")
+    {
+      std::string_view data = R"(
+let x:R^3;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyVector<3ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^1x1")
+    {
+      std::string_view data = R"(
+let x:R^1x1;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyMatrix<1ul, 1ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^2x2")
+    {
+      std::string_view data = R"(
+let x:R^2x2;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyMatrix<2ul, 2ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^3x3")
+    {
+      std::string_view data = R"(
+let x:R^3x3;
+"foo"+x;
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<)" +
+                           string_name + R"(, TinyMatrix<3ul, 3ul, double> >)
+     +-(language::literal:"foo":ValueProcessor)
+     `-(language::name:x:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("B concatenate string")
+    {
+      std::string_view data = R"(
+false+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<bool, )" +
+                           string_name + R"( >)
+     +-(language::false_kw:ValueProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("N concatenate string")
+    {
+      std::string_view data   = R"(
+let n : N, n=0;
+n+"foo";
+)";
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ +-(language::eq_op:AffectationProcessor<language::eq_op, unsigned long, long>)
+ |   +-(language::name:n:NameProcessor)
+ |   `-(language::integer:0:ValueProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<unsigned long, )" +
+                           string_name + R"( >)
+     +-(language::name:n:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("Z concatenate string")
+    {
+      std::string_view data = R"(
+1+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<long, )" +
+                           string_name + R"( >)
+     +-(language::integer:1:ValueProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R concatenate string")
+    {
+      std::string_view data = R"(
+1.2+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<double, )" +
+                           string_name + R"( >)
+     +-(language::real:1.2:ValueProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R^1 concatenate string ")
+    {
+      std::string_view data = R"(
+let x:R^1;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyVector<1ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R^2 concatenate string")
+    {
+      std::string_view data = R"(
+let x:R^2;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyVector<2ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R^3 concatenate string")
+    {
+      std::string_view data = R"(
+let x:R^3;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyVector<3ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R^1x1 concatenate string")
+    {
+      std::string_view data = R"(
+let x:R^1x1;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyMatrix<1ul, 1ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("string concatenate R^2x2")
+    {
+      std::string_view data = R"(
+let x:R^2x2;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyMatrix<2ul, 2ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("R^3x3 concatenate string")
+    {
+      std::string_view data = R"(
+let x:R^3x3;
+x+"foo";
+)";
+
+      std::string string_name = demangle(typeid(std::string{}).name());
+      std::string result      = R"(
+(root:ASTNodeListProcessor)
+ `-(language::plus_op:ConcatExpressionProcessor<TinyMatrix<3ul, 3ul, double>, )" +
+                           string_name + R"( >)
+     +-(language::name:x:NameProcessor)
+     `-(language::literal:"foo":ValueProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
     SECTION("string concatenate string")
     {
       std::string_view data = R"(
@@ -542,7 +852,7 @@ let n : N, n=0;
       std::string result = R"(
 (root:ASTNodeListProcessor)
  `-(language::plus_op:ConcatExpressionProcessor<)" +
-                           string_name + R"( >)
+                           string_name + ", " + string_name + R"( >)
      +-(language::literal:"foo":ValueProcessor)
      `-(language::literal:"bar":ValueProcessor)
 )";
@@ -1649,16 +1959,17 @@ n >> m;
                             "undefined binary operator type: string + void");
       }
 
-      SECTION("right string plus")
+      SECTION("right string plus bad lhs")
       {
         auto ast = std::make_unique<ASTNode>();
         ast->set_type<language::plus_op>();
         ast->children.emplace_back(std::make_unique<ASTNode>());
         ast->children.emplace_back(std::make_unique<ASTNode>());
-        ast->children[0]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::int_t>();
+        ast->children[0]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::void_t>();
         ast->children[1]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::string_t>();
 
-        REQUIRE_THROWS_WITH(ASTNodeBinaryOperatorExpressionBuilder{*ast}, "undefined binary operator type: Z + string");
+        REQUIRE_THROWS_WITH(ASTNodeBinaryOperatorExpressionBuilder{*ast},
+                            "undefined binary operator type: void + string");
       }
 
       SECTION("lhs bad minus")
diff --git a/tests/test_ASTNodeDataTypeBuilder.cpp b/tests/test_ASTNodeDataTypeBuilder.cpp
index 1adbb89a1..6d66df083 100644
--- a/tests/test_ASTNodeDataTypeBuilder.cpp
+++ b/tests/test_ASTNodeDataTypeBuilder.cpp
@@ -2339,18 +2339,5 @@ true xor false;
 
       CHECK_AST(data, result);
     }
-
-    SECTION("invalid operands")
-    {
-      std::string_view data = R"(
-1+"string";
-)";
-
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-      auto ast = ASTBuilder::build(input);
-      ASTSymbolTableBuilder{*ast};
-      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined binary operator\n"
-                                                        "note: incompatible operand types Z and string");
-    }
   }
 }
diff --git a/tests/test_ConcatExpressionProcessor.cpp b/tests/test_ConcatExpressionProcessor.cpp
index 7e8d097f9..62d682249 100644
--- a/tests/test_ConcatExpressionProcessor.cpp
+++ b/tests/test_ConcatExpressionProcessor.cpp
@@ -61,23 +61,45 @@ TEST_CASE("ConcatExpressionProcessor", "[language]")
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let n:N, n = 1; let s:string, s = "foo_"; s = s+n;)", "s", std::string{"foo_1"});
   }
 
+  SECTION("N + string")
+  {
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let n:N, n = 1; let s:string, s = "_foo"; s = n+s;)", "s", std::string{"1_foo"});
+  }
+
   SECTION("string + Z")
   {
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+2;)", "s", std::string{"foo_2"});
   }
 
+  SECTION("Z + string")
+  {
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = 2+s;)", "s", std::string{"2_foo"});
+  }
+
   SECTION("string + R")
   {
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+2.4;)", "s",
                                    std::string{"foo_"} + std::to_string(2.4));
   }
 
+  SECTION("R + string")
+  {
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = 2.4+s;)", "s",
+                                   std::to_string(2.4) + std::string{"_foo"});
+  }
+
   SECTION("string + B")
   {
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+(2>1);)", "s", std::string{"foo_true"});
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+(1>2);)", "s", std::string{"foo_false"});
   }
 
+  SECTION("B + string")
+  {
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = (2>1)+s;)", "s", std::string{"true_foo"});
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = (1>2)+s;)", "s", std::string{"false_foo"});
+  }
+
   SECTION("string + R^1")
   {
     std::ostringstream os;
@@ -86,6 +108,14 @@ TEST_CASE("ConcatExpressionProcessor", "[language]")
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1, x = 1; let s:string, s = "foo_"; s = s+x;)", "s", os.str());
   }
 
+  SECTION("R^1 + string")
+  {
+    std::ostringstream os;
+    os << TinyVector<1>{1} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1, x = 1; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
+  }
+
   SECTION("string + R^2")
   {
     std::ostringstream os;
@@ -94,6 +124,14 @@ TEST_CASE("ConcatExpressionProcessor", "[language]")
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2, x = (1,2); let s:string, s = "foo_"; s = s+x;)", "s", os.str());
   }
 
+  SECTION(" R^2 + string")
+  {
+    std::ostringstream os;
+    os << TinyVector<2>{1, 2} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2, x = (1,2); let s:string, s = "_foo"; s = x+s;)", "s", os.str());
+  }
+
   SECTION("string + R^3")
   {
     std::ostringstream os;
@@ -101,4 +139,62 @@ TEST_CASE("ConcatExpressionProcessor", "[language]")
 
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3, x = (1,2,3); let s:string, s = "foo_"; s = s+x;)", "s", os.str());
   }
+
+  SECTION("R^3 + string")
+  {
+    std::ostringstream os;
+    os << TinyVector<3>{1, 2, 3} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3, x = (1,2,3); let s:string, s = "_foo"; s = x+s;)", "s", os.str());
+  }
+
+  SECTION("string + R^1x1")
+  {
+    std::ostringstream os;
+    os << "foo_" << TinyMatrix<1>{1};
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1x1, x = 1; let s:string, s = "foo_"; s = s+x;)", "s", os.str());
+  }
+
+  SECTION("R^1x1 + string")
+  {
+    std::ostringstream os;
+    os << TinyMatrix<1>{1} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1x1, x = 1; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
+  }
+
+  SECTION("string + R^2x2")
+  {
+    std::ostringstream os;
+    os << "foo_" << TinyMatrix<2>{1, 2, 3, 4};
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2x2, x = (1,2,3,4); let s:string, s = "foo_"; s = s+x;)", "s", os.str());
+  }
+
+  SECTION(" R^2x2 + string")
+  {
+    std::ostringstream os;
+    os << TinyMatrix<2>{1, 2, 3, 4} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2x2, x = (1,2,3,4); let s:string, s = "_foo"; s = x+s;)", "s", os.str());
+  }
+
+  SECTION("string + R^3x3")
+  {
+    std::ostringstream os;
+    os << "foo_" << TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3x3, x = (1,2,3,4,5,6,7,8,9); let s:string, s = "foo_"; s = s+x;)", "s",
+                                   os.str());
+  }
+
+  SECTION("R^3x3 + string")
+  {
+    std::ostringstream os;
+    os << TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9} << "_foo";
+
+    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3x3, x = (1,2,3,4,5,6,7,8,9); let s:string, s = "_foo"; s = x+s;)", "s",
+                                   os.str());
+  }
 }
-- 
GitLab