From fa6e2c5b8464c0970883b75f3a353d0c444f1c07 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 22 Oct 2026 09:16:56 -0700 Subject: [PATCH 1/4] Filter synthesis by the allomorphs used --- .../AnalysisAffixProcessRule.cs | 2 +- .../SynthesisAffixProcessRule.cs | 31 +++++++++++-------- src/SIL.Machine.Morphology.HermitCrab/Word.cs | 19 ++++++++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index 7ccba3372..91c576024 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -89,7 +89,7 @@ out FeatureStruct syntacticFS : FinalTemplateState.None; } } - outWord.MorphologicalRuleUnapplied(_rule); + outWord.MorphologicalRuleUnapplied(_rule, _rule.Allomorphs[i]); outWord.Freeze(); if (_morpher.TraceManager.IsTracing) _morpher.TraceManager.MorphologicalRuleUnapplied(_rule, i, input, outWord); diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs index 68d5a12e1..57869ee0b 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs @@ -142,7 +142,8 @@ public IEnumerable Apply(Word input) AffixProcessAllomorph allo = _rule.Allomorphs[i]; MprFeatureGroup group; if ( - allo.RequiredMprFeatures.Count > 0 + input.IsAllomorphApplicable(allo) + && allo.RequiredMprFeatures.Count > 0 && !allo.RequiredMprFeatures.IsMatchRequired(input.MprFeatures, out group) ) { @@ -159,7 +160,8 @@ public IEnumerable Apply(Word input) continue; } if ( - allo.ExcludedMprFeatures.Count > 0 + input.IsAllomorphApplicable(allo) + && allo.ExcludedMprFeatures.Count > 0 && !allo.ExcludedMprFeatures.IsMatchExcluded(input.MprFeatures, out group) ) { @@ -196,21 +198,24 @@ public IEnumerable Apply(Word input) outWord.MorphologicalRuleApplied(_rule, appliedAllomorphIndices); appliedAllomorphIndices.Add(i); - if (_rule.Blockable && outWord.CheckBlocking(out Word newWord)) + if (input.IsAllomorphApplicable(allo)) { + if (_rule.Blockable && outWord.CheckBlocking(out Word newWord)) + { + if (_morpher.TraceManager.IsTracing) + _morpher.TraceManager.Blocked(_rule, newWord); + outWord = newWord; + } + else + { + outWord.Freeze(); + } + if (_morpher.TraceManager.IsTracing) - _morpher.TraceManager.Blocked(_rule, newWord); - outWord = newWord; - } - else - { - outWord.Freeze(); + _morpher.TraceManager.MorphologicalRuleApplied(_rule, i, input, outWord); + output.Add(outWord); } - if (_morpher.TraceManager.IsTracing) - _morpher.TraceManager.MorphologicalRuleApplied(_rule, i, input, outWord); - output.Add(outWord); - // return all word syntheses that match subrules that are constrained by environments, // HC violates the disjunctive property of allomorphs here because it cannot check the // environmental constraints until it has a surface form, we will enforce the disjunctive diff --git a/src/SIL.Machine.Morphology.HermitCrab/Word.cs b/src/SIL.Machine.Morphology.HermitCrab/Word.cs index 45d2642e6..3d1f9dfcc 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Word.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Word.cs @@ -18,6 +18,7 @@ public class Word : Freezable, IAnnotatedData, ICloneable private RootAllomorph _rootAllomorph; private Shape _shape; private readonly List _mruleApps; + private readonly List _allomorphApps; private int _mruleAppIndex = -1; private readonly Dictionary _mrulesUnapplied; private readonly Dictionary _mrulesApplied; @@ -42,6 +43,7 @@ public Word(RootAllomorph rootAllomorph, FeatureStruct realizationalFS) ResetDirty(); SetRootAllomorph(rootAllomorph); RealizationalFeatureStruct = realizationalFS; + _allomorphApps = new List(); _mruleApps = new List(); _mrulesUnapplied = new Dictionary(); _mrulesApplied = new Dictionary(); @@ -61,6 +63,7 @@ public Word(Stratum stratum, Shape shape) SyntacticFeatureStruct = new FeatureStruct(); RealizationalFeatureStruct = new FeatureStruct(); _mprFeatures = new MprFeatureSet(); + _allomorphApps = new List(); _mruleApps = new List(); _mrulesUnapplied = new Dictionary(); _mrulesApplied = new Dictionary(); @@ -88,6 +91,7 @@ private Word(Word word, bool cloneNonHeadApps) SyntacticFeatureStruct = word.SyntacticFeatureStruct.Clone(); RealizationalFeatureStruct = word.RealizationalFeatureStruct.Clone(); _mprFeatures = word.MprFeatures.Clone(); + _allomorphApps = new List(word._allomorphApps); _mruleApps = new List(word._mruleApps); _mruleAppIndex = word._mruleAppIndex; _mrulesUnapplied = new Dictionary(word._mrulesUnapplied); @@ -263,6 +267,15 @@ internal bool IsMorphologicalRuleApplicable(IMorphologicalRule rule) return curRule == rule || (curRule == null && rule is CompoundingRule); } + internal bool IsAllomorphApplicable(Allomorph allomorph) + { + if (_mruleAppIndex < 0) + return false; + + Allomorph curAllomorph = _allomorphApps[_mruleAppIndex]; + return curAllomorph == null || curAllomorph == allomorph; + } + internal bool HasRemainingRulesFromStratum(Stratum stratum) { if (_mruleAppIndex < 0) @@ -329,7 +342,8 @@ internal void RemoveMorph(Annotation morphAnn) } /// - /// Notifies this word that the specified morphological rule was unapplied. Null + /// Notifies this word that the specified morphological rule and allomorph were unapplied. + /// A null allomorph means that any allomorph is acceptable. A null morphological rule /// indicates that an unknown compounding rule was unapplied. This is used when /// generating a compound word, because the compounding rule is usually not known just /// the non-head allomorph. @@ -340,7 +354,7 @@ internal void RemoveMorph(Annotation morphAnn) /// they do so on both sides of any comparison; any other divergence misaligns the graft. /// /// - internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule) + internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule, Allomorph allomorph = null) { CheckFrozen(); if (mrule != null) @@ -348,6 +362,7 @@ internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule) if (!(mrule is RealizationalAffixProcessRule)) { _mruleApps.Add(mrule); + _allomorphApps.Add(allomorph); _mruleAppIndex++; } } From e36bc03502ef44821055c581f36e088e25940259 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 22 Oct 2026 09:16:56 -0700 Subject: [PATCH 2/4] Filter synthesis by the allomorphs used --- .../AnalysisAffixProcessRule.cs | 2 +- .../SynthesisAffixProcessRule.cs | 31 +++++++++++-------- src/SIL.Machine.Morphology.HermitCrab/Word.cs | 19 ++++++++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs index 59651247c..7e841b1fb 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs @@ -89,7 +89,7 @@ out FeatureStruct syntacticFS : FinalTemplateState.None; } } - outWord.MorphologicalRuleUnapplied(_rule); + outWord.MorphologicalRuleUnapplied(_rule, _rule.Allomorphs[i]); outWord.Freeze(); if (_morpher.TraceManager.IsTracing) _morpher.TraceManager.MorphologicalRuleUnapplied(_rule, i, input, outWord); diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs index 68d5a12e1..57869ee0b 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs @@ -142,7 +142,8 @@ public IEnumerable Apply(Word input) AffixProcessAllomorph allo = _rule.Allomorphs[i]; MprFeatureGroup group; if ( - allo.RequiredMprFeatures.Count > 0 + input.IsAllomorphApplicable(allo) + && allo.RequiredMprFeatures.Count > 0 && !allo.RequiredMprFeatures.IsMatchRequired(input.MprFeatures, out group) ) { @@ -159,7 +160,8 @@ public IEnumerable Apply(Word input) continue; } if ( - allo.ExcludedMprFeatures.Count > 0 + input.IsAllomorphApplicable(allo) + && allo.ExcludedMprFeatures.Count > 0 && !allo.ExcludedMprFeatures.IsMatchExcluded(input.MprFeatures, out group) ) { @@ -196,21 +198,24 @@ public IEnumerable Apply(Word input) outWord.MorphologicalRuleApplied(_rule, appliedAllomorphIndices); appliedAllomorphIndices.Add(i); - if (_rule.Blockable && outWord.CheckBlocking(out Word newWord)) + if (input.IsAllomorphApplicable(allo)) { + if (_rule.Blockable && outWord.CheckBlocking(out Word newWord)) + { + if (_morpher.TraceManager.IsTracing) + _morpher.TraceManager.Blocked(_rule, newWord); + outWord = newWord; + } + else + { + outWord.Freeze(); + } + if (_morpher.TraceManager.IsTracing) - _morpher.TraceManager.Blocked(_rule, newWord); - outWord = newWord; - } - else - { - outWord.Freeze(); + _morpher.TraceManager.MorphologicalRuleApplied(_rule, i, input, outWord); + output.Add(outWord); } - if (_morpher.TraceManager.IsTracing) - _morpher.TraceManager.MorphologicalRuleApplied(_rule, i, input, outWord); - output.Add(outWord); - // return all word syntheses that match subrules that are constrained by environments, // HC violates the disjunctive property of allomorphs here because it cannot check the // environmental constraints until it has a surface form, we will enforce the disjunctive diff --git a/src/SIL.Machine.Morphology.HermitCrab/Word.cs b/src/SIL.Machine.Morphology.HermitCrab/Word.cs index fcff87402..0d1c03234 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Word.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Word.cs @@ -18,6 +18,7 @@ public class Word : Freezable, IAnnotatedData, ICloneable private RootAllomorph _rootAllomorph; private Shape _shape; private readonly List _mruleApps; + private readonly List _allomorphApps; private int _mruleAppIndex = -1; private readonly Dictionary _mrulesUnapplied; private readonly Dictionary _mrulesApplied; @@ -42,6 +43,7 @@ public Word(RootAllomorph rootAllomorph, FeatureStruct realizationalFS) ResetDirty(); SetRootAllomorph(rootAllomorph); RealizationalFeatureStruct = realizationalFS; + _allomorphApps = new List(); _mruleApps = new List(); _mrulesUnapplied = new Dictionary(); _mrulesApplied = new Dictionary(); @@ -61,6 +63,7 @@ public Word(Stratum stratum, Shape shape) SyntacticFeatureStruct = new FeatureStruct(); RealizationalFeatureStruct = new FeatureStruct(); _mprFeatures = new MprFeatureSet(); + _allomorphApps = new List(); _mruleApps = new List(); _mrulesUnapplied = new Dictionary(); _mrulesApplied = new Dictionary(); @@ -83,6 +86,7 @@ protected Word(Word word) SyntacticFeatureStruct = word.SyntacticFeatureStruct.Clone(); RealizationalFeatureStruct = word.RealizationalFeatureStruct.Clone(); _mprFeatures = word.MprFeatures.Clone(); + _allomorphApps = new List(word._allomorphApps); _mruleApps = new List(word._mruleApps); _mruleAppIndex = word._mruleAppIndex; _mrulesUnapplied = new Dictionary(word._mrulesUnapplied); @@ -247,6 +251,15 @@ internal bool IsMorphologicalRuleApplicable(IMorphologicalRule rule) return curRule == rule || (curRule == null && rule is CompoundingRule); } + internal bool IsAllomorphApplicable(Allomorph allomorph) + { + if (_mruleAppIndex < 0) + return false; + + Allomorph curAllomorph = _allomorphApps[_mruleAppIndex]; + return curAllomorph == null || curAllomorph == allomorph; + } + internal bool HasRemainingRulesFromStratum(Stratum stratum) { if (_mruleAppIndex < 0) @@ -313,12 +326,13 @@ internal void RemoveMorph(Annotation morphAnn) } /// - /// Notifies this word that the specified morphological rule was unapplied. Null + /// Notifies this word that the specified morphological rule and allomorph were unapplied. + /// A null allomorph means that any allomorph is acceptable. A null morphological rule /// indicates that an unknown compounding rule was unapplied. This is used when /// generating a compound word, because the compounding rule is usually not known just /// the non-head allomorph. /// - internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule) + internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule, Allomorph allomorph = null) { CheckFrozen(); if (mrule != null) @@ -326,6 +340,7 @@ internal void MorphologicalRuleUnapplied(IMorphologicalRule mrule) if (!(mrule is RealizationalAffixProcessRule)) { _mruleApps.Add(mrule); + _allomorphApps.Add(allomorph); _mruleAppIndex++; } } From bb7211acfcfaa933236ee7e8e07436133dde5cb7 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 23 Oct 2026 08:23:20 -0700 Subject: [PATCH 3/4] Fix issues reported by Claude --- .../MorphologicalRules/SynthesisAffixProcessRule.cs | 10 ++++------ src/SIL.Machine.Morphology.HermitCrab/Word.cs | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs index 57869ee0b..71fff506d 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs @@ -142,12 +142,11 @@ public IEnumerable Apply(Word input) AffixProcessAllomorph allo = _rule.Allomorphs[i]; MprFeatureGroup group; if ( - input.IsAllomorphApplicable(allo) - && allo.RequiredMprFeatures.Count > 0 + allo.RequiredMprFeatures.Count > 0 && !allo.RequiredMprFeatures.IsMatchRequired(input.MprFeatures, out group) ) { - if (_morpher.TraceManager.IsTracing) + if (_morpher.TraceManager.IsTracing && input.IsAllomorphApplicable(allo)) { _morpher.TraceManager.MorphologicalRuleNotApplied( _rule, @@ -160,12 +159,11 @@ public IEnumerable Apply(Word input) continue; } if ( - input.IsAllomorphApplicable(allo) - && allo.ExcludedMprFeatures.Count > 0 + allo.ExcludedMprFeatures.Count > 0 && !allo.ExcludedMprFeatures.IsMatchExcluded(input.MprFeatures, out group) ) { - if (_morpher.TraceManager.IsTracing) + if (_morpher.TraceManager.IsTracing && input.IsAllomorphApplicable(allo)) { _morpher.TraceManager.MorphologicalRuleNotApplied( _rule, diff --git a/src/SIL.Machine.Morphology.HermitCrab/Word.cs b/src/SIL.Machine.Morphology.HermitCrab/Word.cs index 0d1c03234..a1722a18a 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Word.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Word.cs @@ -456,7 +456,7 @@ internal IList ExpandAlternatives() // Add new rules to alternative. int m_start = Source == null ? 0 : Source._mruleApps.Count(); for (int i = m_start; i < _mruleApps.Count(); i++) - alternative.MorphologicalRuleUnapplied(_mruleApps[i]); + alternative.MorphologicalRuleUnapplied(_mruleApps[i], _allomorphApps[i]); int nh_start = Source == null ? 0 : Source._nonHeadApps.Count(); for (int i = nh_start; i < _nonHeadApps.Count(); i++) alternative.NonHeadUnapplied(_nonHeadApps[i]); @@ -551,6 +551,7 @@ protected override int FreezeImpl() code = code * 31 + _nonHeadAppIndex.GetHashCode(); code = code * 31 + _stratum.GetHashCode(); code = code * 31 + (_rootAllomorph == null ? 0 : _rootAllomorph.GetHashCode()); + code = code * 31 + _allomorphApps.GetSequenceHashCode(); code = code * 31 + _mruleApps.GetSequenceHashCode(); code = code * 31 + _mruleAppIndex.GetHashCode(); code = code * 31 + _isLastAppliedRuleFinal.GetHashCode(); @@ -575,6 +576,7 @@ public override bool ValueEquals(Word other) && _nonHeadAppIndex == other._nonHeadAppIndex && _stratum == other._stratum && _rootAllomorph == other._rootAllomorph + && _allomorphApps.SequenceEqual(other._allomorphApps) && _mruleApps.SequenceEqual(other._mruleApps) && _mruleAppIndex == other._mruleAppIndex && _isLastAppliedRuleFinal == other._isLastAppliedRuleFinal From f7aea42874b1ffbf3f4f7031a9696635c1ad5725 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Sat, 24 Oct 2026 12:39:06 -0700 Subject: [PATCH 4/4] Fix F6; add unit tests --- .../Morpher.cs | 7 ++- .../SynthesisAffixProcessRule.cs | 4 +- .../AffixProcessRuleTests.cs | 57 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs b/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs index 83023517e..d0675509e 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/Morpher.cs @@ -413,13 +413,18 @@ private IEnumerable SynthesizeParallel(string word, ConcurrentQueue return matches.Distinct(FreezableEqualityComparer.Default); } + internal IEnumerable SynthesizeWord(Word input) + { + return _synthesisRule.Apply(input).Where(IsWordValid); + } + internal IEnumerable SearchRootAllomorphs(Stratum stratum, Shape shape) { RootAllomorphTrie alloSearcher = _allomorphTries[stratum]; return alloSearcher.Search(shape).Distinct(); } - private IEnumerable LexicalLookup(Word input) + internal IEnumerable LexicalLookup(Word input) { if (_traceManager.IsTracing) _traceManager.LexicalLookup(input.Stratum, input); diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs index 71fff506d..14db5fc80 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs @@ -146,7 +146,7 @@ public IEnumerable Apply(Word input) && !allo.RequiredMprFeatures.IsMatchRequired(input.MprFeatures, out group) ) { - if (_morpher.TraceManager.IsTracing && input.IsAllomorphApplicable(allo)) + if (_morpher.TraceManager.IsTracing) { _morpher.TraceManager.MorphologicalRuleNotApplied( _rule, @@ -163,7 +163,7 @@ public IEnumerable Apply(Word input) && !allo.ExcludedMprFeatures.IsMatchExcluded(input.MprFeatures, out group) ) { - if (_morpher.TraceManager.IsTracing && input.IsAllomorphApplicable(allo)) + if (_morpher.TraceManager.IsTracing) { _morpher.TraceManager.MorphologicalRuleNotApplied( _rule, diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/MorphologicalRules/AffixProcessRuleTests.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/MorphologicalRules/AffixProcessRuleTests.cs index 579d997db..278f46155 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/MorphologicalRules/AffixProcessRuleTests.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/MorphologicalRules/AffixProcessRuleTests.cs @@ -2031,4 +2031,61 @@ public void NonContiguousRules() var morpher = new Morpher(TraceManager, Language); AssertMorphsEqual(morpher.ParseWord("katibɯd"), "49 PER.ACT"); } + + [Test] + public void IsAllomorphApplicable() + { + FeatureStruct any = FeatureStruct.New().Symbol(HCFeatureSystem.Segment).Value; + var sSuffix = new AffixProcessRule + { + Name = "s_suffix", + Gloss = "3SG", + RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value, + OutSyntacticFeatureStruct = FeatureStruct + .New(Language.SyntacticFeatureSystem) + .Feature(Head) + .EqualTo(head => head.Feature("pers").EqualTo("3")) + .Value, + }; + var allomorph1 = new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "s") }, + }; + var allomorph2 = new AffixProcessAllomorph + { + Lhs = { Pattern.New("1").Annotation(any).OneOrMore.Value }, + Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "z") }, + }; + sSuffix.Allomorphs.Add(allomorph1); + sSuffix.Allomorphs.Add(allomorph2); + Morphophonemic.MorphologicalRules.Add(sSuffix); + var morpher = new Morpher(TraceManager, Language) { MergeEquivalentAnalyses = true }; + + Shape shape = Morphophonemic.CharacterDefinitionTable.Segment("sag"); + var input = new Word(Morphophonemic, shape); + input.MorphologicalRuleUnapplied(sSuffix); + input.Freeze(); + Word word = morpher.LexicalLookup(input).First(); + IEnumerable output = morpher.SynthesizeWord(word); + Assert.That(output.Count(), Is.EqualTo(2)); + + shape = Morphophonemic.CharacterDefinitionTable.Segment("sag"); + input = new Word(Morphophonemic, shape); + input.MorphologicalRuleUnapplied(sSuffix, allomorph1); + input.Freeze(); + word = morpher.LexicalLookup(input).First(); + output = morpher.SynthesizeWord(word); + Assert.That(output.Count(), Is.EqualTo(1)); + Assert.That(output.First().ToString(), Is.EqualTo("sags")); + + shape = Morphophonemic.CharacterDefinitionTable.Segment("sag"); + input = new Word(Morphophonemic, shape); + input.MorphologicalRuleUnapplied(sSuffix, allomorph2); + input.Freeze(); + word = morpher.LexicalLookup(input).First(); + output = morpher.SynthesizeWord(word); + Assert.That(output.Count(), Is.EqualTo(1)); + Assert.That(output.First().ToString(), Is.EqualTo("sagz")); + } }