File: Internal\SrgsCompiler\RuleRef.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.

#region Using directives

using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Speech.Internal.SrgsParser;
using System.Text;

#endregion

namespace System.Speech.Internal.SrgsCompiler
{
    internal class RuleRef : ParseElement, IRuleRef
    {
        #region Constructors

        /// <summary>
        /// Special private constructor for Special Rulerefs
        /// </summary>
        private RuleRef(SpecialRuleRefType type)
            : base(null!)
        {
            _type = type;
        }

        /// <summary>
        /// Add transition corresponding to Special or Uri.
        /// </summary>
        internal RuleRef(ParseElementCollection parent, Backend backend, Uri uri, List<Rule> undefRules, string? semanticKey, string? initParameters)
            : base(parent._rule)
        {
            string id = uri.OriginalString;

            Rule? ruleRef = null;
            int posPound = id.IndexOf('#');

            // Get the initial state for the RuleRef.
            if (posPound == 0)
            {
                // Internal RuleRef.  Get InitialState of RuleRef.
                // GetRuleRef() may temporarily create a Rule placeholder for later resolution.
                ruleRef = GetRuleRef(backend, id.Substring(1), undefRules);
            }
            else
            {
                // External RuleRef.  Build URL:GrammarUri#RuleName
                StringBuilder sbExternalRuleUri = new("URL:");

                // Add the parameters to initialize a rule
                if (!string.IsNullOrEmpty(initParameters))
                {
                    // look for the # and insert the parameters
                    sbExternalRuleUri.Append(posPound > 0 ? id.Substring(0, posPound) : id);
                    sbExternalRuleUri.Append('>');
                    sbExternalRuleUri.Append(initParameters);
                    if (posPound > 0)
                    {
                        sbExternalRuleUri.Append(id.Substring(posPound));
                    }
                }
                else
                {
                    sbExternalRuleUri.Append(id);
                }

                // Get InitialState of external RuleRef.
                string sExternalRuleUri = sbExternalRuleUri.ToString();
                ruleRef = backend.FindRule(sExternalRuleUri);
                ruleRef ??= backend.CreateRule(sExternalRuleUri, SPCFGRULEATTRIBUTES.SPRAF_Import);
            }
            Arc rulerefArc = backend.RuleTransition(ruleRef, _rule, 1.0f);
#pragma warning disable 0618
            if (!string.IsNullOrEmpty(semanticKey))
            {
                CfgGrammar.CfgProperty propertyInfo = new();
                propertyInfo._pszName = "SemanticKey";
                propertyInfo._comValue = semanticKey;
                propertyInfo._comType = VarEnum.VT_EMPTY;
                backend.AddPropertyTag(rulerefArc, rulerefArc, propertyInfo);
            }
#pragma warning restore 0618
            parent.AddArc(rulerefArc);
        }

        #endregion

        #region Internal Method

        /// <summary>
        /// Returns the initial state of a special rule.
        /// For each type of special rule we make a rule with a numeric id and return a reference to it.
        /// </summary>
        internal void InitSpecialRuleRef(Backend backend, ParseElementCollection parent)
        {
            Rule? rule = null;

            // Create a transition corresponding to Special or Uri
            switch (_type)
            {
                case SpecialRuleRefType.Null:
                    parent.AddArc(backend.EpsilonTransition(1.0f));
                    break;

                case SpecialRuleRefType.Void:
                    rule = backend.FindRule(szSpecialVoid);
                    if (rule == null)
                    {
                        rule = backend.CreateRule(szSpecialVoid, 0);
                        // Rule with no transitions is a void rule.
                        ((IRule)rule).PostParse(parent);
                    }
                    parent.AddArc(backend.RuleTransition(rule, parent._rule, 1.0f));
                    break;

                case SpecialRuleRefType.Garbage:
                    // Garbage transition is optional whereas Wildcard is not.  So we need additional epsilon transition.
                    OneOf oneOf = new(parent._rule, backend);
                    // Add the garbage transition
                    oneOf.AddArc(backend.RuleTransition(CfgGrammar.SPRULETRANS_WILDCARD, parent._rule, 0.5f));
                    // Add a parallel epsilon path
                    oneOf.AddArc(backend.EpsilonTransition(0.5f));
                    ((IOneOf)oneOf).PostParse(parent);
                    break;

                default:
                    System.Diagnostics.Debug.Fail("Unknown special ruleref type");
                    break;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Return the initial state of the rule with the specified name.
        /// If the rule is not defined yet, create a placeholder Rule.
        /// </summary>
        private static Rule GetRuleRef(Backend backend, string sRuleId, List<Rule> undefRules)
        {
            System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(sRuleId));

            // Get specified rule.
            Rule? rule = backend.FindRule(sRuleId);

            if (rule == null)
            {
                // Rule doesn't exist.  Create a placeholder rule and add StateHandle to UndefinedRules.
                rule = backend.CreateRule(sRuleId, 0);
                undefRules.Insert(0, rule);
            }

            return rule;
        }

        #endregion

        #region internal Properties

        internal static IRuleRef Null
        {
            get
            {
                return new RuleRef(SpecialRuleRefType.Null);
            }
        }

        internal static IRuleRef Void
        {
            get
            {
                return new RuleRef(SpecialRuleRefType.Void);
            }
        }
        internal static IRuleRef Garbage
        {
            get
            {
                return new RuleRef(SpecialRuleRefType.Garbage);
            }
        }

        #endregion

        #region Private Fields

        #region Private Enums
        // Special rule references allow grammars based on CFGs to have powerful
        // additional features, such as transitions into dictation (both recognized
        // or not recognized) and word sequences from SAPI 5.0.
        private enum SpecialRuleRefType
        {
            // Defines a rule that is automatically matched that is, matched without
            // the user speaking any word.
            Null,
            // Defines a rule that can never be spoken. Inserting VOID into a sequence
            // automatically makes that sequence unspeakable.
            Void,
            // Defines a rule that may match any speech up until the next rule match,
            // the next token or until the end of spoken input.
            // Designed for applications that would like to recognize some phrases
            // without failing due to irrelevant, or ignorable words.
            Garbage,
        }

        #endregion

        private SpecialRuleRefType _type;

        private const string szSpecialVoid = "VOID";

        #endregion
    }
}