upm_guru_kcp/Editor/protobuf-net.Reflection/CodeGenerator.cs

477 lines
18 KiB
C#
Raw Normal View History

2023-08-30 05:50:21 +00:00
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);
}
}
}
}
}
}
}
}
}