GAWK AWKのセクション/関数を自動選択する サクラエディタ マクロ
筆者は、エディタ画面上で、マウスドラッグまたは、SHIFTキーを押しながら頻繁に AWK の関数を選択します。長い関数になるとミニマップを使うこともあります。コピーや移動/置換、グローバル変数検査(test_func)にhtml化したりと、30行程度の関数であれば、特に労力を惜しむことはありませんが、100行を超える関数ですと、控えめに言ってもちょっと煩わしく感じます。特に拙作 test_func はよく使いますので、コンテキストメニューで、関数を自動で範囲選択できるようにしてしまおう、と作りました。
GAWK4 以降でないと動作しません。
マクロ実装の結果
対象関数内のどこでもよいので、左クリック
カーソルが対象関数内(上部コメント可)にあるようにする
右クリック、コンテキストメニューから「Select_Section」を選択
関数全体が範囲選択される
作りかけのスクリプトは、注意が必要です。対象関数とは別の、どこか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 : }