477 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			477 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
using Google.Protobuf.Reflection;
 | 
						|
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
 | 
						|
namespace ProtoBuf.Reflection
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// Abstract root for a general purpose code-generator
 | 
						|
    /// </summary>
 | 
						|
    public abstract class CodeGenerator
 | 
						|
    {
 | 
						|
        /// <summary>
 | 
						|
        /// The logical name of this code generator
 | 
						|
        /// </summary>
 | 
						|
        public abstract string Name { get; }
 | 
						|
        /// <summary>
 | 
						|
        /// Get a string representation of the instance
 | 
						|
        /// </summary>
 | 
						|
        public override string ToString() => Name;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files
 | 
						|
        /// </summary>
 | 
						|
        public abstract IEnumerable<CodeFile> Generate(FileDescriptorSet set, NameNormalizer normalizer = null);
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Eexecute this code generator against a code file
 | 
						|
        /// </summary>
 | 
						|
        public CompilerResult Compile(CodeFile file) => Compile(new[] { file });
 | 
						|
        /// <summary>
 | 
						|
        /// Eexecute this code generator against a set of code file
 | 
						|
        /// </summary>
 | 
						|
        public CompilerResult Compile(params CodeFile[] files)
 | 
						|
        {
 | 
						|
            var set = new FileDescriptorSet();
 | 
						|
            foreach (var file in files)
 | 
						|
            {
 | 
						|
                using (var reader = new StringReader(file.Text))
 | 
						|
                {
 | 
						|
                    Console.WriteLine($"Parsing {file.Name}...");
 | 
						|
                    set.Add(file.Name, true, reader);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            set.Process();
 | 
						|
            var results = new List<CodeFile>();
 | 
						|
            var newErrors = new List<Error>();
 | 
						|
 | 
						|
            try
 | 
						|
            {
 | 
						|
                results.AddRange(Generate(set));
 | 
						|
            }
 | 
						|
            catch (Exception ex)
 | 
						|
            {
 | 
						|
                set.Errors.Add(new Error(default(Token), ex.Message, true));
 | 
						|
            }
 | 
						|
            var errors = set.GetErrors();
 | 
						|
 | 
						|
            return new CompilerResult(errors, results.ToArray());
 | 
						|
        }
 | 
						|
    }
 | 
						|
    /// <summary>
 | 
						|
    /// Abstract base class for a code generator that uses a visitor pattern
 | 
						|
    /// </summary>
 | 
						|
    public abstract partial class CommonCodeGenerator : CodeGenerator
 | 
						|
    {
 | 
						|
        private Access? GetAccess(IType parent)
 | 
						|
        {
 | 
						|
            if (parent is DescriptorProto)
 | 
						|
                return GetAccess((DescriptorProto)parent);
 | 
						|
            if (parent is EnumDescriptorProto)
 | 
						|
                return GetAccess((EnumDescriptorProto)parent);
 | 
						|
            if (parent is FileDescriptorProto)
 | 
						|
                return GetAccess((FileDescriptorProto)parent);
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Obtain the access of an item, accounting for the model's hierarchy
 | 
						|
        /// </summary>
 | 
						|
        protected Access GetAccess(FileDescriptorProto obj)
 | 
						|
            => obj?.Options?.GetOptions()?.Access ?? Access.Public;
 | 
						|
 | 
						|
        static Access? NullIfInherit(Access? access)
 | 
						|
            => access == Access.Inherit ? null : access;
 | 
						|
        /// <summary>
 | 
						|
        /// Obtain the access of an item, accounting for the model's hierarchy
 | 
						|
        /// </summary>
 | 
						|
        protected Access GetAccess(DescriptorProto obj)
 | 
						|
            => NullIfInherit(obj?.Options?.GetOptions()?.Access)
 | 
						|
            ?? GetAccess(obj?.Parent) ?? Access.Public;
 | 
						|
        /// <summary>
 | 
						|
        /// Obtain the access of an item, accounting for the model's hierarchy
 | 
						|
        /// </summary>
 | 
						|
        protected Access GetAccess(FieldDescriptorProto obj)
 | 
						|
            => NullIfInherit(obj?.Options?.GetOptions()?.Access)
 | 
						|
            ?? GetAccess(obj?.Parent as IType) ?? Access.Public;
 | 
						|
        /// <summary>
 | 
						|
        /// Obtain the access of an item, accounting for the model's hierarchy
 | 
						|
        /// </summary>
 | 
						|
        protected Access GetAccess(EnumDescriptorProto obj)
 | 
						|
            => NullIfInherit(obj?.Options?.GetOptions()?.Access)
 | 
						|
                ?? GetAccess(obj?.Parent) ?? Access.Public;
 | 
						|
        /// <summary>
 | 
						|
        /// Get the textual name of a given access level
 | 
						|
        /// </summary>
 | 
						|
        public virtual string GetAccess(Access access)
 | 
						|
            => access.ToString();
 | 
						|
        
 | 
						|
        /// <summary>
 | 
						|
        /// The indentation used by this code generator
 | 
						|
        /// </summary>
 | 
						|
        public virtual string Indent => "    ";
 | 
						|
        /// <summary>
 | 
						|
        /// The file extension of the files generatred by this generator
 | 
						|
        /// </summary>
 | 
						|
        protected abstract string DefaultFileExtension { get; }
 | 
						|
        /// <summary>
 | 
						|
        /// Handle keyword escaping in the language of this code generator
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="identifier"></param>
 | 
						|
        /// <returns></returns>
 | 
						|
        protected abstract string Escape(string identifier);
 | 
						|
        /// <summary>
 | 
						|
        /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files
 | 
						|
        /// </summary>
 | 
						|
        public override IEnumerable<CodeFile> Generate(FileDescriptorSet set, NameNormalizer normalizer = null)
 | 
						|
        {
 | 
						|
            foreach (var file in set.Files)
 | 
						|
            {
 | 
						|
                if (!file.IncludeInOutput) continue;
 | 
						|
 | 
						|
                var fileName = Path.ChangeExtension(file.Name, DefaultFileExtension);
 | 
						|
 | 
						|
                string generated;
 | 
						|
                using (var buffer = new StringWriter())
 | 
						|
                {
 | 
						|
                    var ctx = new GeneratorContext(file, normalizer ?? NameNormalizer.Default, buffer, Indent);
 | 
						|
 | 
						|
                    ctx.BuildTypeIndex(); // populates for TryFind<T>
 | 
						|
                    WriteFile(ctx, file);
 | 
						|
                    generated = buffer.ToString();
 | 
						|
                }
 | 
						|
                yield return new CodeFile(fileName, generated);
 | 
						|
 | 
						|
            }
 | 
						|
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Emits the code for a file in a descriptor-set
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteFile(GeneratorContext ctx, FileDescriptorProto obj)
 | 
						|
        {
 | 
						|
            var file = ctx.File;
 | 
						|
            object state = null;
 | 
						|
            WriteFileHeader(ctx, obj, ref state);
 | 
						|
 | 
						|
            foreach (var inner in file.MessageTypes)
 | 
						|
            {
 | 
						|
                WriteMessage(ctx, inner);
 | 
						|
            }
 | 
						|
            foreach (var inner in file.EnumTypes)
 | 
						|
            {
 | 
						|
                WriteEnum(ctx, inner);
 | 
						|
            }
 | 
						|
            foreach (var inner in file.Services)
 | 
						|
            {
 | 
						|
                WriteService(ctx, inner);
 | 
						|
            }
 | 
						|
            if(file.Extensions.Count != 0)
 | 
						|
            {
 | 
						|
                object extState = null;
 | 
						|
                WriteExtensionsHeader(ctx, file, ref extState);
 | 
						|
                foreach(var ext in file.Extensions)
 | 
						|
                {
 | 
						|
                    WriteExtension(ctx, ext);
 | 
						|
                }
 | 
						|
                WriteExtensionsFooter(ctx, file, ref extState);
 | 
						|
            }
 | 
						|
            WriteFileFooter(ctx, obj, ref state);
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing an extension field
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteExtension(GeneratorContext ctx, FieldDescriptorProto ext) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code preceeding a set of extension fields
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following a set of extension fields
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code preceeding a set of extension fields
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto file, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following a set of extension fields
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto file, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing a service
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteService(GeneratorContext ctx, ServiceDescriptorProto obj)
 | 
						|
        {
 | 
						|
            object state = null;
 | 
						|
            WriteServiceHeader(ctx, obj, ref state);
 | 
						|
            foreach (var inner in obj.Methods)
 | 
						|
            {
 | 
						|
                WriteServiceMethod(ctx, inner, ref state);
 | 
						|
            }
 | 
						|
            WriteServiceFooter(ctx, obj, ref state);
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following a set of service methods
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteServiceFooter(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing a service method
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteServiceMethod(GeneratorContext ctx, MethodDescriptorProto inner, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following preceeding a set of service methods
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteServiceHeader(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Check whether a particular message should be suppressed - for example because it represents a map
 | 
						|
        /// </summary>
 | 
						|
        protected virtual bool ShouldOmitMessage(GeneratorContext ctx, DescriptorProto obj, ref object state)
 | 
						|
            => obj.Options?.MapEntry ?? false; // don't write this type - use a dictionary instead
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing a message type
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteMessage(GeneratorContext ctx, DescriptorProto obj)
 | 
						|
        {
 | 
						|
            object state = null;
 | 
						|
            if (ShouldOmitMessage(ctx, obj, ref state)) return;
 | 
						|
 | 
						|
            WriteMessageHeader(ctx, obj, ref state);
 | 
						|
            var oneOfs = OneOfStub.Build(ctx, obj);
 | 
						|
            foreach (var inner in obj.Fields)
 | 
						|
            {
 | 
						|
                WriteField(ctx, inner, ref state, oneOfs);
 | 
						|
            }
 | 
						|
            foreach (var inner in obj.NestedTypes)
 | 
						|
            {
 | 
						|
                WriteMessage(ctx, inner);
 | 
						|
            }
 | 
						|
            foreach (var inner in obj.EnumTypes)
 | 
						|
            {
 | 
						|
                WriteEnum(ctx, inner);
 | 
						|
            }
 | 
						|
            if (obj.Extensions.Count != 0)
 | 
						|
            {
 | 
						|
                object extState = null;
 | 
						|
                WriteExtensionsHeader(ctx, obj, ref extState);
 | 
						|
                foreach (var ext in obj.Extensions)
 | 
						|
                {
 | 
						|
                    WriteExtension(ctx, ext);
 | 
						|
                }
 | 
						|
                WriteExtensionsFooter(ctx, obj, ref extState);
 | 
						|
            }
 | 
						|
            WriteMessageFooter(ctx, obj, ref state);
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing a message field
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following a set of message fields
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code preceeding a set of message fields
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing an enum type
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteEnum(GeneratorContext ctx, EnumDescriptorProto obj)
 | 
						|
        {
 | 
						|
            object state = null;
 | 
						|
            WriteEnumHeader(ctx, obj, ref state);
 | 
						|
            foreach (var inner in obj.Values)
 | 
						|
            {
 | 
						|
                WriteEnumValue(ctx, inner, ref state);
 | 
						|
            }
 | 
						|
            WriteEnumFooter(ctx, obj, ref state);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code preceeding a set of enum values
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code representing an enum value
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code following a set of enum values
 | 
						|
        /// </summary>
 | 
						|
        protected abstract void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state);
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code at the start of a file
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { }
 | 
						|
        /// <summary>
 | 
						|
        /// Emit code at the end of a file
 | 
						|
        /// </summary>
 | 
						|
        protected virtual void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Represents the state of a code-generation invocation
 | 
						|
        /// </summary>
 | 
						|
        protected class GeneratorContext
 | 
						|
        {
 | 
						|
            /// <summary>
 | 
						|
            /// The file being processed
 | 
						|
            /// </summary>
 | 
						|
            public FileDescriptorProto File { get; }
 | 
						|
            /// <summary>
 | 
						|
            /// The token to use for indentation
 | 
						|
            /// </summary>
 | 
						|
            public string IndentToken { get; }
 | 
						|
            /// <summary>
 | 
						|
            /// The current indent level
 | 
						|
            /// </summary>
 | 
						|
            public int IndentLevel { get; private set; }
 | 
						|
            /// <summary>
 | 
						|
            /// The mechanism to use for name normalization
 | 
						|
            /// </summary>
 | 
						|
            public NameNormalizer NameNormalizer { get; }
 | 
						|
            /// <summary>
 | 
						|
            /// The output for this code generation
 | 
						|
            /// </summary>
 | 
						|
            public TextWriter Output { get; }
 | 
						|
            /// <summary>
 | 
						|
            /// The effective syntax of this code-generation cycle, defaulting to "proto2" if not explicity specified
 | 
						|
            /// </summary>
 | 
						|
            public string Syntax => string.IsNullOrWhiteSpace(File.Syntax) ? FileDescriptorProto.SyntaxProto2 : File.Syntax;
 | 
						|
            /// <summary>
 | 
						|
            /// Create a new GeneratorContext instance
 | 
						|
            /// </summary>
 | 
						|
            internal GeneratorContext(FileDescriptorProto file, NameNormalizer nameNormalizer, TextWriter output, string indentToken)
 | 
						|
            {
 | 
						|
                File = file;
 | 
						|
                NameNormalizer = nameNormalizer;
 | 
						|
                Output = output;
 | 
						|
                IndentToken = indentToken;
 | 
						|
            }
 | 
						|
 | 
						|
            /// <summary>
 | 
						|
            /// Ends the current line
 | 
						|
            /// </summary>
 | 
						|
            public GeneratorContext WriteLine()
 | 
						|
            {
 | 
						|
                Output.WriteLine();
 | 
						|
                return this;
 | 
						|
            }
 | 
						|
            /// <summary>
 | 
						|
            /// Appends a value and ends the current line
 | 
						|
            /// </summary>
 | 
						|
            public GeneratorContext WriteLine(string line)
 | 
						|
            {
 | 
						|
                var indentLevel = IndentLevel;
 | 
						|
                var target = Output;
 | 
						|
                while (indentLevel-- > 0)
 | 
						|
                {
 | 
						|
                    target.Write(IndentToken);
 | 
						|
                }
 | 
						|
                target.WriteLine(line);
 | 
						|
                return this;
 | 
						|
            }
 | 
						|
            /// <summary>
 | 
						|
            /// Appends a value to the current line
 | 
						|
            /// </summary>
 | 
						|
            public TextWriter Write(string value)
 | 
						|
            {
 | 
						|
                var indentLevel = IndentLevel;
 | 
						|
                var target = Output;
 | 
						|
                while (indentLevel-- > 0)
 | 
						|
                {
 | 
						|
                    target.Write(IndentToken);
 | 
						|
                }
 | 
						|
                target.Write(value);
 | 
						|
                return target;
 | 
						|
            }
 | 
						|
            /// <summary>
 | 
						|
            /// Increases the indentation level
 | 
						|
            /// </summary>
 | 
						|
            public GeneratorContext Indent()
 | 
						|
            {
 | 
						|
                IndentLevel++;
 | 
						|
                return this;
 | 
						|
            }
 | 
						|
            /// <summary>
 | 
						|
            /// Decreases the indentation level
 | 
						|
            /// </summary>
 | 
						|
            public GeneratorContext Outdent()
 | 
						|
            {
 | 
						|
                IndentLevel--;
 | 
						|
                return this;
 | 
						|
            }
 | 
						|
 | 
						|
            /// <summary>
 | 
						|
            /// Try to find a descriptor of the type specified by T with the given full name
 | 
						|
            /// </summary>
 | 
						|
            public T TryFind<T>(string typeName) where T : class
 | 
						|
            {
 | 
						|
                object obj;
 | 
						|
                if (!_knownTypes.TryGetValue(typeName, out obj) || obj == null)
 | 
						|
                {
 | 
						|
                    return null;
 | 
						|
                }
 | 
						|
                return obj as T;
 | 
						|
            }
 | 
						|
 | 
						|
            private Dictionary<string, object> _knownTypes = new Dictionary<string, object>();
 | 
						|
			void AddMessage(DescriptorProto message)
 | 
						|
			{
 | 
						|
				_knownTypes[message.FullyQualifiedName] = message;
 | 
						|
				foreach (var @enum in message.EnumTypes)
 | 
						|
				{
 | 
						|
					_knownTypes[@enum.FullyQualifiedName] = @enum;
 | 
						|
				}
 | 
						|
				foreach (var msg in message.NestedTypes)
 | 
						|
				{
 | 
						|
					AddMessage(msg);
 | 
						|
				}
 | 
						|
			}
 | 
						|
            internal void BuildTypeIndex()
 | 
						|
            {
 | 
						|
                {
 | 
						|
                    var processedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 | 
						|
                    var pendingFiles = new Queue<FileDescriptorProto>();
 | 
						|
 | 
						|
                    _knownTypes.Clear();
 | 
						|
                    processedFiles.Add(File.Name);
 | 
						|
                    pendingFiles.Enqueue(File);
 | 
						|
 | 
						|
                    while (pendingFiles.Count != 0)
 | 
						|
                    {
 | 
						|
                        var file = pendingFiles.Dequeue();
 | 
						|
 | 
						|
                        foreach (var @enum in file.EnumTypes)
 | 
						|
                        {
 | 
						|
                            _knownTypes[@enum.FullyQualifiedName] = @enum;
 | 
						|
                        }
 | 
						|
                        foreach (var msg in file.MessageTypes)
 | 
						|
                        {
 | 
						|
                            AddMessage(msg);
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (file.HasImports())
 | 
						|
                        {
 | 
						|
                            foreach (var import in file.GetImports())
 | 
						|
                            {
 | 
						|
                                if (processedFiles.Add(import.Path))
 | 
						|
                                {
 | 
						|
                                    var importFile = file.Parent.GetFile(import.Path);
 | 
						|
                                    if (importFile != null) pendingFiles.Enqueue(importFile);
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
}
 |