File: Internal\SrgsCompiler\State.cs
Web Access
Project: src\src\runtime\src\libraries\System.Speech\src\System.Speech.csproj (System.Speech)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Speech.Internal.SrgsParser;
using System.Text;

namespace System.Speech.Internal.SrgsCompiler
{
    /// <summary>
    /// Class representing a state in the grammar. Note that states are not stored in the binary format
    /// instead all the arcs are, with a flag to indicate the end arc out of a state */
    /// </summary>
#if DEBUG
    [DebuggerDisplay("{ToString()}")]
#endif
    internal sealed class State : IComparable<State>
    {
        #region Constructors

        internal State(Rule rule, uint hState, int iSerialize)
        {
            _rule = rule;
            _iSerialize = iSerialize;
            _id = hState;
        }

        internal State(Rule rule, uint hState)
            : this(rule, hState, (int)hState)
        {
        }

        #endregion

        #region internal Methods

        #region IComparable<State> Interface implementation

        int IComparable<State>.CompareTo(State? state2)
        {
            if (state2 is null)
            {
                return 1;
            }

            return Compare(this, state2);
        }

        #endregion

        internal void SerializeStateEntries(StreamMarshaler streamBuffer, bool tagsCannotSpanOverMultipleArcs, float[] pWeights, ref uint iArcOffset, ref int iOffset)
        {
            // The arcs must be sorted before being written to disk.
            List<Arc> outArcs = _outArcs.ToList();
            outArcs.Sort();
            Arc? lastArc = outArcs.Count > 0 ? outArcs[outArcs.Count - 1] : null;

            IEnumerator<Arc> enumArcs = ((IEnumerable<Arc>)outArcs).GetEnumerator();
            enumArcs.MoveNext();

            uint nextAvailableArc = (uint)outArcs.Count + iArcOffset;
            uint saveNextAvailableArc = nextAvailableArc;

            // Write the arc of the first epsilon arc with an arc has more than one semantic tag
            foreach (Arc arc in outArcs)
            {
                // Create the first arc.
                int cSemantics = arc.SemanticTagCount;

                // Set the semantic property reference for the first arc
                if (cSemantics > 0)
                {
                    arc.SetArcIndexForTag(0, iArcOffset, tagsCannotSpanOverMultipleArcs);
                }

                // Serialize the arc
                if (cSemantics <= 1)
                {
                    pWeights[iOffset++] = arc.Serialize(streamBuffer, lastArc == arc, iArcOffset++);
                }
                else
                {
                    // update the position of the current arc
                    ++iArcOffset;

                    // more than one arc, create an epsilon transition
                    pWeights[iOffset++] = Arc.SerializeExtraEpsilonWithTag(streamBuffer, arc, lastArc == arc, nextAvailableArc);

                    // reset the position of the next available slop for an arc
                    nextAvailableArc += (uint)cSemantics - 1;
                }
            }

            enumArcs = ((IEnumerable<Arc>)outArcs).GetEnumerator();
            enumArcs.MoveNext();

            // revert the position for the new arc
            nextAvailableArc = saveNextAvailableArc;

            // write the additional arcs if we have more than one semantic tag
            foreach (Arc arc in outArcs)
            {
                int cSemantics = arc.SemanticTagCount;

                if (cSemantics > 1)
                {
                    // If more than 2 arcs insert extra new epsilon states, one per semantic tag
                    for (int i = 1; i < cSemantics - 1; i++)
                    {
                        // Set the semantic property reference
                        arc.SetArcIndexForTag(i, iArcOffset, tagsCannotSpanOverMultipleArcs);

                        // reset the position of the next available slop for an arc
                        nextAvailableArc++;

                        // create an epsilon transition
                        pWeights[iOffset++] = Arc.SerializeExtraEpsilonWithTag(streamBuffer, arc, true, nextAvailableArc);

                        // update the position of the current arc
                        ++iArcOffset;
                    }

                    // Set the semantic property reference
                    arc.SetArcIndexForTag(cSemantics - 1, iArcOffset, tagsCannotSpanOverMultipleArcs);

                    // Add the real arc at the end
                    pWeights[iOffset++] = arc.Serialize(streamBuffer, true, iArcOffset++);

                    // reset the position of the next available slop for an arc
                    nextAvailableArc++;
                }
            }
        }

        internal void SetEndArcIndexForTags()
        {
            foreach (Arc arc in _outArcs)
            {
                arc.SetEndArcIndexForTags();
            }
        }

        #region State linked list

        // The pointers for 2 linked list are stored within each state.
        // When states are created, they added into a list, the '1' list.

        // The Members of the list are Set, Add, Remove, Prev and Next.

        internal void Init()
        {
            System.Diagnostics.Debug.Assert(_next == null && _prev == null);
        }

        internal State Add(State state)
        {
            _next = state;
            state._prev = this;
            return state;
        }

        internal void Remove()
        {
            _prev?._next = _next;
            _next?._prev = _prev;
            _next = _prev = null;
        }

        internal State? Next
        {
            get
            {
                return _next;
            }
        }

        internal State? Prev
        {
            get
            {
                return _prev;
            }
        }

        #endregion

#if DEBUG
        internal void CheckExitPath(ref int iRecursiveDepth)
        {
            if (iRecursiveDepth > CfgGrammar.MAX_TRANSITIONS_COUNT)
            {
                XmlParser.ThrowSrgsException(SRID.MaxTransitionsCount);
            }

            foreach (Arc arc in _outArcs)
            {
                if (_rule._fHasExitPath)
                {
                    break;
                }

                if (arc.CheckingForExitPath)
                {
                    arc.CheckingForExitPath = true;
                    if (arc.RuleRef != null)
                    {
                        arc.RuleRef.CheckForExitPath(ref iRecursiveDepth);
                        if (arc.RuleRef._fHasExitPath)
                        {
                            if (arc.End == null)
                            {
                                _rule._fHasExitPath = true;
                            }
                            else
                            {
                                arc.End.CheckExitPath(ref iRecursiveDepth);
                            }
                        }
                    }
                    else
                    {
                        if (arc.End == null)
                        {
                            _rule._fHasExitPath = true;
                        }
                        else
                        {
                            arc.End.CheckExitPath(ref iRecursiveDepth);
                        }
                    }

                    arc.CheckingForExitPath = false;
                }
            }
        }
#endif

        internal void CheckLeftRecursion(out bool fReachedEndState)
        {
            fReachedEndState = false;
            if ((int)(_recurseFlag & RecurFlag.RF_IN_LEFT_RECUR_CHECK) != 0)
            {
                XmlParser.ThrowSrgsException(SRID.CircularRuleRef, _rule != null ? _rule._rule.Name : string.Empty);
            }
            else
            {
                if ((_recurseFlag & RecurFlag.RF_CHECKED_LEFT_RECURSION) == 0)
                {
                    _recurseFlag |= RecurFlag.RF_CHECKED_LEFT_RECURSION | RecurFlag.RF_IN_LEFT_RECUR_CHECK;
                    foreach (Arc arc in _outArcs)
                    {
                        bool fRuleReachedEndState = false;                  // Does the rule ref have epsilon path to the end?

                        // Traverse any rule refs to check for circular rule reference.
                        if (arc.RuleRef != null && arc.RuleRef._firstState != null)
                        {
                            State pRuleFirstNode = arc.RuleRef._firstState;

                            if (((int)(pRuleFirstNode._recurseFlag & RecurFlag.RF_IN_LEFT_RECUR_CHECK) != 0) ||   // Circular RuleRef
                                ((int)(pRuleFirstNode._recurseFlag & RecurFlag.RF_CHECKED_LEFT_RECURSION) == 0))  // Untraversed rule
                            {
                                pRuleFirstNode.CheckLeftRecursion(out fRuleReachedEndState);
                            }
                            else
                            {
                                fRuleReachedEndState = arc.RuleRef._fIsEpsilonRule;
                            }
                        }

                        // Can transition be traversed by epsilon?
                        if (fRuleReachedEndState || ((arc.RuleRef == null) && (arc.WordId == 0) && arc.WordId == 0))
                        {
                            if (arc.End != null)
                            {
                                arc.End.CheckLeftRecursion(out fReachedEndState);
                            }
                            else
                            {
                                fReachedEndState = true;
                            }
                        }
                    }

                    _recurseFlag &= (~RecurFlag.RF_IN_LEFT_RECUR_CHECK);
                    if ((_rule._firstState == this) && fReachedEndState)
                    {
                        _rule._fIsEpsilonRule = true;
                    }
                }
            }
        }

        #endregion

        #region Internal Properties

        internal int NumArcs
        {
            get
            {
                // if the number of tags > 1 extra epsilon state needs to be inserted
                int cExtra = 0;
                foreach (Arc arc in _outArcs)
                {
                    if (arc.SemanticTagCount > 0)
                    {
                        cExtra += arc.SemanticTagCount - 1;
                    }
                }

                int cArcs = 0;
                foreach (Arc arc in _outArcs)
                {
                    cArcs++;
                }
                return cArcs + cExtra;
            }
        }

        internal int NumSemanticTags
        {
            get
            {
                int c = 0;

                foreach (Arc arc in _outArcs)
                {
                    c += arc.SemanticTagCount;
                }

                return c;
            }
        }

        internal Rule Rule
        {
            get
            {
                return _rule;
            }
        }

        internal uint Id
        {
            get
            {
                return _id;
            }
        }

        internal ArcList OutArcs
        {
            get
            {
                return _outArcs;
            }
        }

        internal ArcList InArcs
        {
            get
            {
                return _inArcs;
            }
        }

        internal int SerializeId
        {
            get
            {
                return _iSerialize;
            }
            set
            {
                _iSerialize = value;
            }
        }

        #endregion

        #region private Methods

        // Sort based on rule first, so all states, and arcs for a rule end up together.
        // Then sort on index.
        private static int Compare(State state1, State state2)
        {
            if (state1._rule._cfgRule._nameOffset != state2._rule._cfgRule._nameOffset)
            {
                return state1._rule._cfgRule._nameOffset - state2._rule._cfgRule._nameOffset;
            }
            else
            {
                // First state of a rule needs to be in front.
                int isNode1FirstNode = (state1._rule._firstState == state1) ? -1 : 0;
                int isNode2FirstNode = (state2._rule._firstState == state2) ? -1 : 0;

                if (isNode1FirstNode != isNode2FirstNode)
                {
                    return isNode1FirstNode - isNode2FirstNode;
                }
                else
                {
                    // First returns null on empty collections
                    Arc? arc1 = state1._outArcs != null && !state1._outArcs.IsEmpty ? state1._outArcs.First : null;
                    Arc? arc2 = state2._outArcs != null && !state2._outArcs.IsEmpty ? state2._outArcs.First : null;

                    int diff = (arc1 != null ? (arc1.RuleRef != null ? 0x1000000 : 0) + arc1.WordId : state1._iSerialize) - (arc2 != null ? (arc2.RuleRef != null ? 0x1000000 : 0) + arc2.WordId : state2._iSerialize);

                    diff = diff != 0 ? diff : state1._iSerialize - state2._iSerialize;
                    //System.Diagnostics.Debug.Assert (diff != 0);
                    return diff;
                }
            }
        }

#if DEBUG

        public override string ToString()
        {
            StringBuilder sb = new("[#");
            sb.Append(_id.ToString(CultureInfo.InvariantCulture));
            if (_rule != null && _rule._firstState == this)
            {
                sb.Append(' ');
                sb.Append(_rule.Name);
            }
            sb.Append("] ");
            if (_inArcs != null)
            {
                bool first = true;
                foreach (Arc arc in _inArcs)
                {
                    if (!first)
                    {
                        sb.Append("\x20\x25cf\x20");
                    }
                    sb.Append('#');
                    sb.Append(arc.Start != null ? arc.Start._id.ToString(CultureInfo.InvariantCulture) : "S");
                    sb.Append(' ');
                    sb.Append(arc.DebuggerDisplayTags());
                    first = false;
                }
            }
            sb.Append(" <--> ");
            if (_outArcs != null)
            {
                bool first = true;
                foreach (Arc arc in _outArcs)
                {
                    if (!first)
                    {
                        sb.Append("\x20\x25cf\x20");
                    }
                    sb.Append('#');
                    sb.Append(arc.End != null ? arc.End._id.ToString(CultureInfo.InvariantCulture) : "E");
                    sb.Append(' ');
                    sb.Append(arc.DebuggerDisplayTags());
                    first = false;
                }
            }

            return sb.ToString();
        }
#endif

        #endregion

        #region internal Fields

#pragma warning disable 56524 // Arclist does not hold on any resources

        // Collection of transitions leaving this state
        private ArcList _outArcs = new();

        // Collection of transitions entering this state
        private ArcList _inArcs = new();

#pragma warning restore 56524 // Arclist does not hold on any resources

        // Index of the first arc in the state. Also used as the state handle in SR engine interfaces.
        private int _iSerialize;

        private uint _id;

        private Rule _rule;

        private State? _next;
        private State? _prev;

        // Flags used for recursive validation methods
        internal enum RecurFlag : uint
        {
            RF_CHECKED_EPSILON = (1 << 0),
            RF_CHECKED_EXIT_PATH = (1 << 1),
            RF_CHECKED_LEFT_RECURSION = (1 << 2),
            RF_IN_LEFT_RECUR_CHECK = (1 << 3)
        };

        // Flags used by recursive algorithms
        private RecurFlag _recurseFlag;

        #endregion
    }
}