355 lines
17 KiB
C#
355 lines
17 KiB
C#
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
|
||
|
// This tool is used to take .csunion files and generate .cs files. The format of
|
||
|
// the .csunion file is as follows:
|
||
|
//
|
||
|
// <header section>
|
||
|
// %%
|
||
|
// <in class section>
|
||
|
// %%
|
||
|
// datatype <name> : OptionalBaseClass {
|
||
|
// <type> <name>(<arguments>), // Comments can be specified with //
|
||
|
// <type> <name>(<arguments>), // If no arguments, parens can be omitted
|
||
|
// ...
|
||
|
// %%
|
||
|
// [Optional section of raw program text to emit directly into the class]
|
||
|
// %%
|
||
|
// }
|
||
|
//
|
||
|
// <more datatypes>
|
||
|
// ...
|
||
|
//
|
||
|
// The header section is simply emitted at the top of the generated file beneath
|
||
|
// the comment block. The datatype expressions each describe a disjoint union with
|
||
|
// a list of alternatives, each of which can specify associated data arguments.
|
||
|
//
|
||
|
// The output .cs file will contain the following:
|
||
|
//
|
||
|
// * A class for each datatype.
|
||
|
// * A simple subclass for each alternative with more than one argument.
|
||
|
// * An enum TagValue with a value for each alternative and a Tag property.
|
||
|
// * Static constructor methods for each alternative (currently disjoint unions
|
||
|
// are immutable, although set methods could be emitted).
|
||
|
// * "GetAs" methods for each alternative that check the tag with Debug.Assert().
|
||
|
// * A commented-out method ExampleUsage() demonstrating how to switch
|
||
|
// on the tag and extract the corresponding value. This should be
|
||
|
// used as a template for code that reads a value of the union type.
|
||
|
//
|
||
|
// See the example file Tree.csunion under the test subdirectory, and the example
|
||
|
// project MkUnionTest.csproj for an example of how to build a .csunion file
|
||
|
// using <CsUnionFile>.
|
||
|
|
||
|
using System;
|
||
|
using System.IO;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace Microsoft.Singularity.Applications
|
||
|
{
|
||
|
public class MkUnion
|
||
|
{
|
||
|
private class UnionDefinition
|
||
|
{
|
||
|
public string Name;
|
||
|
public List<UnionAlternative> Alternatives;
|
||
|
public string BaseClass;
|
||
|
public string RawText;
|
||
|
}
|
||
|
|
||
|
private class UnionAlternative
|
||
|
{
|
||
|
public string Name;
|
||
|
public List<UnionAlternativeMember> Contents;
|
||
|
}
|
||
|
|
||
|
private class UnionAlternativeMember
|
||
|
{
|
||
|
public string Type;
|
||
|
public string Name;
|
||
|
}
|
||
|
|
||
|
private static List<UnionDefinition> Parse(TextReader inputReader)
|
||
|
{
|
||
|
List<UnionDefinition> result = new List<UnionDefinition>();
|
||
|
string allText = inputReader.ReadToEnd();
|
||
|
|
||
|
// Remove comments and newlines
|
||
|
allText = new Regex(@"/\*.*?\*/", RegexOptions.Multiline).Replace(allText, "");
|
||
|
allText = new Regex(@"//.*$", RegexOptions.Multiline).Replace(allText, "");
|
||
|
allText = new Regex(@"[\r\n]+").Replace(allText, "");
|
||
|
|
||
|
string identifier = @"[A-Za-z][A-Za-z0-9]*";
|
||
|
foreach (Match matchDef in new Regex(@"datatype\s+(" + identifier + @")\s*(:\s*([^ ]+)\s*)?{([^}%]*)(%%(.*?)%%)?}").Matches(allText)) {
|
||
|
UnionDefinition definition = new UnionDefinition();
|
||
|
definition.Name = matchDef.Groups[1].Value;
|
||
|
definition.Alternatives = new List<UnionAlternative>();
|
||
|
|
||
|
definition.BaseClass = matchDef.Groups[3].Success ? matchDef.Groups[3].Value : "";
|
||
|
definition.RawText = matchDef.Groups[5].Success ? matchDef.Groups[6].Value : "";
|
||
|
|
||
|
string alternatives = matchDef.Groups[4].Value;
|
||
|
foreach (Match matchAlt in new Regex(@"\b(" + identifier + @")\s*(\((.*?)\))?").Matches(alternatives)) {
|
||
|
UnionAlternative alternative = new UnionAlternative();
|
||
|
alternative.Name = matchAlt.Groups[1].Value;
|
||
|
alternative.Contents = new List<UnionAlternativeMember>();
|
||
|
|
||
|
string[] contents = matchAlt.Groups[3].Value.Split(new char[] { ' ', ',' },
|
||
|
StringSplitOptions.RemoveEmptyEntries);
|
||
|
if ((contents.Length % 2) != 0) {
|
||
|
throw new Exception("Must specify type and name for each alternative data field. Types cannot contain spaces.");
|
||
|
}
|
||
|
for (int i = 0; i < contents.Length; i += 2) {
|
||
|
UnionAlternativeMember member = new UnionAlternativeMember();
|
||
|
member.Type = contents[i];
|
||
|
member.Name = contents[i + 1];
|
||
|
alternative.Contents.Add(member);
|
||
|
}
|
||
|
|
||
|
definition.Alternatives.Add(alternative);
|
||
|
}
|
||
|
|
||
|
result.Add(definition);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private static string Capitalize(string s) {
|
||
|
if (s.Length == 0) {
|
||
|
return s;
|
||
|
} else {
|
||
|
return new string(Char.ToUpper(s[0]), 1) + s.Substring(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static string Uncapitalize(string s) {
|
||
|
if (s.Length == 0) {
|
||
|
return s;
|
||
|
} else {
|
||
|
return new string(Char.ToLower(s[0]), 1) + s.Substring(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OutputUnionClass(TextWriter outputWriter, UnionDefinition definition, int indent)
|
||
|
{
|
||
|
outputWriter.Write(new string(' ', indent*1) + "public class " + definition.Name);
|
||
|
if (definition.BaseClass != "") {
|
||
|
outputWriter.Write(" : " + definition.BaseClass);
|
||
|
}
|
||
|
outputWriter.WriteLine();
|
||
|
outputWriter.WriteLine(new string(' ', indent*1) + "{");
|
||
|
|
||
|
//
|
||
|
// Tag stuff
|
||
|
//
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "public enum TagValue");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "{");
|
||
|
foreach (UnionAlternative alternative in definition.Alternatives) {
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + alternative.Name + ",");
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}\r\n");
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "private TagValue tag;\r\n");
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "public TagValue Tag");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "{");
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "get {");
|
||
|
outputWriter.WriteLine(new string(' ', indent*4) + "return tag;");
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "}");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}");
|
||
|
outputWriter.WriteLine("");
|
||
|
|
||
|
//
|
||
|
// Generate subclasses for alternatives with contents
|
||
|
//
|
||
|
foreach (UnionAlternative alternative in definition.Alternatives)
|
||
|
{
|
||
|
if (alternative.Contents.Count > 1) {
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "public class " + alternative.Name);
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "{");
|
||
|
foreach (UnionAlternativeMember member in alternative.Contents)
|
||
|
{
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "public " + member.Type + " " + Capitalize(member.Name) + ";");
|
||
|
}
|
||
|
outputWriter.WriteLine("");
|
||
|
outputWriter.Write(new string(' ', indent*3) + "public " + alternative.Name + "(");
|
||
|
outputWriter.Write(String.Join(", ",
|
||
|
alternative.Contents.ConvertAll<string>(delegate(UnionAlternativeMember member) {
|
||
|
return member.Type + " " + member.Name;
|
||
|
}).ToArray()));
|
||
|
outputWriter.WriteLine(") {");
|
||
|
foreach (UnionAlternativeMember member in alternative.Contents)
|
||
|
{
|
||
|
outputWriter.WriteLine(new string(' ', indent*4) + "this." + Capitalize(member.Name) + " = " + member.Name + ";");
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "}");
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}\r\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Value field and private constructor
|
||
|
//
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "private object value;\r\n");
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "private " + definition.Name + "(TagValue tag, object value) {");
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "this.tag = tag;");
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "this.value = value;");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}\r\n");
|
||
|
|
||
|
//
|
||
|
// Generate static constructors
|
||
|
//
|
||
|
foreach (UnionAlternative alternative in definition.Alternatives) {
|
||
|
outputWriter.Write(new string(' ', indent*2) + "public static " + definition.Name + " Create" + Capitalize(alternative.Name) + "(");
|
||
|
outputWriter.Write(String.Join(", ",
|
||
|
alternative.Contents.ConvertAll<string>(delegate(UnionAlternativeMember member) {
|
||
|
return member.Type + " " + member.Name;
|
||
|
}).ToArray()));
|
||
|
outputWriter.WriteLine(")");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "{");
|
||
|
outputWriter.Write(new string(' ', indent*3) + "return new " + definition.Name + "(TagValue." + alternative.Name + ", ");
|
||
|
if (alternative.Contents.Count == 0) {
|
||
|
outputWriter.Write("null");
|
||
|
}
|
||
|
else if (alternative.Contents.Count == 1) {
|
||
|
outputWriter.Write(alternative.Contents[0].Name);
|
||
|
}
|
||
|
else {
|
||
|
outputWriter.Write("new " + alternative.Name + "(");
|
||
|
outputWriter.Write(String.Join(", ",
|
||
|
alternative.Contents.ConvertAll<string>(delegate(UnionAlternativeMember member)
|
||
|
{
|
||
|
return member.Name;
|
||
|
}).ToArray()));
|
||
|
outputWriter.Write(")");
|
||
|
}
|
||
|
outputWriter.WriteLine(");");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}\r\n");
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Read-only accessors
|
||
|
//
|
||
|
|
||
|
foreach (UnionAlternative alternative in definition.Alternatives) {
|
||
|
if (alternative.Contents.Count > 0) {
|
||
|
string returnType;
|
||
|
if (alternative.Contents.Count == 1) {
|
||
|
returnType = alternative.Contents[0].Type;
|
||
|
}
|
||
|
else {
|
||
|
returnType = alternative.Name;
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent * 2) + "public " + returnType + " GetAs" + Capitalize(alternative.Name) + "()");
|
||
|
outputWriter.WriteLine(new string(' ', indent * 2) + "{");
|
||
|
outputWriter.WriteLine(new string(' ', indent * 3) + "Debug.Assert(Tag == TagValue." + alternative.Name + ", \"Type error: tried to access disjoint union as '" + alternative.Name + "' but tag does not match\");");
|
||
|
outputWriter.WriteLine(new string(' ', indent * 3) + "return (" + returnType + ")value;");
|
||
|
outputWriter.WriteLine(new string(' ', indent * 2) + "}\r\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Example usage template
|
||
|
//
|
||
|
|
||
|
outputWriter.WriteLine("#if false");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "// Example usage template for this disjoint union type");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "public static void ExampleUsage(" + definition.Name + " " + Uncapitalize(definition.Name) + ")");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "{");
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "switch (" + Uncapitalize(definition.Name) + ".Tag) {");
|
||
|
foreach (UnionAlternative alternative in definition.Alternatives) {
|
||
|
outputWriter.WriteLine(new string(' ', indent*4) + "case " + definition.Name + ".TagValue." + alternative.Name + ":");
|
||
|
if (alternative.Contents.Count > 0) {
|
||
|
string returnType;
|
||
|
if (alternative.Contents.Count == 1) {
|
||
|
returnType = alternative.Contents[0].Type;
|
||
|
}
|
||
|
else {
|
||
|
returnType = definition.Name + "." + alternative.Name;
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent*5) + returnType + " " +
|
||
|
Uncapitalize(alternative.Name) + " = " + Uncapitalize(definition.Name) + ".GetAs" +
|
||
|
Capitalize(alternative.Name) + "();");
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent*5) + "break;");
|
||
|
}
|
||
|
outputWriter.WriteLine(new string(' ', indent*4) + "default:");
|
||
|
outputWriter.WriteLine(new string(' ', indent*5) + "Debug.Assert(false, \"Unhandled alternative in switch over '" + definition.Name + "'\");");
|
||
|
outputWriter.WriteLine(new string(' ', indent*5) + "break;");
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*3) + "}");
|
||
|
outputWriter.WriteLine(new string(' ', indent*2) + "}");
|
||
|
outputWriter.WriteLine("#endif");
|
||
|
|
||
|
outputWriter.WriteLine(definition.RawText);
|
||
|
|
||
|
outputWriter.WriteLine(new string(' ', indent*1) + "}\r\n");
|
||
|
}
|
||
|
|
||
|
private static void Usage()
|
||
|
{
|
||
|
Console.WriteLine("Usage: mkunion <input filename> <namespace> <output filename>");
|
||
|
}
|
||
|
|
||
|
public static void Main(string[] args)
|
||
|
{
|
||
|
if (args.Length < 3) {
|
||
|
Usage();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
string inputFilename = args[0];
|
||
|
string outputNamespace = args[1];
|
||
|
string outputFilename = args[2];
|
||
|
|
||
|
try {
|
||
|
using (TextReader inputReader = File.OpenText(inputFilename)) {
|
||
|
using (TextWriter outputWriter = new StreamWriter(outputFilename)) {
|
||
|
outputWriter.WriteLine(
|
||
|
"///////////////////////////////////////////////////////////////////////////////\r\n" +
|
||
|
"//\r\n" +
|
||
|
"// Copyright (c) Microsoft Corporation. All rights reserved.\r\n" +
|
||
|
"//\r\n" +
|
||
|
"// Microsoft Research Singularity\r\n" +
|
||
|
"//\r\n" +
|
||
|
"// This file is automatically generated by mkunion. Do not check it in or edit it.\r\n" +
|
||
|
"//\r\n\r\n");
|
||
|
|
||
|
outputWriter.WriteLine("using System.Diagnostics;\r\n\r\n");
|
||
|
|
||
|
while (true) {
|
||
|
string line = inputReader.ReadLine();
|
||
|
if (line == null || line == "%%") {
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
outputWriter.WriteLine(line);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
outputWriter.WriteLine("namespace " + outputNamespace + "\r\n{\r\n");
|
||
|
|
||
|
List<UnionDefinition> definitions = Parse(inputReader);
|
||
|
foreach (UnionDefinition definition in definitions) {
|
||
|
OutputUnionClass(outputWriter, definition, /*indent*/4);
|
||
|
}
|
||
|
|
||
|
outputWriter.WriteLine("}\r\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e) {
|
||
|
Console.WriteLine("mkunion failed: " + e.Message + "\r\n" + e.StackTrace);
|
||
|
Environment.ExitCode = 1;
|
||
|
}
|
||
|
Environment.ExitCode = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|