From 7df2fd3afadd91af094094823dd06a568d9e953c Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 20 Aug 2026 08:58:44 -0700 Subject: [PATCH 1/6] Filter final templates in analysis --- .../AffixTemplate.cs | 20 +++++++++++++- .../ITraceManager.cs | 7 +++++ .../MorphemicMorphologicalRule.cs | 1 + .../Morpher.cs | 17 ++++++++++++ .../AnalysisAffixProcessRule.cs | 26 +++++++++++++++++++ .../TraceManager.cs | 17 ++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/AffixTemplate.cs b/src/SIL.Machine.Morphology.HermitCrab/AffixTemplate.cs index 02e16e8e1..3a5aafdb1 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/AffixTemplate.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/AffixTemplate.cs @@ -16,6 +16,8 @@ public class AffixTemplate : HCRuleBase private Stratum _stratum; private readonly ObservableCollection _slots; + private bool _isFinal; + /// /// Initializes a new instance of the class. /// @@ -37,6 +39,7 @@ private void SlotsChanged(object sender, NotifyCollectionChangedEventArgs e) { rule.Stratum = null; rule.IsTemplateRule = false; + rule.IsFinalTemplateRule = false; } } } @@ -48,6 +51,7 @@ private void SlotsChanged(object sender, NotifyCollectionChangedEventArgs e) { rule.Stratum = Stratum; rule.IsTemplateRule = true; + rule.IsFinalTemplateRule = IsFinal; } } } @@ -55,7 +59,21 @@ private void SlotsChanged(object sender, NotifyCollectionChangedEventArgs e) public FeatureStruct RequiredSyntacticFeatureStruct { get; set; } - public bool IsFinal { get; set; } + public bool IsFinal + { + get { return _isFinal; } + set + { + _isFinal = value; + foreach (AffixTemplateSlot slot in Slots) + { + foreach (MorphemicMorphologicalRule rule in slot.Rules) + { + rule.IsFinalTemplateRule = value; + } + } + } + } public IList Slots { diff --git a/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs b/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs index 31d8fa497..1418ea7cc 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs @@ -47,6 +47,13 @@ public interface ITraceManager void MorphologicalRuleUnapplied(IMorphologicalRule rule, int subruleIndex, Word input, Word output); void MorphologicalRuleNotUnapplied(IMorphologicalRule rule, int subruleIndex, Word input); + void MorphologicalRuleNotUnapplied( + IMorphologicalRule rule, + int subruleIndex, + Word input, + FailureReason reason, + object failureObj + ); void CompoundingRuleNotUnapplied( IMorphologicalRule rule, diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphemicMorphologicalRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphemicMorphologicalRule.cs index 0381fe255..08e12f588 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphemicMorphologicalRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphemicMorphologicalRule.cs @@ -7,6 +7,7 @@ public abstract class MorphemicMorphologicalRule : Morpheme, IMorphologicalRule { public string Name { get; set; } public bool IsTemplateRule { get; set; } + public bool IsFinalTemplateRule { get; set; } public override MorphemeType MorphemeType { diff --git a/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs b/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs index 54ac7b5ec..b12641566 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs @@ -65,6 +65,18 @@ public Morpher(ITraceManager traceManager, Language lang, int maxDegreeOfParalle RuleSelector = rule => true; _morphemes = new ReadOnlyObservableCollection(morphemes); + IsPartial = GetPartialMorphemes().Count() > 0; + } + + public IEnumerable GetPartialMorphemes() + { + var morphemes = new HashSet(); + foreach (Morpheme morpheme in _morphemes) + { + if (morpheme.IsPartial) + morphemes.Add(morpheme); + } + return morphemes; } public ITraceManager TraceManager @@ -88,6 +100,11 @@ public ITraceManager TraceManager /// public bool MergeEquivalentAnalyses { get; set; } + /// + /// A Morpher is partial if any of the elements are partial. + /// + public bool IsPartial { get; set; } + /// /// Caps the concurrency used within a single parse or generation -- analysis cascade, /// affix-template unapplication and synthesis alike. A value of 1 runs the work fully diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index 4e89fef97..364edf07e 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -49,6 +49,32 @@ public IEnumerable Apply(Word input) return Enumerable.Empty(); } + // Do not allow a final template to unapply if the grammar is not partial + // and a non-template was last unapplied. + if ( + !_morpher.IsPartial + && _rule.IsFinalTemplateRule + && input.MorphologicalRules.Count() > 0 + ) + { + IMorphologicalRule lastRule = input.MorphologicalRules.Last(); + if (lastRule is AffixProcessRule affixProcessRule && !affixProcessRule.IsTemplateRule) + { + if (_morpher.TraceManager.IsTracing) + { + _morpher.TraceManager.MorphologicalRuleNotUnapplied( + _rule, + -1, + input, + FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, + null + ); + } + return Enumerable.Empty(); + + } + } + var output = new List(); for (int i = 0; i < _rules.Count; i++) { diff --git a/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs b/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs index 56a4119a5..287b195f4 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs @@ -84,6 +84,23 @@ public void MorphologicalRuleNotUnapplied(IMorphologicalRule rule, int subruleIn ); } + public void MorphologicalRuleNotUnapplied( + IMorphologicalRule rule, + int subruleIndex, + Word input, + FailureReason reason, + object failureObj) + { + ((Trace)input.CurrentTrace).Children.Add( + new Trace(TraceType.MorphologicalRuleAnalysis, rule) + { + SubruleIndex = subruleIndex, + Input = input, + FailureReason = reason, + } + ); + } + public void CompoundingRuleNotUnapplied( IMorphologicalRule rule, int subruleIndex, From 8d6d7214111eda731115e9b17b554b2b2f6d439f Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 20 Aug 2026 12:40:29 -0700 Subject: [PATCH 2/6] Fix format --- src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs | 2 +- .../MorphologicalRules/AnalysisAffixProcessRule.cs | 9 ++------- src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs | 3 ++- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs b/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs index 1418ea7cc..eda5381b8 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs @@ -53,7 +53,7 @@ void MorphologicalRuleNotUnapplied( Word input, FailureReason reason, object failureObj - ); + ); void CompoundingRuleNotUnapplied( IMorphologicalRule rule, diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index 364edf07e..9c84f14f7 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -51,11 +51,7 @@ public IEnumerable Apply(Word input) // Do not allow a final template to unapply if the grammar is not partial // and a non-template was last unapplied. - if ( - !_morpher.IsPartial - && _rule.IsFinalTemplateRule - && input.MorphologicalRules.Count() > 0 - ) + if (!_morpher.IsPartial && _rule.IsFinalTemplateRule && input.MorphologicalRules.Count() > 0) { IMorphologicalRule lastRule = input.MorphologicalRules.Last(); if (lastRule is AffixProcessRule affixProcessRule && !affixProcessRule.IsTemplateRule) @@ -68,10 +64,9 @@ public IEnumerable Apply(Word input) input, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, null - ); + ); } return Enumerable.Empty(); - } } diff --git a/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs b/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs index 287b195f4..0335bcbe5 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/TraceManager.cs @@ -89,7 +89,8 @@ public void MorphologicalRuleNotUnapplied( int subruleIndex, Word input, FailureReason reason, - object failureObj) + object failureObj + ) { ((Trace)input.CurrentTrace).Children.Add( new Trace(TraceType.MorphologicalRuleAnalysis, rule) From 1f0f52dec07bc012a1e61bf4e8a09ae078a275d3 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 20 Aug 2026 14:04:30 -0700 Subject: [PATCH 3/6] Fix format --- .../MorphologicalRules/AnalysisAffixProcessRule.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index 9c84f14f7..ce38358e4 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -61,9 +61,9 @@ public IEnumerable Apply(Word input) _morpher.TraceManager.MorphologicalRuleNotUnapplied( _rule, -1, - input, - FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, - null + input, + FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, + null ); } return Enumerable.Empty(); From 5c1669af1aae205e8a579e0acebf919d52babac3 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 21 Aug 2026 08:23:16 -0700 Subject: [PATCH 4/6] Add test --- .../AffixTemplateTests.cs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs index 54786c82f..a2363cc13 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs @@ -348,6 +348,160 @@ public void NonFinalTemplate() AssertMorphsEqual(morpher.ParseWord("sagdmis"), "32 PAST 53 PL"); } + [Test] + public void EarlyPruningOfFinalTemplate() + { + var any = FeatureStruct.New().Symbol(HCFeatureSystem.Segment).Value; + var alvStop = FeatureStruct + .New(Language.PhonologicalFeatureSystem) + .Symbol(HCFeatureSystem.Segment) + .Symbol("cons+") + .Symbol("strident-") + .Symbol("del_rel-") + .Symbol("alveolar") + .Value; + var voicelessCons = FeatureStruct + .New(Language.PhonologicalFeatureSystem) + .Symbol(HCFeatureSystem.Segment) + .Symbol("cons+") + .Symbol("vd-") + .Value; + + var edSuffix = new AffixProcessRule { Name = "ed_suffix", Gloss = "PAST" }; + edSuffix.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = + { + Pattern.New("1").Annotation(any).OneOrMore.Value, + Pattern.New("2").Annotation(alvStop).Value, + }, + Rhs = { new CopyFromInput("1"), new CopyFromInput("2"), new InsertSegments(Table3, "ɯd") }, + } + ); + edSuffix.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Annotation(voicelessCons).Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "t") }, + } + ); + edSuffix.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "d") }, + } + ); + + var verbTemplate = new AffixTemplate + { + Name = "verb", + RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value, + }; + verbTemplate.Slots.Add(new AffixTemplateSlot(edSuffix)); + Morphophonemic.AffixTemplates.Add(verbTemplate); + + var nominalizer = new AffixProcessRule + { + Name = "nominalizer", + Gloss = "NOM", + RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value, + OutSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("N").Value, + }; + nominalizer.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "v") }, + } + ); + Morphophonemic.MorphologicalRules.Add(nominalizer); + + var crule = new CompoundingRule + { + Name = "rule1", + HeadRequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value, + NonHeadRequiredSyntacticFeatureStruct = FeatureStruct + .New(Language.SyntacticFeatureSystem) + .Symbol("N") + .Value, + OutSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("N").Value, + }; + crule.Subrules.Add( + new CompoundingSubrule + { + HeadLhs = { Pattern.New("head").Annotation(any).OneOrMore.Value }, + NonHeadLhs = { Pattern.New("nonHead").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("head"), new InsertSegments(Table3, "+"), new CopyFromInput("nonHead") }, + } + ); + Morphophonemic.MorphologicalRules.Add(crule); + + var sSuffix = new AffixProcessRule + { + Name = "s_suffix", + Gloss = "PL", + RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("N").Value, + }; + sSuffix.Allomorphs.Add( + new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "s") }, + } + ); + + var nounTemplate = new AffixTemplate + { + Name = "noun", + RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("N").Value, + }; + nounTemplate.Slots.Add(new AffixTemplateSlot(sSuffix) { Optional = true }); + Morphophonemic.AffixTemplates.Add(nounTemplate); + + // Verify early pruning of final template. + var morpher = new Morpher(TraceManager, Language); + morpher.IsPartial = false; // Override for testing purposes. + TraceManager.IsTracing = true; + AssertMorphsEqual(morpher.ParseWord("sagdv", out object trace)); + Assert.That(GetFailureDepth((Trace)trace, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate), Is.EqualTo(2)); + AssertMorphsEqual(morpher.ParseWord("sagdvs")); + TraceManager.IsTracing = false; + + // Verify correctness when non-partial and final. + AssertMorphsEqual(morpher.ParseWord("sagd"), "32 PAST"); + AssertMorphsEqual(morpher.ParseWord("sagdv")); + AssertMorphsEqual(morpher.ParseWord("sagdvs")); + AssertMorphsEqual(morpher.ParseWord("sagdmi")); + AssertMorphsEqual(morpher.ParseWord("sagdmis")); + + // Verify correctness when non-partial and non-final. + verbTemplate.IsFinal = false; + morpher = new Morpher(TraceManager, Language); + morpher.IsPartial = false; + AssertMorphsEqual(morpher.ParseWord("sagd")); + AssertMorphsEqual(morpher.ParseWord("sagdv"), "32 PAST NOM"); + AssertMorphsEqual(morpher.ParseWord("sagdvs"), "32 PAST NOM PL"); + AssertMorphsEqual(morpher.ParseWord("sagdmi"), "32 PAST 53"); + AssertMorphsEqual(morpher.ParseWord("sagdmis"), "32 PAST 53 PL"); + } + + private static int GetFailureDepth(Trace trace, FailureReason reason) + { + if (trace == null) + return 0; + if (trace.FailureReason == reason) + return trace.Depth; + foreach (var child in trace.Children) + { + int depth = GetFailureDepth(child, reason); + if (depth > 0) + return depth; + } + return 0; + } + [Test] public void AffixTemplateAppliedAfterMorphologicalRule() { From 709ea144c005a1243eb48ad0c090736893bb95e8 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 27 Aug 2026 07:41:33 -0700 Subject: [PATCH 5/6] Fix format --- .../AffixTemplateTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs index a2363cc13..696d031f3 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/AffixTemplateTests.cs @@ -465,7 +465,10 @@ public void EarlyPruningOfFinalTemplate() morpher.IsPartial = false; // Override for testing purposes. TraceManager.IsTracing = true; AssertMorphsEqual(morpher.ParseWord("sagdv", out object trace)); - Assert.That(GetFailureDepth((Trace)trace, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate), Is.EqualTo(2)); + Assert.That( + GetFailureDepth((Trace)trace, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate), + Is.EqualTo(2) + ); AssertMorphsEqual(morpher.ParseWord("sagdvs")); TraceManager.IsTracing = false; From f1d37c38c3a995801d5adf184968a46902ac5f98 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 27 Aug 2026 10:20:43 -0700 Subject: [PATCH 6/6] Add IsLastUnappliedRuleNonTemplate --- .../AnalysisStateKey.cs | 5 ++++ .../AnalysisAffixProcessRule.cs | 26 ++++++++----------- src/SIL.Machine.Morphology.HermitCrab/Word.cs | 20 +++++++++++++- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/AnalysisStateKey.cs b/src/SIL.Machine.Morphology.HermitCrab/AnalysisStateKey.cs index 8575ff40e..f4f29d2e0 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/AnalysisStateKey.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/AnalysisStateKey.cs @@ -31,6 +31,7 @@ namespace SIL.Machine.Morphology.HermitCrab private readonly FeatureStruct _realizationalFS; private readonly int _nonHeadCount; private readonly IReadOnlyDictionary _ruleCounts; + private readonly bool? _isLastUnappliedRuleNonTemplate; private readonly int _hashCode; /// @@ -62,6 +63,7 @@ private AnalysisStateKey(Word word) _realizationalFS = word.RealizationalFeatureStruct; _nonHeadCount = word.NonHeadCount; _ruleCounts = word.UnappliedRuleCounts; + _isLastUnappliedRuleNonTemplate = word.IsLastUnappliedRuleNonTemplate; // See PinAndKey for why the key pins these rather than just reading them. _shape.Freeze(); @@ -74,6 +76,7 @@ private AnalysisStateKey(Word word) hash = hash * 31 + _syntacticFS.GetFrozenHashCode(); hash = hash * 31 + _realizationalFS.GetFrozenHashCode(); hash = hash * 31 + _nonHeadCount; + hash = hash * 31 + _isLastUnappliedRuleNonTemplate.GetHashCode(); if (_ruleCounts != null) { // XOR rather than the usual *31 rolling combine: the multiset is unordered, so entries @@ -100,6 +103,8 @@ public bool Equals(AnalysisStateKey other) return false; if (!_syntacticFS.ValueEquals(other._syntacticFS) || !_realizationalFS.ValueEquals(other._realizationalFS)) return false; + if (_isLastUnappliedRuleNonTemplate != other._isLastUnappliedRuleNonTemplate) + return false; return RuleCountsEqual(_ruleCounts, other._ruleCounts); } diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index ce38358e4..0d05e6caf 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -51,24 +51,20 @@ public IEnumerable Apply(Word input) // Do not allow a final template to unapply if the grammar is not partial // and a non-template was last unapplied. - if (!_morpher.IsPartial && _rule.IsFinalTemplateRule && input.MorphologicalRules.Count() > 0) + if (!_morpher.IsPartial && _rule.IsFinalTemplateRule && input.IsLastUnappliedRuleNonTemplate == true) { - IMorphologicalRule lastRule = input.MorphologicalRules.Last(); - if (lastRule is AffixProcessRule affixProcessRule && !affixProcessRule.IsTemplateRule) + if (_morpher.TraceManager.IsTracing) { - if (_morpher.TraceManager.IsTracing) - { - _morpher.TraceManager.MorphologicalRuleNotUnapplied( - _rule, - -1, - input, - FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, - null - ); - } - return Enumerable.Empty(); + _morpher.TraceManager.MorphologicalRuleNotUnapplied( + _rule, + -1, + input, + FailureReason.NonPartialRuleProhibitedAfterFinalTemplate, + null + ); } - } + return Enumerable.Empty(); + } var output = new List(); for (int i = 0; i < _rules.Count; i++) diff --git a/src/SIL.Machine.Morphology.HermitCrab/Word.cs b/src/SIL.Machine.Morphology.HermitCrab/Word.cs index 51c3c53cc..fe35bd8be 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Word.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Word.cs @@ -28,6 +28,7 @@ public class Word : Freezable, IAnnotatedData, ICloneable private FeatureStruct _realizationalFS; private Stratum _stratum; private bool? _isLastAppliedRuleFinal; + private bool? _isLastUnappliedRuleNonTemplate; private bool _isPartial; private readonly Dictionary> _disjunctiveAllomorphIndices; private int _mruleAppCount = 0; @@ -47,6 +48,7 @@ public Word(RootAllomorph rootAllomorph, FeatureStruct realizationalFS) _nonHeadApps = new List(); _obligatorySyntacticFeatures = new IDBearerSet(); _isLastAppliedRuleFinal = null; + _isLastUnappliedRuleNonTemplate = null; _disjunctiveAllomorphIndices = new Dictionary>(); } @@ -65,6 +67,7 @@ public Word(Stratum stratum, Shape shape) _nonHeadApps = new List(); _obligatorySyntacticFeatures = new IDBearerSet(); _isLastAppliedRuleFinal = null; + _isLastUnappliedRuleNonTemplate = null; _isPartial = false; _disjunctiveAllomorphIndices = new Dictionary>(); } @@ -93,6 +96,7 @@ private Word(Word word, bool cloneNonHeadApps) _nonHeadAppIndex = word._nonHeadAppIndex; _obligatorySyntacticFeatures = new IDBearerSet(word._obligatorySyntacticFeatures); _isLastAppliedRuleFinal = word._isLastAppliedRuleFinal; + _isLastUnappliedRuleNonTemplate = word._isLastUnappliedRuleNonTemplate; _isPartial = word._isPartial; CurrentTrace = word.CurrentTrace; AnalysisScope = word.AnalysisScope; @@ -346,6 +350,8 @@ internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule) _mruleApps.Add(mrule); _mruleAppIndex++; } + _isLastUnappliedRuleNonTemplate = + (mrule is MorphemicMorphologicalRule morphRule) && !morphRule.IsTemplateRule; } /// @@ -392,6 +398,16 @@ internal bool? IsLastAppliedRuleFinal } } + internal bool? IsLastUnappliedRuleNonTemplate + { + get { return _isLastUnappliedRuleNonTemplate; } + set + { + CheckFrozen(); + _isLastUnappliedRuleNonTemplate = value; + } + } + /// /// Gets the number of times the specified morphological rule has been applied. /// @@ -621,6 +637,7 @@ protected override int FreezeImpl() code = code * 31 + _mruleApps.GetSequenceHashCode(); code = code * 31 + _mruleAppIndex.GetHashCode(); code = code * 31 + _isLastAppliedRuleFinal.GetHashCode(); + code = code * 31 + _isLastUnappliedRuleNonTemplate.GetHashCode(); return code; } @@ -640,7 +657,8 @@ public override bool ValueEquals(Word other) && _rootAllomorph == other._rootAllomorph && _mruleApps.SequenceEqual(other._mruleApps) && _mruleAppIndex == other._mruleAppIndex - && _isLastAppliedRuleFinal == other._isLastAppliedRuleFinal; + && _isLastAppliedRuleFinal == other._isLastAppliedRuleFinal + && _isLastUnappliedRuleNonTemplate == other._isLastUnappliedRuleNonTemplate; } public Word Clone()