using Google.Protobuf.Reflection; using System; using System.Collections.Generic; using System.IO; namespace ProtoBuf.Reflection { /// /// Abstract root for a general purpose code-generator /// public abstract class CodeGenerator { /// /// The logical name of this code generator /// public abstract string Name { get; } /// /// Get a string representation of the instance /// public override string ToString() => Name; /// /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files /// public abstract IEnumerable Generate(FileDescriptorSet set, NameNormalizer normalizer = null); /// /// Eexecute this code generator against a code file /// public CompilerResult Compile(CodeFile file) => Compile(new[] { file }); /// /// Eexecute this code generator against a set of code file /// 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(); var newErrors = new List(); 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()); } } /// /// Abstract base class for a code generator that uses a visitor pattern /// 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; } /// /// Obtain the access of an item, accounting for the model's hierarchy /// protected Access GetAccess(FileDescriptorProto obj) => obj?.Options?.GetOptions()?.Access ?? Access.Public; static Access? NullIfInherit(Access? access) => access == Access.Inherit ? null : access; /// /// Obtain the access of an item, accounting for the model's hierarchy /// protected Access GetAccess(DescriptorProto obj) => NullIfInherit(obj?.Options?.GetOptions()?.Access) ?? GetAccess(obj?.Parent) ?? Access.Public; /// /// Obtain the access of an item, accounting for the model's hierarchy /// protected Access GetAccess(FieldDescriptorProto obj) => NullIfInherit(obj?.Options?.GetOptions()?.Access) ?? GetAccess(obj?.Parent as IType) ?? Access.Public; /// /// Obtain the access of an item, accounting for the model's hierarchy /// protected Access GetAccess(EnumDescriptorProto obj) => NullIfInherit(obj?.Options?.GetOptions()?.Access) ?? GetAccess(obj?.Parent) ?? Access.Public; /// /// Get the textual name of a given access level /// public virtual string GetAccess(Access access) => access.ToString(); /// /// The indentation used by this code generator /// public virtual string Indent => " "; /// /// The file extension of the files generatred by this generator /// protected abstract string DefaultFileExtension { get; } /// /// Handle keyword escaping in the language of this code generator /// /// /// protected abstract string Escape(string identifier); /// /// Execute the code generator against a FileDescriptorSet, yielding a sequence of files /// public override IEnumerable 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 WriteFile(ctx, file); generated = buffer.ToString(); } yield return new CodeFile(fileName, generated); } } /// /// Emits the code for a file in a descriptor-set /// 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); } /// /// Emit code representing an extension field /// protected virtual void WriteExtension(GeneratorContext ctx, FieldDescriptorProto ext) { } /// /// Emit code preceeding a set of extension fields /// protected virtual void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state) { } /// /// Emit code following a set of extension fields /// protected virtual void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state) { } /// /// Emit code preceeding a set of extension fields /// protected virtual void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto file, ref object state) { } /// /// Emit code following a set of extension fields /// protected virtual void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto file, ref object state) { } /// /// Emit code representing a service /// 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); } /// /// Emit code following a set of service methods /// protected virtual void WriteServiceFooter(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { } /// /// Emit code representing a service method /// protected virtual void WriteServiceMethod(GeneratorContext ctx, MethodDescriptorProto inner, ref object state) { } /// /// Emit code following preceeding a set of service methods /// protected virtual void WriteServiceHeader(GeneratorContext ctx, ServiceDescriptorProto obj, ref object state) { } /// /// Check whether a particular message should be suppressed - for example because it represents a map /// protected virtual bool ShouldOmitMessage(GeneratorContext ctx, DescriptorProto obj, ref object state) => obj.Options?.MapEntry ?? false; // don't write this type - use a dictionary instead /// /// Emit code representing a message type /// 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); } /// /// Emit code representing a message field /// protected abstract void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs); /// /// Emit code following a set of message fields /// protected abstract void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state); /// /// Emit code preceeding a set of message fields /// protected abstract void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state); /// /// Emit code representing an enum type /// 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); } /// /// Emit code preceeding a set of enum values /// protected abstract void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state); /// /// Emit code representing an enum value /// protected abstract void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state); /// /// Emit code following a set of enum values /// protected abstract void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state); /// /// Emit code at the start of a file /// protected virtual void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { } /// /// Emit code at the end of a file /// protected virtual void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state) { } /// /// Represents the state of a code-generation invocation /// protected class GeneratorContext { /// /// The file being processed /// public FileDescriptorProto File { get; } /// /// The token to use for indentation /// public string IndentToken { get; } /// /// The current indent level /// public int IndentLevel { get; private set; } /// /// The mechanism to use for name normalization /// public NameNormalizer NameNormalizer { get; } /// /// The output for this code generation /// public TextWriter Output { get; } /// /// The effective syntax of this code-generation cycle, defaulting to "proto2" if not explicity specified /// public string Syntax => string.IsNullOrWhiteSpace(File.Syntax) ? FileDescriptorProto.SyntaxProto2 : File.Syntax; /// /// Create a new GeneratorContext instance /// internal GeneratorContext(FileDescriptorProto file, NameNormalizer nameNormalizer, TextWriter output, string indentToken) { File = file; NameNormalizer = nameNormalizer; Output = output; IndentToken = indentToken; } /// /// Ends the current line /// public GeneratorContext WriteLine() { Output.WriteLine(); return this; } /// /// Appends a value and ends the current line /// public GeneratorContext WriteLine(string line) { var indentLevel = IndentLevel; var target = Output; while (indentLevel-- > 0) { target.Write(IndentToken); } target.WriteLine(line); return this; } /// /// Appends a value to the current line /// public TextWriter Write(string value) { var indentLevel = IndentLevel; var target = Output; while (indentLevel-- > 0) { target.Write(IndentToken); } target.Write(value); return target; } /// /// Increases the indentation level /// public GeneratorContext Indent() { IndentLevel++; return this; } /// /// Decreases the indentation level /// public GeneratorContext Outdent() { IndentLevel--; return this; } /// /// Try to find a descriptor of the type specified by T with the given full name /// public T TryFind(string typeName) where T : class { object obj; if (!_knownTypes.TryGetValue(typeName, out obj) || obj == null) { return null; } return obj as T; } private Dictionary _knownTypes = new Dictionary(); 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(StringComparer.OrdinalIgnoreCase); var pendingFiles = new Queue(); _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); } } } } } } } } }