yacc を遊ぶ:パーサーの動き確認
「THE UNIX PROGRAMMING ENVIRONMENT」のチャプター8のhocをサンプルに、yaccのパースの動きを確かめてみる。
この本では、hoc4でパーサーからインタプリタコードを生成するステップに移るが、 生成すべきコードとの結びつきがイマイチ想像できんかったから。
とりあえず、サンプル:
[hoc.y]
%{ #include <stdio.h> #include <ctype.h> #define YYSTYPE double %} %token NUMBER %right '=' %left '+' '-' /* left associative, name precedence */ %left '*' '/' /* left assoc., higher procedence */ %% list: /*nothing*/ | list '\n' { printf("list\n"); } | list expr '\n' { printf("list expr: %f\n", $2); } ; expr: NUMBER { printf("NUMBER: %f\n", $1); $$ = $1; } | expr '+' expr { printf("%f + %f\n", $1, $3); $$ = $1 + $3; } | expr '*' expr { printf("%f * %f\n", $1, $3); $$ = $1 * $3; } | '(' expr ')' { printf("( %f )\n", $2); $$ = $2; } ; %% int main(int arc, char **argv) { yyparse(); } int yyerror(char *s) { fprintf(stderr, "%s\n", s); } int yylex() { int c; while ((c=getchar()) == ' ' || c == '\t') { ; } if (c==EOF) { return 0; } if (c=='.' || isdigit(c)) { /*NUMBER*/ ungetc(c, stdin); scanf("%lf", &yylval); return NUMBER; } return c; }
[Makefile]
TARGET = hoc OBJS = y.tab.o all: $(TARGET) $(TARGET): $(OBJS) gcc $(OBJS) -o $@ y.tab.o: y.tab.c gcc -c $< -o $@ y.tab.c: hoc.y yacc $< clean: rm -f y.tab.c rm -f $(OBJS) rm -f $(TARGET)
さて、実行してみる。
hoc4_test $./hoc 1+2+3 NUMBER: 1.000000 NUMBER: 2.000000 1.000000 + 2.000000 NUMBER: 3.000000 3.000000 + 3.000000 list expr: 6.000000
なるほど、これ分かりやすい。
仮想スタックマシンの命令はPUSH
、POP
、 と演算命令なので以下のような感じかな。
NUMBER: 1.000000 : -> PUSH (NUMBER) NUMBER: 2.000000 : -> PUSH (NUMBER) 1.000000 + 2.000000 : -> res = ADD (POP() + POP()); PUSH (res); NUMBER: 3.000000 : -> PUSH (NUMBER) 3.000000 + 3.000000 : -> res = ADD (POP() + POP()); PUSH (res); list expr: 6.000000 : -> 結果 = POP()
まあ、演算命令ADD
はスタックから値を取り出すところも
命令の中に含まれるだろうが。
「The UNIX Programming Environment」読了
ハッカーと画家: 言語の力
ちょっと衝撃を受けた。 昨日読み始めたポール・グレアムの「ハッカーと画家」、これはすごい本だ。
結構前に、Matzさんの以下の記事で知ったのだが、
まつもとゆきひろのハッカーズライフ:第3回 ハッカーと仕事 (2/2) - ITmedia エンタープライズ
ずっとブックリストに入れたままだった・・・これも早めに読んでおくべきだった。 実に色々示唆に富む内容で考えさせられる。良い本は刺激をくれ思考させてくれる本だ。
とりわけ衝撃的だったのは、「言語の力」をとうとうと説いてる章の以下の一文。
あたなが難しい問題を解こうとしているなら、問われているのはパワフルな言語を使うか使わないか、ではない。 (a)パワフルな言語を使うか、(b)パワフルな言語と等価なインタプルタを書くか、(c)自らがパワフルな言語の人間コンパイラとなるか、という選択なのだ
...
コンパイラがやるべきことを人間がシミュレートするという慣行はただ広まっているというだけでなく、思考を型にはめる作用がある。 例えば、オブジェクト指向の世界ではよく「パターン」というのを耳にするだろう。この「パターン」は多くの場合、(c)のケース、すなわち人間コンパイラが動作してる証拠なんじゃないかと私は思う。
こりゃ、こういう視点なかったな。 言語レベルでパラダイムが変われば、パターンとかフレームワークとかは、実はそもそもいらない。 このものの見方があるかないかでずいぶん違うな。
Joel on Software読み始める
最近 Joel on Software を読み始めた。
以前、書店でぱらぱら見たときは面白そうに見えなかったが、Kindleで原書のお試し版を読んでみると、楽しいではないか。とても平易な英語で読みやすい。
ということで、原書のペーパーバック古本をAmazonで購入。すこし汚れていたが、まあ印刷がクリアであれば全然問題ない。
そして、読んでて楽しい。 著者は言いたいこと言ってる感じで、ユーモアに富んでるし、主張してることも納得できる。
これ、もうちょっと早くに読んでおけば良かったな、と。とはいえ、英語読書に慣れてきたのは最近だから、この本が出た2004年は絶対読んでなかったな。
まあ、教訓は翻訳本の内容は原書を読んでから判断すること、だ。技術書に限らず、すべてのジャンルに言えることだけど、日本人作家でも好きな作家とそうでない作家がいる訳で、訳本はやっぱ翻訳者の作品だなと痛感。
さて、まだ1/3程度だが、読んで思ったのはこの人は経験に裏打ちされた現実主義者だな。と。 だいたいにおいて、プロジェクトマネージャーは夢見がちな人種が多いけど、この人は技術者寄りの視点を持っている。(というか技術者だろうけど)
現場で界王拳を多用しても、しょせん現実は20%くらいパワーアップしない。なのに、しっぺ返しは絶対にくらう、的なことを言っている。
仕様書を書く上で重要なことはFunnyであること、と言っているのはぐっと来た。 ユースケースを多少ふざけて書くという発想なかったな。これは確かに楽しい。
結局、仕様書は書かなきゃいけない。そこはさぼれない。 (つまり、仕様書を書かないデメリットを打ち消すウルトラCはない) ならば楽しく書こうではないか。という発想だね。
awk / yacc / lex
いかん、忙しさにかまけて更新が滞っている・・・。 さて、コンスタントに読んでいる「The UNIX Programming Environment」は、第6、7章が終わり。8章に入っている。 yaccで簡易計算言語の作成だぜ。さすがはKernighanさんとPikeさん、yaccがこんな風に楽しいものとは。 すべてはつながっているというか、yaccというツールは、awkと発想が似ている。
list: | list '\n' | list expr '\n' { printf("\t%.8g\n", $2); } ; expr: NUMBER { $$ = $1; } | expr '+' expr { $$ = $1 + $3; } | expr '-' expr { $$ = $1 - $3; } | expr '*' expr { $$ = $1 * $3; } | expr '/' expr { $$ = $1 / $3; } | '(' expr ')' { $$ = $2; } ;
つまり、 左側にパターンを書いて、右側にそのパターンが適合した時の処理を書く、 という点において。$1, $2の発想もね。
lexにいたっては、左側に正規表現を書いて右は処理を書く、からほとんどawkと一緒だ。
このパターン認識側の言語って、どれがオリジンかは分からんが、 shell, ed, sed, awk, yacc, lex。 これらぜんぶ初期UNIXの文化の中から生まれた、と考えたら似てて当たり前かもしれない。 が、面白い。
The UNX Programming Environment 5章、6章
The UNIX Programming Environmentも、第5章を過ぎて、第6章の「PROGRAMMING WITH STANDARD IO」の終わりに差しかかってきた。
UNIX精神というはつまり、過去の資産を最大限に活用すること、多様性を賞賛すること、なのだなと改めて読んでいて思った。この精神と文化は、だからオープンソースとはよく合い、プロプライエタリとは相容れないんだな。とも。
Ken Thompson、Denis Richieが全ての始りで、ひとつのアイディアの実現が、時代を経るごとに多くの人のアイディアを取り込んで、カンブリア期のごとく爆発し、一つの時代を終えた今も、次の世代として継承されている。生き残ったエンッセンスは古典として受け継がれ、古典は濃縮された真理を含んでいる。
この本の第5章「SHELL PROGRAMMING」の最後は、簡易的なバージョン管理ツールをshell scriptで作る例を紹介している。基本的なアイディアはSCCS(Source Code Control System)から来ていて、発想はdiffと同じである。 ファイルの差分をedの編集コマンドとして記述するのだ。
例えば、
2c another line
この2c
は2行目を'another line'にchangeすることを意味する。edのコマンド。
驚いた。なんとKen Thompsonさんの作ったedてやつは、バージョン管理ツールにまで応用されていた。 edは、viの親のみならず、grep, sed, awkの祖として知られている。そして正規表現の祖でもある。
どんだけ凝縮されてんだ、edに!と思った。
というより、一つの優れたアイディアから、どれだけのアイディアが生まれたのか、と言った方が正しいか。
今あるソフトウェア技術というのは、先人達の仕事の一歩一歩の積み重ねである。ということを実感できる例である。
さて、ここまでで、いくつかUNIX哲学に触れられる文章に合ったので、メモしとかんと。
The question of whether to write a new program or add features to an old one araises repeatedly as people have new ideas. We don't have ad definitive answer, but there are some principles that help to decide. The main principle is that a program should only do one basic job - if it does too many things, it gets bigger, slower, harder to maintain, and harder to use. Indeed, the features often lie unused because people can't remember the options anyway. (p185)
新しいプログラムを書くべきか既存のものを拡張すべきかというのは、新しいアイディアが出たときに繰り返し出てくる問題だ。私たちは決定的な答えをもっていない。が、決めるのに役立ついくつかの原則がある。 主な原則としてはプログラムはただ一つの基本的な仕事をすべきだということだ。多くのことをやり過ぎると、大きくなり、遅くなり、メンテが難しくなり、使いにくくなる。事実、ともかくオプションが覚えられないがために使われないまま横たわっている機能がしばしば存在する。
There's no good solution to writing bug-free code except to take care to produce a clean, simple design, to implement it carefully, and to keep it clean as you modify it.(p187)
クリーンでシンプルな設計を行うこと、注意深く実装し、修正するときにもクリーンに保つよう気をつけること以外に、バグのないコードを書く良い方法はない。
It's worth watching for oppotunities to build on someone else's labor instead of doing it yourself - it's a cheap way to be more productive.(p198)
自分でいちから作るのではなく、他の人の労力の上に(新しいものを)作る機会を窺うのは価値のあることだーそれはより生産的になるための安上がりな方法である。
簡単なcalculatorコマンドを作ってみる(Ruby)
たまーにさくっと計算機欲しいときがあるけど、UNIX標準のbcってクセがありすぎて辛い。
探してもシンプルなcalculatorコマンドってないもんだなと、、、。 やりたいことは、単純で
$ calc 1024*24-54 24522 0x5fca 057712
結果を、10進、16進、8進て表示してくれる。 (プログラマー用ね)
引数に0x24
とか0123
とかも受け付けてくれる。
$ calc 12*0x24/0123 5 0x5 05
とかね。
よく考えたら、シンプルなのでRubyのeval使えばできるや。と。
ソース(calc):
#!/usr/bin/ruby ARGV.length > 0 || exit eval("$res = #{ARGV[0]}") $res_hex = "%x" % $res $res_oct = "%o" % $res puts "#{$res}\t0x#{$res_hex}\t0#{$res_oct}"
実際には、引数はクオテーション'
で囲む必要がある。
(shが*とか展開しちゃうので)
まあ、sedもawkも'
で囲ってるしね。
$ calc '1024*24-54' 24522 0x5fca 057712
単にRubyに計算させてるだけなので、やろうと思えば sin(), cos()もできるが、まあ多分使わない。