GAWK レキシカルアナライザをつくる
レキシカルアナライザとは
レキシカルアナライザは日本語で字句解析器(プログラムです)といいます。使用目的はいろいろありますが、最大のものではコンピュータ言語そのものの構築や、他言語への翻訳プログラムのための初期段階処理として使われます。その主たる仕事は、プログラムソースに記述された字句の連なり(コード)を一塊の意味ある最少単位に分割し、リスト化(配列等に格納)することです。
例えば AWK ソースの場合、以下のコードは次表のように分割、リスト化します。
pos = match("r = @/\"prog\"/", /\/.+\//) #サンプル
No. 文字列(トークン) 属性型
No. 1 pos 識別子(変数名)
No. 2 空白(スペース)
No. 3 = 演算子(代入)
No. 4 空白(スペース)
No. 5 match 識別子(組込み関数名)
No. 6 ( 句読記号(開きかっこ)
No. 7 "r = @/\"prog\"/" 文字列定数
No. 8 , 句読記号(カンマ)
No. 9 空白(スペース)
No.10 /\/.+\// 正規表現定数
No.11 ) 句読記号(閉じかっこ)
No.12 空白(水平タブ)
No.13 #サンプル コメント
識別子や演算子等に分割されたこのリストを、次のプロシージャ(構文解析等)または別の関数(検索抽出等)が利用しますので、レキシカルアナライザ単体で用をなすことはあまりありません。
構文解析/意味解析の前段としてのレキシカルアナライザにおいては、空白やコメント、その他特定の句読記号は不要となるため、字句解析で早々に失われる字句となります。筆者の目的はそれとは別ですので、今回作るレキシカルアナライザはそのまま、ありのままを残すモードを用意しました。
AWK 用レキシカルアナライザで字句解析を行う場合は No.7 の文字列定数、No.10 の正規表現定数、No.13 のコメント、これらが一文字ずつバラバラにならず、一塊のデータになっていることが特に肝要です。これらを正確に取り出せるのであれば、筆者の目的の半分は達成できたことになります。
もう半分は、属性型欄に記載してある字句の属性を、各データ(トークン)に紐付ける手法です。これには、GAWK4で実装された2次元配列(配列の配列)を使用することにしました。取り出したリストの使い勝手が、従来に比べ多少良くなると筆者は考えています。
筆者の目的とは、以下のメタなツールを作成することです。
ソースコード自体への加筆(コメンタ等)や整形(ビューティファイア等)、加工(html化等)、ソースの解析検査、他のソースコードからの抽出等、なんとなく内向的なツール作成ばかりではありますが、このレキシカルアナライザさえあれば、微妙に役立つものが作れそうな気がします。
蛇足ですが、そもそも、スクリプトファイルを読んで実行している時点で、当然ながら gawk.exe の中には、AWKソース専用の完璧なレキシカルアナライザが存在しています。GAWK のソース awkgram.y (構文解析器作成ツール Bison 用のファイル)の中に、yylex_ebcdic()/yylex()として実装されています。しかし、それをユーザーが AWK ソースの中で任意に使用することはできません。ゆえに自分で作るしかないのです、AWK で書かれた AWK スクリプトのためのレキシカルアナライザを。
awk_lex.awk
lex_awk() の仕組みを要約すると、
です。
正規表現定数と除算演算子の区別は直前の字句(空白除く)で判断しています。
print/printf/delete
はGAWKのステートメントですが、筆者の都合上、組み込み関数としています。
「in」は厳密にいうと演算子ですが、ステートメントとしています。
BEGIN
1 : # awk_lex.awk:AWKスクリプト用レキシカルアナライザ
2 :
3 : #. BEGIN:初期化
4 : BEGIN {
5 : _lex_init();
6 : _kyw_init();
7 : lex_mode = 1;
8 : }
ACTION_01
9 : #. ACTION_01:結果をダンプ
10 : {
11 : lex_awk($0, arr, lex_mode);
12 : print "\nLine No. " NR "\n$0 :" $0;
13 : for (i in arr) print "属性 :" arr[i][0] " 字句 :" arr[i][1];
14 : }
END
15 : #. END:エラーの報告
16 : END {
17 : if (_erct)
18 : for (i in _er) printf("\n\nNo.%2d: %s\n", i, _er[i]);
19 : }
lex_awk()
20 : #. lex_awk(): レキシカルアナライザ
21 : # 戻値: トークン数(resの要素数) ※空行 = 1
22 : # str:in: 1行のAWKソース
23 : # res:out: 2次元配列 res[x][0]=属性 res[x][1]=トークン(/"含む)
24 : # mode:in: 0 空白カンマ省略 1 原文のまま 2 1+param/local認識[s]
25 : # 行継続/次行にまたがるリテラル/正規表現に対応
26 : # [属性表]
27 : # a 先越的キーワード (kinds of Apriori -include,load,namespace)
28 : # k 文制御キーワード (keyword control statement)
29 : # f 組み込み関数 (function of built-in)
30 : # v 組み込み変数/定数 (variable/const-value of built-in)
31 : # u ユーザー定義関数 (user-defined function)
32 : # i 他の識別子 (identifier of variable)
33 : # n 数値 (numeric)
34 : # o 演算子等 (operator)
35 : # p カンマや括弧 (punctuation)
36 : # l 文字列リテラル (literal)
37 : # r 正規表現 (regular expression)
38 : # c コメント (comment)
39 : # w 空白 (white space -spaces and tabs)
40 : # s 空白 mode0 mode2 (separator of parameters and local var)
41 : # e 改行のみ (empty)
42 : function lex_awk(str, res, mode, len,\
43 : once, fcon, i, ib, ch, prev,\
44 : pos, ct, tmp, ret, fignore) {
45 : ct = i = 1;
46 : prev = "first"; #行頭 直前句初期化
47 : delete res; #配列初期化
48 : (!mode && !_cont_ch) ? fignore = 1 : fignore = 0;
49 : if (fignore) { #前後の空白除去
50 : sub(/^[ \t]+/, "", str);
51 : sub(/[ \t]+$/, "", str);
52 : }
53 : len = length(str);
54 : if (!len) { res[1][0] = "e"; res[1][1] = ""; return 1; } #空行
55 : if (substr(str, len) ~ /\\/) fcon = 1; #継続フラグ
56 :
57 : while (i <= len) {
58 : ch = substr(str, i, 1); #一文字切り出す
59 : ib = i; #ib 切り出し開始位置
60 :
61 : #mode0 空白と不要な記号を削除(非継続行)
62 : if (fignore) {
63 : if (ch ~ /[,\t]/) { i++; continue; }
64 : else if (ch ~ /#/ && prev ~ /first/) {
65 : res[1][0] = "e";
66 : res[1][1] = "";
67 : return 1;
68 : }
69 : else if (ch ~ / /) { #スペース
70 : if (!_inparam) { i++; continue; } #関数パラメータでない
71 : else { #param/local var 境界
72 : while (tmp = substr(str, ++i, 1))
73 : if (tmp !~ / /) break;
74 : if (i - ib < 3) continue;
75 : else { #3個以上 明示的境界
76 : res[ct][0] = "s";
77 : res[ct][1] = " ";
78 : ct++;
79 : continue;
80 : }
81 : }
82 : }
83 : }
84 :
85 : #複数行にまたがる正規表現/リテラル 前行引き継ぎ
86 : if (!once) #1度だけ実行
87 : if (!_cont_ch) once = 1;
88 : else { once = 1; ch = _cont_ch; } #["/]
89 :
90 : if (ch in _a_z) ch = "z"; #/[a-zA-Z_\$]/の代替
91 :
92 : switch (ch) {
93 : case /\$/: #フィールド変数(組み込み)
94 : while (tmp = substr(str, ++i, 1))
95 : if (tmp !~ /[0-9]/) break; #$0,$1等はバラさない
96 : ret = prev = substr(str, ib, i - ib);
97 : res[ct][0] = "v";
98 : break;
99 : case /z/: #識別子
100 : while (tmp = substr(str, ++i, 1)) #次の文字から(++i)
101 : if (!(tmp in _a_9)) break; #/[a_zA_Z0-9_]/の代替
102 : ret = prev = substr(str, ib, i - ib);
103 :
104 : if (ret in _kywd) { #文制御
105 : res[ct][0] = "k";
106 : if (ret ~ /func|function/) _infunc = 1;
107 : }
108 : else if (ret in _bltf) res[ct][0] = "f"; #組み込み関数
109 : else if (ret in _bltv) res[ct][0] = "v"; #組み込み変数
110 : else if (ret in _prpd) res[ct][0] = "a"; #先越的キーワード
111 : else if (tmp ~ /\(/) res[ct][0] = "u"; #ユーザー定義関数
112 : else res[ct][0] = "i"; #変数
113 : break;
114 : case /[0-9]/: #数値 16/10/8進数/指数表記 (+-なし)
115 : if (ch ~ /0/ && substr(str, i + 1, 1) ~ /[xX]/) {
116 : i++; #x Hex
117 : while (tmp = substr(str, ++i, 1))
118 : if (tmp ~ /[^0-9a-fA-F]/) break;
119 : }
120 : else {
121 : while (tmp = substr(str, ++i, 1))
122 : if (tmp ~ /[^0-9\.]/) break;
123 : if (tmp !~ /[eE]/) ; #nop Oct/Dec
124 : else { #指数
125 : if (substr(str, ++i, 1) ~ /[\-+]/) i++; # i +1 or +2
126 : for (; i <= len; i++) # i=i init
127 : if (substr(str, i, 1) ~ /[^0-9]/) break;
128 : }
129 : }
130 : ret = prev = substr(str, ib, i - ib);
131 : res[ct][0] = "n";
132 : break;
133 : case /"/: #文字列リテラル
134 : pos = litendpos(str, i);
135 : if (pos) { i = ++pos; _cont_ch = ""; } #両端の””含む
136 : else if (fcon) { i = len; _cont_ch = "\""; } #継続
137 : else if (_cont_ch ~ /"/ && str ~ /^"/) { i = 2; _cont_ch = ""; }
138 : else { err(NR, i); i++; _cont_ch = ""; } #エラー
139 : ret = substr(str, ib, i - ib);
140 : prev = ch;
141 : res[ct][0] = "l";
142 : break;
143 : case /\//: #正規表現または除算演算子
144 : if (prev in _prev) { #正規表現定数
145 : pos = regendpos(str, i);
146 : if (pos) { i = ++pos; _cont_ch = ""; }
147 : else if (fcon) { i = len; _cont_ch = "/"; }
148 : else if (_cont_ch ~ /\// && str ~ /^\//) {
149 : i = 2;
150 : _cont_ch = "";
151 : }
152 : else { err(NR, i); i++; _cont_ch = ""; }
153 : ret = substr(str, ib, i - ib);
154 : prev = ch;
155 : res[ct][0] = "r";
156 : }
157 : else { #除算演算子
158 : tmp = substr(str, ++i, 1);
159 : ret = prev = ch;
160 : res[ct][0] = "o";
161 : if (tmp ~ /=/) { i++; ret = prev = "/="; }
162 : }
163 : break;
164 : case /#/: #コメント
165 : if (!mode) { i = len + 1; continue; } #mode 0 コメント無視
166 : else {
167 : i = len + 1; #ループ強制終了
168 : ret = substr(str, ib); #残余文字列全て
169 : res[ct][0] = "c";
170 : break;
171 : }
172 : case /\(/:
173 : i++;
174 : ret = prev = ch;
175 : res[ct][0] = "p";
176 : if (_infunc) _inparam = 1;
177 : break;
178 : case /\)/:
179 : i++;
180 : ret = prev = ch;
181 : res[ct][0] = "p";
182 : if (_infunc) _inparam = _infunc = 0;
183 : break;
184 : case /\t/: #tab インデント等
185 : while (tmp = substr(str, ++i, 1)) #まとめる
186 : if (tmp !~ /\t/) break;
187 : ret = substr(str, ib, i - ib);
188 : res[ct][0] = "w";
189 : break;
190 : case / /: #スペース
191 : while (tmp = substr(str, ++i, 1))
192 : if (tmp !~ / /) break;
193 : ret = substr(str, ib, i - ib);
194 : res[ct][0] = "w";
195 : if (mode == 2 && _inparam && i - ib > 2) { #param/local境界
196 : res[ct][0] = "s";
197 : }
198 : break;
199 : default: #演算子 句読記号
200 : ret = prev = ch;
201 : if (ch in _op1) res[ct][0] = "o"; #演算子
202 : else res[ct][0] = "p"; #句読記号
203 : tmp = substr(str, i, 2); #2文字演算子かも
204 : i++;
205 : if (tmp in _op2) {
206 : ret = prev = tmp; #書き換え
207 : res[ct][0] = "o";
208 : i++;
209 : }
210 : break;
211 : }
212 : res[ct][1] = ret; #トークン格納
213 : ct++;
214 : }
215 : return ct - 1; #切り出したトークン数
216 : }
_lex_init()
217 : #. _lex_init():直前字句(正規表現用) 演算子 辞書を初期化
218 : # 戻値:
219 : function _lex_init( ct, ar, i, prev, op1, op2, a_z) {
220 : prev = "(.,.~.!~.==.!=.&&.||.first.case.@.=";
221 : op1 = "+,-,*,/,^,%,!,~,=,<,>,?,|,@";
222 : op2 = "++,--,+=,-=,*=,/=,%=,^=,!~,==,!=,<=,>=,&&,||,>>,<<,::";
223 : a_z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
224 : #高速化のためのハッシュテーブル
225 : ct = split(a_z, ar, "");
226 : for (i = 1; i <= ct; i++) { _a_z[ar[i]]; _a_9[ar[i]]; }
227 : for (i = 0; i < 10; i++) _a_9[i];
228 :
229 : ct = split(prev, ar, ".");
230 : for (i = 1; i <= ct; i++) _prev[ar[i]];
231 :
232 : ct = split(op1, ar, ",");
233 : for (i = 1; i <= ct; i++) _op1[ar[i]];
234 :
235 : ct = split(op2, ar, ",");
236 : for (i = 1; i <= ct; i++) _op2[ar[i]];
237 :
238 : delete _er[0]; _erct = 0; #glb変数 エラー用
239 : _cont_ch = ""; #glb変数 リテラル/正規表現の次行持越し
240 : _infunc = 0; _inparam = 0; #glb変数 関数パラメータフラグ
241 : }
_kyw_init()
242 : #. _kyw_init():GAWKキーワード辞書
243 : # 戻値:
244 : function _kyw_init( ar, i, ct, p_str, k_str, f_str, v_str) {
245 : #先越的なもの
246 : p_str = "include,load,namespace";
247 : #文制御
248 : k_str = "BEGIN,BEGINFILE,END,ENDFILE,next,exit,func,function,return,\
249 : if,else,for,in,do,while,break,continue,switch,case,default,nextfile";
250 : #組み込み関数
251 : f_str = "delete,index,patsplit,typeof,length,substr,match,split,sub,\
252 : gsub,gensub,sprintf,strtonum,tolower,toupper,print,printf,getline,system,\
253 : close,sin,cos,atan2,exp,log,int,sqrt,srand,rand,strftime,systime,mktime,\
254 : and,or,xor,compl,lshift,rshift,fflush,dcgettext,dcngettext,\
255 : bindtextdomain,asort,asorti,isarray";
256 : #組み込み変数(フィールド変数除く)
257 : v_str = "FS,OFS,NF,RS,ORS,NR,FNR,ARGV,ARGC,ARGIND,FILENAME,ENVIRON,\
258 : ERRNO,OFMT,CONVFMT,FIELDWIDTHS,IGNORECASE,RLENGTH,RSTART,SUBSEP,PROCINFO,\
259 : TEXTDOMAIN,SYMTAB,FUNCTAB,RT,FPAT";
260 :
261 : ct = split(p_str, ar, ",");
262 : for (i = 1; i <= ct; i++) _prpd[ar[i]];
263 :
264 : ct = split(k_str, ar, ",");
265 : for (i = 1; i <= ct; i++) _kywd[ar[i]];
266 :
267 : ct = split(f_str, ar, ",");
268 : for (i = 1; i <= ct; i++) _bltf[ar[i]];
269 :
270 : ct = split(v_str, ar, ",");
271 : for (i = 1; i <= ct; i++) _bltv[ar[i]];
272 : }
litendpos()
273 : #. litendpos():右ダブルクォートの正確な位置を返す(C/AWK)
274 : # 戻値: 右ダブルクォート位置
275 : # str:in: 文字列
276 : # pos:in: 左ダブルクォート位置
277 : function litendpos(str, pos, rpos, escape, ch) {
278 : rpos = escape = 0;
279 : while (ch = substr(str, ++pos, 1)) { #posの次文字から
280 : if (rpos) break;
281 : switch (ch) {
282 : case /\\/:
283 : (escape) ? escape = 0 : escape = 1; #偽 フラグ設定
284 : break;
285 : case /"/:
286 : (escape) ? escape = 0 : rpos = pos; #偽 終端
287 : break;
288 : default:
289 : (escape) ? escape = 0 : 0; #真 フラグ解除
290 : break;
291 : }
292 : }
293 : return rpos;
294 : }
regendpos()
295 : #. regendpos(): 正規表現末尾の正確な位置を返す(AWK)
296 : # 戻値: 正規表現終端位置(右スラッシュ位置)
297 : # str:in: 文字列
298 : # pos:in: 左スラッシュ位置
299 : function regendpos(str, pos, rpos, escape, bracket, ch) {
300 : rpos = escape = bracket = 0;
301 : while (ch = substr(str, ++pos, 1)) { #posの次文字から
302 : if (rpos) break;
303 : switch (ch) {
304 : case /\\/:
305 : (escape) ? escape = 0 : escape = 1; #偽 Eフラグ設定
306 : break;
307 : case /\[/:
308 : (escape) ? escape = 0 : bracket = 1; #偽 Bフラグ設定 [ 内 ]
309 : break;
310 : case /]/:
311 : (escape) ? escape = 0 : bracket = 0; #偽 Bフラグ解除 [ ]外
312 : break;
313 : case /\//:
314 : (escape || bracket) ? escape = 0 : rpos = pos; #両フラグ 0
315 : break;
316 : default:
317 : (escape) ? escape = 0 : 0; #真 Eフラグ解除
318 : break;
319 : }
320 : }
321 : return rpos;
322 : }
err()
323 : #. err(): リテラル/正規表現のエラー
324 : # 戻値:
325 : # line:in: エラー行
326 : # pos:in: エラー桁(先頭からn番目の文字)※TABを1文字として数えている
327 : function err(line, pos) {
328 : _er[++_erct] = "Error line: " line " position: " pos;
329 : }
筆者が最もハマった箇所は、正規表現定数「regendpos」と文字列定数「litendpos」の切り出しです。フラグ操作で解決することに気がつくまで、ずいぶん時間を浪費しました。match()を使って正規表現をこねくり回しましたが、GAWKの正規表現では複雑なものを正確に切り出せませんでした。正規表現は便利なものですが、決して万能ではないと思い知った次第です。
また、スクリプト作成中に、正規表現はマッチする文字候補が多くなるに連れ、実行速度は遅くなっていくという体験もできましたので、置換可能なものは連想配列(辞書)に置き換えて使用しています。
レキシカルアナライザのテスト temp.awk
注)まともなスクリプトではありません。
1 : #. BEGIN; temp.awk
2 : @include "my_lib";
3 : BEGIN {
4 : str = "The values indicate what gawk knows about the identifiers \
5 : after it has \"finished parsing\" the program; \
6 : they are not updated while the program runs. "
7 :
8 : reg1 = @/-?([1-9][0-9]*|0|([1-9][0-9]*\.|0\.)[0-9]+)|[abcdefghijk\
9 : lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_]+/;
10 : if (reg2 ~ /\/[^\[\/\(\"\]\\]+\// || reg2 ~ /\/[^\(\)]+\//);
11 : else reg1 = @/[^\y]\/; #エラー
12 :
13 : ct = gindex(str, " ", a_pos);
14 : for (i = 1; i < ct + 1; i++) {
15 : printf("%3d, ", a_pos[i]);
16 : if (i % 5 == 0) print "";
17 : }
18 : }
実行結果 gawk421 -f awk_lex.awk temp.awk (mode = 1)
※水平タブを「→」に置換しています。
コメント
インクルード文
BEGINと最初のカーリーブレース
文字列定数の改行と定数内のダブルクォート
空行
正規表現定数の改行
1行に複数の正規表現と正規表現内のエスケープスラッシュ
正規表現記述ミスによるエラーと部分コメント
エラー後の挙動(スルー)
ユーザー定義関数
AWKでは、ユーザー定義関数名と「(」の間に空白は入れられません。文字列連結のための空白が存在するため、文法的に許さないようです。一方、組み込み関数は空白OKとなりますが、コーディングの際には空白を入れないように統一した方が良いように思います。
2文字演算子 インクリメント
組み込み関数
print/printf/delete は組み込み関数ではなく、AWKではステートメント扱いとなるのですが、属性の実態として if/while/for 等と同列に扱うには無理がありますので、組み込み関数属性としています。他言語の値を返さないメソッドと考えると割としっくりきます。
組み込み関数「()なし」
記号のみの行と最後のカーリーブレース
エラー表示 11行の15文字目の正規表現エラー
レキシカルアナライザのテスト temp2.awk
1 : function gindex(str, find, fpos, len, flen, i, ict) {
2 : delete fpos;
3 : len = length(str);
4 : flen = length(find);
5 : for (i = 1; i < len - flen + 2; i++)
6 : if (substr(str, i, flen) == find) fpos[++ict] = i;
7 : return ict;
8 : }
実行結果 gawk421 -f awk_lex.awk temp2.awk (mode = 0)
mode 0 時の動作ですが、特筆すべきは1行目の [fpos] の後の空白です。属性 s は引数としてのパラメータとローカル変数を分けるセパレータです。この空白を消すのはもったいないので、解析に役立てるよう残しました。
Line No. 1
$0 :function gindex(str, find, fpos, len, flen, i, ict) {
属性 :k 字句 :function
属性 :u 字句 :gindex
属性 :p 字句 :(
属性 :i 字句 :str
属性 :i 字句 :find
属性 :i 字句 :fpos
属性 :s 字句 :
属性 :i 字句 :len
属性 :i 字句 :flen
属性 :i 字句 :i
属性 :i 字句 :ict
属性 :p 字句 :)
属性 :p 字句 :{
Line No. 2
$0 : delete fpos;
属性 :f 字句 :delete
属性 :i 字句 :fpos
属性 :p 字句 :;
Line No. 3
$0 : len = length(str);
属性 :i 字句 :len
属性 :o 字句 :=
属性 :f 字句 :length
属性 :p 字句 :(
属性 :i 字句 :str
属性 :p 字句 :)
属性 :p 字句 :;
Line No. 4
$0 : flen = length(find);
属性 :i 字句 :flen
属性 :o 字句 :=
属性 :f 字句 :length
属性 :p 字句 :(
属性 :i 字句 :find
属性 :p 字句 :)
属性 :p 字句 :;
Line No. 5
$0 : for (i = 1; i < len - flen + 2; i++)
属性 :k 字句 :for
属性 :p 字句 :(
属性 :i 字句 :i
属性 :o 字句 :=
属性 :n 字句 :1
属性 :p 字句 :;
属性 :i 字句 :i
属性 :o 字句 :<
属性 :i 字句 :len
属性 :o 字句 :-
属性 :i 字句 :flen
属性 :o 字句 :+
属性 :n 字句 :2
属性 :p 字句 :;
属性 :i 字句 :i
属性 :o 字句 :++
属性 :p 字句 :)
Line No. 6
$0 : if (substr(str, i, flen) == find) fpos[++ict] = i;
属性 :k 字句 :if
属性 :p 字句 :(
属性 :f 字句 :substr
属性 :p 字句 :(
属性 :i 字句 :str
属性 :i 字句 :i
属性 :i 字句 :flen
属性 :p 字句 :)
属性 :o 字句 :==
属性 :i 字句 :find
属性 :p 字句 :)
属性 :i 字句 :fpos
属性 :p 字句 :[
属性 :o 字句 :++
属性 :i 字句 :ict
属性 :p 字句 :]
属性 :o 字句 :=
属性 :i 字句 :i
属性 :p 字句 :;
Line No. 7
$0 : return ict;
属性 :k 字句 :return
属性 :i 字句 :ict
属性 :p 字句 :;
Line No. 8
$0 :}
属性 :p 字句 :}
・
・
・
レキシカルアナライザのテスト temp3.awk
1 : BEGIN {
2 : exp1=-1.9e-5-2.1e-5-0.9998+0.0002e4+0.0003e+4-4; #指数
3 : exp2=0x0f+0x01; #16進数
4 : exp3=010+010; #8進数
5 : print exp1,exp2,exp3;
6 : }
実行結果 gawk421 -f awk_lex.awk temp3.awk (mode = 0)
数値の字句解析です。通常の10進数、少数以外の指数、16進数、8進数を検証します。
ソースは空白整形処理をしていない状態です。
単項演算子(+-)は現在のところ普通の演算子として取り扱っています。数値にくっつけることも考えましたが、変数にそれを適用するわけにはいかないので、ちょっと扱いに困っています。
Line No. 1
$0 :BEGIN {
属性 :k 字句 :BEGIN
属性 :p 字句 :{
Line No. 2
$0 :→exp1=-1.9e-5-2.1e-5-0.9998+0.0002e4+0.0003e+4-4;→#指数
属性 :i 字句 :exp1
属性 :o 字句 :=
属性 :o 字句 :-
属性 :n 字句 :1.9e-5
属性 :o 字句 :-
属性 :n 字句 :2.1e-5
属性 :o 字句 :-
属性 :n 字句 :0.9998
属性 :o 字句 :+
属性 :n 字句 :0.0002e4
属性 :o 字句 :+
属性 :n 字句 :0.0003e+4
属性 :o 字句 :-
属性 :n 字句 :4
属性 :p 字句 :;
Line No. 3
$0 :→exp2=0x0f+0x01;→#16進数
属性 :i 字句 :exp2
属性 :o 字句 :=
属性 :n 字句 :0x0f
属性 :o 字句 :+
属性 :n 字句 :0x01
属性 :p 字句 :;
Line No. 4
$0 :→exp3=010+010;→#8進数
属性 :i 字句 :exp3
属性 :o 字句 :=
属性 :n 字句 :010
属性 :o 字句 :+
属性 :n 字句 :010
属性 :p 字句 :;
Line No. 5
$0 :→print exp1,exp2,exp3;
属性 :f 字句 :print
属性 :i 字句 :exp1
属性 :i 字句 :exp2
属性 :i 字句 :exp3
属性 :p 字句 :;
Line No. 6
$0 :}
属性 :p 字句 :}