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 @@ -137,8 +137,14 @@ public IEnumerable<Word> Apply(Word input)

var appliedAllomorphIndices = new HashSet<int>();
var output = new List<Word>();
// Allomorphs after the recorded one can never yield output, so the loop ends there.
int recordedIndex = -1;
if (input.CurrentRecordedAllomorph is AffixProcessAllomorph recorded)
recordedIndex = _rule.Allomorphs.IndexOf(recorded);
for (int i = 0; i < _rules.Count; i++)
{
if (recordedIndex >= 0 && i > recordedIndex)

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: F1. The cutoff and input.IsAllomorphApplicable(allo) (line 205) now decide the same thing, each reading _allomorphApps[_mruleAppIndex] separately. If IsAllomorphApplicable is later loosened (e.g. to accept free-fluctuation variants), the break silently drops output it would allow. Consider deriving one from the other, e.g. recordedIndex < 0 || i == recordedIndex at line 205.

break;
AffixProcessAllomorph allo = _rule.Allomorphs[i];
MprFeatureGroup group;
if (
Expand Down
6 changes: 6 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/Word.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ internal bool IsAllomorphApplicable(Allomorph allomorph)
return curAllomorph == null || curAllomorph == allomorph;
}

/// <summary>
/// The allomorph that analysis recorded for the rule now being applied, or null when any allomorph is
/// acceptable or no rule is pending.
/// </summary>
internal Allomorph CurrentRecordedAllomorph => _mruleAppIndex < 0 ? null : _allomorphApps[_mruleAppIndex];

internal bool HasRemainingRulesFromStratum(Stratum stratum)
{
if (_mruleAppIndex < 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2088,4 +2088,123 @@ public void IsAllomorphApplicable()
Assert.That(output.Count(), Is.EqualTo(1));
Assert.That(output.First().ToString(), Is.EqualTo("sagz"));
}

private AffixProcessRule AddThreeSegmentSuffix(
string seg0,
string env0,
string seg1,
string env1,
string seg2,
string env2
)
{
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,
};
foreach ((string seg, string env) in new[] { (seg0, env0), (seg1, env1), (seg2, env2) })
{
var allomorph = new AffixProcessAllomorph
{
Lhs = { Pattern<Word, ShapeNode>.New("1").Annotation(any).OneOrMore.Value },
Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, seg) },
};
if (env.Length > 0)
{
allomorph.Environments.Add(
new AllomorphEnvironment(
ConstraintType.Require,
null,
Pattern<Word, ShapeNode>.New().Annotation(Character(Table3, env)).Value
)
);
}
sSuffix.Allomorphs.Add(allomorph);
}
Morphophonemic.MorphologicalRules.Add(sSuffix);

var edSuffix = new AffixProcessRule
{
Name = "ed_suffix",
Gloss = "PAST",
RequiredSyntacticFeatureStruct = FeatureStruct.New(Language.SyntacticFeatureSystem).Symbol("V").Value,
};
edSuffix.Allomorphs.Add(
new AffixProcessAllomorph
{
Lhs = { Pattern<Word, ShapeNode>.New("1").Annotation(any).OneOrMore.Value },
Rhs = { new CopyFromInput("1"), new InsertSegments(Table3, "+ɯd") },
}
);
Morphophonemic.MorphologicalRules.Add(edSuffix);
return sSuffix;
}

private static void CollectSubruleApplications(
Trace trace,
IMorphologicalRule rule,
List<(int Index, int Recorded)> found
)
{
if (
trace.Type == TraceType.MorphologicalRuleSynthesis
&& trace.Source == rule
&& trace.SubruleIndex >= 0
&& trace.Input?.CurrentRecordedAllomorph is AffixProcessAllomorph recorded
)
{
found.Add((trace.SubruleIndex, ((AffixProcessRule)rule).Allomorphs.IndexOf(recorded)));
}
foreach (Trace child in trace.Children)
CollectSubruleApplications(child, rule, found);
}

private List<(int Index, int Recorded)> ParseTraced(AffixProcessRule rule, string word, params string[] expected)
{
TraceManager.IsTracing = true;
var morpher = new Morpher(TraceManager, Language);
AssertMorphsEqual(morpher.ParseWord(word, out object trace), expected);
TraceManager.IsTracing = false;
var found = new List<(int Index, int Recorded)>();
CollectSubruleApplications((Trace)trace, rule, found);
return found;
}

[Test]
public void SynthesisStopsAtRecordedAllomorph()
{
AffixProcessRule sSuffix = AddThreeSegmentSuffix("s", "ɯ", "z", "a", "z", "a");

var applications = ParseTraced(sSuffix, "sagsɯd", "32 3SG PAST");

Assert.That(applications, Is.Not.Empty);
Assert.That(applications.Any(a => a.Index == a.Recorded), Is.True);
Assert.That(applications.Any(a => a.Index > a.Recorded), Is.False);
}

[Test]
public void SynthesisTriesEveryAllomorphUpToRecordedLast()
{
AffixProcessRule sSuffix = AddThreeSegmentSuffix("s", "a", "z", "a", "z", "ɯ");

var applications = ParseTraced(sSuffix, "sagzɯd", "32 3SG PAST");

Assert.That(applications.Any(a => a.Recorded == 2 && a.Index == 2), Is.True);
Assert.That(applications.Any(a => a.Index > a.Recorded), Is.False);
}

[Test]
public void EarlierEnvironmentFreeAllomorphStillBlocksLaterOnes()
{
AffixProcessRule sSuffix = AddThreeSegmentSuffix("s", "", "z", "ɯ", "z", "ɯ");

// allomorph 0 has no environment, so the disjunctive property rejects the later z allomorphs
var applications = ParseTraced(sSuffix, "sagzɯd");

Assert.That(applications.Any(a => a.Index > a.Recorded), Is.False);
ParseTraced(sSuffix, "sagsɯd", "32 3SG PAST");
}
}
Loading