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/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..14db5fc80 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs @@ -196,21 +196,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..a1722a18a 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++; } } @@ -441,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]); @@ -536,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(); @@ -560,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 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")); + } }