Skip to content
Draft
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
144 changes: 124 additions & 20 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,17 +27,47 @@ public AnalysisAffixTemplateRule(Morpher morpher, AffixTemplate template)
FreezableEqualityComparer<Word>.Default
))
);
_lookahead = new RequiredSlotMaterial(template);
}

public IEnumerable<Word> Apply(Word input)
{
if (!_morpher.RuleSelector(_template))
if (!CanApply(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);
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, fired);
else
ParallelApplySlots(inWord, output, fired);

return output;
}

internal AffixTemplate Template => _template;

internal IReadOnlyList<IRule<Word, ShapeNode>> SlotRules => _rules;

internal bool CanApply(Word input)
{
if (!_morpher.RuleSelector(_template))
return false;

// A check only: adding the template's required features here would make SyntacticFeatureStruct
// depend on whether a template was un-applied, which Word.ValueEquals assumes cannot happen.
if (!input.SyntacticFeatureStruct.IsUnifiable(_template.RequiredSyntacticFeatureStruct))
return Enumerable.Empty<Word>();
return false;

if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.BeginUnapplyTemplate(_template, input);
Expand All @@ -58,27 +89,82 @@ public IEnumerable<Word> Apply(Word input)
FailureReason.NonPartialRuleProhibitedAfterFinalTemplate
);
}
return Enumerable.Empty<Word>();
return false;
}

Word inWord = input.Clone();
inWord.Freeze();
return true;
}

var output = new HashSet<Word>(FreezableEqualityComparer<Word>.Default);
if (_morpher.MaxDegreeOfParallelism == 1)
ApplySlots(inWord, _rules.Count - 1, output);
else
ParallelApplySlots(inWord, 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.
internal bool RequiredSlotCannotUnapply(Word word, int lastSlot, ref int[] fired)
{
RequiredSlotLookaheadMode mode = _morpher.RequiredSlotLookahead;
if (mode == RequiredSlotLookaheadMode.Off)
return false;

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

private void ApplySlots(Word inWord, int index, HashSet<Word> output)
internal static bool IsFired(int[] fired, int slot)
{
return fired != null && Array.IndexOf(fired, slot) >= 0;
}

internal 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 +176,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 +198,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 +244,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
Loading
Loading