File: Internal\SrgsCompiler\Rule.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.Diagnostics.CodeAnalysis;
using System.Speech.Internal.SrgsParser;
using System.Speech.Synthesis.TtsEngine;
using System.Text;

namespace System.Speech.Internal.SrgsCompiler
{
    [DebuggerDisplay("{Name}")]
    internal sealed class Rule : ParseElementCollection, IRule, IComparable<Rule>
    {
        #region Constructors

        // Only used for the special transition
        internal Rule(int iSerialize)
            : base(null!, null!)
        {
            _iSerialize = iSerialize;
            _id = null!;
        }

        internal Rule(Backend backend, string name, CfgRule cfgRule, int iSerialize, GrammarOptions SemanticFormat, ref int cImportedRules)
            : base(backend, null! /* Cannot pass 'this' into base constructor - setting this on _rule instead */)
        {
            _rule = this;
            _id = name;
            _cfgRule = cfgRule;
            _firstState = null;
            _cfgRule.DirtyRule = true;
            _iSerialize = iSerialize;
            _fHasExitPath = false;
            _fHasDynamicRef = false;
            _fIsEpsilonRule = false;
            _fStaticRule = false;
            if (_cfgRule.Import)
            {
                cImportedRules++;
            }
        }

        internal Rule(Backend backend, string name, int offsetName, SPCFGRULEATTRIBUTES attributes, int id, int iSerialize, GrammarOptions SemanticFormat, ref int cImportedRules)
            : this(backend, name, new CfgRule(id, offsetName, attributes), iSerialize, SemanticFormat, ref cImportedRules)
        {
        }

        #endregion

        #region internal Methods

        #region IComparable<Rule> Interface implementation

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

            Rule rule1 = this;

            if (rule1._cfgRule.Import)
            {
                return (rule2._cfgRule.Import) ? rule1._cfgRule._nameOffset - rule2._cfgRule._nameOffset : -1;
            }
            else if (rule1._cfgRule.Dynamic)
            {
                return (rule2._cfgRule.Dynamic) ? rule1._cfgRule._nameOffset - rule2._cfgRule._nameOffset : 1;
            }
            else
            {
                return (rule2._cfgRule.Import) ? 1 : (rule2._cfgRule.Dynamic) ? -1 : rule1._cfgRule._nameOffset - rule2._cfgRule._nameOffset;
            }
        }

        #endregion

#if DEBUG

        internal void CheckForExitPath(ref int iRecursiveDepth)
        {
            if (!_fHasExitPath)
            {
                // This check allows empty rules.
                if (_firstState != null && _firstState.NumArcs != 0)
                {
                    _firstState.CheckExitPath(ref iRecursiveDepth);
                }
            }
        }
#endif

        internal void Validate()
        {
            if ((!_cfgRule.Dynamic) && (!_cfgRule.Import) && _id != "VOID" && _firstState!.NumArcs == 0)
            {
                XmlParser.ThrowSrgsException(SRID.EmptyRule);
            }
            else
            {
                _fHasDynamicRef = _cfgRule.Dynamic;
            }
        }

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

            foreach (Rule rule in _listRules)
            {
                if (!rule._fHasDynamicRef)
                {
                    rule._fHasDynamicRef = true;
                    rule.PopulateDynamicRef(ref iRecursiveDepth);
                }
            }
        }

        internal Rule Clone(StringBlob symbol, string ruleName)
        {
            Rule rule = new(_iSerialize);

            int idWord;
            int offsetName = symbol.Add(ruleName, out idWord);

            rule._id = ruleName;
            rule._cfgRule = new CfgRule(idWord, offsetName, _cfgRule._flag)
            {
                DirtyRule = true,
                FirstArcIndex = 0
            };
            return rule;
        }

        internal void Serialize(StreamMarshaler streamBuffer)
        {

            // Dynamic rules and imports have no arcs
            _cfgRule.FirstArcIndex = _firstState != null && !_firstState.OutArcs.IsEmpty ? (uint)_firstState.SerializeId : 0;

            _cfgRule.DirtyRule = true;

            streamBuffer.WriteStream(_cfgRule);
        }

        void IElement.PostParse(IElement? grammar)
        {
            // Empty rule
            if (_endArc == null)
            {
                System.Diagnostics.Debug.Assert(_startArc == null);
                _firstState = _backend.CreateNewState(this);
            }
            else
            {
                // The last arc may contain an epsilon value. Remove it.
                TrimEndEpsilons(_endArc, _backend);

                // If the first arc was an epsilon value then there is no need to create a new state
                if (_startArc!.IsEpsilonTransition && _startArc.End != null && Graph.MoveSemanticTagRight(_startArc))
                {
                    // Discard the arc and replace it with the startArc
                    _firstState = _startArc.End;
                    System.Diagnostics.Debug.Assert(_startArc.End == _startArc.End);
                    _startArc.End = null;
                }
                else
                {
                    // if _first has not be set, create it
                    _firstState = _backend.CreateNewState(this);

                    // Attach the start and end arc to the rule
                    _startArc.Start = _firstState;
                }
            }
        }

        void IRule.CreateScript(IGrammar grammar, string rule, string method, RuleMethodScript type)
        {
            ((GrammarElement)grammar).CustomGrammar._scriptRefs.Add(new ScriptRef(rule, method, type));
        }

        #endregion

        #region Internal Properties

        internal string Name
        {
            get
            {
                return _id;
            }
        }

        string? IRule.BaseClass
        {
            get
            {
                return _baseclass;
            }
            set
            {
                _baseclass = value;
            }
        }

        internal string? BaseClass
        {
            get
            {
                return _baseclass;
            }
        }

        internal StringBuilder Script
        {
            get
            {
                return _script;
            }
        }

        internal StringBuilder Constructors
        {
            get
            {
                return _constructors;
            }
        }

        #endregion

        #region Private Methods

        private static void TrimEndEpsilons(Arc end, Backend backend)
        {
            Arc endArc = end;

            State? endState = endArc.Start;
            if (endState != null)
            {
                // Remove the end arc if possible, check done by MoveSemanticTagRight
                if (endArc.IsEpsilonTransition && endState.OutArcs.CountIsOne && Graph.MoveSemanticTagLeft(endArc))
                {
                    // State has a single input epsilon transition
                    // Delete the input epsilon transition and delete state.
                    endArc.Start = null;

                    // Remove all the in arcs duplicate the arcs first
                    foreach (Arc inArc in endState.InArcs.ToList())
                    {
                        inArc.End = null;
                        TrimEndEpsilons(inArc, backend);
                    }

                    // Delete the input epsilon transition and delete state if appropriate.
                    backend.DeleteState(endState);
                }
            }
        }

        #endregion

        #region Internal Fields

        internal CfgRule _cfgRule;

        internal State? _firstState;

        internal bool _fHasExitPath;

        internal bool _fHasDynamicRef;

        internal bool _fIsEpsilonRule;

        internal int _iSerialize;
        internal int _iSerialize2;

#if DEBUG
        internal int _cStates;
#endif
        internal List<Rule> _listRules = new();

        // this is used to refer to a static rule from a dynamic rule
        internal bool _fStaticRule;

        #endregion

        #region Private Fields

        private string _id;

        // STG fields
        private string? _baseclass;

        private StringBuilder _script = new();

        private StringBuilder _constructors = new();

        #endregion
    }
}