Sample Site

GAWK AWKのセクション/関数を自動選択する
サクラエディタ マクロ

筆者は、エディタ画面上で、マウスドラッグまたは、SHIFTキーを押しながら頻繁に AWK の関数を選択します。長い関数になるとミニマップを使うこともあります。コピーや移動/置換、グローバル変数検査(test_func)にhtml化したりと、30行程度の関数であれば、特に労力を惜しむことはありませんが、100行を超える関数ですと、控えめに言ってもちょっと煩わしく感じます。特に拙作 test_func はよく使いますので、コンテキストメニューで、関数を自動で範囲選択できるようにしてしまおう、と作りました。

GAWK4 以降でないと動作しません。

マクロ実装の結果

select_01.jpg 対象関数内のどこでもよいので、左クリック カーソルが対象関数内(上部コメント可)にあるようにする 右クリック、コンテキストメニューから「Select_Section」を選択 select_02.jpg 関数全体が範囲選択される

作りかけのスクリプトは、注意が必要です。対象関数とは別の、どこか1つのセクションが完結していない(中括弧/カーリーブラケット的に)だけでも、エラーを出します。

select_section.vbs

1 : 'サクラマクロ select_section.vbs 2 : 'AWKファイル限定 カーソル位置のセクション/関数範囲を選択 3 : 'select_section.awk の出力を取得する 4 : '対象ファイルを上書きするので注意 5 : 6 : Option Explicit 7 : Const gawk = "gawk414 -v ARG1=" 8 : Const awkfile = " -f y:\select_section\select_section.awk " 9 : Dim spath, slext, ncurline, objwsh, objexe, scmd 10 : Dim gawk_ret, aselect, fail, erstr 11 : 12 : With Editor 13 : spath = .GetFileName 14 : slext = LCase(Mid(spath, InStrRev(spath,".") + 1)) 15 : End With 16 : 17 : If slext <> "awk" Then 18 : msgbox "AWKファイルではありません",vbOKOnly, "select_section" 19 : Else 20 : With Editor 21 : .FileSave 22 : ncurline = .ExpandParameter("$y") 23 : scmd = gawk & ncurline & awkfile & spath 24 : End With 25 : 26 : Set objwsh = CreateObject("WScript.Shell") 27 : Set objexe = objwsh.exec(scmd) 28 : 29 : If Err.Number = 0 Then 30 : Do While objexe.Status = 0 31 : Editor.Sleep(100) 32 : Loop 33 : gawk_ret = objexe.stdout.readall 34 : erstr = objexe.stderr.readall 35 : fail = objexe.exitcode 36 : Else 37 : msgbox "コマンドが不正です", vbOKOnly, "select_section" 38 : End If 39 : 40 : Set objexe = Nothing 41 : Set objwsh = Nothing 42 : 43 : If fail = 1 Then 44 : msgbox erstr, vbOKOnly, "select_section" 45 : Else 46 : aselect = Split(gawk_ret) 'gawk_ret : "head tail" 47 : With Editor 48 : .CancelMode 0 49 : .SetDrawSwitch 0 50 : .Jump aselect(0), 1 51 : .BeginSelect 0 52 : .Jump aselect(1), 1 53 : .GoLineEnd 54 : .SetDrawSwitch 1 55 : .ReDraw 0 56 : End With 57 : End If 58 : End If

下記 select_section.awk の出力(エラー出力含む)を VBS 上で利用しています。

select_section.awk

BEGIN
1 : #. BEGIN; 2 : BEGIN { 3 : _lex_init(); 4 : curline = ARG1; 5 : }
ACTION_01
6 : #. PatternAction; 7 : { 8 : lex_awk_s($0, arr); 9 : if (retval = infoproc(arr, NR, sections)) { 10 : (retval == 1) ? nr = NR - 1 : nr = retval; 11 : split(_csv, a, ","); 12 : errstr = a[1] ": line " nr; 13 : exit 1; 14 : } 15 : }
END
16 : #. END; 17 : END { 18 : if (retval) { 19 : print "ERROR: " errstr > "/dev/stderr"; 20 : exit 1; 21 : } 22 : else if (_cb) { 23 : split(_csv, a, ","); 24 : errstr = a[1] ": line " NR; 25 : print "ERROR: " errstr > "/dev/stderr"; 26 : exit 1; 27 : } 28 : 29 : sect_div(sections, NR, result); 30 : 31 : for (n in result) 32 : if (curline >= result[n][0] && curline <= result[n][2]) { 33 : print result[n][0], result[n][2]; 34 : done = n; 35 : break; 36 : } 37 : 38 : if (!done){ 39 : print "ERROR: Cursor position out of range." > "/dev/stderr"; 40 : exit 1; 41 : } 42 : }
_lex_init()
43 : #. _lex_init();辞書と大域変数 44 : function _lex_init( ct, ar, i, dlm, op1, op2, a_z) { 45 : dlm = "(.,.~.!~.==.!=._fst.@.="; 46 : op1 = "+,-,*,/,^,%,!,~,=,<,>,?,@" 47 : op2 = "++,--,+=,-=,*=,/=,%=,^=,!~,==,!=,<=,>=,&&,||"; 48 : # lex 識別子 正規表現 /[a-zA-Z_]/ と /[a-zA-Z0-9_]/ の代替 49 : # 高速化のためのハッシュテーブル 50 : 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,\ 51 : 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,_,$"; 52 : ct = split(a_z, ar, ","); 53 : for (i = 1; i <= ct; i++) { 54 : _a_9[ar[i]]; 55 : } 56 : for (i = 0; i < 10; i++) _a_9[i]; 57 : 58 : ct = split(dlm, ar, "."); 59 : for (i = 1; i <= ct; i++) _dlm[ar[i]]; 60 : 61 : ct = split(op1, ar, ","); 62 : for (i = 1; i <= ct; i++) _op1[ar[i]]; 63 : 64 : ct = split(op2, ar, ","); 65 : for (i = 1; i <= ct; i++) _op2[ar[i]]; 66 : 67 : delete _er[0]; _erct = 0; #エラー用大域変数 68 : 69 : _cont_ch = ""; #大域変数 行継続によるリテラルか正規表現の持越し 70 : }
lex_awk_s()
71 : #. lex_awk_s();粗レキシカルアナライザ(AWK Script) 72 : # 戻り値 トークン数(resの要素数) 73 : # str: 1行のAWKソース 74 : # res: 2次元配列 res[x][0]=属性 res[x][1]=トークン(/"含む) 75 : # 行継続/次行にまたがるリテラル/正規表現に対応 76 : function lex_awk_s(str, res, len,\ 77 : fonetime, fcon, i, ib, ch, dlm,\ 78 : frtok, pos, ct, tmp, ia, ret) { 79 : ct = i = 1; 80 : dlm = "_fst"; #行頭デリミタ 81 : delete res; #初期化 82 : 83 : len = length(str); 84 : if (!len) { res[1][0] = "e"; res[1][1] = ""; return 1; } #空行 85 : if (substr(str, len) ~ /\\/) fcon = 1; #継続フラグ 86 : 87 : while (i <= len) { 88 : ch = substr(str, i, 1); #一文字切り出す 89 : ib = i; #ib 切り出し開始位置 90 : 91 : #複数行にまたがる正規表現/リテラル 前行引き継ぎ 92 : if (!fonetime) #最初の1度だけ実行 93 : if (!_cont_ch) fonetime = 1; 94 : else { fonetime = 1; ch = _cont_ch; } #i=1 " or / 95 : 96 : if (ch in _a_9) ch = "z"; #正規表現の代替 97 : 98 : switch (ch) { 99 : case /[ \t]/: #空白 100 : while (tmp = substr(str, ++i, 1)) #空白まとめる 101 : if (tmp !~ /[ \t]/) break; 102 : ret = substr(str, ib, i - ib); 103 : res[ct][0] = "w"; 104 : break; 105 : case /z/: #識別子 106 : while (tmp = substr(str, ++i, 1)) #次の文字から(++i) 107 : if (!(tmp in _a_9)) break; #正規表現の代替 108 : ret = substr(str, ib, i - ib); 109 : res[ct][0] = "i"; 110 : (ret ~ /^case$/) ? frtok = 0 : frtok = 1; #算術的先行句 111 : break; 112 : case /"/: #文字列リテラル 113 : pos = litendpos(str, i); 114 : if (pos) { i = ++pos; _cont_ch = ""; } #両端の””含む 115 : else if (fcon) { i = len; _cont_ch = "\""; } #継続 116 : else if (_cont_ch ~ /"/ && str ~ /^"/) { 117 : i = 2; #被継続 行頭" (fonetime参照) 118 : _cont_ch = ""; #被継続解除 119 : } 120 : else { #エラー 121 : err(NR, i); 122 : i++; 123 : _cont_ch = ""; 124 : } 125 : ret = substr(str, ib, i - ib); 126 : res[ct][0] = "l"; 127 : frtok = 0; 128 : break; 129 : case /\//: #正規表現または除算演算子 130 : if (dlm in _dlm && !frtok) { #正規表現定数 131 : pos = regendpos(str, i); 132 : if (pos) { i = ++pos; _cont_ch = ""; } 133 : else if (fcon) { i = len; _cont_ch = "/"; } 134 : else if (_cont_ch ~ /\// && str ~ /^\//) { 135 : i = 2; 136 : _cont_ch = ""; 137 : } 138 : else { 139 : err(NR, i); 140 : i++; 141 : _cont_ch = ""; 142 : } 143 : ret = substr(str, ib, i - ib); 144 : res[ct][0] = "r"; 145 : frtok = 0; 146 : break; 147 : } 148 : else { #除算演算子 149 : i++; 150 : ret = dlm = ch; 151 : res[ct][0] = "o"; 152 : frtok = 0; 153 : break; 154 : } 155 : case /#/: #コメント 156 : i = len + 1; #ループ強制終了 157 : ret = substr(str, ib); #残余文字列全て 158 : res[ct][0] = "c"; 159 : break; 160 : default: #演算子/句読記号 161 : ret = dlm = ch; 162 : if (ch in _op1) res[ct][0] = "o"; 163 : else res[ct][0] = "p"; 164 : frtok = 0; 165 : i++; 166 : break; 167 : } 168 : res[ct++][1] = ret; #トークン格納 169 : } 170 : return ct - 1; #切り出したトークン数 171 : }
litendpos()
172 : #. litendpos();右ダブルクォートの正確な位置を返す(C/AWK) 173 : # 戻り値: 右ダブルクォート位置 174 : # str: 文字列 175 : # pos: 左ダブルクォート位置 176 : function litendpos(str, pos, rpos, escape, ch) { 177 : rpos = escape = 0; 178 : while (ch = substr(str, ++pos, 1)) { #posの次文字から 179 : if (rpos) break; 180 : switch (ch) { 181 : case /\\/: 182 : (escape) ? escape = 0 : escape = 1; #偽 フラグ設定 183 : break; 184 : case /"/: 185 : (escape) ? escape = 0 : rpos = pos; #偽 終端 186 : break; 187 : default: 188 : (escape) ? escape = 0 : 0; #真 フラグ解除 189 : break; 190 : } 191 : } 192 : return rpos; 193 : }
regendpos()
194 : #. regendpos();正規表現末尾の正確な位置を返す(AWK) 195 : # 戻り値: 正規表現終端位置(右スラッシュ位置) 196 : # str: 文字列 197 : # pos: 左スラッシュ位置 198 : function regendpos(str, pos, rpos, escape, bracket, ch) { 199 : rpos = escape = bracket = 0; 200 : while (ch = substr(str, ++pos, 1)) { #posの次文字から 201 : if (rpos) break; 202 : switch (ch) { 203 : case /\\/: 204 : (escape) ? escape = 0 : escape = 1; #偽 Eフラグ設定 205 : break; 206 : case /\[/: 207 : (escape) ? escape = 0 : bracket = 1; #偽 Bフラグ設定 [ 内 ] 208 : break; 209 : case /]/: 210 : (escape) ? escape = 0 : bracket = 0; #偽 Bフラグ解除 [ ]外 211 : break; 212 : case /\//: 213 : (escape || bracket) ? escape = 0 : rpos = pos; #両フラグ 0 214 : break; 215 : default: 216 : (escape) ? escape = 0 : 0; #真 Eフラグ解除 217 : break; 218 : } 219 : } 220 : return rpos; 221 : }
err()
222 : #. err();リテラル/正規表現のエラー 223 : # line: エラー行 224 : # pos : エラー桁(先頭からn番目の文字)※TABを1文字として数えている 225 : function err(line, pos) { 226 : _er[++_erct] = "Error line: " line " position: " pos; 227 : }
_infoproc_init()
228 : #. _infoproc_init(); 229 : function _infoproc_init() { 230 : delete _sections[0]; #_csv格納 231 : _sectct = 0; #セクション/関数の合計数 232 : _csv = ""; #セクション/関数名,宣言行,開始行,終了行 233 : _cb = 0; #カーリーブラケット開閉数 234 : _fnamed = 0; #セクション名の有無 235 : _panum = 0; #Actionセクション名の一部 236 : _lastline = 0; #sect_div()が使用; 237 : }
infoproc()
238 : #. infoproc();セクションと関数の位置(行)を取得 239 : # lexa: レキシカルアナライザが吐き出した配列 240 : # lnr: 現在行 241 : # sections: sections[i] = _csv 242 : # ..._csv セクション/関数名, 宣言行, 開始行, 終了行 243 : function infoproc(lexa, lnr, sections, ct, i, tok, ci) { 244 : ct = length(lexa); 245 : for (i = 1; i <= ct; i++) { 246 : tok = lexa[i][1]; 247 : if (tok ~ /^BEGIN$|^END$|^func$|^function$/) { #section/func宣言行 248 : if (_cb) return 1; 249 : _fnamed = 1; #セクション/関数名がある 250 : switch (tok) { 251 : case /BEGIN|END/: 252 : _csv = tok "," lnr; 253 : break; 254 : case /func/: 255 : for (ci = i + 1; ci <= ct; ci++) 256 : if (lexa[ci][0] !~ /w/) break; 257 : _csv = lexa[ci][1] "()" "," lnr; #関数名 258 : break; 259 : default: 260 : break; 261 : } 262 : } 263 : else if (tok ~ /^{$/) { #section/function { 本文開始行 264 : _cb++; 265 : if (_cb == 1) { # 0 → 1 266 : if (!_fnamed) #Actionに上から順に名前を付与 267 : _csv = sprintf("ACTION_%02d,%d", ++_panum, lnr); 268 : _csv = _csv "," lnr; 269 : } 270 : } 271 : else if (tok ~ /^}$/) { #section/function } 終了行 272 : _cb--; 273 : if (!_cb) { #1 → 0 274 : sections[++_sectct] = _csv "," lnr; 275 : _fnamed = 0; 276 : } 277 : else if (_cb < 0) return lnr; 278 : } 279 : } 280 : return 0; 281 : }
sect_div()
282 : #. sect_div();infoproc() _sections[]からs_div[][]を作成 283 : # sect: _sections[] infoproc()が作成するglb配列 284 : # fnr: 最終行番号 285 : # s_div:out s_div[i][0] 開始行 sdiv[i][1] 名前 s_div[i][2] 終了行 286 : # infoproc()から得られるデータはセクション外の余白(空行)やコメントを 287 : # 考慮していない sect_div()はこれを補完する 288 : function sect_div(sect, fnr, s_div, csva, ct, i) { 289 : ct = length(sect); 290 : for (i = 1; i <= ct; i++) { 291 : split(sect[i], csva, ","); 292 : if (i == 1) { 293 : s_div[1][0] = 1; 294 : s_div[1][1] = csva[1]; 295 : _lastline = s_div[1][2] = csva[4]; 296 : } 297 : else { 298 : s_div[i][0] = _lastline + 1; 299 : s_div[i][1] = csva[1]; 300 : _lastline = s_div[i][2] = csva[4]; 301 : } 302 : } 303 : s_div[ct][2] = fnr; #最後の関数(セクション)はEOFまで 304 : }