diff --git a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/RewriteRuleSpec.cs b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/RewriteRuleSpec.cs index f91899d61..f17b7b4ba 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/RewriteRuleSpec.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/PhonologicalRules/RewriteRuleSpec.cs @@ -85,7 +85,7 @@ out PhonologicalSubruleMatch subruleMatch leftNode, varBindings ); - if (leftEnvMatch == null || leftEnvMatch.Success) + if (leftEnvMatch == null || leftEnvMatch.Success || subruleSpec.LeftEnvironmentMatcher.AcceptsEmpty()) { if (leftEnvMatch != null && leftEnvMatch.VariableBindings != null) varBindings = leftEnvMatch.VariableBindings; @@ -95,7 +95,11 @@ out PhonologicalSubruleMatch subruleMatch rightNode, varBindings ); - if (rightEnvMatch == null || rightEnvMatch.Success) + if ( + rightEnvMatch == null + || rightEnvMatch.Success + || subruleSpec.RightEnvironmentMatcher.AcceptsEmpty() + ) { if (rightEnvMatch != null && rightEnvMatch.VariableBindings != null) varBindings = rightEnvMatch.VariableBindings; diff --git a/src/SIL.Machine/FiniteState/Fst.cs b/src/SIL.Machine/FiniteState/Fst.cs index f56d8f86d..b90f9ac40 100644 --- a/src/SIL.Machine/FiniteState/Fst.cs +++ b/src/SIL.Machine/FiniteState/Fst.cs @@ -2130,5 +2130,19 @@ public int GetFrozenHashCode() { return GetHashCode(); } + + internal bool IsAcceptingState(State state) + { + State curState = state; + while (!curState.IsAccepting) + { + Arc highestPriArc = curState.Arcs.MinBy(a => a.Priority); + if (!highestPriArc.Input.IsEpsilon) + break; + curState = highestPriArc.Target; + } + + return curState.IsAccepting; + } } } diff --git a/src/SIL.Machine/Matching/Matcher.cs b/src/SIL.Machine/Matching/Matcher.cs index 10089063e..54150f3c2 100644 --- a/src/SIL.Machine/Matching/Matcher.cs +++ b/src/SIL.Machine/Matching/Matcher.cs @@ -336,5 +336,10 @@ private Annotation GetStartAnnotation(TData input, TOffset start) startAnn = startAnn.GetNextDepthFirst(_settings.Direction, _settings.Filter); return startAnn; } + + public bool AcceptsEmpty() + { + return _fsa.IsAcceptingState(_fsa.StartState); + } } } diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs index 92dd64f88..f57de738a 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/PhonologicalRules/RewriteRuleTests.cs @@ -47,6 +47,12 @@ public void SimpleRules() new RewriteSubrule { Rhs = Pattern.New().Annotation(asp).Value, + // the following should be a NOOP because it accepts the empty string. + LeftEnvironment = Pattern + .New() + .Annotation(nonCons) + .Optional.Annotation(nonCons) + .Optional.Value, RightEnvironment = Pattern.New().Annotation(nonCons).Value } );