/// <summary> /// Compiles a rule context transformation. /// </summary> /// <param name="transformation">The transformation.</param> /// <param name="builder">The builder.</param> /// <param name="glyphIdToRuleIndexCallback">The callback used to resolve rule index from glyph IDs.</param> /// <param name="createTransitionCallback">The callback used to create transitions.</param> /// <exception cref="System.InvalidOperationException">Transformations in transformation set don't share LookupFlags.</exception> private void CompileChainingRuleContextTransformation( IGlyphTransformationTable transformation, IStateMachineBuilder builder, Func <ushort, ushort> glyphIdToRuleIndexCallback, CreateContextTransformationTransitionDelegate <ushort> createTransitionCallback) { var table = (ChainingRuleContextTransformationTableBase)transformation; // Flatten the collection of rules and associate each rule with glyph received from the coverage table var rulesWithFirstGlyph = table .TransformationRules .Zip(table.Coverage.CoveredGlyphIds.Values) .SelectMany(ruleSetWithFirstGlyph => ruleSetWithFirstGlyph.Item1.Select(rule => Tuple.Create(rule, glyphIdToRuleIndexCallback(ruleSetWithFirstGlyph.Item2)))); foreach (var ruleTuple in rulesWithFirstGlyph) { foreach (var transformationSet in ruleTuple.Item1.TransformationSets) { var completeInputContext = ruleTuple.Item1.Context .Prepend(ruleTuple.Item2) .ToList(); this.CompileSingleContextTransformation( table, builder, transformationSet, createTransitionCallback, completeInputContext, ruleTuple.Item1.Lookback, ruleTuple.Item1.Lookahead); } } }
/// <summary> /// Compacts transitions by eliminating trailing null tansitions. /// I.e if the last couple characters in the alphabet, for one state don't lead to another state, these cells can be /// eliminated from the table. /// Transitions that point to a later state are adjusted by the amount of eliminated cells. /// </summary> public TransitionCompacter(IStateMachineBuilder builder) { // Build a set of indices that can be skipped and a table of offsets for adjusting the remaining transitions. var skippedIndices = new HashSet <int>(); var offsetMap = new int[builder.Transitions.Count]; var skipTotal = 0; for (var i = 0; i < builder.Transitions.Count; i += builder.AlphabetSize) { // Count the trailing nulls. var transitionsToKeep = builder.AlphabetSize; for (; transitionsToKeep > 0; transitionsToKeep--) { var transition = i + transitionsToKeep - 1; if (!builder.IsNull(transition) || builder.IsResult(transition)) // Results and normal transitions can't be skipped. { break; } skippedIndices.Add(transition); } // Build the set and table. var toSkip = builder.AlphabetSize - transitionsToKeep; skipTotal += toSkip; for (var j = 0; j < builder.AlphabetSize; ++j) { var index = i + builder.AlphabetSize + j; if (index >= offsetMap.Length) { break; } offsetMap[index] = skipTotal; } } // Adjust the remaining transitions. var clone = builder.Transitions.ToList(); for (var i = 0; i < clone.Count; ++i) { if (builder.IsNull(i) || builder.IsResult(i)) // nulls and results are not adjusted. { continue; } clone[i] -= offsetMap[clone[i]]; } // Copy into a new list while skipping eliminated cells. Transitions = clone.Where((t, i) => !skippedIndices.Contains(i)).ToList(); // Create offsets. var offsets = new List <int>(); for (var i = 0; i < builder.Transitions.Count; i += builder.AlphabetSize) { offsets.Add(offsetMap[i]); } Offsets = offsets; }
static DocStateMachineBuilder() { var dll = typeof(DocStateMachineBuilder).Assembly; using (var stream = dll.GetManifestResourceStream("DocManager.Resources.Doc.csv")) { realBuilder = CsvStateMachineBuilder <DocStates, DocActions> .FromStream(stream); } }
private static IReadOnlyList <int> GetMinOffsets(IStateMachineBuilder builder, TransitionCompacter compacter, int wordLength) { var alphabet = Enumerable.Range(0, builder.AlphabetSize).ToList(); var minOffsets = new int[wordLength]; var maxOffsets = new int[wordLength]; var transitionsInCurrentLevel = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize)); var compactTransitionsInCurrentLevel = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize - compacter.Offsets[e])); for (var i = 0; i < wordLength; ++i) { var transitionsInPreviousLevel = transitionsInCurrentLevel; var compactTransitionsInPreviousLevel = compactTransitionsInCurrentLevel; transitionsInCurrentLevel = new HashSet <int>(); compactTransitionsInCurrentLevel = new HashSet <int>(); var orderedPrevious = transitionsInPreviousLevel.OrderBy(x => x).ToList(); var orderedCompactPrevious = compactTransitionsInPreviousLevel.OrderBy(x => x).ToList(); for (var j = 0; j < orderedPrevious.Count; ++j) { var previous = orderedPrevious[j]; var compactPrevious = orderedCompactPrevious[j]; foreach (var c in alphabet) { if (builder.IsNull(previous + c)) { continue; } var n = builder.Transitions[previous + c]; var m = compacter.Transitions[compactPrevious + c]; transitionsInCurrentLevel.Add(n); compactTransitionsInCurrentLevel.Add(m); } } minOffsets[i] = compactTransitionsInCurrentLevel.Min(); maxOffsets[i] = compactTransitionsInCurrentLevel.Max(); } if (maxOffsets.Max() < ushort.MaxValue) { return(Enumerable.Repeat(0, wordLength).ToList()); } var largestDistance = Enumerable.Range(0, wordLength).Max(i => maxOffsets[i] - minOffsets[i]); for (var i = 0; i < wordLength; ++i) { if (maxOffsets[i] < largestDistance) { minOffsets[i] = 0; } } return(minOffsets); }
public void BuildStateMachine(IStateMachineBuilder <PhilisopherState, PhilisopherTrigger> machine) { machine.State(PhilisopherState.Thinking) .SetAsInitial() .OnTrigger(PhilisopherTrigger.Hungry).TransitionTo(PhilisopherState.AcquiringForks); machine.State(PhilisopherState.AcquiringForks) .OnTrigger(PhilisopherTrigger.GotForks).TransitionTo(PhilisopherState.Eating) .OnTrigger(PhilisopherTrigger.Deadlocked).TransitionTo(PhilisopherState.Thinking); machine.State(PhilisopherState.Eating) .OnTrigger(PhilisopherTrigger.Full).TransitionTo(PhilisopherState.Thinking); }
/// <summary> /// Counts the number of null transitions per character in the alphabet. /// </summary> private static IEnumerable <int> CountNullTransitions(IStateMachineBuilder builder) { var nullTransitions = new int[builder.AlphabetSize]; for (var i = 0; i < builder.Transitions.Count; i++) { if (builder.IsResult(i)) { continue; } if (builder.IsNull(i)) { nullTransitions[i % builder.AlphabetSize] += 1; } } return(nullTransitions); }
public void BuildStateMachine(IStateMachineBuilder builder) { if (builder == null) { throw new ArgumentNullException("builder"); } StateMachine = builder; PendingTransitions = new List <TransitionDef>(); Prepare(); //We have to add transitions to a temporary cache and apply them last //because in the definition of the state machine states may specify //transitions to states not specified yet in the file, and circular references //are allowed, so we need to collect all states and then configure transitions //between them. CommitPendingTransitions(); }
//------------------------------------------------------------------------------------------------------------------------------------------------- public void BuildStateMachine(IStateMachineBuilder <PhilisopherState, PhilisopherTrigger> machine) { machine.State(PhilisopherState.Thinking) .SetAsInitial() .OnEntered(ThinkingEntered) .OnLeaving(ThinkingLeaving) .OnTrigger(PhilisopherTrigger.Hungry).TransitionTo(PhilisopherState.AcquiringForks, TransitioningFromThinkingToAcquiringForks); machine.State(PhilisopherState.AcquiringForks) .OnEntered(AcquiringForksEntered) .OnLeaving(AcquiringForksLeaving) .OnTrigger(PhilisopherTrigger.GotForks).TransitionTo(PhilisopherState.Eating, TransitioningFromAcquiringForksToEating) .OnTrigger(PhilisopherTrigger.Deadlocked).TransitionTo(PhilisopherState.Thinking, TransitioningFromAcquiringForksToThinking); machine.State(PhilisopherState.Eating) .OnEntered(EatingEntered) .OnLeaving(EatingLeaving) .OnTrigger(PhilisopherTrigger.Full).TransitionTo(PhilisopherState.Thinking, TransitioningFromEatingToThinking); }
//------------------------------------------------------------------------------------------------------------------------------------------------- public void BuildStateMachine(IStateMachineBuilder <MicroserviceState, MicroserviceTrigger> machine) { machine.State(MicroserviceState.Down) .SetAsInitial() .OnTrigger(MicroserviceTrigger.Configure).TransitionTo(MicroserviceState.Configuring); machine.State(MicroserviceState.Configuring) .OnTrigger(MicroserviceTrigger.Failed).TransitionTo(MicroserviceState.Down) .OnTrigger(MicroserviceTrigger.OK).TransitionTo(MicroserviceState.Configured) .OnEntered(ConfiguringEntered); machine.State(MicroserviceState.Configured) .OnTrigger(MicroserviceTrigger.Unload).TransitionTo(MicroserviceState.Down) .OnTrigger(MicroserviceTrigger.Load).TransitionTo(MicroserviceState.Loading); machine.State(MicroserviceState.Loading) .OnTrigger(MicroserviceTrigger.Failed).TransitionTo(MicroserviceState.Down) .OnTrigger(MicroserviceTrigger.OK).TransitionTo(MicroserviceState.Standby) .OnEntered(LoadingEntered); machine.State(MicroserviceState.Standby) .OnTrigger(MicroserviceTrigger.Unload).TransitionTo(MicroserviceState.Unloading) .OnTrigger(MicroserviceTrigger.Activate).TransitionTo(MicroserviceState.Activating); machine.State(MicroserviceState.Activating) .OnTrigger(MicroserviceTrigger.Failed).TransitionTo(MicroserviceState.Unloading) .OnTrigger(MicroserviceTrigger.OK).TransitionTo(MicroserviceState.Active) .OnEntered(ActivatingEntered); machine.State(MicroserviceState.Active) .OnTrigger(MicroserviceTrigger.Deactivate).TransitionTo(MicroserviceState.Deactivating); machine.State(MicroserviceState.Deactivating) .OnTrigger(MicroserviceTrigger.Failed).TransitionTo(MicroserviceState.Unloading) .OnTrigger(MicroserviceTrigger.OK).TransitionTo(MicroserviceState.Standby) .OnEntered(DeactivatingEntered); machine.State(MicroserviceState.Unloading) .OnTrigger(MicroserviceTrigger.Done).TransitionTo(MicroserviceState.Down) .OnEntered(UnloadingEntered); }
private static IEnumerable <int> SubtractOffsets(IStateMachineBuilder builder, TransitionCompacter compacter, IReadOnlyList <int> minOffsets, int wordLength) { var alphabet = Enumerable.Range(0, builder.AlphabetSize).ToList(); var transitionsInCurrentLevel = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize)); var compactTransitionsInCurrentLevel = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize - compacter.Offsets[e])); var transitionsWithMinOffsets = new int[compacter.Transitions.Count]; Array.Fill(transitionsWithMinOffsets, -1); for (var i = 0; i < wordLength; ++i) { var statesInPreviousLevel = transitionsInCurrentLevel; var compactStatesInPreviousLevel = compactTransitionsInCurrentLevel; transitionsInCurrentLevel = new HashSet <int>(); compactTransitionsInCurrentLevel = new HashSet <int>(); var orderedPrevious = statesInPreviousLevel.OrderBy(x => x).ToList(); var orderedCompactPrevious = compactStatesInPreviousLevel.OrderBy(x => x).ToList(); for (var j = 0; j < orderedPrevious.Count; ++j) { var previous = orderedPrevious[j]; var compactPrevious = orderedCompactPrevious[j]; foreach (var c in alphabet) { if (builder.IsNull(previous + c)) { continue; } var n = builder.Transitions[previous + c]; var m = compacter.Transitions[compactPrevious + c]; transitionsInCurrentLevel.Add(n); compactTransitionsInCurrentLevel.Add(m); transitionsWithMinOffsets[compactPrevious + c] = m - minOffsets[i]; } } } return(transitionsWithMinOffsets); }
/// <inheritdoc /> public override void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder) { if (transformation is ConstantPositioningTable) { var table = transformation as ConstantPositioningTable; var paths = table.Coverage.CoveredGlyphIds.Values.Select(glyphId => new[] { new SimpleTransition { GlyphId = glyphId, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new PositioningAdjustmentAction { PositionChanges = new[] { table.PositionChange } } } }); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is IndividualChangePositioningTable) { var table = transformation as IndividualChangePositioningTable; var paths = table.Coverage.CoveredGlyphIds.Zip( table.PositionChanges, (coveragePair, positioningChange) => new[] { new SimpleTransition { GlyphId = coveragePair.Value, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new PositioningAdjustmentAction { PositionChanges = new[] { positioningChange }, } } }); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is GlyphPairPositioningTable) { var table = transformation as GlyphPairPositioningTable; var coverageDictionary = table.Coverage.CoveredGlyphIds; var paths = table.PairSets.SelectMany( (pairSet, coverageIndex) => pairSet.Select( pair => new[] { // First state just checks first glyph of the pair. new SimpleTransition { GlyphId = coverageDictionary[(ushort)coverageIndex], HeadShift = 1, LookupFlags = table.LookupFlags }, // Seconds state checks the second glyph and does the positioning. new SimpleTransition { GlyphId = pair.SecondGlyphID, LookupFlags = table.LookupFlags, HeadShift = 0, // TODO: 0? Action = new PositioningAdjustmentAction { PositionChanges = new[] { pair.FirstGlyphPositionChange, pair.SecondGlyphPositionChange }, } } })); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is ClassPairPositioningTable) { var table = transformation as ClassPairPositioningTable; var paths = table .PairSets .Zip(table.FirstClassDef.ClassAssignments) .SelectMany( pairSetTuple => pairSetTuple .Item1 .Zip(table.SecondClassDef.ClassAssignments) .Select(pairTuple => new[] { new SetTransition { GlyphIdSet = new HashSet <ushort>(pairSetTuple.Item2), HeadShift = 1, LookupFlags = table.LookupFlags }, new SetTransition { GlyphIdSet = new HashSet <ushort>(pairTuple.Item2), HeadShift = 0, LookupFlags = table.LookupFlags, Action = new PositioningAdjustmentAction { PositionChanges = new[] { pairTuple.Item1.Item1, pairTuple.Item1.Item2 } } } })); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is CursivePositioningTable) { var table = transformation as CursivePositioningTable; var glyphsWithEntryExits = table.Coverage.CoveredGlyphIds.Zip(table.EntryExitRecords, (coveragePair, entryExit) => new { GlyphId = coveragePair.Value, EntryAnchorPoint = entryExit.Item1, ExitAnchorPoint = entryExit.Item2 }).ToList(); // Only pairs where the first glyph has exit anchor and the second glyph has entry anchor are valid var paths = from firstGlyph in glyphsWithEntryExits where firstGlyph.ExitAnchorPoint != null from secondGlyph in glyphsWithEntryExits where secondGlyph.EntryAnchorPoint != null select new [] { // First state just checks first glyph of the pair. new SimpleTransition { GlyphId = firstGlyph.GlyphId, HeadShift = 1, LookupFlags = table.LookupFlags }, // Seconds state checks the second glyph and does the positioning. new SimpleTransition { GlyphId = secondGlyph.GlyphId, LookupFlags = table.LookupFlags, HeadShift = 0, Action = new AnchorPointToAnchorPointAction { PreviousGlyphAnchorPoint = firstGlyph.ExitAnchorPoint, CurrentGlyphAnchorPoint = secondGlyph.EntryAnchorPoint } } }; foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is MarkToBasePositioningTable) { var table = transformation as MarkToBasePositioningTable; /*var glyphIdWithMarkEntry = table.MarkCoverage.CoveredGlyphIds.Zip( * table.MarkAnchorPoints, * (coveragePair, markArrayEntry) => new KeyValuePair<ushort, Tuple<uushort, AnchorPoint>>(coveragePair.Value, markArrayEntry)).ToDictionary(); * * var glyphIdWithBaseEntry = table.MarkCoverage.CoveredGlyphIds.Zip( * table.BaseAnchorPoints, * (coveragePair, baseArrayEntry) => new KeyValuePair<ushort, IEnumerable<AnchorPoint>>(coveragePair.Value, baseArrayEntry)).ToDictionary(); */ var glyphIdWithMarkEntry = table.MarkCoverage.CoveredGlyphIds.Zip( table.MarkAnchorPoints, (coveragePair, markArrayEntry) => new { GlyphId = coveragePair.Value, ClassId = markArrayEntry.Item1, AnchorPoint = markArrayEntry.Item2 }); var glyphIdWithBaseEntry = table.BaseCoverage.CoveredGlyphIds.Zip( table.BaseAnchorPoints, (coveragePair, baseArrayEntry) => new { GlyphId = coveragePair.Value, AnchorPointByClassId = baseArrayEntry.ToList() }); var paths = from baseGlyph in glyphIdWithBaseEntry from markGlyph in glyphIdWithMarkEntry select new [] { new SimpleTransition { GlyphId = baseGlyph.GlyphId, HeadShift = 1, LookupFlags = table.LookupFlags }, new SimpleTransition { GlyphId = markGlyph.GlyphId, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new AnchorPointToAnchorPointAction { PreviousGlyphAnchorPoint = baseGlyph.AnchorPointByClassId[markGlyph.ClassId], CurrentGlyphAnchorPoint = markGlyph.AnchorPoint } } }; foreach (var path in paths) { builder.AddPath(path); } } }
public static IStateMachineEvent AddNewEvent(this IStateMachineBuilder stateMachine, string eventName) { return(stateMachine.AddNewEvent(eventName, eventName)); }
public static IStateMachine <int, TData> Build <TData>(this IStateMachineBuilder <int, TData> builder, TData data) { return(builder.Build(0, data)); }
/// <inheritdoc /> public override void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder) { if (transformation is SimpleReplacementSubstitutionTable) { var table = transformation as SimpleReplacementSubstitutionTable; var paths = table.Coverage.CoveredGlyphIds.Zip( table.ReplacementGlyphIds, (coveragePair, replacementGlyphId) => new[] { new SimpleTransition { GlyphId = coveragePair.Value, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new SubstitutionAction { ReplacementGlyphIds = new[] { replacementGlyphId }, ReplacedGlyphCount = 1 } } }); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is DeltaSubstitutionTable) { var table = transformation as DeltaSubstitutionTable; var paths = from coveragePair in table.Coverage.CoveredGlyphIds select new[] { new SimpleTransition { GlyphId = coveragePair.Value, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new SubstitutionAction { ReplacementGlyphIds = new[] { (ushort)(coveragePair.Value + table.GlyphIdDelta) }, ReplacedGlyphCount = 1 } } }; foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is LigatureSubstitutionTable) { var table = transformation as LigatureSubstitutionTable; var ligatureSetInfos = table.Coverage.CoveredGlyphIds.Zip( table.LigatureSets, (coveragePair, ligatureSet) => new { CoveredGlyphId = coveragePair.Value, LigatureSet = ligatureSet }); var paths = from ligatureSetInfo in ligatureSetInfos from ligature in ligatureSetInfo.LigatureSet select this.ConvertLigatureToPath(ligatureSetInfo.CoveredGlyphId, ligature, table.LookupFlags).ToList(); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is MultipleSubstitutionTable) { var table = transformation as MultipleSubstitutionTable; var paths = table.Coverage.CoveredGlyphIds.Zip( table.ReplacementSequences, (coveragePair, replacementSequence) => { var list = replacementSequence.ToList(); return(new[] { new SimpleTransition { GlyphId = coveragePair.Value, HeadShift = 1, LookupFlags = table.LookupFlags, Action = new SubstitutionAction { ReplacementGlyphIds = list, ReplacedGlyphCount = 1 } } }); }); foreach (var path in paths) { builder.AddPath(path); } } else if (transformation is ReverseChainingContextSubstitutionTable) { var table = transformation as ReverseChainingContextSubstitutionTable; builder.SetProcessingDirection(ProcessingDirection.EndToStart); // The entire machine is executed from the end of the string towards its start var lookbackCoveragesList = table.LookbackCoverages.ToList(); var completeContext = (((IEnumerable <ICoverageTable>)lookbackCoveragesList) .Reverse() .Append(table.Coverage) .Append(table.LookaheadCoverages) ).Reverse().ToList(); var contextCheckPathSeqment = completeContext .Take(completeContext.Count - 1) .Select(coverage => (ITransition) new SetTransition { GlyphIdSet = new HashSet <ushort>(coverage.CoveredGlyphIds.Select(p => p.Value)), HeadShift = -1, LookupFlags = table.LookupFlags }) .Append(new SetTransition { GlyphIdSet = new HashSet <ushort>(completeContext.Last().CoveredGlyphIds.Select(p => p.Value)), HeadShift = lookbackCoveragesList.Count, LookupFlags = table.LookupFlags }).ToList(); int i = 0; var replacementGlyphIds = table.SubstituteGlyphIds.ToList(); foreach (var coveredGlyphId in table.Coverage.CoveredGlyphIds.Values) { var transition = new SimpleTransition { GlyphId = coveredGlyphId, HeadShift = lookbackCoveragesList.Count, LookupFlags = table.LookupFlags, Action = new SubstitutionAction { ReplacedGlyphCount = 1, ReplacementGlyphIds = new [] { replacementGlyphIds[i] } } }; builder.AddPath(contextCheckPathSeqment.Append(transition)); i++; } builder.AddPath(contextCheckPathSeqment.Append(new AlwaysTransition { HeadShift = lookbackCoveragesList.Count, LookupFlags = table.LookupFlags })); } else { base.CompileTransformation(transformation, builder); } }
public StateMachineBuilder() { _builder = Machine <TState, TImpl, TInput> .GetBuilder(); }
/// <summary> /// Compiles a single transformation. /// </summary> /// <param name="transformation">The transformation.</param> /// <param name="builder">The builder.</param> public virtual void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder) { if (transformation is GlyphContextTransformationTable) { this.CompileRuleContextTransformation( transformation, builder, glyphId => glyphId, (glyphId, headShift, lookupFlags, action, zone) => new SimpleTransition { GlyphId = glyphId, HeadShift = headShift, LookupFlags = lookupFlags, Action = action }); } else if (transformation is ClassContextTransformationTable) { var table = transformation as ClassContextTransformationTable; var classAssignments = table.ClassDefinitions.ClassAssignments; /* Here is slight difference from spec - first transition of each "rule" state chain used to recognize the context * will alway be a set transition where glyph set will be defined by appropriate class definition. Instead, only glyps * which are mentioned in the coverage table should be recognized by the first transition. */ this.CompileRuleContextTransformation( transformation, builder, glyphId => table.ClassDefinitions.ClassAssignments.Single(classAssignment => classAssignment.Contains(glyphId)).Key, (classId, headShift, lookupFlags, action, zone) => new SetTransition { GlyphIdSet = new HashSet <ushort>(classAssignments[classId]), HeadShift = headShift, LookupFlags = lookupFlags, Action = action }); } else if (transformation is CoverageContextTransformationTable) { var table = transformation as CoverageContextTransformationTable; foreach (var transformationSet in table.TransformationSets) { this.CompileSingleContextTransformation( table, builder, transformationSet, (coverageTable, headShift, lookupFlags, action, zone) => new SetTransition { GlyphIdSet = new HashSet <ushort>(coverageTable.CoveredGlyphIds.Values), HeadShift = headShift, LookupFlags = lookupFlags, Action = action }, table.Coverages); } } else if (transformation is ChainingGlyphContextTransformationTable) { this.CompileChainingRuleContextTransformation( transformation, builder, glyphId => glyphId, (glyphId, headShift, lookupFlags, action, zone) => new SimpleTransition { GlyphId = glyphId, HeadShift = headShift, LookupFlags = lookupFlags, Action = action }); } else if (transformation is ChainingClassContextTransformationTable) { var table = transformation as ChainingClassContextTransformationTable; var contextClassAssignments = table.ContextClassDefinitions.ClassAssignments; var lookbackClassAssignments = table.LookbackClassDefinition.ClassAssignments; var lookaheadClassAssignments = table.LookaheadClassDefinition.ClassAssignments; var zoneToClassesMap = new Dictionary <ContextZone, ILookup <ushort, ushort> > { { ContextZone.Context, contextClassAssignments }, { ContextZone.Lookback, lookbackClassAssignments }, { ContextZone.Lookahead, lookaheadClassAssignments } }; this.CompileChainingRuleContextTransformation( transformation, builder, glyphId => contextClassAssignments.Single(classAssignment => classAssignment.Contains(glyphId)).Key, (classId, headShift, lookupFlags, action, zone) => new SetTransition { GlyphIdSet = new HashSet <ushort>((IEnumerable <ushort>)zoneToClassesMap[zone].SingleOrDefault(p => p.Key == classId) ?? new List <ushort>()), HeadShift = headShift, LookupFlags = lookupFlags, Action = action }); } else if (transformation is ChainingCoverageContextSubstitutionTable) { var table = transformation as ChainingCoverageContextSubstitutionTable; foreach (var transformationSet in table.TransformationSets) { this.CompileSingleContextTransformation( table, builder, transformationSet, (coverageTable, headShift, lookupFlags, action, zone) => new SetTransition { GlyphIdSet = new HashSet <ushort>(coverageTable.CoveredGlyphIds.Values), HeadShift = headShift, LookupFlags = lookupFlags, Action = action }, table.ContextCoverages, table.LookbackCoverages, table.LookaheadCoverages); } } else { throw new ArgumentOutOfRangeException("transformation"); } }
/// <summary> /// Compiles a context transformation for a single context. /// </summary> /// <typeparam name="TContextItem">The type of items in the context. This is arbitrary - it is only used to construct individual transitions in the context recognition section of the machine.</typeparam> /// <param name="transformation">The transformation.</param> /// <param name="builder">The builder.</param> /// <param name="subTransformationSet">The set of tranformation to execute on the matched context.</param> /// <param name="createTransitionCallback">The callback used to create transitions from individual items of the context. </param> /// <param name="context">The context.</param> /// <param name="lookbackContext">The lookback context (NULL if the transition is not chaining).</param> /// <param name="lookaheadContext">The lookahead context (NULL if the transition is not chaining). If lookaheadContext is defined, lookbackContext must be defined as well.</param> /// <exception cref="System.InvalidOperationException">Transformations in transformation set don't share LookupFlags.</exception> private void CompileSingleContextTransformation <TContextItem>( IGlyphTransformationTable transformation, IStateMachineBuilder builder, ContextTransformationSet subTransformationSet, CreateContextTransformationTransitionDelegate <TContextItem> createTransitionCallback, IEnumerable <TContextItem> context, IEnumerable <TContextItem> lookbackContext = null, IEnumerable <TContextItem> lookaheadContext = null) { if (!subTransformationSet.Transformations.Any()) { return; } var contextList = context.ToList(); var lookbackContextList = lookbackContext == null ? new List <TContextItem>() : lookbackContext.Reverse().ToList(); var lookaheadContextList = lookaheadContext == null ? new List <TContextItem>() : lookaheadContext.ToList(); var completeContext = lookbackContextList.Append(contextList).Append(lookaheadContextList).ToList(); var subMachineLookupFlags = subTransformationSet.Transformations.First().LookupFlags; // All the transformations in one transformation set must have the same lookup flags (they are supposed to come from // a single lookup). if (subTransformationSet.Transformations.Any(p => p.LookupFlags != subMachineLookupFlags)) { throw new InvalidOperationException("Transformations in transformation set don't share LookupFlags."); } // The head must end pointing to the first glyph after end of the context var subBuilder = new SubMachineBuilder(subMachineLookupFlags); foreach (var subTransformation in subTransformationSet.Transformations) { this.CompileTransformation(subTransformation, subBuilder); } /* The path for each rule is constructed as follows: * 1) A chain of states which match the context (including lookback and lookahead context). * 2) State which positions the head to adhere to first glyph index of the transformation set * and creates the context terminator glyph. The transition checking the last glyph of the context * leads to this state. Additonal state is inserted before this one in case the table has defined a * lookahead * 3) The sub-machine to be applied on the context. This state machine is modified to jump to 4 whenever * it encounters context terminator. * 4) State which positions the head to end of the context and removes the context terminator. */ // Part 1. Doesn't include the transition and state checking the last glyph (see part 2). var contextMatchPathSegment = completeContext .Take(completeContext.Count - 1) .Select( (contextMember, index) => createTransitionCallback( contextMember, 1, transformation.LookupFlags, null, this.GetContextZoneForGlyphIndex(index, contextList, lookbackContextList, lookaheadContextList))).ToList(); List <ITransition> subMachineInitializationPathSegment; if (lookaheadContextList.Count > 0) { // Part 2. Sub-machine initialization state. subMachineInitializationPathSegment = new List <ITransition> { createTransitionCallback( completeContext.Last(), -lookaheadContextList.Count, transformation.LookupFlags, null, this.GetContextZoneForGlyphIndex(completeContext.Count - 1, contextList, lookbackContextList, lookaheadContextList)), new AlwaysTransition { HeadShift = -contextList.Count, LookupFlags = transformation.LookupFlags, Action = new SubstitutionAction { ReplacedGlyphCount = 0, ReplacementGlyphIds = new[] { SubMachineBuilder.ContextTerminatorGlyphId } } } }; } else { // Part 2. Sub-machine initialization state. subMachineInitializationPathSegment = new List <ITransition> { createTransitionCallback( completeContext.Last(), -contextList.Count, transformation.LookupFlags, new SubstitutionAction { ReplacementGlyphIds = new[] { SubMachineBuilder.ContextTerminatorGlyphId } }, this.GetContextZoneForGlyphIndex(completeContext.Count - 1, contextList, lookbackContextList, lookaheadContextList)) }; } /* Part 3 a 4. Sub-machine iself + deinitialization state. * Including its entry state (to which lead all the internal transitions). */ var subMachinePaths = subBuilder.GetPaths(); // Assemble the paths and add them into the outer machine builder. foreach (var subMachinePath in subMachinePaths) { var assembledPath = contextMatchPathSegment.Append(subMachineInitializationPathSegment).Append(subMachinePath).ToList(); builder.AddPath(assembledPath); } }
public static IStateMachineBuilder <TState, TImpl, TInput> AddDefaultStates <TState, TImpl, TInput>(this IStateMachineBuilder <TState, TImpl, TInput> builder, Expression <Action <TImpl, TState, TInput> > enter, Expression <Action <TImpl, TState, TInput> > error, Expression <Action <TImpl, TState, TInput> > leave) where TState : struct, Enum where TImpl : class { return(builder.AddStates(Enum.GetValues(typeof(TState)).Cast <TState>().Select(state => (state, enter, error, leave)).ToArray())); }
public StateMachineFluentBuilder(IBuilderFactory factory) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); _builder = factory.CreateStateMachineBuilder(null); _builder.SetDataModelType(@"runtime"); }