C#コードツリーを実装してみる
思ったより簡単にできた。
成果物
前回まで
コード
- Program.cs
- CodeGenerator.cs
- CodeTree.cs
- CodeNode.cs
- BlockCodeNode.cs
- CodeNode.cs
Program.cs
using System; namespace CodeGen { class Program { static void Main(string[] args) { var gen = new CodeGenerator(); var tree = new CodeTree() .Add(new CodeNode("using System;")) .Add(new CodeNode("")) .Add(new BlockCodeNode("namespace MyNamespace") .Add(new BlockCodeNode("class MyClass") .Add(new BlockCodeNode("static void Main(string[] args)") .Add(new CodeNode(@"Console.WriteLine(""Hello world"");"))))); Console.WriteLine(gen.Generate(tree)); } } }
CodeGenerator.cs
using System; using System.Text; namespace CodeGen { class CodeGenerator { public string Indent { get; set; } = " "; private StringBuilder builder = new StringBuilder(); public string Generate(CodeTree tree) { builder.Clear(); foreach(CodeNode node in tree.Children) { _Generate(node, 0); } return builder.ToString().TrimStart('\n'); // 先頭の改行を1つ削除 } private void _Generate(CodeNode node, int indent) { builder.Append("\n"); builder.Insert(builder.Length, Indent, indent).Append(node.Value); // ブロック開始 BlockStart(node, indent); if (0 < node.Children.Count) { indent++; foreach(CodeNode n in node.Children) { _Generate(n, indent); } indent--; } // ブロック終了 BlockEnd(node, indent); } // 改行コードの前でインデントする。これを全改行コードに対して行う private void BlockStart(CodeNode node, int indent) => Block(node, "{", indent); private void BlockEnd(CodeNode node, int indent) => Block(node, "}", indent); private void Block(CodeNode node, string keyword, int indent) { if (node is BlockCodeNode n) { switch (n.Style) { case BlockCodeNode.StyleType.None: return; case BlockCodeNode.StyleType.Minimum: builder.Append(keyword); return; case BlockCodeNode.StyleType.Space: builder.Append($" {keyword}"); return; case BlockCodeNode.StyleType.NewLine: builder.AppendLine().Insert(builder.Length, Indent, indent).Append(keyword); return; default: return; } } } } }
CodeTree.cs
using System; using System.Collections.Generic; using System.Collections.ObjectModel; // ReadOnlyCollection namespace CodeGen { class CodeTree { protected IList<CodeNode> children; public IReadOnlyList<CodeNode> Children { get; } public CodeTree() { children = new List<CodeNode>(); Children = new ReadOnlyCollection<CodeNode>(children); } public CodeTree Add(CodeNode node) { children.Add(node); return this; } } }
CodeNode.cs
using System; using System.Collections.Generic; using System.Collections.ObjectModel; // ReadOnlyCollection namespace CodeGen { class CodeNode : CodeTree { public string Value { get; } public CodeNode(string value) : base() => Value = value; public new CodeNode Add(CodeNode node) { children.Add(node); return this; } } }
BlockCodeNode.cs
using System; using System.IO; using Microsoft.CSharp; using System.CodeDom.Compiler; namespace CodeGen { class BlockCodeNode : CodeNode { public enum StyleType { None, Minimum, Space, NewLine }; public StyleType Style { get; private set; } public BlockCodeNode(string value, StyleType style=StyleType.NewLine) : base(value) => Style = style; public string BlockStart { get => GetBlock("{"); } public string BlockEnd { get => GetBlock("}"); } private string GetBlock(string keyword) => Style switch { StyleType.None => "", StyleType.Minimum => keyword, StyleType.Space => $" {keyword}", StyleType.NewLine => $"\n{keyword}", // NewLine => $"{Environment.NewLine}{keyword}", _ => "", }; } }
実行結果
using System; namespace MyNamespace { class MyClass { static void Main(string[] args) { Console.WriteLine("Hello world"); } } }
所感
思ったより簡単に書けた。やはりC#は書いていて楽しい。
次はリファクタリングしよう。ブロック文作成のところが特に汚い。もう少し応用が効くようにしたい。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100 ※
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux