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
116 changes: 105 additions & 11 deletions src/SIL.Machine.Morphology.HermitCrab/AnalysisAffixTemplateRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal class AnalysisAffixTemplateRule : IRule<Word, ShapeNode>
private readonly Morpher _morpher;
private readonly AffixTemplate _template;
private readonly List<IRule<Word, ShapeNode>> _rules;
private readonly RequiredSlotMaterial _lookahead;

public AnalysisAffixTemplateRule(Morpher morpher, AffixTemplate template)
{
Expand All @@ -26,6 +27,7 @@ public AnalysisAffixTemplateRule(Morpher morpher, AffixTemplate template)
FreezableEqualityComparer<Word>.Default
))
);
_lookahead = new RequiredSlotMaterial(template);
}

public IEnumerable<Word> Apply(Word input)
Expand Down Expand Up @@ -61,24 +63,98 @@ public IEnumerable<Word> Apply(Word input)
return Enumerable.Empty<Word>();
}

int[] fired = null;
if (RequiredSlotCannotUnapply(input, _rules.Count - 1, ref fired))
{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.EndUnapplyTemplate(_template, input, false, FailureReason.None);
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
return Enumerable.Empty<Word>();
}

Word inWord = input.Clone();
inWord.Freeze();

var output = new HashSet<Word>(FreezableEqualityComparer<Word>.Default);
if (_morpher.MaxDegreeOfParallelism == 1)
ApplySlots(inWord, _rules.Count - 1, output);
ApplySlots(inWord, _rules.Count - 1, output, fired);
else
ParallelApplySlots(inWord, output);
ParallelApplySlots(inWord, output, fired);

return output;
}

private void ApplySlots(Word inWord, int index, HashSet<Word> output)
// Testing runs from the next slot to unapply and stops after an impure slot, which makes the test
// for any slot behind it unsound. Shadow mode prunes nothing and carries the fired slots in fired.
private bool RequiredSlotCannotUnapply(Word word, int lastSlot, ref int[] fired)
{
RequiredSlotLookaheadMode mode = _morpher.RequiredSlotLookahead;
if (mode == RequiredSlotLookaheadMode.Off || _morpher.TraceManager.IsTracing)

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: F8. Dead trace branches: this method now returns false whenever TraceManager.IsTracing is set, so the IsTracing blocks that trace a prune at lines 69-70, 152-153, and 200-206 can never run. They suggest pruned partials still show up in traces, but they don't. Remove them.

return false;

long unifications = 0;
bool cannot = false;
SlotWalk walk = SlotWalk.Start();
for (int j = lastSlot; j >= 0; j--)
{
if (
!_template.Slots[j].Optional
&& !IsFired(fired, j)
&& _lookahead.CannotUnapply(j, word.Shape, ref walk, ref unifications)
)
{
if (mode == RequiredSlotLookaheadMode.On)
{
cannot = true;
break;
}
fired = fired == null ? new[] { j } : fired.Append(j).ToArray();
_morpher.RequiredSlotShadow?.Invoke("fired", _template, j);
}
_lookahead.Advance(j, ref walk);
if (!_lookahead.IsPure(j))
break;
}
if (unifications != 0)
_morpher.RequiredSlotCheckObserver?.Invoke(unifications);
return cannot;
}

private static bool IsFired(int[] fired, int slot)
{
return fired != null && Array.IndexOf(fired, slot) >= 0;
}

private void ReportShadow(string kind, int[] fired, int slot)
{
if (fired == null || _morpher.RequiredSlotShadow == null)
return;
if (slot < 0)
{
foreach (int j in fired)
_morpher.RequiredSlotShadow(kind, _template, j);
}
else if (IsFired(fired, slot))
{
_morpher.RequiredSlotShadow(kind, _template, slot);
}
}

private void ApplySlots(Word inWord, int index, HashSet<Word> output, int[] fired)
{
for (int i = index; i >= 0; i--)
{
foreach (Word outWord in _rules[i].Apply(inWord))
ApplySlots(outWord, i - 1, output);
{
ReportShadow("violated", fired, i);
int[] outFired = fired;
if (RequiredSlotCannotUnapply(outWord, i - 1, ref outFired))
{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.EndUnapplyTemplate(_template, outWord, false, FailureReason.None);
continue;
}
ApplySlots(outWord, i - 1, output, outFired);
}

if (!_template.Slots[i].Optional)
{
Expand All @@ -90,16 +166,17 @@ private void ApplySlots(Word inWord, int index, HashSet<Word> output)

if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.EndUnapplyTemplate(_template, inWord, true, FailureReason.None);
ReportShadow("completed-under-fired", fired, -1);
output.Add(inWord);
}

private void ParallelApplySlots(Word inWord, HashSet<Word> output)
private void ParallelApplySlots(Word inWord, HashSet<Word> output, int[] fired)
{
ParallelOptions parallelOptions = _morpher.CreateParallelOptions();
var outStack = new ConcurrentStack<Word>();
var from = new ConcurrentStack<Tuple<Word, int>>();
from.Push(Tuple.Create(inWord, _rules.Count - 1));
var to = new ConcurrentStack<Tuple<Word, int>>();
var from = new ConcurrentStack<Tuple<Word, int, int[]>>();
from.Push(Tuple.Create(inWord, _rules.Count - 1, fired));
var to = new ConcurrentStack<Tuple<Word, int, int[]>>();
while (!from.IsEmpty)
{
to.Clear();
Expand All @@ -111,9 +188,25 @@ private void ParallelApplySlots(Word inWord, HashSet<Word> output)
bool add = true;
for (int i = work.Item2; i >= 0; i--)
{
Tuple<Word, int>[] workItems = _rules[i]
int next = i - 1;
Tuple<Word, int, int[]>[] workItems = _rules[i]
.Apply(work.Item1)
.Select(res => Tuple.Create(res, i - 1))
.Select(res =>
{
ReportShadow("violated", work.Item3, i);
int[] resFired = work.Item3;
if (!RequiredSlotCannotUnapply(res, next, ref resFired))
return Tuple.Create(res, next, resFired);
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.EndUnapplyTemplate(
_template,
res,
false,
FailureReason.None
);
return null;
})
.Where(item => item != null)
.ToArray();
if (workItems.Length > 0)
to.PushRange(workItems);
Expand Down Expand Up @@ -141,11 +234,12 @@ private void ParallelApplySlots(Word inWord, HashSet<Word> output)
true,
FailureReason.None
);
ReportShadow("completed-under-fired", work.Item3, -1);
outStack.Push(work.Item1);
}
}
);
ConcurrentStack<Tuple<Word, int>> temp = from;
ConcurrentStack<Tuple<Word, int, int[]>> temp = from;
from = to;
to = temp;
}
Expand Down
30 changes: 30 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/Morpher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ public ITraceManager TraceManager

public int DeletionReapplications { get; set; }

/// <summary>
/// Whether affix template walks drop partials in which a required slot can no longer unapply because
/// the material it inserts is absent. See <see cref="RequiredSlotMaterial"/>.
/// </summary>
internal RequiredSlotLookaheadMode RequiredSlotLookahead { get; set; } = RequiredSlotLookaheadMode.On;
Comment thread
claude[bot] marked this conversation as resolved.
Resolved

/// <summary>
/// Enable required-slot pruning during affix template analysis. Enabled by default. Set it before
/// parsing; changing it during a parse alters how much is pruned, not the analyses returned.
/// </summary>
public bool PruneRequiredSlots
{
get { return RequiredSlotLookahead != RequiredSlotLookaheadMode.Off; }
set { RequiredSlotLookahead = value ? RequiredSlotLookaheadMode.On : RequiredSlotLookaheadMode.Off; }
}

/// <summary>
/// Called in <see cref="RequiredSlotLookaheadMode.Shadow"/> with a kind and the template and slot it is about:
/// "fired" (the test is true for a required slot), "violated" (a rule of a fired slot then unapplied under
/// that partial) and "completed-under-fired" (the template walk completed with a fired slot on its path).
/// Any "violated" or "completed-under-fired" is a soundness failure.
/// </summary>
internal Action<string, AffixTemplate, int> RequiredSlotShadow { get; set; }

/// <summary>
/// Called after each required-slot check that unified, with the number of unifications it performed.
/// Null unless a diagnostic sets it; it may be called from several threads.
/// </summary>
internal Action<long> RequiredSlotCheckObserver { get; set; }
Comment thread
claude[bot] marked this conversation as resolved.
Resolved

public int MaxStemCount { get; set; }

/// <summary>
Expand Down
Loading
Loading