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
12 changes: 12 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab.Tool/ParseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ private static bool IsAnalysis(TraceType type)
case TraceType.StratumAnalysisOutput:
case TraceType.TemplateAnalysisInput:
case TraceType.TemplateAnalysisOutput:
case TraceType.TemplateSlotAnalysis:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FYI: F6. The HC tool now prints slot traces without SlotIndex or SlotOutcome: PrintTrace writes only source, input, output, and reason. Applied, skipped, and unfilled lines for the same template look alike, so the tool trace gets longer without telling the reader more.

case TraceType.MorphologicalRuleAnalysis:
case TraceType.CompoundingRuleAnalysis:
case TraceType.PhonologicalRuleAnalysis:
return true;

Expand All @@ -152,7 +154,9 @@ private static bool IsAnalysis(TraceType type)
case TraceType.StratumSynthesisOutput:
case TraceType.TemplateSynthesisInput:
case TraceType.TemplateSynthesisOutput:
case TraceType.TemplateSlotSynthesis:
case TraceType.MorphologicalRuleSynthesis:
case TraceType.CompoundingRuleSynthesis:
case TraceType.PhonologicalRuleSynthesis:
return false;
}
Expand Down Expand Up @@ -203,6 +207,14 @@ private static string GetTraceTypeString(TraceType type)
return "Template Synthesis In";
case TraceType.TemplateSynthesisOutput:
return "Template Synthesis Out";
case TraceType.TemplateSlotAnalysis:
return "Template Slot Analysis";
case TraceType.TemplateSlotSynthesis:
return "Template Slot Synthesis";
case TraceType.CompoundingRuleAnalysis:
return "Compounding Rule Analysis";
case TraceType.CompoundingRuleSynthesis:
return "Compounding Rule Synthesis";
case TraceType.MorphologicalRuleAnalysis:
return "Morphological Rule Analysis";
case TraceType.MorphologicalRuleSynthesis:
Expand Down
25 changes: 25 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/AnalysisAffixTemplateRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,18 @@ private void ApplySlots(Word inWord, int index, HashSet<Word> output)
{
for (int i = index; i >= 0; i--)
{
bool producedOutput = false;
foreach (Word outWord in _rules[i].Apply(inWord))
{
producedOutput = true;
TraceSlot(inWord, outWord, i, TemplateSlotOutcome.RuleApplied);
ApplySlots(outWord, i - 1, output);
}

if (_template.Slots[i].Optional)
TraceSlot(inWord, null, i, TemplateSlotOutcome.OptionalSkipped);
else if (!producedOutput)
TraceSlot(inWord, null, i, TemplateSlotOutcome.RequiredUnfilled);

if (!_template.Slots[i].Optional)
{
Expand Down Expand Up @@ -116,7 +126,16 @@ private void ParallelApplySlots(Word inWord, HashSet<Word> output)
.Select(res => Tuple.Create(res, i - 1))
.ToArray();
if (workItems.Length > 0)
{
foreach (Tuple<Word, int> item in workItems)
TraceSlot(work.Item1, item.Item1, i, TemplateSlotOutcome.RuleApplied);
to.PushRange(workItems);
}

if (_template.Slots[i].Optional)
TraceSlot(work.Item1, null, i, TemplateSlotOutcome.OptionalSkipped);
else if (workItems.Length == 0)
TraceSlot(work.Item1, null, i, TemplateSlotOutcome.RequiredUnfilled);

if (!_template.Slots[i].Optional)
{
Expand Down Expand Up @@ -152,5 +171,11 @@ private void ParallelApplySlots(Word inWord, HashSet<Word> output)

output.UnionWith(outStack);
}

private void TraceSlot(Word input, Word output, int index, TemplateSlotOutcome outcome)
{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.TemplateSlotProcessed(_template, index, input, output, true, outcome);
}
}
}
63 changes: 63 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/IDetailedTraceManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
namespace SIL.Machine.Morphology.HermitCrab
{
public enum TemplateSlotOutcome
{
RuleApplied,
OptionalSkipped,
RequiredUnfilled,
}

public enum PartialParseCause
{
RealizationalFeatureMismatch,
UnappliedMorphologicalRules,
NonFinalTemplateAppliedLast,
ApplicableTemplatesNotApplied,
}

public class PartialParseFailure
{
public PartialParseFailure(PartialParseCause cause, IMorphologicalRule rule = null)
{
Cause = cause;
Rule = rule;
}

public PartialParseCause Cause { get; }

public IMorphologicalRule Rule { get; }
}

/// <summary>
/// Optional tracing capability. ITraceManager implementations receive the existing callbacks without opting in.
/// </summary>
public interface IDetailedTraceManager : ITraceManager
{
void MorphologicalRuleNotUnapplied(
IMorphologicalRule rule,
int subruleIndex,
Word input,
FailureReason reason,
object failureObj
);

/// <summary>
/// Reports fully enumerated lookup results as yielded Word candidates, before surface validation.
/// Interrupted lookups do not receive this callback. Each input has at most one active lookup.
/// </summary>
void LexicalLookupCompleted(Stratum stratum, Word input, int candidateCount, bool guessed);

/// <summary>
/// Reports a slot branch. OptionalSkipped can accompany RuleApplied for the same input.
/// RequiredUnfilled means the slot produced no output; no linguistic cause is implied.
/// </summary>
void TemplateSlotProcessed(
AffixTemplate template,
int slotIndex,
Word input,
Word output,
bool analysis,
TemplateSlotOutcome outcome
);
}
}
4 changes: 4 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/ITraceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public enum FailureReason
NonPartialRuleProhibitedAfterFinalTemplate,
NonPartialRuleRequiredAfterNonFinalTemplate,
MaxApplicationCount,
OutputSyntacticFeatureStruct,
RealizationalFeatureStruct,
MaxStemCount,
NonHeadLexicalLookup,
}

public interface ITraceManager
Expand Down
36 changes: 31 additions & 5 deletions src/SIL.Machine.Morphology.HermitCrab/Morpher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ internal IEnumerable<RootAllomorph> SearchRootAllomorphs(Stratum stratum, Shape

private IEnumerable<Word> LexicalLookup(Word input)
{
int candidateCount = 0;
if (_traceManager.IsTracing)
_traceManager.LexicalLookup(input.Stratum, input);
foreach (
Expand All @@ -464,16 +465,20 @@ LexEntry entry in SearchRootAllomorphs(input.Stratum, input.Shape)
if (_traceManager.IsTracing)
_traceManager.SynthesizeWord(_lang, newWord);
newWord.Freeze();
candidateCount++;
yield return newWord;
}
}
if (_traceManager.IsTracing)
_traceManager.LexicalLookupCompleted(input.Stratum, input, candidateCount, false);
}

/// <summary>
/// Match the input against lexical patterns and return matches.
/// </summary>
private IEnumerable<Word> LexicalGuess(Word input)
{
int candidateCount = 0;
if (_traceManager.IsTracing)
_traceManager.LexicalLookup(input.Stratum, input);
CharacterDefinitionTable table = input.Stratum.CharacterDefinitionTable;
Expand Down Expand Up @@ -540,10 +545,13 @@ private IEnumerable<Word> LexicalGuess(Word input)
if (_traceManager.IsTracing)
_traceManager.SynthesizeWord(_lang, newWord);
newWord.Freeze();
candidateCount++;
yield return newWord;
}
}
}
if (_traceManager.IsTracing)
_traceManager.LexicalLookupCompleted(input.Stratum, input, candidateCount, true);
}

/// <summary>
Expand Down Expand Up @@ -657,13 +665,31 @@ private IEnumerable<string> GetShapeStrings(IList<ShapeNode> nodes, CharacterDef

private bool IsWordValid(Word word)
{
if (
!word.RealizationalFeatureStruct.IsUnifiable(word.SyntacticFeatureStruct)
|| !word.IsAllMorphologicalRulesApplied
)
if (!word.RealizationalFeatureStruct.IsUnifiable(word.SyntacticFeatureStruct))
{
if (_traceManager.IsTracing)
_traceManager.Failed(
_lang,
word,
FailureReason.PartialParse,
null,
new PartialParseFailure(PartialParseCause.RealizationalFeatureMismatch)
);
return false;
}
if (!word.IsAllMorphologicalRulesApplied)
{
if (_traceManager.IsTracing)
_traceManager.Failed(_lang, word, FailureReason.PartialParse, null, null);
_traceManager.Failed(
_lang,
word,
FailureReason.PartialParse,
null,
new PartialParseFailure(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FYI: F4. Failed(..., PartialParse, null, failureObj) now passes a PartialParseFailure to plain managers, where base passed null; SynthesisStratumRule.cs:74 does the same. A legacy manager that serializes failureObj gets an unknown type. Unverified: I did not read FwXmlTraceManager.Failed.

PartialParseCause.UnappliedMorphologicalRules,
word.NextMorphologicalRule
)
);
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,38 @@ public IEnumerable<Word> Apply(Word input)
if (!_morpher.RuleSelector(_rule))
return Enumerable.Empty<Word>();

if (
input.GetUnapplicationCount(_rule) >= _rule.MaxApplicationCount
|| !_outputFS.IsUnifiable(input.SyntacticFeatureStruct)
)
if (input.GetUnapplicationCount(_rule) >= _rule.MaxApplicationCount)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor: F2. Plain managers now get calls they never got before: MorphologicalRuleNotUnapplied(rule, -1, input) from the MaxApplicationCount and output-FS gates here, and from the realizational gate at AnalysisRealizationalAffixProcessRule.cs:53. Base returned silently, and -1 was never a subrule index on this callback, so a consumer that indexes Subrules[subruleIndex] throws. Unverified against FieldWorks, whose handler the description says is empty. Gate these the way CompoundingRuleRejected does.

{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.MorphologicalRuleNotUnapplied(
_rule,
-1,
input,
FailureReason.MaxApplicationCount,
_rule.MaxApplicationCount
);
return Enumerable.Empty<Word>();
}
if (!_outputFS.IsUnifiable(input.SyntacticFeatureStruct))
{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.MorphologicalRuleNotUnapplied(
_rule,
-1,
input,
FailureReason.OutputSyntacticFeatureStruct,
_outputFS
);
return Enumerable.Empty<Word>();
}

var output = new List<Word>();
for (int i = 0; i < _rules.Count; i++)
{
bool unapplied = false;
bool matched = false;
foreach (Word outWord in _rules[i].Apply(input).RemoveDuplicates())
{
matched = true;
// the paths Out writes describe this rule's output, not the stem it applied to, so they are
// stripped rather than carried backwards; what is left must unify with Required
outWord.SyntacticFeatureStruct.Restrict(_rule.OutSyntacticFeatureStruct);
Expand All @@ -75,6 +93,14 @@ out FeatureStruct syntacticFS
)
)
{
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.MorphologicalRuleNotUnapplied(
_rule,
i,
outWord,
FailureReason.RequiredSyntacticFeatureStruct,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FYI: F3. The legacy callback changes shape here. When every match fails Required, base made one call with input; now a plain manager gets one call per rejected outWord, each carrying the output with Out stripped, and none with input.

_rule.RequiredSyntacticFeatureStruct
);
continue;
}

Expand All @@ -91,11 +117,10 @@ out FeatureStruct syntacticFS
if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.MorphologicalRuleUnapplied(_rule, i, input, outWord);
output.Add(outWord);
unapplied = true;
}

if (_morpher.TraceManager.IsTracing && !unapplied)
_morpher.TraceManager.MorphologicalRuleNotUnapplied(_rule, i, input);
if (_morpher.TraceManager.IsTracing && !matched)
_morpher.TraceManager.MorphologicalRuleNotUnapplied(_rule, i, input, FailureReason.Pattern, null);
}
return output;
}
Expand Down
Loading
Loading