Support C++17 fold expressions

Unary left fold expressions are not supported currently.  Also the same
restrictions that apply to other expressions apply here too.

Fixes #2868
diff --git a/CHANGES.current b/CHANGES.current
index 4280ded..dff98f2 100644
--- a/CHANGES.current
+++ b/CHANGES.current
@@ -8,6 +8,9 @@
 ===========================
 
 2024-05-15: olly
+	    #2868 Support C++17 fold expressions.
+
+2024-05-15: olly
 	    #2876 Report error if parser stack depth exceeded.  Previously SWIG
 	    would quietly exit with status 0 in this situation.
 
diff --git a/Doc/Manual/CPlusPlus17.html b/Doc/Manual/CPlusPlus17.html
index a234666..b07a410 100644
--- a/Doc/Manual/CPlusPlus17.html
+++ b/Doc/Manual/CPlusPlus17.html
@@ -17,6 +17,7 @@
 <li><a href="#CPlusPlus17_nested_namespaces">Nested namespace definitions</a>
 <li><a href="#CPlusPlus17_u8_char_literals">UTF-8 character literals</a>
 <li><a href="#CPlusPlus17_hexadecimal_floating_literals">Hexadecimal floating literals</a>
+<li><a href="#CPlusPlus17_fold_expressions">Fold expressions</a>
 </ul>
 <li><a href="#CPlusPlus17_standard_library_changes">Standard library changes</a>
 </ul>
@@ -102,6 +103,16 @@
 </pre>
 </div>
 
+<H3><a name="CPlusPlus17_fold_expressions">9.2.4 Fold expressions</a></H3>
+
+
+<p>
+C++17 added template fold expressions.  SWIG 4.3.0 and later support
+parsing these with a few restrictions.  Unary left fold expressions are
+not supported currently.  Also the same restrictions that apply to other
+expressions apply here too.
+</p>
+
 <H2><a name="CPlusPlus17_standard_library_changes">9.3 Standard library changes</a></H2>
 
 
diff --git a/Examples/test-suite/cpp17_enable_if_t.i b/Examples/test-suite/cpp17_enable_if_t.i
index a6695bc..88d3d6f 100644
--- a/Examples/test-suite/cpp17_enable_if_t.i
+++ b/Examples/test-suite/cpp17_enable_if_t.i
@@ -87,3 +87,32 @@
 //  xxx<TestStruct>(); // compilation error
 }
 %}
+
+// Check fold expressions parse (#2868):
+#define FOLD_EXPR_TEST(OP, FUNC) \
+  template< \
+    typename... Ts, \
+    typename R = typename std::common_type_t<Ts...>, \
+    std::enable_if_t< \
+        (std::is_same_v<typename std::decay_t<Ts>,HalfInt> OP ...) \
+        && (std::is_constructible_v<HalfInt, R> \
+        || std::is_convertible_v<R, HalfInt>) \
+      >* = nullptr \
+  > \
+  constexpr inline R FUNC(const Ts&... t) { return std::min(static_cast<R>(t)...); }
+FOLD_EXPR_TEST(+, f1)
+FOLD_EXPR_TEST(-, f2)
+FOLD_EXPR_TEST(*, f3)
+FOLD_EXPR_TEST(/, f4)
+FOLD_EXPR_TEST(%, f5)
+FOLD_EXPR_TEST(&, f6)
+FOLD_EXPR_TEST(|, f7)
+FOLD_EXPR_TEST(^, f8)
+FOLD_EXPR_TEST(<<, f9)
+FOLD_EXPR_TEST(>>, f10)
+FOLD_EXPR_TEST(&&, f11)
+FOLD_EXPR_TEST(||, f12)
+FOLD_EXPR_TEST(==, f13)
+FOLD_EXPR_TEST(!=, f14)
+FOLD_EXPR_TEST(>=, f15)
+FOLD_EXPR_TEST(<=, f16)
diff --git a/Source/CParse/parser.y b/Source/CParse/parser.y
index 1ce7d3c..fcd146f 100644
--- a/Source/CParse/parser.y
+++ b/Source/CParse/parser.y
@@ -6907,6 +6907,100 @@
 		 $$.val = NewStringf("%s <= %s", COMPOUND_EXPR_VAL($lhs), COMPOUND_EXPR_VAL($rhs));
 		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
 	       }
+
+	       // C++17 fold expressions.
+	       //
+	       // We don't handle unary left fold currently, since the obvious
+	       // approach introduces shift/reduce conflicts.  (Binary folds
+	       // should be handled by composition of expressions.)
+	       //
+	       // Fold expressions using the following operators are not
+	       // currently handled (because we don't actually seem to handle
+	       // these operators in expressions at all!):
+	       //
+	       // = += -= *= /= %= ^= &= |= <<= >>= , .* ->*.
+	       | expr[lhs] PLUS ELLIPSIS {
+		 $$.val = NewStringf("%s+...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] MINUS ELLIPSIS {
+		 $$.val = NewStringf("%s-...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] STAR ELLIPSIS {
+		 $$.val = NewStringf("%s*...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] SLASH ELLIPSIS {
+		 $$.val = NewStringf("%s/...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] MODULO ELLIPSIS {
+		 $$.val = NewStringf("%s%%...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] AND ELLIPSIS {
+		 $$.val = NewStringf("%s&...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] OR ELLIPSIS {
+		 $$.val = NewStringf("%s|...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] XOR ELLIPSIS {
+		 $$.val = NewStringf("%s^...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] LSHIFT ELLIPSIS {
+		 $$.val = NewStringf("%s << ...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] RSHIFT ELLIPSIS {
+		 $$.val = NewStringf("%s >> ...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = promote_type($lhs.type);
+	       }
+	       | expr[lhs] LAND ELLIPSIS {
+		 $$.val = NewStringf("%s&&...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       | expr[lhs] LOR ELLIPSIS {
+		 $$.val = NewStringf("%s||...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       | expr[lhs] EQUALTO ELLIPSIS {
+		 $$.val = NewStringf("%s==...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       | expr[lhs] NOTEQUALTO ELLIPSIS {
+		 $$.val = NewStringf("%s!=...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       /* Trying to parse `>` in the general case results in conflicts
+		* in the parser, but all user-reported cases are actually inside
+		* parentheses and we can handle that case.
+		*/
+	       | LPAREN expr[lhs] GREATERTHAN ELLIPSIS RPAREN {
+		 $$.val = NewStringf("(%s > ...)", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       /* Similarly for `<` except trying to handle exprcompound on the
+		* left side gives a shift/reduce conflict, so also restrict
+		* handling to non-compound subexpressions there.  Again this
+		* covers all user-reported cases.
+		*/
+	       | LPAREN exprsimple[lhs] LESSTHAN ELLIPSIS RPAREN {
+		 $$.val = NewStringf("(%s < %s)", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       | expr[lhs] GREATERTHANOREQUALTO ELLIPSIS {
+		 $$.val = NewStringf("%s >= ...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+	       | expr[lhs] LESSTHANOREQUALTO ELLIPSIS {
+		 $$.val = NewStringf("%s <= ...", COMPOUND_EXPR_VAL($lhs));
+		 $$.type = cparse_cplusplus ? T_BOOL : T_INT;
+	       }
+
 	       | expr[lhs] LESSEQUALGREATER expr[rhs] {
 		 $$.val = NewStringf("%s <=> %s", COMPOUND_EXPR_VAL($lhs), COMPOUND_EXPR_VAL($rhs));
 		 /* `<=>` returns one of `std::strong_ordering`,
OSZAR »