From ea6f61f9d311007f1e99f2272075063cbb8a6caa Mon Sep 17 00:00:00 2001
From: Suzanne Soy <env@suzanne.soy>
Date: Tue, 18 Aug 2020 23:42:41 +0000
Subject: [PATCH] Generate record (immutable plain data objects) types in
 addition to variant-like (safe enums with values)

---
 AstGenerator.cs   | 17 ++++++-----
 LexerGenerator.cs | 27 ++++++++--------
 Makefile          |  4 +--
 T4/Generator.cs   | 78 +++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 99 insertions(+), 27 deletions(-)

diff --git a/AstGenerator.cs b/AstGenerator.cs
index 4aef334..ce71f25 100644
--- a/AstGenerator.cs
+++ b/AstGenerator.cs
@@ -1,12 +1,15 @@
-// Run with: sh ./T4/Generators.sh
-
-using System.Collections.Generic;
+using static Generator;
 
 public static class AstGenerator {
   public static void Main() {
-    Generator.Generate("AstGenerated.cs", "namespace Ast {", "}", "Ast.", "Expr", new Dictionary<string, string> {
-      { "Int", "int" },
-      { "String", "string" },
-    });
+    Generate(
+      "AstGenerated.cs",
+      "namespace Ast {",
+      "}",
+      "Ast.",
+      Types(
+        Variant("Expr",
+          Case("Int", "int"),
+          Case("String", "string"))));
   }
 }
\ No newline at end of file
diff --git a/LexerGenerator.cs b/LexerGenerator.cs
index 7fb74cd..ac5c6db 100644
--- a/LexerGenerator.cs
+++ b/LexerGenerator.cs
@@ -1,17 +1,20 @@
-// Run with: sh ./T4/Generators.sh
-
-using System.Collections.Generic;
+using static Generator;
 
 public static class LexerGenerator {
   public static void Main() {
-    Generator.Generate("LexerGenerated.cs", "public static partial class Lexer {", "}", "Lexer.", "S", new Dictionary<string, string> {
-      { "End", null },
-      { "Space", null },
-      { "Int", null },
-      { "Decimal", null },
-      { "String", null },
-      { "StringOpen", null },
-      { "StringClose", null },
-    });
+    Generator.Generate(
+      "LexerGenerated.cs",
+      "public static partial class Lexer {",
+      "}",
+      "Lexer.",
+      Types(
+        Variant("S",
+          Case("End"),
+          Case("Space"),
+          Case("Int"),
+          Case("Decimal"),
+          Case("String"),
+          Case("StringOpen"),
+          Case("StringClose"))));
   }
 }
\ No newline at end of file
diff --git a/Makefile b/Makefile
index ce83160..431e621 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ main.exe: $(sort $(CS) $(GENERATED))
 %Generated.cs: .%Generator.exe
 	mono $<
 
-.%Generator.exe: %Generator.cs
-	mcs -out:$@ T4/Generator.cs $<
+.%Generator.exe: %Generator.cs T4/Generator.cs
+	mcs -out:$@ $^
 
 
diff --git a/T4/Generator.cs b/T4/Generator.cs
index b8389d9..2f57dc8 100644
--- a/T4/Generator.cs
+++ b/T4/Generator.cs
@@ -1,14 +1,15 @@
-// Run with: sh ./T4/Generators.sh
+// Code quality of this file: low.
 
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
+public enum Kind {
+  Record,
+  Variant,
+}
 public static class Generator {
   public static void WriteVariant(this System.IO.StreamWriter o, string header, string footer, string qualifier, string name, Dictionary<string, string> variant) {
-    o.WriteLine("// This file was generated by Generator.cs");
-    o.WriteLine("");
-
     o.WriteLine($"{header}");
     o.WriteLine("");
 
@@ -112,11 +113,76 @@ public static class Generator {
     o.WriteLine($"{footer}");
   }
 
-  public static void Generate(string outputFile, string header, string footer, string qualifier, string name, Dictionary<string, string> variant) {
+  public static void WriteRecord(this System.IO.StreamWriter o, string header, string footer, string qualifier, string name, Dictionary<string, string> record) {
+    o.WriteLine($"{header}");
+    o.WriteLine("");
+    o.WriteLine($"  public class {name} {{");
+    foreach (var @field in record) {
+      var F = @field.Key;
+      var Ty = @field.Value;
+      o.WriteLine($"    public readonly {Ty} {F};");
+    }
+    o.WriteLine($"    public {name}(");
+    o.WriteLine(String.Join(",\n", record.Select(@field =>
+                $"        {@field.Value} {@field.Key}")));
+    o.WriteLine($"      ) {{");
+    foreach (var @field in record) {
+      var F = @field.Key;
+      var Ty = @field.Value;
+      o.WriteLine($"    this.{F} = {F};");
+    }
+    o.WriteLine($"    }}");
+    o.WriteLine($"  }}");
+    o.WriteLine($"{footer}");
+  }
+
+  public static void Generate(string outputFile, string header, string footer, string qualifier, Dictionary<string, Tuple<Kind, Dictionary<string, string>>> types) {
     using (var o = new System.IO.StreamWriter(outputFile)) {
+      o.WriteLine("// This file was generated by Generator.cs");
+      o.WriteLine("");
+
       o.WriteLine("using System;");
       o.WriteLine("");
-      o.WriteVariant(header, footer, qualifier, name, variant);
+      foreach (var type in types) {
+        var name = type.Key;
+        var kind = type.Value.Item1;
+        var components = type.Value.Item2;
+        switch (kind) {
+          case Kind.Record:
+            o.WriteRecord(header, footer, qualifier, name, @components);
+            break;
+          case Kind.Variant:
+            o.WriteVariant(header, footer, qualifier, name, @components);
+            break;
+        }
+      }
     }
   }
+
+  // Below are shorthands for making the last argument to Generate().
+  public static Dictionary<string, Tuple<Kind, Dictionary<string, string>>> Types(params Tuple<string, Tuple<Kind, Dictionary<string, string>>>[] types)
+    => types.ToDictionary(t => t.Item1, t => t.Item2);
+
+  public static Tuple<string, Tuple<Kind, Dictionary<string, string>>> Record(string name, params Tuple<string, string>[] fields)
+    => new Tuple<string, Tuple<Kind, Dictionary<string, string>>>(
+         name,
+         new Tuple<Kind, Dictionary<string, string>>(
+           Kind.Record,
+           fields.ToDictionary(t => t.Item1, t => t.Item2)));
+
+  public static Tuple<string, Tuple<Kind, Dictionary<string, string>>> Variant(string name, params Tuple<string, string>[] cases)
+    => new Tuple<string, Tuple<Kind, Dictionary<string, string>>>(
+         name,
+         new Tuple<Kind, Dictionary<string, string>>(
+           Kind.Variant,
+           cases.ToDictionary(t => t.Item1, t => t.Item2)));
+
+  public static Tuple<string, string> Field(string name, string type)
+    => new Tuple<string, string>(name, type);
+
+  public static Tuple<string, string> Case(string name, string type)
+    => new Tuple<string, string>(name, type);
+
+  public static Tuple<string, string> Case(string name)
+    => new Tuple<string, string>(name, null);
 }
\ No newline at end of file