hogashi.*

日記から何から

冬休みの自由研究(2)

 昨日は大掃除をして疲れ果てたので The LEX & YACC Pageプログラミング言語を作る/yaccとlex などを読んでなるほど〜となっていた。 Lex の処理部分を改行しつつ書くと、正規表現にマッチしたら何かの処理をするという書き方が AWK に似ているということに気づいてひとりで納得したりした。
 今日は JSON でパースの練習をするために、そのさきがけとしてまず JSON の字句解析をできる君を Lex で書いていた。 JSON の仕様は RFC8259 を斜め読みしている。 Strings の定義が以外と単純で、そうなのか、という感じだった。 Lex で書くのも楽。 Numbers は完全に忘れていたけど 1e3 みたいな表記もできたのだった。

unescaped [ !#-\[\]-􏿿]
escaped \\["\\/bfnrt]
escapedunicode \\u[0-9a-fA-F]{4}
minus -
plus \+
dot \.
napiere [eE]
digit19 [1-9]
digit [0-9]
%%
\{ { printf("L_CBR\n"); }
\} { printf("R_CBR\n"); }
\[ { printf("L_SBR\n"); }
\] { printf("R_SBR\n"); }
: { printf("COLON\n"); }
, { printf("COMMA\n"); }
\"({unescaped}|{escaped}|{escapedunicode})*\" { printf("STRING\n"); }
{minus}?(0|{digit19}{digit}*)({dot}{digit}+)?({napiere}({minus}|{plus})?{digit}+)? { printf("NUM\n"); }
true { printf("V_TRUE\n"); }
false { printf("V_FALSE\n"); }
null { printf("V_NULL\n"); }
" " { printf("WHITE\n"); }
. { printf("CHAR\n"); }

 余談だけど U+10FFFF は手元で上手く出せなくて U+10FFFF Unicode Character からコピペした (UTF8 では 0x10FFFF なんて無理で 0xF4 0x8F 0xBF 0xBF とかになるという話だった……)。

f:id:hogashi:20191230220110p:plain
0x10FFFFは大きい閉じ中括弧らしいことが端末で偶然わかった様子

 この lex ファイルを lex コマンドにかけて gcc コマンドにかけて、とやって得た実行ファイルを実行してやると、入力した JSON の文字を順番に読んでいって字句を解析する様子が見られる。改行を.でマッチできていないのかよくわからないけど、 JSON ファイルに改行があるとそのまま出力にも改行で入ってしまう。壊れた JSON を渡すと、全く予期していないところで文字列が終わったりすることになるので、急に CHAR とかにフォールバック(?)して面白い。

$ cat in2.json 
{
  "クオートMAX": "\\s\\\"\\\"\\\"\\\\\"",
  "any array": [null, { "str": 0e2 }]
}
$ ./ajson1.out < in2.json 
L_CBR

WHITE
WHITE
STRING
COLON
WHITE
STRING
COMMA

WHITE
WHITE
STRING
COLON
WHITE
L_SBR
V_NULL
COMMA
WHITE
L_CBR
WHITE
STRING
COLON
WHITE
NUM
WHITE
R_CBR
R_SBR

R_CBR

 あと、大学でやった実験のテキストの PDF を見つけてきて読みつつ、確かに定義部とかがあって定義しておけたのだった、とか、 Yacc のやることは Lex ができない括弧の対応とか入れ子になっていてとかそういう表現をカバーすることだったな、とか、 Lex と Yacc それぞれを実行して出てくる C言語のファイル lex.yy.c と y.tab.c をそのまま include して使うことができるのだった、とか色々思い出していた。
 ちなみに Lex で頑張ってやった Strings や Numbers のつくりは、おそらく字句解析では部品だけにしておいて構文解析でカバーするという手もありそうで、そこは好きに境界を動かしてよさそう。今の感覚では正規表現でできるならできるだけやってしまったほうが BNF が単純になっていいのかな〜という感じ。 Yacc で定義すべき BNF のイメージはなんとなくついたし、そもそも RFC があるのでやはりそこに載っている通りにつくればできるはずなので、続きをやりたい。

(追記 201912302215) 改行は . でマッチしないの仕様ですと教えてもらえたのと、そういえば RFC にちゃんと空白系はまとめてあったのでちゃんとやるとこうなった:

unescaped [ !#-\[\]-􏿿]
escaped \\["\\/bfnrt]
escapedunicode \\u[0-9a-fA-F]{4}
minus -
plus \+
dot \.
napiere [eE]
digit19 [1-9]
digit [0-9]
%%
\{ { printf("L_CBR\n"); }
\} { printf("R_CBR\n"); }
\[ { printf("L_SBR\n"); }
\] { printf("R_SBR\n"); }
: { printf("COLON\n"); }
, { printf("COMMA\n"); }
\"({unescaped}|{escaped}|{escapedunicode})*\" { printf("STRING\n"); }
{minus}?(0|{digit19}{digit}*)({dot}{digit}+)?({napiere}({minus}|{plus})?{digit}+)? { printf("NUM\n"); }
true { printf("V_TRUE\n"); }
false { printf("V_FALSE\n"); }
null { printf("V_NULL\n"); }
[ \t\n\r]+ { printf("WHITE\n"); }
. { printf("CHAR\n"); }
$ ./ajson1.out < in2.json
L_CBR
WHITE
STRING
COLON
WHITE
STRING
COMMA
WHITE
STRING
COLON
WHITE
L_SBR
V_NULL
COMMA
WHITE
L_CBR
WHITE
STRING
COLON
WHITE
NUM
WHITE
R_CBR
R_SBR
WHITE
R_CBR
WHITE