diff --git a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/NarrowAnalysisRewriteRuleSpec.cs b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/NarrowAnalysisRewriteRuleSpec.cs index 9d913b06c..6380eaab1 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/NarrowAnalysisRewriteRuleSpec.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/NarrowAnalysisRewriteRuleSpec.cs @@ -40,6 +40,9 @@ RewriteSubrule subrule private void Unapply(Match targetMatch, Range range, VariableBindings varBindings) { ShapeNode curNode = IsTargetEmpty ? range.Start : range.End; + ShapeNode targetNode = range.Start; + int targetPos = 0; + bool zipper = true; foreach ( Constraint constraint in _analysisRhs.Children.CastToConstraints( "The target (left-hand side) of a rewrite rule" @@ -49,14 +52,33 @@ Constraint constraint in _analysisRhs.Children.CastToConstraint FeatureStruct fs = constraint.FeatureStruct.Clone(); if (varBindings != null) fs.ReplaceVariables(varBindings); - curNode = targetMatch.Input.Shape.AddAfter(curNode, fs, true); + if ( + zipper + && targetPos < _targetCount + && constraint.Type() == HCFeatureSystem.Segment + && targetNode.Type() == HCFeatureSystem.Segment + && !fs.HasVariables // This can be removed when Union is fixed. + && !targetNode.Annotation.FeatureStruct.HasVariables + ) + { + // Union fs into the existing node's feature structure. + targetNode.Annotation.FeatureStruct.Union(fs); + targetNode = targetNode.Next; + targetPos++; + } + else + { + // Add an optional node for fs. + curNode = targetMatch.Input.Shape.AddAfter(curNode, fs, true); + zipper = false; + } } - curNode = range.Start; - for (int i = 0; i < _targetCount; i++) + // Make the rest of the existing nodes optional. + for (int i = targetPos; i < _targetCount; i++) { - curNode.Annotation.Optional = true; - curNode = curNode.Next; + targetNode.Annotation.Optional = true; + targetNode = targetNode.Next; } } } diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs index 5e0bf867b..be7135bc3 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs @@ -553,6 +553,63 @@ public void MultipleMergeRules() AssertMorphsEqual(morpher.ParseWord("bttbtt"), "27"); } + [Test] + public void MergeWithBoundary() + { + var merge = new RewriteRule + { + Name = "merge", + Lhs = Pattern + .New() + .Annotation(Character(Table3, "b")) + .Annotation(Character(Table3, "+")) + .Annotation(Character(Table3, "b")) + .Value, + }; + merge.Subrules.Add( + new RewriteSubrule + { + Rhs = Pattern + .New() + .Annotation(Character(Table3, "p")) + .Annotation(Character(Table3, "i")) + .Value, + } + ); + Morphophonemic.PhonologicalRules.Add(merge); + + var morpher = new Morpher(TraceManager, Language, 1); + AssertMorphsEqual(morpher.ParseWord("apia"), "39"); + } + + [Test] + public void AlphaAgreementMergeFindsAbbaFromApa() + { + FeatureStruct alphaVoicedConsonant = FeatureStruct + .New(Language.PhonologicalFeatureSystem) + .Symbol(HCFeatureSystem.Segment) + .Symbol("cons+") + .Feature("vd") + .EqualToVariable("a") + .Value; + var merge = new RewriteRule + { + Name = "alphaMerge", + Lhs = Pattern + .New() + .Annotation(alphaVoicedConsonant) + .Annotation(alphaVoicedConsonant) + .Value, + }; + merge.Subrules.Add( + new RewriteSubrule { Rhs = Pattern.New().Annotation(Character(Table3, "p")).Value } + ); + Morphophonemic.PhonologicalRules.Add(merge); + + var morpher = new Morpher(TraceManager, Language); + AssertMorphsEqual(morpher.ParseWord("apa"), "40"); + } + [Test] public void ExpandRules() {