namespace Vellum.Generator
{
///
/// Writes a [Saveable] type's generated half: its SaveReader constructors and its nested SaveConverter.
///
public static class SaveableWriter
{
public const string WriteFieldsName = "__WriteFields";
public const string ListFieldsName = "__ListFields";
public const string FillFieldsName = "__FillFields";
public const string FillWriterPrefix = "__Fill_";
private static string Quoted(string key)
{
return "\"" + key + "\"";
}
private static string LoaderAccess(SaveableShape shape)
{
if (shape.IsDerivable)
{
return "protected";
}
return "private";
}
private static void WriteLoaders(CodeWriter code, SaveableShape shape)
{
string name = shape.Name;
string access = LoaderAccess(shape);
// Declaring any constructor takes away the one C# writes for a class that has none.
if (shape.KeepsImplicitConstructor)
{
string implicitAccess = "public";
if (shape.IsAbstract)
{
implicitAccess = "protected";
}
code.OpenNewBlock(implicitAccess + " " + name + "()");
code.CloseBlock();
code.NewLine();
}
// A base class loads its own fields through its own loader, which may do more than the generated one.
string chain = "";
if (shape.HasSaveableBase)
{
chain = " : base(reader)";
}
else if (shape.ChainsToParameterless)
{
chain = " : this()";
}
code.OpenNewBlock(access + " " + name + "(global::Vellum.SaveReader reader, " +
"global::Vellum.AutoLoadEveryField load)" + chain);
// A value that refers back to this one is handed it part-built, before its fields are read.
if (shape.IsClass)
{
code.AddLine("reader.Publish(this);");
}
foreach (MemberShape member in shape.Members.Items)
{
code.AddLine("{ if (reader.TryRead<" + member.TypeName + ">(" + Quoted(member.Key) + ", out " +
member.TypeName + " loaded)) { " + member.On("this") + " = loaded; } }");
}
code.CloseBlock();
code.NewLine();
if (!shape.WritesOwnLoader)
{
code.AddLine(access + " " + name + "(global::Vellum.SaveReader reader)");
code.Indent();
code.AddLine(": this(reader, default(global::Vellum.AutoLoadEveryField))");
code.Dedent();
code.OpenNewBlock();
code.CloseBlock();
code.NewLine();
}
}
private static bool WritesFieldsInAMethod(SaveableShape shape)
{
return shape.IsDerivable || shape.HasSaveableBase;
}
// The fields of a class others derive from are written by a method, so each class in a hierarchy writes only
// its own and the one above writes the rest.
private static void WriteFieldsMethod(CodeWriter code, SaveableShape shape)
{
code.AddLine("[global::System.ComponentModel.EditorBrowsable(" +
"global::System.ComponentModel.EditorBrowsableState.Never)]");
string modifier = "protected virtual";
if (shape.HasSaveableBase)
{
modifier = "protected override";
}
code.OpenNewBlock(modifier + " void " + WriteFieldsName + "(global::Vellum.SaveNode node, " +
"global::Vellum.SaveSession session)");
if (shape.HasSaveableBase)
{
code.AddLine("base." + WriteFieldsName + "(node, session);");
}
// Add rather than assign, so a key a base class already saved is an error instead of lost data.
foreach (MemberShape member in shape.Members.Items)
{
code.AddLine("node.Entries.Add(" + Quoted(member.Key) + ", session.ToNode(" + member.On("this") +
"));");
}
code.CloseBlock();
code.NewLine();
}
private static void AddChildLines(CodeWriter code, SaveableShape shape)
{
foreach (MemberShape member in shape.ChildMembers.Items)
{
code.AddLine("{ if (node.Entries.TryGetValue(" + Quoted(member.Key) + ", out global::Vellum.SaveNode "
+ "child)) { children.Add(child, typeof(" + member.TypeName + ")); } }");
}
}
// Static, since loading lists a table's parts before any instance of the type exists. Each class in a
// hierarchy lists its own members and the one above lists the rest.
private static void WriteListFieldsMethod(CodeWriter code, SaveableShape shape)
{
code.AddLine("[global::System.ComponentModel.EditorBrowsable(" +
"global::System.ComponentModel.EditorBrowsableState.Never)]");
string hides = "";
if (shape.HasSaveableBase)
{
hides = "new ";
}
code.OpenNewBlock(LoaderAccess(shape) + " static " + hides + "void " + ListFieldsName +
"(global::Vellum.SaveNode node, global::Vellum.ChildList children)");
if (shape.HasSaveableBase)
{
code.AddLine(shape.BaseTypeName + "." + ListFieldsName + "(node, children);");
}
AddChildLines(code, shape);
code.CloseBlock();
code.NewLine();
}
// C# only lets a constructor assign a readonly field, so loading the data before any constructor has run
// writes those through a FieldWriter, which each such member gets in the type.
private static void WriteFillWriters(CodeWriter code, SaveableShape shape)
{
bool wroteAny = false;
foreach (MemberShape member in shape.Members.Items)
{
if (member.AssignsDirectly)
{
continue;
}
string writer = "global::Vellum.FieldWriter<" + shape.TypeName + ", " + member.TypeName + ">";
code.AddLine("private static readonly " + writer + " " + FillWriterPrefix + member.Key + " = new " +
writer + "(" + Quoted(member.Key) + ");");
wroteAny = true;
}
if (wroteAny)
{
code.NewLine();
}
}
private static void AddFillLines(CodeWriter code, SaveableShape shape)
{
foreach (MemberShape member in shape.Members.Items)
{
string assign = member.On("blank") + " = loaded;";
if (!member.AssignsDirectly)
{
assign = FillWriterPrefix + member.Key + ".Set(blank, loaded);";
}
code.AddLine("{ if (reader.TryRead<" + member.TypeName + ">(" + Quoted(member.Key) + ", out " +
member.TypeName + " loaded)) { " + assign + " } }");
}
}
// Static, like the listing method, so each class in a hierarchy writes only its own members into the blank
// and the one above writes the rest.
private static void WriteFillFieldsMethod(CodeWriter code, SaveableShape shape)
{
code.AddLine("[global::System.ComponentModel.EditorBrowsable(" +
"global::System.ComponentModel.EditorBrowsableState.Never)]");
// Each class's blank has its own type, so these overload the one above rather than hiding it.
code.OpenNewBlock(LoaderAccess(shape) + " static void " + FillFieldsName + "(" + shape.TypeName +
" blank, global::Vellum.SaveReader reader)");
if (shape.HasSaveableBase)
{
code.AddLine(shape.BaseTypeName + "." + FillFieldsName + "(blank, reader);");
}
AddFillLines(code, shape);
code.CloseBlock();
code.NewLine();
}
private static void WriteFill(CodeWriter code, SaveableShape shape)
{
code.OpenNewBlock("public override void Fill(" + shape.TypeName + " blank, global::Vellum.SaveNode node, " +
"global::Vellum.SaveSession session)");
code.AddLine("global::Vellum.SaveReader reader = new global::Vellum.SaveReader(node, session);");
if (WritesFieldsInAMethod(shape))
{
code.AddLine(shape.TypeName + "." + FillFieldsName + "(blank, reader);");
}
else
{
AddFillLines(code, shape);
}
code.CloseBlock();
}
private static void WriteTryListChildren(CodeWriter code, SaveableShape shape)
{
code.OpenNewBlock("public override bool TryListChildren(global::Vellum.SaveNode node, " +
"global::Vellum.ChildList children)");
code.OpenNewBlock("if (node.Kind != global::Vellum.SaveNode.Kinds.Table)");
code.AddLine("return false;");
code.CloseBlock();
code.NewLine();
if (WritesFieldsInAMethod(shape))
{
code.AddLine(shape.TypeName + "." + ListFieldsName + "(node, children);");
}
else
{
AddChildLines(code, shape);
}
code.AddLine("return true;");
code.CloseBlock();
}
private static void WriteConverter(CodeWriter code, SaveableShape shape)
{
string type = shape.TypeName;
code.AddLine("[global::System.ComponentModel.EditorBrowsable(" +
"global::System.ComponentModel.EditorBrowsableState.Never)]");
string hides = "";
if (shape.HidesBaseConverter)
{
hides = "new ";
}
code.OpenNewBlock("public " + hides + "sealed class " + SaveableClass.ConverterName +
" : global::Vellum.SaveConverter<" + type + ">");
code.OpenNewBlock("public override global::Vellum.SaveNode Write(" + type + " value, " +
"global::Vellum.SaveSession session)");
code.AddLine("global::Vellum.SaveNode returnMe = global::Vellum.SaveNode.NewTable();");
if (WritesFieldsInAMethod(shape))
{
code.AddLine("value." + WriteFieldsName + "(returnMe, session);");
}
else
{
foreach (MemberShape member in shape.Members.Items)
{
code.AddLine("returnMe.Entries[" + Quoted(member.Key) + "] = session.ToNode(" +
member.On("value") + ");");
}
}
code.AddLine("return returnMe;");
code.CloseBlock();
code.NewLine();
code.OpenNewBlock("public override " + type + " Read(global::Vellum.SaveNode node, " +
"global::Vellum.SaveSession session)");
code.AddLine("return new " + type + "(new global::Vellum.SaveReader(node, session));");
code.CloseBlock();
code.NewLine();
if (shape.IsClass)
{
WriteFill(code, shape);
code.NewLine();
}
WriteTryListChildren(code, shape);
code.CloseBlock();
}
public static string Write(SaveableShape shape)
{
var code = new CodeWriter();
code.AddLine("// ");
code.AddLine("// Vellum.Generator's loading constructors and SaveConverter for a [Saveable] type.");
bool hasNamespace = shape.NamespaceName.Length > 0;
if (hasNamespace)
{
code.OpenNewBlock("namespace " + shape.NamespaceName);
}
foreach (string declaration in shape.Declarations.Items)
{
code.OpenNewBlock(declaration);
}
if (shape.IsClass)
{
WriteFillWriters(code, shape);
}
WriteLoaders(code, shape);
if (WritesFieldsInAMethod(shape))
{
WriteFieldsMethod(code, shape);
WriteListFieldsMethod(code, shape);
if (shape.IsClass)
{
WriteFillFieldsMethod(code, shape);
}
}
if (!shape.IsAbstract)
{
WriteConverter(code, shape);
}
for (int i = 0; i < shape.Declarations.Items.Length; ++i)
{
code.CloseBlock();
}
if (hasNamespace)
{
code.CloseBlock();
}
return code.ToString();
}
}
}