やってみる

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

C#ソースコード雛形作成スクリプトの汎用化2

 多段インデントについて。

成果物

前回まで

 1層までのインデントができた。

多段インデント

 今回は複数の階層を同時に作成したい。

インタフェース考察

メソッドチェーン

 簡単に表現したい。たとえばメソッドチェーン的に。

"namespace MyProj"
.child("class MyClass")
.child("static void Main()")
.child("Console.WriteLine("Hello World");")
namespace MyProj
{
    class MyClass
    {
        static void Main()
        {
            Console.WriteLine("Hello World");
        }
    }
}
  • 最後だけ{, }がない。これをどう簡単に再現するか

 もちろんbash構文においてメソッドチェーン構文など書けない。

パイプ

 bashで表現できるとしたらパイプか。

indent 'namespace MyProj' | indent 'class MyClass' | indent 'static void Main()' | indent 'Console.WriteLine("Hello World");'
  • 次の挿入位置である{}の間をどうやって探すか

 末尾に追記していけばいいだけなら楽でよかったのだが……。ツリー構造への要素追加が難しいのはこのせい。

位置引数

 位置引数をインデント階層と紐付けるようにすれば、以下のように書ける。

indent_pos 'namespace MyProj' 'class MyClass' 'static void Main()' 'Console.WriteLine("Hello World");'

 複数行を書きたいなら以下。

indent_pos 'namespace MyProj' 'class MyClass' 'static void Main()' "$(echo -e "int i;\ni = 5;")"
  • 複数行のところが冗長
  • シェル文脈におけるエスケープがウザい(',",\n
  • namespace, class, static, voidなどのキーワードを省略したい

 インタフェースは位置引数を用いるべきだと判明した。

出力結果

namespace MyProj
    class MyClass
        static void Main()
            int i;
            i = 5;
  • {,}の囲み文字については未実装

実装

indent.sh

# stdin: インデントしたいテキスト  $1: インデント用文字列(任意。省略時はタブ)
indent() { echo "$(cat -)" | sed "s/^/${1:-\t}/g"; }
# $1..9..{10}.. 位置引数の位置がそのままインデント階層である
indent_pos() {
    local result=""
    local indent=0
    # $1: 繰り返したいテキスト(省略時"")  $2: 回数(省略時1)
    repeat() {
        local target=${1:-}; local count=${2:-2}; let count--;
        [ 0 -lt $count ] && printf "$target"'%*s' $count;
    }
    # ソフトインデントを返す。$1: インデント数(省略時0) $2: 1インデントあたりのスペース数(省略時4)  
    soft_indent() { repeat ' ' $((${1:-0} * ${2:-4})); }
    # $1を$2だけインデントする
    indent_part() {
        local indent=${2:-0}
        [ 0 -eq $indent ] && echo "$1" || \
            echo "$1" | indent "$(soft_indent $indent)";
    }
    for code in "$@"; do
        result+="$(indent_part "$code" $indent)""\n"
        let indent++
    done
    echo -e "$result" | head -c -1
}

細かい課題

 細かい課題は内部関数にした。

  • repeat(): ソフトインデントするときのスペース文字列を取得したい
    • 何インデントする?
    • 1インデントあたり何個のスペースにする?
  • soft_indent(): 指定した文字を指定した回数だけ繰り返した文字列を作成したい
    • printf ' %*s' 0
      • 0回のときは空文字列にしたいが、1個スペースが出力されてしまう
        • 0回のときは何もしないよう分岐する
        • 1回以上のときはスペース数-1する
  • indent_part(): 指定テキストを指定した数だけインデントする

 もしrepeat()を別関数化せねばsoft_indent()が以下のように煩雑化してしまう。zshならrepeat()が存在するらしい。これくらい標準化してくれ。

soft_indent() {
    local indent=${1:-0}; local size=${2:-4};
    local count=$((($indent * $size) - 1));
    [ 0 -lt $indent ] && printf ' %*s' $count;
}

まとめ

 端末で以下コマンドを実行する。

git clone https://github.com/ytyaru/Shell.CSharp.Code.Generator.MultiIndent.20191104150156
cd Shell.CSharp.Code.Generator.MultiIndent.20191104150156/src/
. indent.sh
indent_pos 'namespace MyProj' 'class MyClass' 'static void Main()' "$(echo -e "int i;\ni = 5;")"

 出力結果。

namespace MyProj
    class MyClass
        static void Main()
            int i;
            i = 5;

所感

 いい感じに書けた。次は{,}で囲みたい。

  • インデントを表す囲み文字を指定したい
  • インデントするからといって改行するとは限らない
  • C#言語
    • 構文の要素
      • ブロック(block)
      • 式: どこでも書ける。戻り値必須。(expression)
      • 文: ブロック内にのみ書ける(statement)
    • ;
      • ブロック{,}末尾には;が不要
        • int M() {}
      • 式なら末尾に;が必要
        • int M() => {};
        • int M(int a) a switch => { _ => 0 };
    • 参考

対象環境

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