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