Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ public IEnumerable<Word> Apply(Word input)
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.BeginUnapplyTemplate(_template, input);

// Do not allow a final template to unapply if we don't need to worry about partials
// Do not allow a final template to unapply if we don't need to worry about partial rules
// and a non-template was last unapplied.
if (
(!_morpher.IsPartial || _morpher.AlwaysEnforceFinalTemplates)
(!_morpher.HasPartialRules || _morpher.AlwaysEnforceFinalTemplates)
&& input.FinalTemplateState == FinalTemplateState.NonTemplate
&& _template.IsFinal
)
Expand Down
12 changes: 7 additions & 5 deletions src/SIL.Machine.Morphology.HermitCrab/Morpher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Morpher(ITraceManager traceManager, Language lang, int maxDegreeOfParalle
RuleSelector = rule => true;

_morphemes = new ReadOnlyObservableCollection<Morpheme>(morphemes);
IsPartial = GetPartialMorphemes().Count() > 0;
HasPartialRules = GetPartialMorphemes().Any(morpheme => !(morpheme is LexEntry));
}

public IEnumerable<Morpheme> GetPartialMorphemes()
Expand Down Expand Up @@ -98,9 +98,13 @@ public ITraceManager TraceManager
public bool MergeEquivalentAnalyses { get; set; }

/// <summary>
/// A Morpher is partial if any of the elements are partial.
/// True if any morpheme other than a <see cref="LexEntry"/> is partial. Analysis may prune a final
/// template that follows a non-template rule only when this is false. Partial lexical entries do not
/// count: <see cref="SynthesisAffixTemplatesRule"/> applies no template to a word whose root is partial,
/// and when it substitutes an inflectional-family relative for the root, the relative's word is
/// non-partial, so a following non-partial rule is blocked either way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F3. The summary's reasoning leaves out blocking. A blockable template slot rule can call Word.CheckBlocking (SynthesisAffixProcessRule.cs:199), which swaps in a partial family relative. SynthesisAffixTemplatesRule.cs:45 then marks that word final, and a non-partial rule may follow it because input.IsPartial (SynthesisAffixProcessRule.cs:67). The output matches the relative analyzed directly as a root, so I expect no parse is lost. That is Unverified. Consider cutting the summary to its first sentence.

/// </summary>
public bool IsPartial { get; }
public bool HasPartialRules { get; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F4. Removing Morpher.IsPartial is a breaking public API change. It shipped in v3.9.5 and v3.9.6 (added in #491), so a consumer that reads it fails to compile, or throws MissingMethodException on a drop-in upgrade. I know this was requested. If no consumer such as FieldWorks reads it, nothing more is needed. Otherwise keep it as an [Obsolete] property for one release. Unverified: I did not search FieldWorks.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FieldWorks doesn't read it. I think that nothing further needs to be done.


/// <summary>
/// Enforce final templates even if some of the morphemes were partial.
Expand Down Expand Up @@ -491,8 +495,6 @@ private IEnumerable<Word> LexicalGuess(Word input)
SyntacticFeatureStruct = input.SyntacticFeatureStruct,
Stratum = input.Stratum,
};
// Don't update Morpher.IsPartial even if lexEntry.IsPartial is true.
// We don't mind if final templates are filtered during analysis in this case.
lexEntry.Allomorphs.Add(root);
// Point the root allomorph to the lexical pattern in FieldWorks.
if (lexicalPattern.Morpheme != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ out FeatureStruct syntacticFS

outWord.SyntacticFeatureStruct = syntacticFS;
}
if (!_morpher.IsPartial || _morpher.AlwaysEnforceFinalTemplates)
if (!_morpher.HasPartialRules || _morpher.AlwaysEnforceFinalTemplates)
{
if (!_rule.GetIsCliticRule())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ public void EarlyPruningOfFinalTemplate()

// Verify early pruning of final template.
var morpher = new Morpher(TraceManager, Language);
Assert.That(morpher.IsPartial, Is.True);
Assert.That(morpher.GetPartialMorphemes(), Is.Not.Empty);
morpher.AlwaysEnforceFinalTemplates = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F5. No test covers the analysis-side || _morpher.AlwaysEnforceFinalTemplates override any more. The base grammar's only partial morphemes are lexical entries, so HasPartialRules is false here and at lines 697-743 and 860, and the flag has no effect on analysis. Your new test shows the same depth-2 prune at line 547 with the flag off. A partial rule with the flag on (for example, at the end of the new test) would cover it.

TraceManager.IsTracing = true;
AssertMorphsEqual(morpher.ParseWord("sagdv", out object trace));
Expand Down Expand Up @@ -501,6 +501,79 @@ public void EarlyPruningOfFinalTemplate()
AssertMorphsEqual(morpher.ParseWord("sagdmis"), "32 PAST 53 PL");
}

[Test]
public void PartialLexicalEntryDoesNotDisableEarlyPruningOfFinalTemplate()
{
var any = FeatureStruct.New().Symbol(HCFeatureSystem.Segment).Value;
var verb = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value;

AffixProcessRule CreateSuffix(string name, string gloss, string suffix)
{
var rule = new AffixProcessRule
{
Name = name,
Gloss = gloss,
RequiredSyntacticFeatureStruct = verb.Clone(),
};
rule.Allomorphs.Add(
new AffixProcessAllomorph
{
Lhs = { Pattern<Word, ShapeNode>.New("1").Annotation(any).OneOrMore.Value },
Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, suffix) },
}
);
return rule;
}

var edSuffix = CreateSuffix("ed_suffix", "PAST", "d");
var verbTemplate = new AffixTemplate { Name = "verb", RequiredSyntacticFeatureStruct = verb.Clone() };
verbTemplate.Slots.Add(new AffixTemplateSlot(edSuffix));
Morphophonemic.AffixTemplates.Add(verbTemplate);
var nominalizer = CreateSuffix("nominalizer", "NOM", "v");
Morphophonemic.MorphologicalRules.Add(nominalizer);
var partialRoot = AddEntry("partial_root", verb.Clone(), Morphophonemic, "sab");
partialRoot.IsPartial = true;
try
{
// The test grammar already has a partial entry, so only the rules decide the prune.
var morpher = new Morpher(TraceManager, Language);
Assert.That(morpher.GetPartialMorphemes(), Is.Not.Empty);
Assert.That(morpher.HasPartialRules, Is.False);
Assert.That(morpher.AlwaysEnforceFinalTemplates, Is.False);
TraceManager.IsTracing = true;
AssertMorphsEqual(morpher.ParseWord("sagdv", out object trace));
Assert.That(
GetFailureDepth((Trace)trace, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate),
Is.EqualTo(2)
);
TraceManager.IsTracing = false;
AssertMorphsEqual(morpher.ParseWord("sagd"), "32 PAST");

// No template applies to a partial root, so pruning loses nothing for it.
AssertMorphsEqual(morpher.ParseWord("sab"), "partial_root");
AssertMorphsEqual(morpher.ParseWord("sabd"));
AssertMorphsEqual(morpher.ParseWord("sabdv"));
AssertMorphsEqual(morpher.ParseWord("sabv"), "partial_root NOM");

// A partial rule still turns the prune off, and what it licenses after a final template survives.
nominalizer.IsPartial = true;
morpher = new Morpher(TraceManager, Language);
Assert.That(morpher.HasPartialRules, Is.True);
TraceManager.IsTracing = true;
AssertMorphsEqual(morpher.ParseWord("sagdv", out trace), "32 PAST NOM");
Assert.That(
GetFailureDepth((Trace)trace, FailureReason.NonPartialRuleProhibitedAfterFinalTemplate),
Is.EqualTo(0)
);
TraceManager.IsTracing = false;
}
finally
{
Morphophonemic.Entries.Remove(partialRoot);
Entries.Remove("partial_root");
}
}

private static int GetFailureDepth(Trace trace, FailureReason reason)
{
if (trace == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,11 @@ public void PartialMorphemesParseTheSameWithAndWithoutPruning(bool alwaysEnforce
string[] Parse(bool pruneCopies, string word)
{
Morpher morpher = CreateMorpher(pruneCopies);
Assert.That(morpher.IsPartial, Is.True, "entry 54 has an empty syntactic feature structure");
Assert.That(
morpher.GetPartialMorphemes(),
Is.Not.Empty,
"entry 54 has an empty syntactic feature structure"
);
morpher.AlwaysEnforceFinalTemplates = alwaysEnforceFinalTemplates;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F6. The two TestCases now run the same path. Entry 54 is a LexEntry, so HasPartialRules is false, and analysis prunes final templates whether alwaysEnforceFinalTemplates is true or false. Copy-pruning with the final-template prune off is no longer tested. A partial rule in the fixture would restore the false case.

return ParseGlosses(morpher, word);
}
Expand Down
Loading