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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|