やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#コードツリーを実装してみる

 思ったより簡単にできた。

成果物

前回まで

コード

  • Program.cs
    • CodeGenerator.cs
    • CodeTree.cs
      • CodeNode.cs
        • BlockCodeNode.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#は書いていて楽しい。

 次はリファクタリングしよう。ブロック文作成のところが特に汚い。もう少し応用が効くようにしたい。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux