// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.Fonts; using System; using System.Collections.Generic; using System.Linq; namespace UnicodeTrieGenerator.StateAutomation { internal class StateMachine { private const int InitialState = 1; private const int FailState = 0; /// /// Initializes a new instance of the class. /// /// The state table. /// The accepting states. /// The tags. public StateMachine(int[][] stateTable, bool[] accepting, string[][] tags) { this.StateTable = stateTable; this.Accepting = accepting; this.Tags = tags; } /// /// Gets the state table. /// public int[][] StateTable { get; } /// /// Gets the accepting states. /// public bool[] Accepting { get; } /// /// Gets the tags. /// public string[][] Tags { get; } /// /// Returns an iterable object that yields pattern matches over the input sequence. /// /// The input sequence. /// The . public IEnumerable Match(ReadOnlySpan input) { int state = InitialState; int? startRun = null; int? lastAccepting = null; List matches = new(input.Length); for (int i = 0; i < input.Length; i++) { int c = input[i]; int lastState = state; state = this.StateTable[state][c]; if (state == FailState) { // yield the last match if any. if (startRun != null && lastAccepting != null && lastAccepting >= startRun) { matches.Add(new StateMatch() { StartIndex = startRun.Value, EndIndex = lastAccepting.Value, Tags = this.Tags[lastState] }); } // reset the state as if we started over from the initial state state = this.StateTable[InitialState][c]; startRun = null; } // start a run if not in the failure state if (state != FailState && startRun == null) { startRun = i; } // if accepting, mark the potential match end if (this.Accepting[state]) { lastAccepting = i; } // reset the state to the initial state if we get into the failure state if (state == FailState) { state = InitialState; } } // yield the last match if any. if (startRun != null && lastAccepting != null && lastAccepting >= startRun) { matches.Add(new StateMatch() { StartIndex = startRun.Value, EndIndex = lastAccepting.Value, Tags = this.Tags[state] }); } return matches; } /// /// For each match over the input sequence, action functions matching /// the tag definitions in the input pattern are called with the startIndex, /// length, and the sequence to be sliced. /// /// The input sequence. /// The collection of actions. public void Apply(int[] input, Dictionary>> actions) { foreach (StateMatch match in this.Match(input)) { foreach (string tag in match.Tags) { if (actions.TryGetValue(tag, out Action>? action)) { action(match.StartIndex, match.EndIndex, new ArraySlice(input, match.StartIndex, match.EndIndex + 1 - match.StartIndex)); } } } } } internal class StateMatch : IEquatable { public int StartIndex { get; set; } public int EndIndex { get; set; } public IList Tags { get; set; } = Array.Empty(); public override bool Equals(object? obj) => this.Equals(obj as StateMatch); public bool Equals(StateMatch? other) => other is not null && this.StartIndex == other.StartIndex && this.EndIndex == other.EndIndex && this.Tags.SequenceEqual(other.Tags); public override int GetHashCode() => HashCode.Combine(this.StartIndex, this.EndIndex, this.Tags); } }