File: Internal\SrgsCompiler\SRGSCompiler.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.Globalization;
using System.IO;
using System.Speech.Internal.SrgsParser;
using System.Speech.Recognition.SrgsGrammar;
using System.Text;
using System.Xml;

namespace System.Speech.Internal.SrgsCompiler
{
    internal static class SrgsCompiler
    {
        #region Internal Methods

        /// <summary>
        /// Loads the SRGS XML grammar and produces the binary grammar format.
        /// </summary>
        /// <param name="xmlReaders">Source SRGS XML streams</param>
        /// <param name="filename">filename to compile to</param>
        /// <param name="stream">stream to compile to</param>
        /// <param name="fOutputCfg">Compile for CFG or DLL</param>
        /// <param name="originalUri">in xmlReader.Count == 1, name of the original file</param>
        /// <param name="referencedAssemblies">List of referenced assemblies</param>
        /// <param name="keyFile">Strong name</param>
        internal static void CompileStream(XmlReader[] xmlReaders, string? filename, Stream? stream, bool fOutputCfg, Uri? originalUri, string[]? referencedAssemblies, string? keyFile)
        {
            // raft of files to compiler is only available for class library
            System.Diagnostics.Debug.Assert(!fOutputCfg || xmlReaders.Length == 1);

            int cReaders = xmlReaders.Length;
            List<CustomGrammar.CfgResource> cfgResources = new();

            CustomGrammar cgCombined = new();
            for (int iReader = 0; iReader < cReaders; iReader++)
            {
                // Set the current directory to the location where is the grammar
                string? srgsPath = null;
                Uri? uri = originalUri;
                if (uri == null)
                {
                    if (xmlReaders[iReader].BaseURI != null && xmlReaders[iReader].BaseURI.Length > 0)
                    {
                        uri = new Uri(xmlReaders[iReader].BaseURI);
                    }
                }
                if (uri != null && (!uri.IsAbsoluteUri || uri.IsFile))
                {
                    srgsPath = Path.GetDirectoryName(uri.IsAbsoluteUri ? uri.AbsolutePath : uri.OriginalString);
                }

                CultureInfo culture;
                StringBuilder innerCode = new();
                ISrgsParser srgsParser = new XmlParser(xmlReaders[iReader], uri);
                CustomGrammar cg = CompileStream(iReader + 1, srgsParser, srgsPath, filename, stream, fOutputCfg, innerCode, cfgResources, out culture, referencedAssemblies, keyFile);
                if (!fOutputCfg)
                {
                    cgCombined.Combine(cg, innerCode.ToString());
                }
            }

            // Create the DLL if this needs to be done
            if (!fOutputCfg)
            {
                throw new PlatformNotSupportedException();
            }
        }

        /// <summary>
        /// Produces the binary grammar format.
        /// </summary>
        /// <param name="srgsGrammar">Source SRGS XML streams</param>
        /// <param name="filename">filename to compile to</param>
        /// <param name="stream">stream to compile to</param>
        /// <param name="fOutputCfg">Compile for CFG or DLL</param>
        /// <param name="referencedAssemblies">List of referenced assemblies</param>
        /// <param name="keyFile">Strong name</param>
        internal static void CompileStream(SrgsDocument srgsGrammar, string? filename, Stream? stream, bool fOutputCfg, string[]? referencedAssemblies, string? keyFile)
        {
            ISrgsParser srgsParser = new SrgsDocumentParser(srgsGrammar.Grammar);

            List<CustomGrammar.CfgResource> cfgResources = new();

            StringBuilder innerCode = new();
            CultureInfo culture;

            // Validate the grammar before compiling it. Set the tag-format and sapi flags too.
            srgsGrammar.Grammar.Validate();

            object cg = CompileStream(1, srgsParser, null, filename, stream, fOutputCfg, innerCode, cfgResources, out culture, referencedAssemblies, keyFile);

            // Create the DLL if this needs to be done
            if (!fOutputCfg)
            {
                throw new PlatformNotSupportedException();
            }
        }

        #endregion

        private static CustomGrammar CompileStream(int iCfg, ISrgsParser srgsParser, string? srgsPath, string? filename, Stream? stream, bool fOutputCfg, StringBuilder innerCode, object cfgResources, out CultureInfo culture, string[]? referencedAssemblies, string? keyFile)
        {
            Backend backend = new();
            CustomGrammar cg = new();
            SrgsElementCompilerFactory elementFactory = new(backend, cg);
            srgsParser.ElementFactory = elementFactory;
            srgsParser.Parse();

            // Optimize in-memory graph representation of the grammar.
            backend.Optimize();
            culture = backend.LangId == 0x540A ? new CultureInfo("es-us") : new CultureInfo(backend.LangId);

            // A grammar may contains references to other files in codebehind.
            // Set the current directory to the location where is the grammar
            if (cg._codebehind.Count > 0 && !string.IsNullOrEmpty(srgsPath))
            {
                for (int i = 0; i < cg._codebehind.Count; i++)
                {
                    if (!File.Exists(cg._codebehind[i]))
                    {
                        cg._codebehind[i] = srgsPath + "\\" + cg._codebehind[i];
                    }
                }
            }

            // Add the referenced assemblies
            if (referencedAssemblies != null)
            {
                foreach (string assembly in referencedAssemblies)
                {
                    cg._assemblyReferences.Add(assembly);
                }
            }

            // Assign the key file
            cg._keyFile = keyFile;

            // Assign the Scripts to the backend
            backend.ScriptRefs = cg._scriptRefs;

            // If the target is a dll, then create first the CFG and stuff it as an embedded resource
            if (!fOutputCfg)
            {
                throw new PlatformNotSupportedException();
            }
            else
            {
                //if semantic processing for a rule is defined, a script needs to be defined
                if (cg._scriptRefs.Count > 0 && !cg.HasScript)
                {
                    XmlParser.ThrowSrgsException(SRID.NoScriptsForRules);
                }

                // Creates a CFG with IL embedded
                CreateAssembly(backend, cg);

                // Save binary grammar to dest
                if (!string.IsNullOrEmpty(filename))
                {
                    // Create a stream if a filename was given
                    stream = new FileStream(filename, FileMode.Create, FileAccess.Write);
                }
                if (stream == null)
                {
                    throw new ArgumentException();
                }
                try
                {
                    using (StreamMarshaler streamHelper = new(stream))
                    {
                        backend.Commit(streamHelper);
                    }
                }
                finally
                {
                    if (!string.IsNullOrEmpty(filename))
                    {
                        stream.Close();
                    }
                }
            }
            return cg;
        }

        /// <summary>
        /// Generate the assembly code for a back. The scripts are defined in custom
        /// grammars.
        /// </summary>
        private static void CreateAssembly(Backend backend, CustomGrammar cg)
        {
            if (cg.HasScript)
            {
                throw new PlatformNotSupportedException();
            }
        }
    }

    internal enum RuleScope
    {
        PublicRule,
        PrivateRule
    }
}