自動作成するためにツリー構造を再現する。
成果物
前回
前回は、以下のような間抜けコードを書くハメになった。
// コード生成 public void CreateCode(in IndentedTextWriter tw) { tw.WriteLine("using System;"); tw.WriteLine(); tw.WriteLine("namespace MyNamespace"); tw.WriteLine("{"); tw.Indent++; tw.WriteLine("public sealed class MyClass"); tw.WriteLine("{"); tw.Indent++; tw.WriteLine("static void Main(string[] args)"); tw.WriteLine("{"); tw.Indent++; tw.WriteLine(@"Console.WriteLine(""Hello world"");"); tw.Indent--; tw.WriteLine("}"); tw.Indent--; tw.WriteLine("}"); tw.Indent--; tw.WriteLine("}"); }
問題
問題はDRYに書けていない。特にIndent
やブロック文字は定形処理にできるはず。
解決案
ツリー構造をモデル化できればDRYに書けるはず。たとえば以下。
new CodeTree() .Add(new CodeNode() .Add(new CodeNode() .Add(new CodeNode())) .Add(new CodeNode())))
ノードに挿入したいテキストを与える。
new CodeTree() .Add(new CodeNode("using System;")) .Add(new CodeNode("namespace MyNamespace") .Add(new CodeNode("class MyClass") .Add(new CodeNode("static void Main(string[] args)") .Add(new CodeNode(@"Console.WriteLine(""Hello world"");"))))
namespace
, class
などを独自の型にすると、一層わかりやすい。ミスも減る。
new CodeTree() .Add(new UsingCode("System")) .Add(new NamespaceCode("MyNamespace") .Add(new ClassCode("MyClass") .Add(new EntryPointCode(hasArgs=True) .Add(new CodeNode(@"Console.WriteLine(""Hello world"");"))))
考察
ブロック文
ブロックの{
,}
における改行の指定。主に以下の2タイプがある。
- 改行+インデント+
{
- スペース1個+
{
コードで書くと以下。
class A
{
}
class A {
}
これを以下のように設定できるようにしたい。
new CodeTree() { BlockStyle=BlockStyle.NewLine } .Add(new UsingCode("System")) .Add(new NamespaceCode("MyNamespace") .Add(new ClassCode("MyClass") .Add(new EntryPointCode(hasArgs=True) .Add(new CodeNode(@"Console.WriteLine(""Hello world"");"))))
ただ、全体で統一してしまうと思い通りに書けない場合が出てしまう。ブロックスタイルは併用できるようにすべき。
new CodeTree() { BlockStyle=BlockStyle.NewLine } .Add(new UsingCode("System")) .Add(new NamespaceCode("MyNamespace") .Add(new ClassCode("MyClass") .Add(new EntryPointCode(hasArgs=True) .Add(new IfCodeNode(ExpressionCode(@"true"), BlockStyle=BlockStyle.Space) .Add(new CodeNode(@"Console.WriteLine(""Hello if"");")) .Add(new ElseCodeNode(BlockStyle=BlockStyle.Space) .Add(new CodeNode(@"Console.WriteLine(""Hello else"");"))
上記は改行するときとスペースのときの2タイプのブロック文を書く意図。期待値は以下。
using System; namespace MyNamespace { class MyClass { static void Main(string[] args) { if (true) { Console.WriteLine("Hello if"); } else { Console.WriteLine("Hello else"); } } } }
namespace
やclass
などの{
,}
は改行されている。だが、if
やelse
の{
,}
は改行されずスペースされている。
他にも以下のような6タイプがある。{
の前後スペース削除も含めるとさらに増える。今回は上記2タイプ以外の細かいものは除外する。
if (true) { Console.WriteLine("Hello if"); } else { Console.WriteLine("Hello else"); }
if (true) { Console.WriteLine("Hello if"); } else { Console.WriteLine("Hello else"); }
if (true) { Console.WriteLine("Hello if"); } else { Console.WriteLine("Hello else"); }
if (true) Console.WriteLine("Hello if");
if (true) Console.WriteLine("Hello if");
(true) ? "if" : "else";
そもそもif
文は特殊。ブロック文を連続して書くから。3項演算子はその最たるもの。もはやブロックの改行がどうとかいうレベルでない。分岐はポリモーフィズムやswitch式で書きたい。できるだけif文は書きたくない。
他
他に共通化できそうなところは以下。
- block系: IBlock
{
,}
: Block(
,)
: ParameterBlock[
,]
: IndexBlock<
,>
: GenericBlock
;
- 二項演算子
=
:
さらに末尾に;
がいる場合といらない場合の2種類がある。
- ブロック
- ステートメント(文)
もっと細かくみてみると以下のような種類がある。参考
- 文(ステートメント)
- 式
x + 1
new T()
- lambda式
- switch式
- ...
- 宣言
- 代入
- ブロック
- 式
文の中に式が含まれている。文の中には
- ブロック文
- block
- namespace
- class, interface
- struct
- enum
- method(非仮想メソッド)
- if,else,else-if
- switch
- for, foreach
- while, do-while
- checked, unchecked
- lock, unlock
- using
- try-catch-finally
いずれ、どこまでやるか考えねばならない。
セミコロン
ところで、}
の後ろにセミコロン;
が必要なときと不要なときがある。;
はブロック文のときは不要なのかと思っていたが違う。lambda式なら() => {};
だし、switch式のときはa switch => {};
。でもif(){}
のときは不要。また、インタフェース仮想メソッド宣言のときにも必要。void M();
。このあたりのルールがよくわからない。
仮想メソッドと抽象メソッド
クラスvirtual
メソッド、抽象クラスabstract
メソッド、インタフェースメソッド(暗黙public virtual
)の違い。
型 | メソッド | 規定実装の提供 | 提供先 | override |
多重継承 |
---|---|---|---|---|---|
class | virtual |
する | - | 要 | ☓ |
abstract class | abstract |
しない | 継承先のみ | 不 | ☓ |
interface | 暗黙public virtual |
しない | すべて | 不 | ○ |
abstract class
は1つしか継承できない。interface
は多重継承できる。
インタフェースを考えてみる
- CodeTree
- CodeNode
- IBlock
- Block
- ParameterBlock
- IndexBlock
- GenericBlock
ブロックはよく考えると別物だった。でも先頭と末尾に付与する点は共通。間に複数のいくつかをもつことも。ただしブロックの場合はツリーノードなのに対して()
などは単一要素。いや、switch
式はツリーノードか?
- Block: 複数行:
{ 文; 文; 文; ... }
- FlowBlock: セミコロン区切り:
(1,2,3,...)
,<T1,T2,...>
,(値, 値, 値,...);
,{ 式, 式, 式, ... };
- KeyValueFlow:
{ Key = Value, Key = Value, ...}
- LambdaFlow:
a switch => { 0=>;, _=>; }
- FlowBlock: セミコロン区切り:
複雑そう。まずは簡単にしたい。
というわけで、とりあえず以下4つがあればいいか。
- CodeGenerator
- CodeTree
- CodeNode
- Block
所感
次回、コードを書いてみる。
対象環境
- 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