C,C++のインデントをPerlで

投稿日:2019年04月14日 21時36分35秒

またPerlネタです。正直ツールを作るにはちょっと便利ですね。今回はC,C++をWinMergeするときにちょっと書式が違うと一致してくれないので自動インデントするツールを作ります。
まずファイルを読み込んで行に分解します。

$str = ReadFile( $file);
@list = split(/\n/, $str);

これで1行ずつになりました。プログラムでは1行ずつ読み込んで設定する空白の量を計算します。1行呼んだら次に行の前後のタブや空白、改行等を取り去ります。

#TRIM
while( length $line and ( substr($line,0,1) eq " " or substr($line,0,1) eq "\t" ) ){
$line = substr( $line,1);
}
while( length $line and ( substr($line,-1,1) eq " " or substr($line,0,1) eq "\t" ) ){
$line = substr( $line,0,-1);
}
chomp($line);

さらに文字列と1行リマークは誤作動の元になるので削除します。

my $work = $line;
$work =~ s/"([^\\"]|\\.)*"//g;
$work =~ s/\/\/.*//g;

ここで文字列中の\\”等の文字も抜けるようになってます。最小一致にはなってないかも知れないですね。ダメだった人は教えて下さい。
images

ここまで前処理です。ここからは空白の量の計算をします。現在の行に出すべき空白の量と次の行で増えるべき空白の量の2つの変数で管理します。
/* */は特殊な扱いで処理しません。あとは}と{の数で増減すればOK。さらっと書いただけなので詳しくはソース見てください。
indent.pl

#!c:/perl/bin/perl
#***********************************************************************
#
# Source Indenter
#
#***********************************************************************
use strict;
use warnings;

my $str;
my $numn = $#ARGV;

for( my $cnt = 0 ; $cnt <= $numn ; $cnt++ ){ my $file = $ARGV[$cnt]; my @list=(); my @spac=(); my $spc=0; my $nxt=0; $str = ReadFile( $file); @list = split(/\n/, $str); my $remark = 0; for(my $i=0 ; $i<=$#list ; $i++){ my $line = $list[$i]; #TRIM while( length $line and ( substr($line,0,1) eq " " or substr($line,0,1) eq "\t" ) ){ $line = substr( $line,1); } while( length $line and ( substr($line,-1,1) eq " " or substr($line,-1,1) eq "\t" ) ){ $line = substr( $line,0,-1); } chomp($line); my $work = $line; $work =~ s/"([^\\"]|\\.)*"//g; $work =~ s/\/\/.*//g; if( ! $remark ){ if ( $work=~ /\/\*.*\*\// ){ $remark = 0; }elsif ( $work=~ /\/\*/ ){ $remark = 1; }elsif ( $work=~ /^#define/ ){ }elsif( $work=~ /^}.*{/ ){ $nxt = 4; if( $spc >= 4 ){
$spc -= 4;
    }
        }elsif( $work=~ /.*{.*}/ ){
        }elsif( $work=~ /.*{/ ){
            $nxt = 4;
        }elsif ( $work =~ /}/ ){
                if( $spc >= 4 ){
                    $spc -= 4;
                }
                if( $nxt >= 4 ){
                    $nxt -= 4;
                }
            }
        }else{
            if( $work=~ /\*\// ){
                $remark = 0;
            }
        }
        #print $work,$spc,$nxt.$remark,"\n";
        $list[$i] = $line;
        $spac[$i] = $spc;
        $spc += $nxt;
        $nxt = 0;
    }
    my $sp = " ";
    for(my $i=0 ; $i<=$#list ; $i++)
    {
        $list[$i] = substr( $sp, 0, $spac[$i] ).$list[$i];
        # print $list[$i]."\n";
        }
        $str = join("\n",@list);
        SaveFile( $file,$str);
    } 1;     

    #----------------------------------------------------------------------- 
    # save File 
    #----------------------------------------------------------------------- 
    sub SaveFile{
        my $targetPath1 = shift ;
        my $str = shift ;
        open (FH,">$targetPath1") or return 0;
        print FH $str;
        close FH;
        return 1;
    }
    #-----------------------------------------------------------------------
    # read File
    #-----------------------------------------------------------------------
    sub ReadFile{
        my $targetPath2 = shift ;
        my $str;
        #出力されたファイルを読み込む
        $str = "";
        open (FH,"<$targetPath2") or return 0; while () {
        $str .= $_;
    }
    close FH;
    return $str;
}

めっちゃ簡単ですね。あとこれを呼び出すのにどうしてもドラッグ&ドロップしたかったのですが意外にバッチの書き方しらないですね。任意の数のファイルを変更するには以下のようなバッチを書きます。

rem indenter
echo off
cd /d %0\..
perl indent.pl %*
pause

%*なんて書き方やカレントフォルダの移動の方法とか知りませんでした。ともあれこれで簡単なindentは付けられます。リマーク中のリマークとかブレースのあとの空白量とかこだわりたい方は自由に改変してください。
これでやっとサーバの仕事に戻れそうです。次回からはまたC++だと思います。

[<< Perlでデータを永続化する]

[JavaScriptのObject.keysを実装 >>]