Compare commits

..

No commits in common. "b4f8f621ca4bb33df6aacaf9130a0ff280cc1fc8" and "ebeb17898429ee90983f1843b49e4b2fe52c9e98" have entirely different histories.

3 changed files with 96 additions and 128 deletions

View File

@ -19,7 +19,7 @@ public class OrderRegex(World world)
public string FullLocation => $"(?:{Timeline}-)?{world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?(?:@{Turn})?"; public string FullLocation => $"(?:{Timeline}-)?{world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?(?:@{Turn})?";
public string UnitSpec => $"(?:{Type} )?{FullLocation}"; public string UnitSpec => $"(?:(?:{world.Map.PowerRegex} )?{Type} )?{FullLocation}";
public const string HoldVerb = "(h|hold|holds)"; 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 const string ViaConvoy = "(convoy|via convoy|by convoy)";
public Regex Hold => new( public Regex Hold => new($"^{Unit} {HoldVerb}$");
$"^{UnitSpec} {HoldVerb}$",
RegexOptions.IgnoreCase);
public static ( public static (
string power,
string type, string type,
string timeline, string timeline,
string province, string province,
string location, string location,
string turn, string turn,
string holdVerb) string verb)
ParseHold(Match match) => ( ParseHold(Match match)
match.Groups[1].Value, => (match.Groups[1].Value,
match.Groups[2].Value, match.Groups[2].Value,
match.Groups[3].Value, match.Groups[3].Value,
match.Groups[4].Length > 0 match.Groups[4].Value,
? match.Groups[4].Value match.Groups[5].Length > 0
: match.Groups[5].Value, ? match.Groups[5].Value
match.Groups[6].Value, : match.Groups[6].Value,
match.Groups[7].Value); match.Groups[7].Value,
match.Groups[8].Value);
public Regex Move => new( public Regex Move => new($"^{UnitSpec} {MoveVerb} {FullLocation}$(?: {ViaConvoy})?");
$"^{UnitSpec} {MoveVerb} {FullLocation}(?: {ViaConvoy})?$",
RegexOptions.IgnoreCase);
public static ( public static (
string power,
string type, string type,
string timeline, string timeline,
string province, string province,
string location, string location,
string turn, string turn,
string moveVerb, string verb,
string destTimeline, string timeline2,
string destProvince, string province2,
string destLocation, string location2,
string destTurn, string turn2,
string viaConvoy) string viaConvoy)
ParseMove(Match match) => ( ParseMove(Match match)
match.Groups[1].Value, => (match.Groups[1].Value,
match.Groups[2].Value, match.Groups[2].Value,
match.Groups[3].Value, match.Groups[3].Value,
match.Groups[4].Length > 0 match.Groups[4].Value,
? match.Groups[4].Value match.Groups[5].Length > 0
: match.Groups[5].Value, ? match.Groups[5].Value
match.Groups[6].Value, : match.Groups[6].Value,
match.Groups[7].Value, match.Groups[7].Value,
match.Groups[8].Value, match.Groups[8].Value,
match.Groups[9].Value, match.Groups[9].Value,
match.Groups[10].Length > 0 match.Groups[10].Value,
? match.Groups[10].Value match.Groups[11].Length > 1
: match.Groups[11].Value, ? match.Groups[11].Value
match.Groups[12].Value, : match.Groups[12].Value,
match.Groups[13].Value); match.Groups[13].Value,
match.Groups[14].Value);
public static bool TryParseUnit(World world, string unitSpec, [NotNullWhen(true)] out Unit? newUnit) 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)) { if (OrderRegex.TryParseOrder(World, orderPower, orderText, out Order? order)) {
Console.WriteLine($"Parsed {orderPower} order \"{orderText}\" but doing anything with it isn't implemented yet"); 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}\""); Console.WriteLine($"Failed to parse \"{orderText}\"");
return Strict ? null : this; return Strict ? null : this;

View File

@ -1,3 +1,5 @@
using System.Text.RegularExpressions;
using NUnit.Framework; using NUnit.Framework;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
@ -6,96 +8,62 @@ namespace MultiversalDiplomacyTests;
public class RegexTest public class RegexTest
{ {
private static TestCaseData Test(string order, params string[] expected) [Test]
=> new TestCaseData(order, expected).SetName($"{{m}}(\"{order}\")"); public void UnitTokenizer()
static IEnumerable<TestCaseData> HoldRegexMatchesTestCases()
{ {
// Full specification World world = World.WithStandardMap();
yield return Test( OrderRegex re = new(world);
"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");
}
[TestCaseSource(nameof(HoldRegexMatchesTestCases))] var match = re.Hold.Match("Germany Army a-Munich/l@0 holds");
public void HoldRegexMatches(string order, string[] expected) Assert.That(match.Success, Is.True);
{ var hold = OrderRegex.ParseHold(match);
OrderRegex re = new(World.WithStandardMap()); Assert.That(hold.power, Is.EqualTo("Germany"));
var match = re.Hold.Match(order); Assert.That(hold.type, Is.EqualTo("Army"));
Assert.True(match.Success, "Match failed"); Assert.That(hold.timeline, Is.EqualTo("a"));
var (type, timeline, province, location, turn, holdVerb) = OrderRegex.ParseHold(match); Assert.That(hold.province, Is.EqualTo("Munich"));
string[] actual = [type, timeline, province, location, turn, holdVerb]; Assert.That(hold.location, Is.EqualTo("l"));
// Use EquivalentTo for more detailed error message Assert.That(hold.turn, Is.EqualTo("0"));
Assert.That(actual, Is.EquivalentTo(expected), "Unexpected parse results"); Assert.That(hold.verb, Is.EqualTo("holds"));
Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results");
}
static IEnumerable<TestCaseData> MoveRegexMatchesTestCases() match = re.Hold.Match("F Venice hold");
{ Assert.That(match.Success, Is.True);
static TestCaseData Test(string order, params string[] expected) hold = OrderRegex.ParseHold(match);
=> new TestCaseData(order, expected).SetName($"{{m}}(\"{order}\")"); Assert.That(hold.power, Is.EqualTo(""));
// Full specification Assert.That(hold.type, Is.EqualTo("F"));
yield return Test( Assert.That(hold.timeline, Is.EqualTo(""));
"Army a-Munich/l@0 - a-Tyrolia/l@0", Assert.That(hold.province, Is.EqualTo("Venice"));
"Army", "a", "Munich", "l", "0", "-", "a", "Tyrolia", "l", "0", ""); Assert.That(hold.location, Is.EqualTo(""));
// Case insensitivity Assert.That(hold.turn, Is.EqualTo(""));
yield return Test( Assert.That(hold.verb, Is.EqualTo("hold"));
"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", "", "");
}
[TestCaseSource(nameof(MoveRegexMatchesTestCases))] match = re.Move.Match("F Gascony - Spain(nc)");
public void MoveRegexMatches(string order, string[] expected) Assert.That(match.Success, Is.True);
{ var move = OrderRegex.ParseMove(match);
OrderRegex re = new(World.WithStandardMap()); Assert.That(move.power, Is.EqualTo(""));
var match = re.Move.Match(order); Assert.That(move.type, Is.EqualTo("F"));
Assert.True(match.Success, "Match failed"); Assert.That(move.timeline, Is.EqualTo(""));
var (type, timeline, province, location, turn, moveVerb, Assert.That(move.province, Is.EqualTo("Gascony"));
destTimeline, destProvince, destLocation, destTurn, viaConvoy) = OrderRegex.ParseMove(match); Assert.That(move.location, Is.EqualTo(""));
string[] actual = [type, timeline, province, location, turn, moveVerb, Assert.That(move.turn, Is.EqualTo(""));
destTimeline, destProvince, destLocation, destTurn, viaConvoy]; Assert.That(move.verb, Is.EqualTo("-"));
// Use EquivalentTo for more detailed error message Assert.That(move.timeline2, Is.EqualTo(""));
Assert.That(actual, Is.EquivalentTo(expected), "Unexpected parse results"); Assert.That(move.province2, Is.EqualTo("Spain"));
Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results"); 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(""));
} }
} }