diff --git a/MultiversalDiplomacy/Model/Regex.cs b/MultiversalDiplomacy/Model/Regex.cs
index e79bc09..d2eaae3 100644
--- a/MultiversalDiplomacy/Model/Regex.cs
+++ b/MultiversalDiplomacy/Model/Regex.cs
@@ -5,6 +5,11 @@ using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacy.Model;
+///
+/// This class defines the regular expressions that are used to build up larger expressions for matching orders
+/// and other script inputs. It also provides helper functions to extract the captured order elements as tuples,
+/// which function as the structured intermediate representation between raw user input and full Order objects.
+///
public class OrderRegex(World world)
{
public const string Type = "(A|F|Army|Fleet)";
@@ -25,6 +30,8 @@ public class OrderRegex(World world)
public const string MoveVerb = "(-|(?:->)|(?:=>)|(?:attack(?:s)?)|(?:move(?:s)?(?: to)?))";
+ public const string SupportVerb = "(s|support|supports)";
+
public const string ViaConvoy = "(convoy|via convoy|by convoy)";
public Regex Hold => new(
@@ -81,6 +88,39 @@ public class OrderRegex(World world)
match.Groups[12].Value,
match.Groups[13].Value);
+ public Regex SupportHold => new(
+ $"^{UnitSpec} {SupportVerb} {UnitSpec}$",
+ RegexOptions.IgnoreCase);
+
+ public static (
+ string type,
+ string timeline,
+ string province,
+ string location,
+ string turn,
+ string supportVerb,
+ string targetType,
+ string targetTimeline,
+ string targetProvince,
+ string targetLocation,
+ string targetTurn)
+ ParseSupportHold(Match match) => (
+ match.Groups[1].Value,
+ match.Groups[2].Value,
+ match.Groups[3].Value,
+ match.Groups[4].Length > 0
+ ? match.Groups[4].Value
+ : match.Groups[5].Value,
+ match.Groups[6].Value,
+ match.Groups[7].Value,
+ match.Groups[8].Value,
+ match.Groups[9].Value,
+ match.Groups[10].Value,
+ match.Groups[11].Length > 0
+ ? match.Groups[11].Value
+ : match.Groups[12].Value,
+ match.Groups[13].Value);
+
public static bool TryParseUnit(World world, string unitSpec, [NotNullWhen(true)] out Unit? newUnit)
{
newUnit = null;
diff --git a/MultiversalDiplomacyTests/RegexTest.cs b/MultiversalDiplomacyTests/RegexTest.cs
index 5b10af7..1e07da2 100644
--- a/MultiversalDiplomacyTests/RegexTest.cs
+++ b/MultiversalDiplomacyTests/RegexTest.cs
@@ -70,10 +70,18 @@ public class RegexTest
yield return Test(
"A F-STP - MOS",
"A", "F", "STP", "", "", "-", "", "MOS", "", "", "");
+ // No confusion of timeline and hold verb
+ yield return Test(
+ "A Mun - h-Tyr",
+ "A", "", "Mun", "", "", "-", "h", "Tyr", "", "", "");
+ // No confusion of timeline and support verb
+ yield return Test(
+ "A Mun - s-Tyr",
+ "A", "", "Mun", "", "", "-", "s", "Tyr", "", "", "");
// Elements with spaces
yield return Test(
- "Fleet Western Mediterranean Sea moves to Gulf of Lyons via convoy",
- "Fleet", "", "Western Mediterranean Sea", "", "", "moves to", "", "Gulf of Lyons", "", "", "via convoy");
+ "Western Mediterranean Sea moves to Gulf of Lyons via convoy",
+ "", "", "Western Mediterranean Sea", "", "", "moves to", "", "Gulf of Lyons", "", "", "via convoy");
// Parenthesis location
yield return Test(
"F Spain(nc) - Spain(sc)",
@@ -98,4 +106,57 @@ public class RegexTest
Assert.That(actual, Is.EquivalentTo(expected), "Unexpected parse results");
Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results");
}
+
+ static IEnumerable SupportHoldRegexMatchesTestCases()
+ {
+ static TestCaseData Test(string order, params string[] expected)
+ => new TestCaseData(order, expected).SetName($"{{m}}(\"{order}\")");
+ // Full specification
+ yield return Test(
+ "Army a-Munich/l@0 s A a-Tyrolia/l@0",
+ "Army", "a", "Munich", "l", "0", "s", "A", "a", "Tyrolia", "l", "0");
+ // Case insensitivity
+ yield return Test(
+ "fleet B-lon/C@0 SUPPORTS B-enc/W@0",
+ "fleet", "B", "lon", "C", "0", "SUPPORTS", "", "B", "enc", "W", "0");
+ // All optionals missing
+ yield return Test(
+ "ROM s VIE",
+ "", "", "ROM", "", "", "s", "", "", "VIE", "", "");
+ // No confusion of unit type and timeline
+ yield return Test(
+ "A F-STP s MOS",
+ "A", "F", "STP", "", "", "s", "", "", "MOS", "", "");
+ // No confusion of timeline and support verb
+ yield return Test(
+ "A Mun s Tyr",
+ "A", "", "Mun", "", "", "s", "", "", "Tyr", "", "");
+ // Elements with spaces
+ yield return Test(
+ "Western Mediterranean Sea supports Gulf of Lyons",
+ "", "", "Western Mediterranean Sea", "", "", "supports", "", "", "Gulf of Lyons", "", "");
+ // Parenthesis location
+ yield return Test(
+ "F Spain(nc) s Spain(sc)",
+ "F", "", "Spain", "nc", "", "s", "", "", "Spain", "sc", "");
+ // Timeline designation spells out a province
+ yield return Test(
+ "A tyr-MUN(vie) s mun-TYR/vie",
+ "A", "tyr", "MUN", "vie", "", "s", "", "mun", "TYR", "vie", "");
+ }
+
+ [TestCaseSource(nameof(SupportHoldRegexMatchesTestCases))]
+ public void SupportHoldRegexMatches(string order, string[] expected)
+ {
+ OrderRegex re = new(World.WithStandardMap());
+ var match = re.SupportHold.Match(order);
+ Assert.True(match.Success, "Match failed");
+ var (type, timeline, province, location, turn, supportVerb,
+ targetType, targetTimeline, targetProvince, targetLocation, targetTurn) = OrderRegex.ParseSupportHold(match);
+ string[] actual = [type, timeline, province, location, turn, supportVerb,
+ targetType, targetTimeline, targetProvince, targetLocation, targetTurn];
+ // Use EquivalentTo for more detailed error message
+ Assert.That(actual, Is.EquivalentTo(expected), "Unexpected parse results");
+ Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results");
+ }
}