やってみる

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

C#コードツリー実装2(ブロック文生成のリファクタリング)

 名前なども少し整えた。

成果物

前回まで

コード

  • 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 CodeBlock("namespace MyNamespace")
                    .Add(new CodeBlock("class MyClass")
                        .Add(new CodeBlock("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;
        private BlockCodeGenerator blockGen;
        public CodeGenerator()
        {
            this.builder = new StringBuilder();
            this.blockGen = new BlockCodeGenerator(this.builder, Indent);
        }
        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);
            blockGen.Blocking(node, indent);
            if (0 < node.Children.Count)
            {
                indent++;
                foreach(CodeNode n in node.Children) { _Generate(n, indent); }
                indent--;
            }
            blockGen.Blocked(node, indent);
        }

        class BlockCodeGenerator
        {
            public string Start { get; private set; }
            public string End { get; private set; }
            public string Indent { get; private set; }
            private StringBuilder builder;
            public BlockCodeGenerator(in StringBuilder builder, in string Indent, string start="{", string end="}")
                => (this.builder, this.Indent, Start, End) = (builder, Indent, start, end);
            public void Blocking(CodeNode node, int indent) => Block(node, Start, indent);
            public void Blocked(CodeNode node, int indent) => Block(node, End, indent);
            private void Block(CodeNode node, string keyword, int indent)
            {
                if (node is CodeBlock n) {
                    switch (n.Style) {
                        case CodeBlock.StyleType.None: return;
                        case CodeBlock.StyleType.Minimum: builder.Append(keyword); return;
                        case CodeBlock.StyleType.Space: builder.Append($" {keyword}"); return;
                        case CodeBlock.StyleType.NewLine: builder.AppendLine().Insert(builder.Length, Indent, indent).Append(keyword); return;
                        default: return;
                    }
                }
            }
        }
    }
}

 ブロック文の作成を内部クラスBlockCodeGeneratorに一任した。まだ汚い。builderIndentはDRYに共有したい。

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

CodeBlock.cs

using System;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace CodeGen
{
    class CodeBlock: CodeNode
    {
        public enum StyleType { None, Minimum, Space, NewLine };
        public StyleType Style { get; private set; }
        public CodeBlock(string value, StyleType style=StyleType.NewLine) : base(value) => Style = style;
     }
}

 BlockCodeNodeCodeBlockに改名した。CodeDomのCodeNamespaceなどをみると、クラス名の先頭がCodeで統一されているので踏襲してみた。

実行結果

using System;

namespace MyNamespace
{
    class MyClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello world");
        }
    }
}

所感

 名前空間とかクラスとかの生成に特化したクラスも作りたい。

対象環境

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