Sample Site

GAWK ソースをスペース整形する

これは所謂ビューティファイアの一種です。簡易のレキシカルアナライザを用いて、ソースの自動整形を試みました。ソースの各所にスペースを入れて、可読性を高めるものです。筆者はものぐさな為、ソースにまったくスペースを入れませんので、とても助かります。このソース自体も自動整形されたものです。

Before if(ch~/[ \t]/&&dlm~/_fst/){ After if (ch ~ /[ \t]/ && dlm ~ /_fst/) {

GAWK for win32 4.1.4、4.2.1、5.0.1 にて動作確認しました。

GAWK 3.1.5(日本語版)では動作しません。

 以下にサクラエディタ(マクロ)の使用例を掲載します。


マクロ実装の結果

既存awkスクリプトファイルの編集中 padformat1.jpg
右クリック、コンテキストメニューから「padformat」を選択 MODE 0 動作 padformat2.jpg
スクリプト全文がスペース(空白)にて整形され、可読性が多少高まる MODE 1 動作 padformat3.jpg
配列のブラケット内は整形しない
(参考) GAWK 内臓 pretty print (GAWK 5.0.1 for win32) ファイル内容確認 padformat4.jpg
以下のコマンドで pretty print 出力 padformat5.jpg
コーディングスタイルが変更され、不要なセミコロンは捨てられる パラメータ内のローカル変数境界(連続スペース)が失われる 関数の上部に改行が加えられる -o の後の - は stdout に出力するという意味で ファイルに打ち出す場合は -ofoo.awk と書く(空白NG) コマンドプロンプト上では more でつながない場合、日本語が文字化けする

padformat.vbs  サクラエディタ用マクロ

1 : 'サクラマクロ padformat.vbs 2 : 3 : Option Explicit 4 : Dim scmd, spath, sexte, nline, serr 5 : 6 : scmd = "gawk414 -f Y:\padformat\awk_padformat.awk " 7 : With Editor 8 : spath = .GetFilename 9 : sexte = Mid(spath, InStrRev(spath, ".") + 1) 10 : If LCase(sexte) = "awk" Then 11 : .FileSave 12 : scmd = scmd & spath 13 : nline = .ExpandParameter("$y") 'current cursor line num 14 : .SetDrawSwitch 0 15 : .SelectAll 'to overwrite them all 16 : .ExecCommand scmd, 3 'redirect append 17 : 18 : .CancelMode 0 19 : .GoFileEnd 20 : .Up 21 : .SelectLine 22 : serr = .GetSelectedString(0) 23 : 24 : If serr = ("#OK" & vbCrLf) Then 'if padformat returns "#OK" 25 : .Delete 26 : .FileSave 27 : .Jump nline, 1 28 : .CurLineCenter 29 : Else 30 : .Undo 'error 31 : Call MsgBox(serr,vbOKOnly,"padformat") 32 : End If 33 : 34 : .SetDrawSwitch 1 35 : .ReDraw 0 36 : End If 37 : End With

6行目 scmd に awk_padformat.awk のパスを書いて、サクラエディタの macros ディレクトリにコピーしてください。

ファイル内の文字列を全選択して、上書きするだけですので、他のエディタでもマクロを搭載しているものであれば、同じように扱えるのではないかと思います。


awk_padformat.awk

BEGIN
1 : # awk_padformat.awk AWKソースを空白整形する 2 : 3 : #. BEGIN:各辞書の初期化 4 : BEGIN { 5 : _lex_init(); 6 : _pad_init(); 7 : MODE = 0; #MODE0 配列[]の中も整形する MODE1 整形しない 8 : }
ACTION_01
9 : #. ACTION_01:字句解析し、空白整形する 10 : { 11 : lex_awk_s($0, la); 12 : print padformat(la, MODE); 13 : }
END
14 : 15 : #. END:最終行に書き込み(マクロで削除) 16 : END { 17 : if (_erct) for (i in _er) print _er[i]; 18 : else print "#OK"; 19 : }
padformat()
20 : #. padformat():適切な位置に空白を挿入する(行単位) 21 : # 戻値: 整形後の1行 22 : # lexa:in: lex_awk_simple()で作られた配列 23 : # mode:in: mode1 配列[]の中を整形しない mode0 する 24 : # 余分な空白を除去しない(付加するのみ) 25 : function padformat(lexa, mode, c, i, tok,\ 26 : ptok, ntok, nhead, tstr, brct) { 27 : c = length(lexa); 28 : 29 : while (i <= c) { 30 : tok = lexa[++i]; 31 : #注目tokの前後を取得 ※要素数が増加するので注意 32 : ntok = lexa[i + 1]; #注 i=cの時lexa[c+1]=""を作成 33 : ptok = lexa[i - 1]; #注 i=1の時lexa[0]=""を作成 34 : nhead = substr(ntok, 1, 1); 35 : 36 : if (mode && tok ~ /^\[$/) { 37 : tstr = tstr tok; 38 : brct++; 39 : } 40 : else if (mode && tok ~ /^\]$/) { 41 : tstr = tstr tok; 42 : brct--; 43 : } 44 : else if (brct) { #mode1 []の中を整形しない 45 : tstr = tstr tok; 46 : } 47 : else if (tok in _cond) { #if/for/while/[,]の挙動 48 : if (ntok in _cond_s) tstr = tstr tok; 49 : else tstr = tstr tok _sp; 50 : } 51 : else if (tok in _oper) { #演算子の挙動 52 : if (ptok in _oper_s) tstr = tstr tok; #前方 53 : else { 54 : #スペースがダブる可能性 作成中のtstrの末尾を確認 55 : if (substr(tstr, length(tstr)) !~ / /) 56 : tstr = tstr _sp tok; 57 : else tstr = tstr tok; 58 : } 59 : if (!(ntok in _sp_tab)) { #後方 60 : if (tok !~ /^-$/) tstr = tstr _sp; 61 : #単項演算子マイナス 62 : else if (!isunary(lexa, i)) tstr = tstr _sp; 63 : } 64 : } 65 : else if (tok ~ /^)$/) { #[)]の挙動 66 : if (nhead in _a_z) tstr = tstr tok _sp; 67 : else tstr = tstr tok; 68 : } 69 : else if (tok in _semi_s) { #[;}]の挙動 70 : if (i != c && !(ntok in _comt_s)) { 71 : tstr = tstr tok _sp; 72 : } else tstr = tstr tok 73 : } 74 : else if (tok ~ /^:$/) { #[:]の挙動 75 : if (incase(lexa, i)) tstr = tstr tok; #case文か 76 : else { #3項演算子 77 : if (ptok in _sp_tab) tstr = tstr tok; #前方 78 : else tstr = tstr _sp tok; 79 : if (!(ntok in _sp_tab)) tstr = tstr _sp; #後方 80 : } 81 : } 82 : else if (tok ~ /^{$/) { #[{]の挙動 83 : if (i != 1 && !(ptok in _sp_tab)) { #前方 84 : #スペースがダブる可能性 tstrを確認 85 : if (substr(tstr, length(tstr)) !~ / /) 86 : tstr = tstr _sp tok; 87 : else tstr = tstr tok; 88 : } else tstr = tstr tok; 89 : if (i != c && !(ntok in _sp_tab)) tstr = tstr _sp; #後方 90 : } 91 : else if (tok in _ret_ex) { #return exitの挙動 92 : if (i != c && !(ntok in _retx_s)) tstr = tstr tok _sp; 93 : else tstr = tstr tok; 94 : } 95 : else { 96 : tstr = tstr tok; 97 : } 98 : } 99 : return tstr; 100 : }
_pad_init()
101 : #. _pad_init():辞書と大域変数 102 : # 戻値: 103 : function _pad_init( oper, cond, unary, ct, ar, i) { 104 : # ! ++ -- : :: を除外した全ての演算子 105 : oper = "+,-,*,/,^,%,=,~,<,>,?,&,|,+=,-=,*=,/=,%=,\ 106 : ^=,!~,==,!=,<=,>=,&=,&&,||,>>,<<"; 107 : unary = "(.,.[.{.:.return.exit" ; 108 : cond = ",.if.for.do.while.switch.case"; 109 : 110 : ct = split(oper, ar, ","); 111 : for (i = 1; i <= ct; i++) { 112 : _oper[ar[i]]; 113 : _unary[ar[i]]; 114 : } 115 : 116 : ct = split(unary, ar, "."); 117 : for (i = 1; i <= ct; i++) _unary[ar[i]]; 118 : 119 : ct = split(cond, ar, "."); 120 : for (i = 1; i <= ct; i++) _cond[ar[i]]; 121 : 122 : _cond_s[" "]; _cond_s["\t"]; _cond_s["\\"]; 123 : _oper_s[" "]; _oper_s["\t"]; _oper_s["("]; 124 : _semi_s[";"]; _semi_s["}"]; 125 : _comt_s["#"]; _comt_s[" "]; _comt_s["\t"]; 126 : _ret_ex["return"]; _ret_ex["exit"]; 127 : _retx_s[" "]; _retx_s["\t"]; _retx_s[";"]; 128 : _case_s["case"]; _case_s["default"]; 129 : _sp_tab[" "]; _sp_tab["\t"]; #lex兼用 130 : 131 : _sp = " "; #整形に使うスペース 132 : }
isunary()
133 : #. isunary():マイナス単項演算子であるか i>0 必須 134 : # 戻値: 1 単項演算子である 0 単項演算子でない 135 : # ar:in: 配列(lex後のtoken配列) 136 : # i:in: 添字(現在カウンタ位置) 137 : function isunary(ar, i) { 138 : #空白でなくなるまで配列の前方に戻る 139 : while (--i) if (!(ar[i] in _sp_tab)) break; 140 : if (!i) return 1; #左辺第一項(?) 141 : else if (ar[i] in _unary) return 1; #演算子と「(,[{:return exit」 142 : else return 0; 143 : }
incase()
144 : #. incase():添字i以前にcase/defaultがあるか i>0 必須 145 : # 戻値: 1 case/defaultが前方にある 0 ない 146 : # ar:in: 配列(lex後のtoken配列) 147 : # i:in: 添字(現在カウンタ位置) 148 : function incase(ar, i) { 149 : while (--i) if (ar[i] in _case_s) break; 150 : if (i) return 1; 151 : return 0; 152 : }
lex_awk_s()
153 : #. lex_awk_s():簡易レキシカルアナライザ 154 : # 戻値: トークン数(resの要素数) 155 : # str:in: 1行のAWKソース 156 : # res:out: 配列 157 : function lex_awk_s(str, res, len, i, ib, ch, ret,\ 158 : once, fcon, prev, pos, ct, tmp) { 159 : i = 1; 160 : prev = "first"; 161 : delete res; #配列を初期化 162 : len = length(str); 163 : if (substr(str, len) ~ /\\/) fcon = 1; #行継続確認 164 : 165 : while (i <= len) { 166 : ch = substr(str, i, 1); 167 : ib = i; #切り取り開始位置保存 168 : 169 : if (!once) #正規表現/リテラル 前行よりデリミタ引き継ぎ 170 : if (_cont_ch == "") once = 1; 171 : else { once = 1; ch = _cont_ch; } #デリミタ設定(i=1) 172 : 173 : if (ch in _a_z) ch = "z"; 174 : 175 : switch (ch) { 176 : case /z/: #識別子 177 : while (tmp = substr(str, ++i, 1)) 178 : if (!(tmp in _a_9)) break; 179 : ret = prev = substr(str, ib, i - ib); 180 : break; 181 : case /[0-9]/: #数値 182 : if (ch ~ /0/ && substr(str, i + 1, 1) ~ /[xX]/) { 183 : i++; #x Hex 184 : while (tmp = substr(str, ++i, 1)) 185 : if (!(tmp in _hexd)) break; 186 : } 187 : else { 188 : while (tmp = substr(str, ++i, 1)) 189 : if (!(tmp in _frac)) break; 190 : if (tmp !~ /[eE]/) ; #nop Oct/Dec 191 : else { #指数 192 : if (substr(str, ++i, 1) ~ /[\-+]/) i++; # i +1 or +2 193 : for (; i <= len; i++) # i=i init 194 : if (!(substr(str, i, 1) in _nume)) break; 195 : } 196 : } 197 : ret = prev = substr(str, ib, i - ib); 198 : break; 199 : case /"/: #文字列リテラル 200 : pos = litendpos(str, i); 201 : if (pos) { i = ++pos; _cont_ch = ""; } #両端の””含む 202 : else if (fcon) { i = len; _cont_ch = "\""; } #継続 203 : else if (_cont_ch ~ /"/ && str ~ /^"/) { i = 2; _cont_ch = ""; } 204 : else { err(NR, i); i++; _cont_ch = ""; } #エラー 205 : ret = substr(str, ib, i - ib); 206 : prev = ch; 207 : break; 208 : case /\//: #正規表現か除算演算子(直前のデリミタと先行句で判断) 209 : if (prev in _prev) { #正規表現定数 210 : pos = regendpos(str, i); 211 : if (pos) { i = ++pos; _cont_ch = ""; } 212 : else if (fcon) { i = len; _cont_ch = "/"; } 213 : else if (_cont_ch ~ /\// && str ~ /^\//) { 214 : i = 2; 215 : _cont_ch = ""; 216 : } 217 : else { err(NR, i); i++; _cont_ch = ""; } 218 : ret = substr(str, ib, i - ib); 219 : prev = ch; 220 : } 221 : else { #除算演算子 222 : tmp = substr(str, ++i, 1); 223 : ret = prev = ch; 224 : if (tmp ~ /=/) { i++; ret = prev = "/="; } 225 : } 226 : break; 227 : case /#/: #コメント 228 : i = len + 1; 229 : ret = substr(str, ib); 230 : break; 231 : default: #演算子 区切り文字等 232 : if (ch in _sp_tab) { #空白 233 : i++; 234 : ret = ch; 235 : } 236 : else { #演算子/句読記号 237 : ret = prev = ch; 238 : tmp = substr(str, i, 2); 239 : i++; #とりあえず1つ進める 240 : if (tmp in _op2) { #2文字演算子? 241 : i++; #もう1つ進める 242 : ret = prev = tmp; #デリミタ書き換え 243 : } 244 : } 245 : break; 246 : } 247 : res[++ct] = ret; #トークン格納 248 : } 249 : return ct - 1; 250 : }
_lex_init()
251 : #. _lex_init():辞書とglb変数 252 : # 戻値: 253 : function _lex_init( i, ct, ar, prev, op2, a_z, hex_d) { 254 : prev = "(.,.~.!~.==.!=.first.case.@.=.||.&&"; 255 : op2 = "++,--,+=,-=,*=,/=,%=,^=,!~,==,!=,<=,>=,&=,&&,||,<<,>>,::"; 256 : a_z = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,\ 257 : A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,_"; 258 : hex_d = "a,b,c,d,e,f,A,B,C,D,E,F"; 259 : 260 : #直前字句/正規表現判断 261 : ct = split(prev, ar, "."); 262 : for (i = 1; i <= ct; i++) _prev[ar[i]]; 263 : #2文字演算子 264 : ct = split(op2, ar, ","); 265 : for (i = 1; i <= ct; i++) _op2[ar[i]]; 266 : #数値 267 : for (i = 0; i <= 9; i++) { 268 : _nume[i]; 269 : _frac[i]; 270 : _hexd[i]; 271 : _a_9[i]; 272 : } 273 : _frac["."]; 274 : 275 : ct = split(hex_d, ar, ","); 276 : for (i = 1; i <= ct; i++) _hexd[ar[i]]; 277 : # lex 識別子 正規表現 /[a-zA-Z_]/ と /[a-zA-Z0-9_]/ の代替 278 : ct = split(a_z, ar, ","); 279 : for (i = 1; i <= ct; i++) { 280 : _a_z[ar[i]]; 281 : _a_9[ar[i]]; 282 : } 283 : _a_z["$"]; 284 : 285 : delete _er; _erct = 0; #エラー用glb変数 286 : _cont_ch = ""; #リテラルか正規表現の持越し用glb変数 287 : }
litendpos()
288 : #. litendpos():右ダブルクォートの正確な位置を返す(C/AWK) 289 : # 戻値: 右ダブルクォート位置 290 : # str:in: 文字列 291 : # pos:in: 左ダブルクォート位置 292 : function litendpos(str, pos, rpos, escape, ch) { 293 : rpos = escape = 0; 294 : while (ch = substr(str, ++pos, 1)) { #posの次文字から 295 : if (rpos) break; 296 : switch (ch) { 297 : case /\\/: 298 : (escape) ? escape = 0 : escape = 1; #偽 フラグ設定 299 : break; 300 : case /"/: 301 : (escape) ? escape = 0 : rpos = pos; #偽 終端 302 : break; 303 : default: 304 : (escape) ? escape = 0 : 0; #真 フラグ解除 305 : break; 306 : } 307 : } 308 : return rpos; 309 : }
regendpos()
310 : #. regendpos():正規表現末尾の正確な位置を返す(AWK) 311 : # 戻値: 正規表現終端位置(右スラッシュ位置) 312 : # str:in: 文字列 313 : # pos:in: 左スラッシュ位置 314 : function regendpos(str, pos, rpos, escape, bracket, ch) { 315 : rpos = escape = bracket = 0; 316 : while (ch = substr(str, ++pos, 1)) { #posの次文字から 317 : if (rpos) break; 318 : switch (ch) { 319 : case /\\/: 320 : (escape) ? escape = 0 : escape = 1; #偽 Eフラグ設定 321 : break; 322 : case /\[/: 323 : (escape) ? escape = 0 : bracket = 1; #偽 Bフラグ設定 [ 内 ] 324 : break; 325 : case /]/: 326 : (escape) ? escape = 0 : bracket = 0; #偽 Bフラグ解除 [ ]外 327 : break; 328 : case /\//: 329 : (escape || bracket) ? escape = 0 : rpos = pos; #両フラグ 0 330 : break; 331 : default: 332 : (escape) ? escape = 0 : 0; #真 Eフラグ解除 333 : break; 334 : } 335 : } 336 : return rpos; 337 : }
err()
338 : #. err():リテラル/正規表現のエラー 339 : # 戻値: 340 : # line:in: エラーが起きた行 341 : # pos:in: エラーが起きた文字位置(タブは1文字) 342 : function err(line, pos) { 343 : _er[++_erct] = "Error line: " line " position: " pos; 344 : }