diff --git a/src/SIL.Machine.Morphology.HermitCrab/CharacterDefinition.cs b/src/SIL.Machine.Morphology.HermitCrab/CharacterDefinition.cs index b5998e594..028611dea 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/CharacterDefinition.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/CharacterDefinition.cs @@ -19,7 +19,11 @@ internal CharacterDefinition(IList representations, FeatureStruct fs) public FeatureSymbol Type { - get { return (FeatureSymbol)_fs.GetValue(HCFeatureSystem.Type); } + get + { + FeatureSymbol fsym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct(_fs, HCFeatureSystem.Type); + return fsym; + } } public ReadOnlyCollection Representations diff --git a/src/SIL.Machine.Morphology.HermitCrab/HCFeatureSystem.cs b/src/SIL.Machine.Morphology.HermitCrab/HCFeatureSystem.cs index b5478c5eb..96174fe88 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/HCFeatureSystem.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/HCFeatureSystem.cs @@ -51,7 +51,7 @@ static HCFeatureSystem() Modified = new SymbolicFeature(Guid.NewGuid().ToString(), Dirty, Clean) { Description = "Modified", - DefaultValue = new SymbolicFeatureValue(Clean) + DefaultValue = SymbolicFeatureValueFactory.Instance.Create(Clean) }; Deleted = new FeatureSymbol(Guid.NewGuid().ToString()) { Description = "Deleted" }; @@ -60,7 +60,7 @@ static HCFeatureSystem() Deletion = new SymbolicFeature(Guid.NewGuid().ToString(), Deleted, NotDeleted) { Description = "Deletion", - DefaultValue = new SymbolicFeatureValue(NotDeleted) + DefaultValue = SymbolicFeatureValueFactory.Instance.Create(NotDeleted) }; LeftSide = new FeatureSymbol(Guid.NewGuid().ToString()) { Description = "LeftSide" }; diff --git a/src/SIL.Machine.Morphology.HermitCrab/HermitCrabExtensions.cs b/src/SIL.Machine.Morphology.HermitCrab/HermitCrabExtensions.cs index bd05a1c74..4febf151a 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/HermitCrabExtensions.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/HermitCrabExtensions.cs @@ -13,17 +13,29 @@ public static class HermitCrabExtensions { public static FeatureSymbol Type(this ShapeNode node) { - return (FeatureSymbol)node.Annotation.FeatureStruct.GetValue(HCFeatureSystem.Type); + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + node.Annotation.FeatureStruct, + HCFeatureSystem.Type + ); + return fSym; } public static FeatureSymbol Type(this Annotation ann) { - return (FeatureSymbol)ann.FeatureStruct.GetValue(HCFeatureSystem.Type); + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + HCFeatureSystem.Type + ); + return fSym; } public static FeatureSymbol Type(this Constraint constraint) { - return (FeatureSymbol)constraint.FeatureStruct.GetValue(HCFeatureSystem.Type); + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + constraint.FeatureStruct, + HCFeatureSystem.Type + ); + return fSym; } internal static FeatureStruct AntiFeatureStruct(this FeatureStruct fs) @@ -55,8 +67,13 @@ internal static FeatureStruct AntiFeatureStruct(this FeatureStruct fs) internal static bool IsDirty(this ShapeNode node) { - return ((FeatureSymbol)node.Annotation.FeatureStruct.GetValue(HCFeatureSystem.Modified)) - == HCFeatureSystem.Dirty; + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + node.Annotation.FeatureStruct, + HCFeatureSystem.Modified + ); + return fSym == HCFeatureSystem.Dirty; + //return ((FeatureSymbol)node.Annotation.FeatureStruct.GetValue(HCFeatureSystem.Modified)) + // == HCFeatureSystem.Dirty; } internal static void SetDirty(this ShapeNode node, bool dirty) @@ -71,7 +88,13 @@ internal static bool IsDeleted(this Annotation ann) { SymbolicFeatureValue sfv; if (ann.FeatureStruct.TryGetValue(HCFeatureSystem.Deletion, out sfv)) - return ((FeatureSymbol)sfv) == HCFeatureSystem.Deleted; + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + HCFeatureSystem.Deleted.Feature + ); + return fSym == HCFeatureSystem.Deleted; + } return false; } diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisMorphologicalTransform.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisMorphologicalTransform.cs index 93e7a0ef6..5ffc7c132 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisMorphologicalTransform.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisMorphologicalTransform.cs @@ -96,7 +96,11 @@ Shape output { foreach (ShapeNode node in output.GetNodes(outputRange)) { - if ((FeatureSymbol)modifyFromFS.GetValue(HCFeatureSystem.Type) == node.Annotation.Type()) + FeatureSymbol fsym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + modifyFromFS, + HCFeatureSystem.Type + ); + if (fsym == node.Annotation.Type()) node.Annotation.FeatureStruct.Add(modifyFromFS, match.VariableBindings); } } diff --git a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/ModifyFromInput.cs b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/ModifyFromInput.cs index 70cae03ac..804ed47e9 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/ModifyFromInput.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/ModifyFromInput.cs @@ -44,7 +44,13 @@ IDictionary capturedParts Constraint constraint in group .GetNodesDepthFirst() .OfType>() - .Where(c => c.Type() == (FeatureSymbol)_simpleCtxt.FeatureStruct.GetValue(HCFeatureSystem.Type)) + .Where(c => + c.Type() + == SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + _simpleCtxt.FeatureStruct, + HCFeatureSystem.Type + ) + ) ) { constraint.FeatureStruct.PriorityUnion(_simpleCtxt.FeatureStruct); @@ -65,7 +71,10 @@ ShapeNode inputNode in GetSkippedOptionalNodes(match.Input.Shape, inputGroup.Ran ShapeNode outputNode = inputNode.Clone(); if ( outputNode.Annotation.Type() - == (FeatureSymbol)_simpleCtxt.FeatureStruct.GetValue(HCFeatureSystem.Type) + == SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + _simpleCtxt.FeatureStruct, + HCFeatureSystem.Type + ) ) { outputNode.Annotation.FeatureStruct.PriorityUnion( diff --git a/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs b/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs index 97d69dad3..7f96f4651 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs @@ -1450,7 +1450,7 @@ Dictionary> variables var varID = (string)varElem.Attribute("variableFeature"); Tuple variable = variables[varID]; ctxtVars.Add( - new SymbolicFeatureValue( + SymbolicFeatureValueFactory.Instance.Create( variable.Item2, variable.Item1, ((string)varElem.Attribute("polarity") ?? "plus") == "plus" diff --git a/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageWriter.cs b/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageWriter.cs index 4dec714cb..01b39f023 100644 --- a/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageWriter.cs +++ b/src/SIL.Machine.Morphology.HermitCrab/XmlLanguageWriter.cs @@ -1139,7 +1139,10 @@ private bool IsAnchor(PatternNode node, FeatureSymbol type) if (node is Constraint constraint) { return constraint.Type() == HCFeatureSystem.Anchor - && (FeatureSymbol)constraint.FeatureStruct.GetValue(HCFeatureSystem.AnchorType) == type; + && SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + constraint.FeatureStruct, + HCFeatureSystem.AnchorType + ) == type; } return false; diff --git a/src/SIL.Machine/FeatureModel/FeatureStruct.cs b/src/SIL.Machine/FeatureModel/FeatureStruct.cs index 8fe64c34c..7fead13e2 100644 --- a/src/SIL.Machine/FeatureModel/FeatureStruct.cs +++ b/src/SIL.Machine/FeatureModel/FeatureStruct.cs @@ -53,6 +53,7 @@ public static IFeatureStructSyntax NewMutable(FeatureSystem featSys, FeatureStru private readonly IDBearerDictionary _definite; private int? _hashCode; + private readonly SymbolicFeatureValueFactory _valueFactory = SymbolicFeatureValueFactory.Instance; /// /// Initializes a new instance of the class. @@ -126,15 +127,12 @@ public void AddValue(SymbolicFeature feature, IEnumerable values) throw new ArgumentNullException("values"); FeatureSymbol[] vals = values.ToArray(); - AddValue(feature, vals.Length == 0 ? new SymbolicFeatureValue(feature) : new SymbolicFeatureValue(vals)); + AddValue(feature, vals.Length == 0 ? _valueFactory.Create(feature) : _valueFactory.Create(vals)); } public void AddValue(SymbolicFeature feature, params FeatureSymbol[] values) { - AddValue( - feature, - values.Length == 0 ? new SymbolicFeatureValue(feature) : new SymbolicFeatureValue(values) - ); + AddValue(feature, values.Length == 0 ? _valueFactory.Create(feature) : _valueFactory.Create(values)); } public void AddValue(StringFeature feature, IEnumerable values) @@ -456,7 +454,7 @@ IDictionary> visited else if (otherValue is StringFeatureValue) thisValue = new StringFeatureValue(); else - thisValue = new SymbolicFeatureValue((SymbolicFeature)featVal.Key); + thisValue = _valueFactory.Create((SymbolicFeature)featVal.Key); _definite[featVal.Key] = thisValue; } if (!thisValue.AddImpl(otherValue, varBindings, visited)) diff --git a/src/SIL.Machine/FeatureModel/Fluent/FeatureStructBuilder.cs b/src/SIL.Machine/FeatureModel/Fluent/FeatureStructBuilder.cs index 70955772d..57f7ea79d 100644 --- a/src/SIL.Machine/FeatureModel/Fluent/FeatureStructBuilder.cs +++ b/src/SIL.Machine/FeatureModel/Fluent/FeatureStructBuilder.cs @@ -10,6 +10,7 @@ public class FeatureStructBuilder : IFeatureStructSyntax, IFeatureValueSyntax private readonly FeatureStruct _fs; private readonly IDictionary _ids; private readonly bool _mutable; + private readonly SymbolicFeatureValueFactory _valueFactory = SymbolicFeatureValueFactory.Instance; private Feature _lastFeature; private bool _not; @@ -242,7 +243,7 @@ private bool AddSymbols(Feature feature, FeatureSymbol[] symbols, int id) if (symbols.Any(s => s.Feature != feature)) return false; var symbolFeature = (SymbolicFeature)feature; - var value = new SymbolicFeatureValue(_not ? symbolFeature.PossibleSymbols.Except(symbols) : symbols); + var value = _valueFactory.Create(_not ? symbolFeature.PossibleSymbols.Except(symbols) : symbols); _fs.AddValue(symbolFeature, value); _not = false; if (id > -1) @@ -441,7 +442,7 @@ private void AddVariable(string name, int id) if (_lastFeature is StringFeature) vfv = new StringFeatureValue(name, !_not); else - vfv = new SymbolicFeatureValue((SymbolicFeature)_lastFeature, name, !_not); + vfv = _valueFactory.Create((SymbolicFeature)_lastFeature, name, !_not); _fs.AddValue(_lastFeature, vfv); _not = false; if (id > -1) diff --git a/src/SIL.Machine/FeatureModel/SimpleFeatureValue.cs b/src/SIL.Machine/FeatureModel/SimpleFeatureValue.cs index c81a8ff16..e52d72048 100644 --- a/src/SIL.Machine/FeatureModel/SimpleFeatureValue.cs +++ b/src/SIL.Machine/FeatureModel/SimpleFeatureValue.cs @@ -8,11 +8,15 @@ public abstract class SimpleFeatureValue : FeatureValue, ICloneable)possibleSymbols) { } @@ -21,7 +24,11 @@ public SymbolicFeature(string id, IEnumerable possibleSymbols) symbol.Feature = this; symbol.Index = i++; } - _mask = (1UL << _possibleSymbols.Count) - 1UL; + int symbolCount = _possibleSymbols.Count; + if (symbolCount > SymbolicFeatureValueFactory.Instance.NeedToUseBitArray) + _maskBA = new BitArray(symbolCount, true); + else + _mask = (1UL << symbolCount) - 1UL; } /// @@ -35,12 +42,17 @@ public IReadOnlyKeyedCollection PossibleSymbols public string DefaultSymbolID { - set { DefaultValue = new SymbolicFeatureValue(_possibleSymbols[value]); } + set { DefaultValue = SymbolicFeatureValueFactory.Instance.Create(_possibleSymbols[value]); } } internal ulong Mask { get { return _mask; } } + + internal BitArray MaskBA + { + get { return _maskBA; } + } } } diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs index 4f982c5d5..2ae402de8 100644 --- a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs @@ -19,13 +19,25 @@ public static explicit operator FeatureSymbol(SymbolicFeatureValue sfv) return sfv._first; } - private readonly SymbolicFeature _feature; + public static FeatureSymbol GetFeatureSymbolFromFeatureStruct(FeatureStruct fs, SymbolicFeature sf) + { + var value = fs.GetValue(sf); + FeatureSymbol fSym = + (value is SymbolicFeatureValueBA ba) + ? fSym = (FeatureSymbol)(SymbolicFeatureValueBA)value + : fSym = (FeatureSymbol)value; + return fSym; + } + + protected SymbolicFeature _feature; private ulong _flags; private FeatureSymbol _first; + public SymbolicFeatureValue() { } + public SymbolicFeatureValue(SymbolicFeature feature) { - _feature = feature; + Feature = feature; } public SymbolicFeatureValue(IEnumerable values) @@ -33,14 +45,14 @@ public SymbolicFeatureValue(IEnumerable values) FeatureSymbol[] symbols = values.ToArray(); if (symbols.Length == 0) throw new ArgumentException("values cannot be empty", "values"); - _feature = symbols[0].Feature; + Feature = symbols[0].Feature; _first = symbols[0]; Set(symbols); } public SymbolicFeatureValue(FeatureSymbol value) { - _feature = value.Feature; + Feature = value.Feature; _first = value; Set(value.ToEnumerable()); } @@ -48,24 +60,27 @@ public SymbolicFeatureValue(FeatureSymbol value) public SymbolicFeatureValue(SymbolicFeature feature, string varName, bool agree) : base(varName, agree) { - _feature = feature; + Feature = feature; } - private SymbolicFeatureValue(SymbolicFeatureValue sfv) + protected SymbolicFeatureValue(SymbolicFeatureValue sfv) : base(sfv) { - _feature = sfv._feature; + Feature = sfv.Feature; _first = sfv._first; _flags = sfv._flags; } - private SymbolicFeatureValue(SymbolicFeature feature, ulong flags) + protected SymbolicFeatureValue(SymbolicFeature feature, ulong flags) { - _feature = feature; + Feature = feature; _flags = flags; SetFirst(); } + public SymbolicFeatureValue(string varName, bool agree) + : base(varName, agree) { } + private void Set(IEnumerable symbols) { foreach (FeatureSymbol symbol in symbols) @@ -75,9 +90,9 @@ private void Set(IEnumerable symbols) } } - public IEnumerable Values + public virtual IEnumerable Values { - get { return _feature.PossibleSymbols.Where(Get); } + get { return Feature.PossibleSymbols.Where(Get); } } public bool IsSupersetOf(SymbolicFeatureValue other, bool notOther = false) @@ -92,10 +107,10 @@ public bool Overlaps(SymbolicFeatureValue other, bool notOther = false) private void SetFirst() { - _first = _flags == 0 ? null : _feature.PossibleSymbols.First(Get); + _first = _flags == 0 ? null : Feature.PossibleSymbols.First(Get); } - private bool Get(FeatureSymbol symbol) + protected virtual bool Get(FeatureSymbol symbol) { return (_flags & (1UL << symbol.Index)) != 0; } @@ -103,6 +118,7 @@ private bool Get(FeatureSymbol symbol) public SymbolicFeature Feature { get { return _feature; } + internal set { _feature = value; } } protected override bool IsSatisfiable @@ -112,7 +128,7 @@ protected override bool IsSatisfiable protected override bool IsUninstantiated { - get { return base.IsUninstantiated && _flags == _feature.Mask; } + get { return base.IsUninstantiated && _flags == Feature.Mask; } } protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool notOther) @@ -121,27 +137,47 @@ protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool no return false; if (!not && !notOther) + { return (_flags & otherSfv._flags) == otherSfv._flags; - if (!not) - return (_flags & (~otherSfv._flags & _feature.Mask)) == (~otherSfv._flags & _feature.Mask); - if (!notOther) - return ((~_flags & _feature.Mask) & otherSfv._flags) == otherSfv._flags; - return ((~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask)) - == (~otherSfv._flags & _feature.Mask); + } + else if (!not) + { + return (_flags & (~otherSfv._flags & Feature.Mask)) == (~otherSfv._flags & Feature.Mask); + } + else if (!notOther) + { + return ((~_flags & Feature.Mask) & otherSfv._flags) == otherSfv._flags; + } + else + { + return ((~_flags & Feature.Mask) & (~otherSfv._flags & Feature.Mask)) + == (~otherSfv._flags & Feature.Mask); + } } protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOther) { if (!(other is SymbolicFeatureValue otherSfv)) + { return false; + } if (!not && !notOther) + { return (_flags & otherSfv._flags) != 0; - if (!not) - return (_flags & (~otherSfv._flags & _feature.Mask)) != 0; - if (!notOther) - return ((~_flags & _feature.Mask) & otherSfv._flags) != 0; - return ((~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask)) != 0; + } + else if (!not) + { + return (_flags & (~otherSfv._flags & Feature.Mask)) != 0; + } + else if (!notOther) + { + return ((~_flags & Feature.Mask) & otherSfv._flags) != 0; + } + else + { + return ((~_flags & Feature.Mask) & (~otherSfv._flags & Feature.Mask)) != 0; + } } protected override void IntersectWith(bool not, SimpleFeatureValue other, bool notOther) @@ -150,13 +186,21 @@ protected override void IntersectWith(bool not, SimpleFeatureValue other, bool n return; if (!not && !notOther) + { _flags = _flags & otherSfv._flags; + } else if (!not) - _flags = _flags & (~otherSfv._flags & _feature.Mask); + { + _flags = _flags & (~otherSfv._flags & Feature.Mask); + } else if (!notOther) - _flags = (~_flags & _feature.Mask) & otherSfv._flags; + { + _flags = (~_flags & Feature.Mask) & otherSfv._flags; + } else - _flags = (~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask); + { + _flags = (~_flags & Feature.Mask) & (~otherSfv._flags & Feature.Mask); + } SetFirst(); } @@ -166,13 +210,21 @@ protected override void UnionWith(bool not, SimpleFeatureValue other, bool notOt return; if (!not && !notOther) + { _flags = _flags | otherSfv._flags; + } else if (!not) - _flags = _flags | (~otherSfv._flags & _feature.Mask); + { + _flags = _flags | (~otherSfv._flags & Feature.Mask); + } else if (!notOther) - _flags = (~_flags & _feature.Mask) | otherSfv._flags; + { + _flags = (~_flags & Feature.Mask) | otherSfv._flags; + } else - _flags = (~_flags & _feature.Mask) | (~otherSfv._flags & _feature.Mask); + { + _flags = (~_flags & Feature.Mask) | (~otherSfv._flags & Feature.Mask); + } SetFirst(); } @@ -182,13 +234,21 @@ protected override void ExceptWith(bool not, SimpleFeatureValue other, bool notO return; if (!not && !notOther) - _flags = _flags & (~otherSfv._flags & _feature.Mask); + { + _flags = _flags & (~otherSfv._flags & Feature.Mask); + } else if (!not) + { _flags = _flags & otherSfv._flags; + } else if (!notOther) - _flags = (~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask); + { + _flags = (~_flags & Feature.Mask) & (~otherSfv._flags & Feature.Mask); + } else - _flags = (~_flags & _feature.Mask) & otherSfv._flags; + { + _flags = (~_flags & Feature.Mask) & otherSfv._flags; + } } protected override SimpleFeatureValue CloneImpl() @@ -199,8 +259,8 @@ protected override SimpleFeatureValue CloneImpl() public override SimpleFeatureValue Negation() { return IsVariable - ? new SymbolicFeatureValue(_feature, VariableName, !Agree) - : new SymbolicFeatureValue(_feature, (~_flags & _feature.Mask)); + ? new SymbolicFeatureValue(Feature, VariableName, !Agree) + : new SymbolicFeatureValue(Feature, ~_flags & Feature.Mask); } public override bool ValueEquals(SimpleFeatureValue other) diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBA.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBA.cs new file mode 100644 index 000000000..6b9aa915b --- /dev/null +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBA.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using SIL.Extensions; + +namespace SIL.Machine.FeatureModel +{ + // This class uses BitArray instead of ulong for processing symbol feature values + // to overcome the 64 value limit that ulong has. + public class SymbolicFeatureValueBA : SymbolicFeatureValue + { + public static implicit operator SymbolicFeatureValueBA(FeatureSymbol symbol) + { + return new SymbolicFeatureValueBA(symbol); + } + + public static explicit operator FeatureSymbol(SymbolicFeatureValueBA sfv) + { + return sfv._first; + } + + private BitArray _flagsBA = new BitArray(sizeof(ulong) * 8, false); + private FeatureSymbol _first; + private readonly int _bitArraySize; + + public SymbolicFeatureValueBA(SymbolicFeature feature) + { + Feature = feature; + _bitArraySize = Feature.PossibleSymbols.Count; + _flagsBA = new BitArray(_bitArraySize, false); + } + + public SymbolicFeatureValueBA(IEnumerable values) + { + FeatureSymbol[] symbols = values.ToArray(); + if (symbols.Length == 0) + throw new ArgumentException("values cannot be empty", "values"); + Feature = symbols[0].Feature; + _first = symbols[0]; + _bitArraySize = symbols[0].Feature.PossibleSymbols.Count; + _flagsBA = new BitArray(_bitArraySize, false); + Set(symbols); + } + + public SymbolicFeatureValueBA(FeatureSymbol value) + { + Feature = value.Feature; + _bitArraySize = Feature.PossibleSymbols.Count; + _flagsBA = new BitArray(_bitArraySize, false); + _first = value; + Set(value.ToEnumerable()); + } + + public SymbolicFeatureValueBA(SymbolicFeature feature, string varName, bool agree) + : base(varName, agree) + { + Feature = feature; + _bitArraySize = Feature.PossibleSymbols.Count; + _flagsBA = new BitArray(_bitArraySize, false); + } + + private SymbolicFeatureValueBA(SymbolicFeatureValueBA sfv) + : base(sfv) + { + Feature = sfv.Feature; + _first = sfv._first; + _flagsBA = new BitArray(sfv._flagsBA); + } + + private SymbolicFeatureValueBA(SymbolicFeature feature, BitArray flagsBA) + { + Feature = feature; + _flagsBA = new BitArray(flagsBA); + SetFirst(); + } + + private void Set(IEnumerable symbols) + { + BitArray maskBA = new BitArray(_bitArraySize); + foreach (FeatureSymbol symbol in symbols) + { + maskBA.Set(symbol.Index, true); + _flagsBA.Or(maskBA); + } + } + + public override IEnumerable Values + { + get { return Feature.PossibleSymbols.Where(Get); } + } + + private void SetFirst() + { + _first = HasAnySet(_flagsBA) ? Feature.PossibleSymbols.First(Get) : null; + } + + protected override bool Get(FeatureSymbol symbol) + { + return _flagsBA.Get(symbol.Index); + } + + // TODO: replace with C# version when available (starting with v.8) + private bool HasAnySet(BitArray mask) + { + foreach (bool flag in mask) + { + if (flag) + return true; + } + return false; + } + + protected override bool IsSatisfiable + { + get { return IsVariable || HasAnySet(_flagsBA); } + } + + protected override bool IsUninstantiated + { + get { return !IsVariable && BitArraysAreEqual(_flagsBA, Feature.MaskBA); } + } + + private bool BitArraysAreEqual(BitArray array1, BitArray array2) + { + return array1.Cast().SequenceEqual(array2.Cast()); + } + + protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool notOther) + { + if (!(other is SymbolicFeatureValueBA otherSfv)) + return false; + + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(_flagsBA); + BitArray otherFlags = new BitArray(otherSfv._flagsBA); + if (!not && !notOther) + { + return BitArraysAreEqual(flags.And(otherFlags), otherSfv._flagsBA); + } + else + { + BitArray notOtherFlags = new BitArray(otherFlags).Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(Feature.MaskBA)); + if (!not) + { + return BitArraysAreEqual(flags.And(notOtherFlagsAndFeatureMask), notOtherFlagsAndFeatureMask); + } + else + { + BitArray notFlags = new BitArray(_flagsBA).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(Feature.MaskBA)); + if (!notOther) + { + return BitArraysAreEqual(notFlagsAndFeatureMask.And(otherSfv._flagsBA), otherSfv._flagsBA); + } + else + { + return BitArraysAreEqual( + notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask), + notOtherFlagsAndFeatureMask + ); + } + } + } + } + + protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOther) + { + if (!(other is SymbolicFeatureValueBA otherSfv)) + { + return false; + } + + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(_flagsBA); + BitArray otherFlags = new BitArray(otherSfv._flagsBA); + if (!not && !notOther) + { + return HasAnySet(flags.And(otherFlags)); + } + else + { + BitArray notOtherFlags = new BitArray(otherFlags).Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(Feature.MaskBA)); + if (!not) + { + return HasAnySet(flags.And(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlags = new BitArray(_flagsBA).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(Feature.MaskBA)); + if (!notOther) + { + return HasAnySet(notFlagsAndFeatureMask.And(otherSfv._flagsBA)); + } + else + { + return HasAnySet((notFlagsAndFeatureMask).And(notOtherFlagsAndFeatureMask)); + } + } + } + } + + protected override void IntersectWith(bool not, SimpleFeatureValue other, bool notOther) + { + if (!(other is SymbolicFeatureValueBA otherSfv)) + return; + + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray otherFlags = new BitArray(otherSfv._flagsBA); + BitArray flags = new BitArray(_flagsBA); + if (!not && !notOther) + { + _flagsBA = new BitArray(_flagsBA.And(otherSfv._flagsBA)); + } + else + { + BitArray notOtherFlags = ((BitArray)otherFlags.Clone()).Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(Feature.MaskBA)); + if (!not) + { + _flagsBA = new BitArray(_flagsBA.And(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlags = ((BitArray)flags.Clone()).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(Feature.MaskBA)); + if (!notOther) + { + _flagsBA = new BitArray(notFlagsAndFeatureMask.And(otherSfv._flagsBA)); + } + else + { + _flagsBA = new BitArray(notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask)); + } + } + } + SetFirst(); + } + + protected override void UnionWith(bool not, SimpleFeatureValue other, bool notOther) + { + if (!(other is SymbolicFeatureValueBA otherSfv)) + return; + + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(_flagsBA); + BitArray otherFlags = new BitArray(otherSfv._flagsBA); + if (!not && !notOther) + { + _flagsBA = new BitArray(flags.Or(otherFlags)); + } + else + { + BitArray notOtherFlags = ((BitArray)otherFlags.Clone()).Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(Feature.MaskBA)); + if (!not) + { + _flagsBA = new BitArray(_flagsBA.Or(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlags = ((BitArray)flags.Clone()).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(Feature.MaskBA)); + if (!notOther) + { + _flagsBA = new BitArray(notFlagsAndFeatureMask.Or(otherFlags)); + } + else + { + _flagsBA = new BitArray(notFlagsAndFeatureMask.Or(notOtherFlagsAndFeatureMask)); + } + } + } + SetFirst(); + } + + protected override void ExceptWith(bool not, SimpleFeatureValue other, bool notOther) + { + if (!(other is SymbolicFeatureValueBA otherSfv)) + return; + + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray otherFlags = new BitArray(otherSfv._flagsBA); + BitArray notOtherFlags = otherFlags.Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(Feature.MaskBA)); + if (!not && !notOther) + { + _flagsBA.And(notOtherFlagsAndFeatureMask); + } + else if (!not) + { + _flagsBA.And(otherSfv._flagsBA); + } + else + { + BitArray flags = new BitArray(_flagsBA); + BitArray notFlags = ((BitArray)flags.Clone()).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(Feature.MaskBA)); + if (!notOther) + { + _flagsBA = notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask); + } + else + { + _flagsBA = notFlagsAndFeatureMask.And(otherSfv._flagsBA); + } + } + } + + protected override SimpleFeatureValue CloneImpl() + { + return Clone(); + } + + public override SimpleFeatureValue Negation() + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(_flagsBA); + BitArray notFlagsAndFeatureMask = flags.Not().And(Feature.MaskBA); + return IsVariable + ? new SymbolicFeatureValueBA(Feature, VariableName, !Agree) + : new SymbolicFeatureValueBA(Feature, notFlagsAndFeatureMask); + } + + public override bool ValueEquals(SimpleFeatureValue other) + { + return other is SymbolicFeatureValueBA otherSfv && ValueEquals(otherSfv); + } + + public bool ValueEquals(SymbolicFeatureValueBA other) + { + if (other == null) + return false; + + return base.ValueEquals(other) && BitArraysAreEqual(_flagsBA, other._flagsBA); + } + + protected override int GetValuesHashCode() + { + int code = base.GetValuesHashCode(); + return code * 31 + GetHashCode(_flagsBA); + } + + public int GetHashCode(BitArray array) + { + int hash = 0; + foreach (bool value in array) + { + hash ^= (value ? 2 : 1); + } + return hash; + } + + public new SymbolicFeatureValueBA Clone() + { + return new SymbolicFeatureValueBA(this); + } + } +} diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueFactory.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueFactory.cs new file mode 100644 index 000000000..eb3a3fa4b --- /dev/null +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueFactory.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SIL.Machine.FeatureModel +{ + public class SymbolicFeatureValueFactory + { + private static readonly Lazy Lazy = new Lazy( + () => new SymbolicFeatureValueFactory() + ); + + public static SymbolicFeatureValueFactory Instance + { + get { return Lazy.Value; } + } + + // this can be set to 0 for testing the BitArray code + public int NeedToUseBitArray { get; set; } = sizeof(ulong) * 8; + + // To test using the BitArray method, comment the preceding line and uncomment the following line + //public int NeedToUseBitArray { get; set; } = 0; + + private SymbolicFeatureValueFactory() { } + + public SymbolicFeatureValue Create(SymbolicFeature feature) + { + if (feature.PossibleSymbols.Count > NeedToUseBitArray) + return new SymbolicFeatureValueBA(feature); + return new SymbolicFeatureValue(feature); + } + + public SymbolicFeatureValue Create(IEnumerable values) + { + FeatureSymbol[] symbols = values.ToArray(); + if (symbols.Length == 0) + throw new ArgumentException("values cannot be empty", "values"); + if (symbols[0].Feature.PossibleSymbols.Count > NeedToUseBitArray) + return new SymbolicFeatureValueBA(values); + return new SymbolicFeatureValue(values); + } + + public SymbolicFeatureValue Create(FeatureSymbol value) + { + if (value.Feature.PossibleSymbols.Count > NeedToUseBitArray) + return new SymbolicFeatureValueBA(value); + return new SymbolicFeatureValue(value); + } + + public SymbolicFeatureValue Create(SymbolicFeature feature, string varName, bool agree) + { + if (feature.PossibleSymbols.Count > NeedToUseBitArray) + return new SymbolicFeatureValueBA(feature, varName, agree); + return new SymbolicFeatureValue(feature, varName, agree); + } + } +} diff --git a/tests/SIL.Machine.Morphology.HermitCrab.Tests/HermitCrabTestBase.cs b/tests/SIL.Machine.Morphology.HermitCrab.Tests/HermitCrabTestBase.cs index a7736f41a..b4f52b579 100644 --- a/tests/SIL.Machine.Morphology.HermitCrab.Tests/HermitCrabTestBase.cs +++ b/tests/SIL.Machine.Morphology.HermitCrab.Tests/HermitCrabTestBase.cs @@ -851,7 +851,7 @@ params string[] symbols foreach (string symbolID in symbols) { FeatureSymbol symbol = phoneticFeatSys.GetSymbol(symbolID); - fs.AddValue(symbol.Feature, new SymbolicFeatureValue(symbol)); + fs.AddValue(symbol.Feature, SymbolicFeatureValueFactory.Instance.Create(symbol)); } table.AddSegment(strRep, fs); } @@ -898,7 +898,7 @@ protected void AssertSyntacticFeatureStructsEqual(IEnumerable words, Featu protected SymbolicFeatureValue Variable(string featureID, string variable, bool agree = true) { - return new SymbolicFeatureValue( + return SymbolicFeatureValueFactory.Instance.Create( Language.PhonologicalFeatureSystem.GetFeature(featureID), variable, agree diff --git a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs index 16c0bd513..429daf565 100644 --- a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs +++ b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs @@ -21,7 +21,7 @@ public void Unify() { return fs1.Unify(fs2, varBindings, out FeatureStruct res) ? res : null; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -205,7 +205,7 @@ public void IsUnifiable() Func resultsSelector = (fs1, fs2) => fs1.IsUnifiable(fs2); Func varResultsSelector = (fs1, fs2, varBindings) => fs1.IsUnifiable(fs2, varBindings); - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( EqualityComparer.Default, resultsSelector, varResultsSelector, @@ -256,7 +256,7 @@ public void PriorityUnion() fs1.PriorityUnion(fs2, varBindings); return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -412,7 +412,7 @@ public void Union() return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -490,7 +490,7 @@ public void Subtract() return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -589,6 +589,21 @@ public void Subtract() ); } + private static void TestBinaryOperationUlongAndBitArray( + IEqualityComparer comparer, + Func resultsSelector, + Func varResultsSelector, + params Func[] expectedSelectors + ) + { + // Use default ulong for values + TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); + // Use BitArray for values + SymbolicFeatureValueFactory.Instance.NeedToUseBitArray = 0; + TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); + SymbolicFeatureValueFactory.Instance.NeedToUseBitArray = sizeof(ulong) * 8; + } + private static void TestBinaryOperation( IEqualityComparer comparer, Func resultsSelector, @@ -880,7 +895,7 @@ params Func[] expectedSelectors fs2 = FeatureStruct.NewMutable(featSys).Feature("a").Not.EqualToVariable("var1").Symbol("b-").Value; var varBindings = new VariableBindings(); - varBindings["var1"] = new SymbolicFeatureValue(featSys.GetSymbol("a-")); + varBindings["var1"] = SymbolicFeatureValueFactory.Instance.Create(featSys.GetSymbol("a-")); Assert.That( varResultsSelector(fs1, fs2, varBindings), Is.EqualTo(expectedSelectors[20](featSys)).Using(comparer) @@ -891,10 +906,130 @@ params Func[] expectedSelectors fs2 = FeatureStruct.NewMutable(featSys).Feature("a").Not.EqualToVariable("var1").Symbol("b-").Value; varBindings = new VariableBindings(); - varBindings["var1"] = new SymbolicFeatureValue(featSys.GetSymbol("a+")); + varBindings["var1"] = SymbolicFeatureValueFactory.Instance.Create(featSys.GetSymbol("a+")); Assert.That( varResultsSelector(fs1, fs2, varBindings), Is.EqualTo(expectedSelectors[21](featSys)).Using(comparer) ); } + + [Test] + public void BitArray() + { + // Parts of Speech for Ket has more than 64 values. + // Using ulong alone in SymbolicFeatureValue can result in extra values being matched. + var featSys = new FeatureSystem + { + new SymbolicFeature( + "POS", + new FeatureSymbol("adv"), + new FeatureSymbol("adv.inc"), + new FeatureSymbol("n"), + new FeatureSymbol("nprop"), + new FeatureSymbol("n.inc"), + new FeatureSymbol("pro"), + new FeatureSymbol("pers"), + new FeatureSymbol("dempro"), + new FeatureSymbol("posspro"), + new FeatureSymbol("reflpro"), + new FeatureSymbol("que"), + new FeatureSymbol("adj"), + new FeatureSymbol("adj.inc"), + new FeatureSymbol("actn"), + new FeatureSymbol("v"), + new FeatureSymbol("v.inc"), + new FeatureSymbol("vi"), + new FeatureSymbol("v1"), + new FeatureSymbol("v1.aoln"), + new FeatureSymbol("v1.ao"), + new FeatureSymbol("v1.sln"), + new FeatureSymbol("v1.ln"), + new FeatureSymbol("v2"), + new FeatureSymbol("v2.aoln"), + new FeatureSymbol("v2.ao"), + new FeatureSymbol("v2.sln"), + new FeatureSymbol("v2.ln"), + new FeatureSymbol("v3"), + new FeatureSymbol("v3.aoln"), + new FeatureSymbol("v3.ao"), + new FeatureSymbol("v3.sln"), + new FeatureSymbol("v3.ln"), + new FeatureSymbol("v4"), + new FeatureSymbol("v4.aoln"), + new FeatureSymbol("v4.ao"), + new FeatureSymbol("v4.sln"), + new FeatureSymbol("v4.ln"), + new FeatureSymbol("v5"), + new FeatureSymbol("v5.aoln"), + new FeatureSymbol("v5.ao"), + new FeatureSymbol("v5.sln"), + new FeatureSymbol("v5.ln"), + new FeatureSymbol("v2.appl"), + new FeatureSymbol("v2.appl.ao"), + new FeatureSymbol("v2.appl.aoln"), + new FeatureSymbol("vt"), + new FeatureSymbol("vt1"), + new FeatureSymbol("vt1.aoln"), + new FeatureSymbol("vt1.ao"), + new FeatureSymbol("vt1.sln"), + new FeatureSymbol("vt1.ln"), + new FeatureSymbol("vt2"), + new FeatureSymbol("vt2.aoln"), + new FeatureSymbol("vt2.ao"), + new FeatureSymbol("vt2.sln"), + new FeatureSymbol("vt2.ln"), + new FeatureSymbol("vt3"), + new FeatureSymbol("vt3.aoln"), + new FeatureSymbol("vt3.ao"), + new FeatureSymbol("vt3.sln"), + new FeatureSymbol("vt3.ln"), + new FeatureSymbol("vt4"), + new FeatureSymbol("vt4.aoln"), + new FeatureSymbol("vt4.ao"), + new FeatureSymbol("vt4.sln"), + new FeatureSymbol("vt4.ln"), + new FeatureSymbol("vt2/appl"), + new FeatureSymbol("vt2.appl.aoln"), + new FeatureSymbol("vt2.appl.ln"), + new FeatureSymbol("vt1.appl"), + new FeatureSymbol("vt1.appl.ln"), + new FeatureSymbol("v.irr"), + new FeatureSymbol("ptcl"), + new FeatureSymbol("sub"), + new FeatureSymbol("pp"), + new FeatureSymbol("vpred"), + new FeatureSymbol("conj"), + new FeatureSymbol("interj"), + new FeatureSymbol("num"), + new FeatureSymbol("ncp") + ) + }; + featSys.Freeze(); + // 80 values + CheckFirstAndLastValues(featSys, "adv"); + + SymbolicFeature featPos = (SymbolicFeature)featSys.GetFeature("POS"); + // 65 values + SkipAndCheck(featPos, 15, "v.inc"); + // 64 values ( = sizeof(ulong) ) + SkipAndCheck(featPos, 16, "vi"); + // 63 values + SkipAndCheck(featPos, 17, "v1"); + } + + private static void SkipAndCheck(SymbolicFeature featPos, int iSkip, string sFirst) + { + var symbols = featPos.PossibleSymbols.Skip(iSkip); + Assert.That(symbols.Count, Is.EqualTo(80 - iSkip)); + FeatureSystem featSys = new FeatureSystem { new SymbolicFeature("POS", symbols) }; + CheckFirstAndLastValues(featSys, sFirst); + } + + private static void CheckFirstAndLastValues(FeatureSystem featSys, string sFirst) + { + FeatureStruct fs1 = FeatureStruct.NewMutable(featSys).Symbol(sFirst).Value; + Assert.That(fs1.ToString(), Is.EqualTo("[POS:" + sFirst + "]")); + FeatureStruct fs2 = FeatureStruct.NewMutable(featSys).Symbol("ncp").Value; + Assert.That(fs2.ToString(), Is.EqualTo("[POS:ncp]")); + } } diff --git a/tests/SIL.Machine.Tests/Matching/MatcherTests.cs b/tests/SIL.Machine.Tests/Matching/MatcherTests.cs index 917f2d40b..e8107a480 100644 --- a/tests/SIL.Machine.Tests/Matching/MatcherTests.cs +++ b/tests/SIL.Machine.Tests/Matching/MatcherTests.cs @@ -1111,7 +1111,17 @@ public void MatcherSettings() matcher = new Matcher( pattern, - new MatcherSettings { Filter = ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) == Word } + new MatcherSettings + { + Filter = ann => + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Word.Feature + ); + return fSym == Word; + } + } ); Match match = matcher.Match(sentence); Assert.IsTrue(match.Success); @@ -1189,7 +1199,17 @@ public void MatcherSettings() matcher = new Matcher( pattern, - new MatcherSettings { Filter = ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) == Word } + new MatcherSettings + { + Filter = ann => + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Word.Feature + ); + return fSym == Word; + } + } ); match = matcher.Match(sentence); Assert.IsTrue(match.Success); @@ -1199,7 +1219,14 @@ public void MatcherSettings() pattern, new MatcherSettings { - Filter = ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) == Word, + Filter = ann => + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Word.Feature + ); + return fSym == Word; + }, Direction = Direction.RightToLeft } ); @@ -1217,7 +1244,15 @@ public void MatcherSettings() pattern, new MatcherSettings { - Filter = ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) != Word, + Filter = ann => + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Word.Feature + ); + return fSym != Word; + }, + MatchingMethod = MatchingMethod.Unification } ); @@ -1228,7 +1263,14 @@ public void MatcherSettings() pattern, new MatcherSettings { - Filter = ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) != Word, + Filter = ann => + { + FeatureSymbol fSym = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Word.Feature + ); + return fSym != Word; + }, UseDefaults = true, MatchingMethod = MatchingMethod.Unification } diff --git a/tests/SIL.Machine.Tests/Rules/RuleTests.cs b/tests/SIL.Machine.Tests/Rules/RuleTests.cs index 554cbed92..a346de24f 100644 --- a/tests/SIL.Machine.Tests/Rules/RuleTests.cs +++ b/tests/SIL.Machine.Tests/Rules/RuleTests.cs @@ -118,7 +118,14 @@ public void Batch() }, input => input - .Annotations.Single(ann => ((FeatureSymbol)ann.FeatureStruct.GetValue(Type)) == Word) + .Annotations.Single(ann => + { + FeatureSymbol f = SymbolicFeatureValue.GetFeatureSymbolFromFeatureStruct( + ann.FeatureStruct, + Type + ); + return f == Word; + }) .FeatureStruct.IsUnifiable(FeatureStruct.New(WordFeatSys).Symbol("verb").Value) );