Skip to content

Commit 13eabbb

Browse files
rmunnmyieye
andauthored
Add morph types to MiniLcm (#1857)
* Add MorphType enum and MorphTypeData class * Set morph type when ILexEntry is created * Add get methods for morph types to read API * Add write API methods for MorphTypeData * CrdtMiniLcmApi methods for morph types should throw * Fix bug in SimpleStringDiff and IntegerDiff The "add" operation, if we had ever used it, would have added nothing because the value to be added wasn't being passed in. Fixed now. * Implement necessary methods in dry run and legacy * Address review comments * Add DB migration for CRDT database * Persist and update morph type in CRDT * Fix linting errors * Make AutoFaker exclude Unknown and Other, because they don't round trip * Explicitly default to MorphType.Stem in several places * Handle undocumented nullability of ILexEntry.PrimaryMorphType * Exclude MorphTypeData from AllObjectsAreRegistered test --------- Co-authored-by: Tim Haasdyk <tim_haasdyk@sil.org>
1 parent c3b3467 commit 13eabbb

File tree

31 files changed

+1394
-16
lines changed

31 files changed

+1394
-16
lines changed

backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,70 @@ await Cache.DoUsingNewOrCurrentUOW("Delete Complex Form Type",
489489
});
490490
}
491491

492+
public IAsyncEnumerable<MorphTypeData> GetAllMorphTypeData()
493+
{
494+
return
495+
MorphTypeRepository
496+
.AllInstances()
497+
.ToAsyncEnumerable()
498+
.Select(FromLcmMorphType);
499+
}
500+
501+
public Task<MorphTypeData?> GetMorphTypeData(Guid id)
502+
{
503+
MorphTypeRepository.TryGetObject(id, out var lcmMorphType);
504+
if (lcmMorphType is null) return Task.FromResult<MorphTypeData?>(null);
505+
return Task.FromResult<MorphTypeData?>(FromLcmMorphType(lcmMorphType));
506+
}
507+
508+
internal MorphTypeData FromLcmMorphType(IMoMorphType morphType)
509+
{
510+
return new MorphTypeData
511+
{
512+
Id = morphType.Guid,
513+
MorphType = LcmHelpers.FromLcmMorphType(morphType),
514+
Name = FromLcmMultiString(morphType.Name),
515+
Abbreviation = FromLcmMultiString(morphType.Abbreviation),
516+
Description = FromLcmMultiString(morphType.Description),
517+
LeadingToken = morphType.Prefix,
518+
TrailingToken = morphType.Postfix,
519+
SecondaryOrder = morphType.SecondaryOrder,
520+
};
521+
}
522+
523+
public Task<MorphTypeData> CreateMorphTypeData(MorphTypeData morphTypeData)
524+
{
525+
// Creating new morph types not allowed in FwData projects, so silently ignore operation
526+
return Task.FromResult(morphTypeData);
527+
}
528+
529+
public Task<MorphTypeData> UpdateMorphTypeData(Guid id, UpdateObjectInput<MorphTypeData> update)
530+
{
531+
var lcmMorphType = MorphTypeRepository.GetObject(id);
532+
if (lcmMorphType is null) throw new NullReferenceException($"unable to find morph type with id {id}");
533+
UndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW("Update Morph Type",
534+
"Revert Morph Type",
535+
Cache.ServiceLocator.ActionHandler,
536+
() =>
537+
{
538+
var updateProxy = new UpdateMorphTypeDataProxy(lcmMorphType, this);
539+
update.Apply(updateProxy);
540+
});
541+
return Task.FromResult(FromLcmMorphType(lcmMorphType));
542+
}
543+
544+
public async Task<MorphTypeData> UpdateMorphTypeData(MorphTypeData before, MorphTypeData after, IMiniLcmApi? api = null)
545+
{
546+
await MorphTypeDataSync.Sync(before, after, api ?? this);
547+
return await GetMorphTypeData(after.Id) ?? throw new NullReferenceException("unable to find morph type with id " + after.Id);
548+
}
549+
550+
public Task DeleteMorphTypeData(Guid id)
551+
{
552+
// Deleting morph types not allowed in FwData projects, so silently ignore operation
553+
return Task.CompletedTask;
554+
}
555+
492556
public IAsyncEnumerable<VariantType> GetVariantTypes()
493557
{
494558
return VariantTypes.PossibilitiesOS
@@ -534,6 +598,7 @@ private Entry FromLexEntry(ILexEntry entry)
534598
LexemeForm = FromLcmMultiString(entry.LexemeFormOA?.Form),
535599
CitationForm = FromLcmMultiString(entry.CitationForm),
536600
LiteralMeaning = FromLcmMultiString(entry.LiteralMeaning),
601+
MorphType = LcmHelpers.FromLcmMorphType(entry.PrimaryMorphType), // TODO: Decide what to do about entries with *mixed* morph types
537602
Senses = entry.AllSenses.Select(FromLexSense).ToList(),
538603
ComplexFormTypes = ToComplexFormTypes(entry),
539604
Components = ToComplexFormComponents(entry).ToList(),
@@ -843,7 +908,7 @@ public async Task<Entry> CreateEntry(Entry entry)
843908
Cache.ServiceLocator.ActionHandler,
844909
() =>
845910
{
846-
var lexEntry = Cache.CreateEntry(entry.Id);
911+
var lexEntry = Cache.CreateEntry(entry.Id, entry.MorphType);
847912
UpdateLcmMultiString(lexEntry.LexemeFormOA.Form, entry.LexemeForm);
848913
UpdateLcmMultiString(lexEntry.CitationForm, entry.CitationForm);
849914
UpdateLcmMultiString(lexEntry.LiteralMeaning, entry.LiteralMeaning);

backend/FwLite/FwDataMiniLcmBridge/Api/LcmHelpers.cs

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,66 @@ internal static bool SearchValue(this ITsMultiString multiString, string value)
8383
'\u0640', // Arabic Tatweel
8484
];
8585

86+
internal static MorphType FromLcmMorphType(IMoMorphType? morphType)
87+
{
88+
var lcmMorphTypeId = morphType?.Id.Guid;
89+
90+
return lcmMorphTypeId switch
91+
{
92+
null => MorphType.Unknown,
93+
// Can't switch on Guids since they're not compile-type constants, but thankfully pattern matching works
94+
Guid g when g == MoMorphTypeTags.kguidMorphBoundRoot => MorphType.BoundRoot,
95+
Guid g when g == MoMorphTypeTags.kguidMorphBoundStem => MorphType.BoundStem,
96+
Guid g when g == MoMorphTypeTags.kguidMorphCircumfix => MorphType.Circumfix,
97+
Guid g when g == MoMorphTypeTags.kguidMorphClitic => MorphType.Clitic,
98+
Guid g when g == MoMorphTypeTags.kguidMorphEnclitic => MorphType.Enclitic,
99+
Guid g when g == MoMorphTypeTags.kguidMorphInfix => MorphType.Infix,
100+
Guid g when g == MoMorphTypeTags.kguidMorphParticle => MorphType.Particle,
101+
Guid g when g == MoMorphTypeTags.kguidMorphPrefix => MorphType.Prefix,
102+
Guid g when g == MoMorphTypeTags.kguidMorphProclitic => MorphType.Proclitic,
103+
Guid g when g == MoMorphTypeTags.kguidMorphRoot => MorphType.Root,
104+
Guid g when g == MoMorphTypeTags.kguidMorphSimulfix => MorphType.Simulfix,
105+
Guid g when g == MoMorphTypeTags.kguidMorphStem => MorphType.Stem,
106+
Guid g when g == MoMorphTypeTags.kguidMorphSuffix => MorphType.Suffix,
107+
Guid g when g == MoMorphTypeTags.kguidMorphSuprafix => MorphType.Suprafix,
108+
Guid g when g == MoMorphTypeTags.kguidMorphInfixingInterfix => MorphType.InfixingInterfix,
109+
Guid g when g == MoMorphTypeTags.kguidMorphPrefixingInterfix => MorphType.PrefixingInterfix,
110+
Guid g when g == MoMorphTypeTags.kguidMorphSuffixingInterfix => MorphType.SuffixingInterfix,
111+
Guid g when g == MoMorphTypeTags.kguidMorphPhrase => MorphType.Phrase,
112+
Guid g when g == MoMorphTypeTags.kguidMorphDiscontiguousPhrase => MorphType.DiscontiguousPhrase,
113+
_ => MorphType.Other,
114+
};
115+
}
116+
117+
internal static Guid? ToLcmMorphTypeId(MorphType morphType)
118+
{
119+
return morphType switch
120+
{
121+
MorphType.BoundRoot => MoMorphTypeTags.kguidMorphBoundRoot,
122+
MorphType.BoundStem => MoMorphTypeTags.kguidMorphBoundStem,
123+
MorphType.Circumfix => MoMorphTypeTags.kguidMorphCircumfix,
124+
MorphType.Clitic => MoMorphTypeTags.kguidMorphClitic,
125+
MorphType.Enclitic => MoMorphTypeTags.kguidMorphEnclitic,
126+
MorphType.Infix => MoMorphTypeTags.kguidMorphInfix,
127+
MorphType.Particle => MoMorphTypeTags.kguidMorphParticle,
128+
MorphType.Prefix => MoMorphTypeTags.kguidMorphPrefix,
129+
MorphType.Proclitic => MoMorphTypeTags.kguidMorphProclitic,
130+
MorphType.Root => MoMorphTypeTags.kguidMorphRoot,
131+
MorphType.Simulfix => MoMorphTypeTags.kguidMorphSimulfix,
132+
MorphType.Stem => MoMorphTypeTags.kguidMorphStem,
133+
MorphType.Suffix => MoMorphTypeTags.kguidMorphSuffix,
134+
MorphType.Suprafix => MoMorphTypeTags.kguidMorphSuprafix,
135+
MorphType.InfixingInterfix => MoMorphTypeTags.kguidMorphInfixingInterfix,
136+
MorphType.PrefixingInterfix => MoMorphTypeTags.kguidMorphPrefixingInterfix,
137+
MorphType.SuffixingInterfix => MoMorphTypeTags.kguidMorphSuffixingInterfix,
138+
MorphType.Phrase => MoMorphTypeTags.kguidMorphPhrase,
139+
MorphType.DiscontiguousPhrase => MoMorphTypeTags.kguidMorphDiscontiguousPhrase,
140+
MorphType.Unknown => null,
141+
MorphType.Other => null, // Note that this will not round-trip with FromLcmMorphType
142+
_ => null,
143+
};
144+
}
145+
86146
internal static void ContributeExemplars(ITsMultiString multiString, IReadOnlyDictionary<int, HashSet<char>> wsExemplars)
87147
{
88148
for (var i = 0; i < multiString.StringCount; i++)
@@ -148,21 +208,51 @@ internal static string PickText(this ICmObject obj, ITsMultiString multiString,
148208
return multiString.get_String(wsHandle)?.Text ?? string.Empty;
149209
}
150210

151-
internal static IMoStemAllomorph CreateLexemeForm(this LcmCache cache)
211+
internal static IMoForm CreateLexemeForm(this LcmCache cache, MorphType morphType)
152212
{
153-
return cache.ServiceLocator.GetInstance<IMoStemAllomorphFactory>().Create();
213+
return
214+
IsAffixMorphType(morphType)
215+
? cache.ServiceLocator.GetInstance<IMoAffixAllomorphFactory>().Create()
216+
: cache.ServiceLocator.GetInstance<IMoStemAllomorphFactory>().Create();
154217
}
155218

156-
internal static ILexEntry CreateEntry(this LcmCache cache, Guid id)
219+
internal static bool IsAffixMorphType(MorphType morphType)
220+
{
221+
return morphType switch
222+
{
223+
// Affixes of all types should use the Affix morph type factory
224+
MorphType.Circumfix => true,
225+
MorphType.Infix => true,
226+
MorphType.Prefix => true,
227+
MorphType.Simulfix => true,
228+
MorphType.Suffix => true,
229+
MorphType.Suprafix => true,
230+
MorphType.InfixingInterfix => true,
231+
MorphType.PrefixingInterfix => true,
232+
MorphType.SuffixingInterfix => true,
233+
234+
// Everything else should use the Stem morph type factory
235+
_ => false,
236+
};
237+
}
238+
239+
internal static ILexEntry CreateEntry(this LcmCache cache, Guid id, MorphType morphType)
157240
{
158241
var lexEntry = cache.ServiceLocator.GetInstance<ILexEntryFactory>().Create(id,
159242
cache.ServiceLocator.GetInstance<ILangProjectRepository>().Singleton.LexDbOA);
160-
lexEntry.LexemeFormOA = cache.CreateLexemeForm();
161-
//must be done after the IMoForm is set on the LexemeForm property
162-
lexEntry.LexemeFormOA.MorphTypeRA = cache.ServiceLocator.GetInstance<IMoMorphTypeRepository>().GetObject(MoMorphTypeTags.kguidMorphStem);
243+
SetLexemeForm(lexEntry, morphType, cache);
163244
return lexEntry;
164245
}
165246

247+
internal static IMoForm SetLexemeForm(ILexEntry lexEntry, MorphType morphType, LcmCache cache)
248+
{
249+
lexEntry.LexemeFormOA = cache.CreateLexemeForm(morphType);
250+
//must be done after the IMoForm is set on the LexemeForm property
251+
var lcmMorphType = ToLcmMorphTypeId(morphType) ?? ToLcmMorphTypeId(MorphType.Stem);
252+
lexEntry.LexemeFormOA.MorphTypeRA = cache.ServiceLocator.GetInstance<IMoMorphTypeRepository>().GetObject(lcmMorphType!.Value);
253+
return lexEntry.LexemeFormOA;
254+
}
255+
166256
internal static string GetSemanticDomainCode(ICmSemanticDomain semanticDomain)
167257
{
168258
var abbr = semanticDomain.Abbreviation;

backend/FwLite/FwDataMiniLcmBridge/Api/UpdateProxy/UpdateEntryProxy.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ public override MultiString LexemeForm
2121
{
2222
get
2323
{
24-
_lcmEntry.LexemeFormOA ??= _lexboxLcmApi.Cache.CreateLexemeForm();
25-
return new UpdateMultiStringProxy(_lcmEntry.LexemeFormOA.Form, _lexboxLcmApi);
24+
var form = _lcmEntry.LexemeFormOA ?? LcmHelpers.SetLexemeForm(
25+
_lcmEntry,
26+
LcmHelpers.FromLcmMorphType(_lcmEntry.PrimaryMorphType),
27+
_lexboxLcmApi.Cache);
28+
return new UpdateMultiStringProxy(form.Form, _lexboxLcmApi);
2629
}
2730
set => throw new NotImplementedException();
2831
}
@@ -39,6 +42,12 @@ public override RichMultiString LiteralMeaning
3942
set => throw new NotImplementedException();
4043
}
4144

45+
public override MorphType MorphType
46+
{
47+
get => throw new NotImplementedException();
48+
set => Console.WriteLine("setting MorphType not implemented"); // Not throwing, for now
49+
}
50+
4251
public override List<Sense> Senses
4352
{
4453
get => throw new NotImplementedException();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using MiniLcm.Models;
2+
using SIL.LCModel;
3+
4+
namespace FwDataMiniLcmBridge.Api.UpdateProxy;
5+
6+
public class UpdateMorphTypeDataProxy : MorphTypeData
7+
{
8+
private readonly IMoMorphType _lcmMorphType;
9+
private readonly FwDataMiniLcmApi _lexboxLcmApi;
10+
11+
public UpdateMorphTypeDataProxy(IMoMorphType lcmMorphType, FwDataMiniLcmApi lexboxLcmApi)
12+
{
13+
_lcmMorphType = lcmMorphType;
14+
Id = lcmMorphType.Guid;
15+
_lexboxLcmApi = lexboxLcmApi;
16+
}
17+
18+
public override MultiString Name
19+
{
20+
get => new UpdateMultiStringProxy(_lcmMorphType.Name, _lexboxLcmApi);
21+
set => throw new NotImplementedException();
22+
}
23+
24+
public override MultiString Abbreviation
25+
{
26+
get => new UpdateMultiStringProxy(_lcmMorphType.Abbreviation, _lexboxLcmApi);
27+
set => throw new NotImplementedException();
28+
}
29+
30+
public override RichMultiString Description
31+
{
32+
get => new UpdateRichMultiStringProxy(_lcmMorphType.Description, _lexboxLcmApi);
33+
set => throw new NotImplementedException();
34+
}
35+
36+
public override string LeadingToken
37+
{
38+
get => _lcmMorphType.Prefix;
39+
set => _lcmMorphType.Prefix = value;
40+
}
41+
42+
public override string TrailingToken
43+
{
44+
get => _lcmMorphType.Postfix;
45+
set => _lcmMorphType.Postfix = value;
46+
}
47+
48+
public override int SecondaryOrder
49+
{
50+
get => _lcmMorphType.SecondaryOrder;
51+
set => _lcmMorphType.SecondaryOrder = value;
52+
}
53+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using MiniLcm.SyncHelpers;
2+
using SystemTextJsonPatch.Operations;
3+
4+
namespace FwLiteProjectSync.Tests;
5+
6+
public class IntegerDiffTests
7+
{
8+
private record Placeholder();
9+
10+
[Fact]
11+
public void DiffEmptyIntegersDoNothing()
12+
{
13+
int? before = null;
14+
int? after = null;
15+
var result = IntegerDiff.GetIntegerDiff<Placeholder>("test", before, after);
16+
result.Should().BeEmpty();
17+
}
18+
19+
[Fact]
20+
public void DiffOneToEmptyAddsOne()
21+
{
22+
int? before = null;
23+
var after = 1;
24+
var result = IntegerDiff.GetIntegerDiff<Placeholder>("test", before, after);
25+
result.Should().BeEquivalentTo([
26+
new Operation<Placeholder>("add", "/test", null, 1)
27+
]);
28+
}
29+
30+
[Fact]
31+
public void DiffOneToTwoReplacesOne()
32+
{
33+
var before = 1;
34+
var after = 2;
35+
var result = IntegerDiff.GetIntegerDiff<Placeholder>("test", before, after);
36+
result.Should().BeEquivalentTo([
37+
new Operation<Placeholder>("replace", "/test", null, 2)
38+
]);
39+
}
40+
41+
[Fact]
42+
public void DiffNoneToOneRemovesOne()
43+
{
44+
var before = 1;
45+
int? after = null;
46+
var result = IntegerDiff.GetIntegerDiff<Placeholder>("test", before, after);
47+
result.Should().BeEquivalentTo([
48+
new Operation<Placeholder>("remove", "/test", null)
49+
]);
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using MiniLcm.SyncHelpers;
2+
using SystemTextJsonPatch.Operations;
3+
4+
namespace FwLiteProjectSync.Tests;
5+
6+
public class SimpleStringDiffTests
7+
{
8+
private record Placeholder();
9+
10+
[Fact]
11+
public void DiffEmptyStringsDoNothing()
12+
{
13+
string? before = null;
14+
string? after = null;
15+
var result = SimpleStringDiff.GetStringDiff<Placeholder>("test", before, after);
16+
result.Should().BeEmpty();
17+
}
18+
19+
[Fact]
20+
public void DiffOneToEmptyAddsOne()
21+
{
22+
string? before = null;
23+
var after = "hello";
24+
var result = SimpleStringDiff.GetStringDiff<Placeholder>("test", before, after);
25+
result.Should().BeEquivalentTo([
26+
new Operation<Placeholder>("add", "/test", null, "hello")
27+
]);
28+
}
29+
30+
[Fact]
31+
public void DiffOneToOneReplacesOne()
32+
{
33+
var before = "hello";
34+
var after = "world";
35+
var result = SimpleStringDiff.GetStringDiff<Placeholder>("test", before, after);
36+
result.Should().BeEquivalentTo([
37+
new Operation<Placeholder>("replace", "/test", null, "world")
38+
]);
39+
}
40+
41+
[Fact]
42+
public void DiffNoneToOneRemovesOne()
43+
{
44+
var before = "hello";
45+
string? after = null;
46+
var result = SimpleStringDiff.GetStringDiff<Placeholder>("test", before, after);
47+
result.Should().BeEquivalentTo([
48+
new Operation<Placeholder>("remove", "/test", null)
49+
]);
50+
}
51+
}

0 commit comments

Comments
 (0)