diff --git a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/AnalysisMetathesisRuleSpec.cs b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/AnalysisMetathesisRuleSpec.cs index b98495a6..352162f8 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/AnalysisMetathesisRuleSpec.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/AnalysisMetathesisRuleSpec.cs @@ -31,8 +31,13 @@ PatternNode node in pattern.Children.TakeWhile(n => !(n is Grou _pattern.Children.Add(node.Clone()); } - AddGroup(groups, leftGroupName); - AddGroup(groups, rightGroupName); + // This pattern matches the surface, where the switch groups appear in reverse pattern order. + int leftIndex = Array.FindIndex(groupOrder, g => g.Name == leftGroupName); + int rightIndex = Array.FindIndex(groupOrder, g => g.Name == rightGroupName); + string firstName = leftIndex < rightIndex ? rightGroupName : leftGroupName; + string secondName = leftIndex < rightIndex ? leftGroupName : rightGroupName; + AddGroup(groups, firstName); + AddGroup(groups, secondName); foreach ( PatternNode node in pattern diff --git a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/SynthesisMetathesisRuleSpec.cs b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/SynthesisMetathesisRuleSpec.cs index e2031f64..a58f3de6 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/SynthesisMetathesisRuleSpec.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/SynthesisMetathesisRuleSpec.cs @@ -100,6 +100,14 @@ public void ApplyRhs(Match targetMatch, Range range, GroupCapture leftGroup = targetMatch.GroupCaptures[_leftGroupName]; GroupCapture rightGroup = targetMatch.GroupCaptures[_rightGroupName]; + // The splice below needs the later shape-order group first, or its second move re-anchors past its own end. + if (leftGroup.Success && rightGroup.Success && leftGroup.Range.Start.CompareTo(rightGroup.Range.Start) < 0) + { + GroupCapture earlier = leftGroup; + leftGroup = rightGroup; + rightGroup = earlier; + } + ShapeNode beforeRightGroup = rightGroup.Range.Start.Prev; MoveNodesAfter(targetMatch.Input.Shape, leftGroup.Range.End, rightGroup.Range); MoveNodesAfter(targetMatch.Input.Shape, beforeRightGroup, leftGroup.Range); diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/MetathesisRuleTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/MetathesisRuleTests.cs index 54d0e745..85b6faf2 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/MetathesisRuleTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/MetathesisRuleTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using SIL.Machine.Annotations; +using SIL.Machine.DataStructures; using SIL.Machine.FeatureModel; using SIL.Machine.Matching; using SIL.Machine.Morphology.HermitCrab.MorphologicalRules; @@ -28,6 +29,49 @@ public void SimpleRule() AssertMorphsEqual(morpher.ParseWord("mui"), "51"); } + // Differs from SimpleRule only in the switch-name order, and must give the same result. + [Test] + public void SimpleRule_LeftSwitchNamesEarlierGroup() + { + var rule1 = new MetathesisRule + { + Name = "rule1", + Pattern = Pattern + .New() + .Group("1", group => group.Annotation(Character(Table3, "i"))) + .Group("2", group => group.Annotation(Character(Table3, "u"))) + .Value, + LeftSwitchName = "1", + RightSwitchName = "2", + }; + Morphophonemic.PhonologicalRules.Add(rule1); + + var morpher = new Morpher(TraceManager, Language); + AssertMorphsEqual(morpher.ParseWord("mui"), "51"); + } + + // Differs from SimpleRule only in the switch-name order, plus a right-to-left direction. + [Test] + public void SimpleRule_LeftSwitchNamesEarlierGroup_RightToLeft() + { + var rule1 = new MetathesisRule + { + Name = "rule1", + Direction = Direction.RightToLeft, + Pattern = Pattern + .New() + .Group("1", group => group.Annotation(Character(Table3, "i"))) + .Group("2", group => group.Annotation(Character(Table3, "u"))) + .Value, + LeftSwitchName = "1", + RightSwitchName = "2", + }; + Morphophonemic.PhonologicalRules.Add(rule1); + + var morpher = new Morpher(TraceManager, Language); + AssertMorphsEqual(morpher.ParseWord("mui"), "51"); + } + [Test] public void ComplexRule() { @@ -62,6 +106,41 @@ public void ComplexRule() AssertMorphsEqual(morpher.ParseWord("mui"), "53 3SG"); } + // ComplexRule with the switch names reversed: a group sits between the two switches. + [Test] + public void ComplexRule_LeftSwitchNamesEarlierGroup() + { + var any = FeatureStruct.New().Symbol(HCFeatureSystem.Segment).Value; + + var rule1 = new MetathesisRule + { + Name = "rule1", + Pattern = Pattern + .New() + .Group("1", group => group.Annotation(Character(Table3, "i"))) + .Group("middle", group => group.Annotation(Character(Table3, "+"))) + .Group("2", group => group.Annotation(Character(Table3, "u"))) + .Group("rightEnv", group => group.Annotation(HCFeatureSystem.RightSideAnchor)) + .Value, + LeftSwitchName = "1", + RightSwitchName = "2", + }; + Morphophonemic.PhonologicalRules.Add(rule1); + + var uSuffix = new AffixProcessRule { Name = "u_suffix", Gloss = "3SG" }; + Morphophonemic.MorphologicalRules.Add(uSuffix); + uSuffix.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "+u") }, + } + ); + + var morpher = new Morpher(TraceManager, Language); + AssertMorphsEqual(morpher.ParseWord("mui"), "53 3SG"); + } + [Test] public void SimpleRuleNotUnapplied() {