Compare commits

...

2 Commits

3 changed files with 131 additions and 99 deletions

View File

@ -19,7 +19,7 @@ public class OrderRegex(World world)
public string FullLocation => $"(?:{Timeline}-)?{world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?(?:@{Turn})?";
public string UnitSpec => $"(?:(?:{world.Map.PowerRegex} )?{Type} )?{FullLocation}";
public string UnitSpec => $"(?:{Type} )?{FullLocation}";
public const string HoldVerb = "(h|hold|holds)";
@ -27,59 +27,59 @@ public class OrderRegex(World world)
public const string ViaConvoy = "(convoy|via convoy|by convoy)";
public Regex Hold => new($"^{Unit} {HoldVerb}$");
public Regex Hold => new(
$"^{UnitSpec} {HoldVerb}$",
RegexOptions.IgnoreCase);
public static (
string power,
string type,
string timeline,
string province,
string location,
string turn,
string verb)
ParseHold(Match match)
=> (match.Groups[1].Value,
match.Groups[2].Value,
match.Groups[3].Value,
match.Groups[4].Value,
match.Groups[5].Length > 0
? match.Groups[5].Value
: match.Groups[6].Value,
match.Groups[7].Value,
match.Groups[8].Value);
string holdVerb)
ParseHold(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);
public Regex Move => new($"^{UnitSpec} {MoveVerb} {FullLocation}$(?: {ViaConvoy})?");
public Regex Move => new(
$"^{UnitSpec} {MoveVerb} {FullLocation}(?: {ViaConvoy})?$",
RegexOptions.IgnoreCase);
public static (
string power,
string type,
string timeline,
string province,
string location,
string turn,
string verb,
string timeline2,
string province2,
string location2,
string turn2,
string moveVerb,
string destTimeline,
string destProvince,
string destLocation,
string destTurn,
string viaConvoy)
ParseMove(Match match)
=> (match.Groups[1].Value,
match.Groups[2].Value,
match.Groups[3].Value,
match.Groups[4].Value,
match.Groups[5].Length > 0
? 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 > 1
? match.Groups[11].Value
: match.Groups[12].Value,
match.Groups[13].Value,
match.Groups[14].Value);
ParseMove(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].Length > 0
? match.Groups[10].Value
: 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)
{

View File

@ -71,8 +71,8 @@ public class GameScriptHandler(World world, bool strict = false) : IScriptHandle
if (OrderRegex.TryParseOrder(World, orderPower, orderText, out Order? order)) {
Console.WriteLine($"Parsed {orderPower} order \"{orderText}\" but doing anything with it isn't implemented yet");
return this;
}
return this;
}
Console.WriteLine($"Failed to parse \"{orderText}\"");
return Strict ? null : this;

View File

@ -1,5 +1,3 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using MultiversalDiplomacy.Model;
@ -8,62 +6,96 @@ namespace MultiversalDiplomacyTests;
public class RegexTest
{
[Test]
public void UnitTokenizer()
private static TestCaseData Test(string order, params string[] expected)
=> new TestCaseData(order, expected).SetName($"{{m}}(\"{order}\")");
static IEnumerable<TestCaseData> HoldRegexMatchesTestCases()
{
World world = World.WithStandardMap();
OrderRegex re = new(world);
// Full specification
yield return Test(
"Army a-Munich/l@0 holds",
"Army", "a", "Munich", "l", "0", "holds");
// Case insensitivity
yield return Test(
"fleet B-lon/C@0 H",
"fleet", "B", "lon", "C", "0", "H");
// All optionals missing
yield return Test(
"ROM h",
"", "", "ROM", "", "", "h");
// No confusion of unit type and timeline
yield return Test(
"A F-STP hold",
"A", "F", "STP", "", "", "hold");
// Province with space in name
yield return Test(
"Fleet North Sea Hold",
"Fleet", "", "North Sea", "", "", "Hold");
// Parenthesis location
yield return Test(
"F Spain(nc) holds",
"F", "", "Spain", "nc", "", "holds");
}
var match = re.Hold.Match("Germany Army a-Munich/l@0 holds");
Assert.That(match.Success, Is.True);
var hold = OrderRegex.ParseHold(match);
Assert.That(hold.power, Is.EqualTo("Germany"));
Assert.That(hold.type, Is.EqualTo("Army"));
Assert.That(hold.timeline, Is.EqualTo("a"));
Assert.That(hold.province, Is.EqualTo("Munich"));
Assert.That(hold.location, Is.EqualTo("l"));
Assert.That(hold.turn, Is.EqualTo("0"));
Assert.That(hold.verb, Is.EqualTo("holds"));
[TestCaseSource(nameof(HoldRegexMatchesTestCases))]
public void HoldRegexMatches(string order, string[] expected)
{
OrderRegex re = new(World.WithStandardMap());
var match = re.Hold.Match(order);
Assert.True(match.Success, "Match failed");
var (type, timeline, province, location, turn, holdVerb) = OrderRegex.ParseHold(match);
string[] actual = [type, timeline, province, location, turn, holdVerb];
// 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");
}
match = re.Hold.Match("F Venice hold");
Assert.That(match.Success, Is.True);
hold = OrderRegex.ParseHold(match);
Assert.That(hold.power, Is.EqualTo(""));
Assert.That(hold.type, Is.EqualTo("F"));
Assert.That(hold.timeline, Is.EqualTo(""));
Assert.That(hold.province, Is.EqualTo("Venice"));
Assert.That(hold.location, Is.EqualTo(""));
Assert.That(hold.turn, Is.EqualTo(""));
Assert.That(hold.verb, Is.EqualTo("hold"));
static IEnumerable<TestCaseData> MoveRegexMatchesTestCases()
{
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 - a-Tyrolia/l@0",
"Army", "a", "Munich", "l", "0", "-", "a", "Tyrolia", "l", "0", "");
// Case insensitivity
yield return Test(
"fleet B-lon/C@0 - B-enc/W@0",
"fleet", "B", "lon", "C", "0", "-", "B", "enc", "W", "0", "");
// All optionals missing
yield return Test(
"ROM - VIE",
"", "", "ROM", "", "", "-", "", "VIE", "", "", "");
// No confusion of unit type and timeline
yield return Test(
"A F-STP - MOS",
"A", "F", "STP", "", "", "-", "", "MOS", "", "", "");
// 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");
// Parenthesis location
yield return Test(
"F Spain(nc) - Spain(sc)",
"F", "", "Spain", "nc", "", "-", "", "Spain", "sc", "", "");
// Timeline designation spells out a province
yield return Test(
"A tyr-MUN(vie) - mun-TYR/vie",
"A", "tyr", "MUN", "vie", "", "-", "mun", "TYR", "vie", "", "");
}
match = re.Move.Match("F Gascony - Spain(nc)");
Assert.That(match.Success, Is.True);
var move = OrderRegex.ParseMove(match);
Assert.That(move.power, Is.EqualTo(""));
Assert.That(move.type, Is.EqualTo("F"));
Assert.That(move.timeline, Is.EqualTo(""));
Assert.That(move.province, Is.EqualTo("Gascony"));
Assert.That(move.location, Is.EqualTo(""));
Assert.That(move.turn, Is.EqualTo(""));
Assert.That(move.verb, Is.EqualTo("-"));
Assert.That(move.timeline2, Is.EqualTo(""));
Assert.That(move.province2, Is.EqualTo("Spain"));
Assert.That(move.location2, Is.EqualTo("nc"));
Assert.That(move.turn2, Is.EqualTo(""));
match = re.Move.Match("F North Sea - Picardy");
Assert.That(match.Success, Is.True);
move = OrderRegex.ParseMove(match);
Assert.That(move.power, Is.EqualTo(""));
Assert.That(move.type, Is.EqualTo("F"));
Assert.That(move.timeline, Is.EqualTo(""));
Assert.That(move.province, Is.EqualTo("North Sea"));
Assert.That(move.location, Is.EqualTo(""));
Assert.That(move.turn, Is.EqualTo(""));
Assert.That(move.verb, Is.EqualTo("-"));
Assert.That(move.timeline2, Is.EqualTo(""));
Assert.That(move.province2, Is.EqualTo("Picardy"));
Assert.That(move.location2, Is.EqualTo(""));
Assert.That(move.turn2, Is.EqualTo(""));
[TestCaseSource(nameof(MoveRegexMatchesTestCases))]
public void MoveRegexMatches(string order, string[] expected)
{
OrderRegex re = new(World.WithStandardMap());
var match = re.Move.Match(order);
Assert.True(match.Success, "Match failed");
var (type, timeline, province, location, turn, moveVerb,
destTimeline, destProvince, destLocation, destTurn, viaConvoy) = OrderRegex.ParseMove(match);
string[] actual = [type, timeline, province, location, turn, moveVerb,
destTimeline, destProvince, destLocation, destTurn, viaConvoy];
// 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");
}
}