コンパイラ作成日誌0x2 『まずファイル入力対応』

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

コンパイラ作成日誌0x2 『まずファイル入力対応』

投稿記事 by MoNoQLoREATOR » 11年前

世界中のほぼ全てのコンパイラがそうであるように、ファイルからソースを受け付けるように改変して…みようとしました。
とりあえず適当にやってみたところ、ソースの内容にかかわらず常に"syntax error"と表示されるコンパイラが出来上がりました。

bison用のソース
► スポイラーを表示
コンパイルさせたソース(の例)

CODE:

3 5 +
まあ、どうせどこかでしょうもないミスをしているのでしょう。
とにかく今は眠くて仕方がないので寝ます。
これが解決できたら、一度flexを使ってみるつもりです。


それにしても、これはコンパイラと呼べるのだろうか?


そんなわけで、みなさんもぜひ良いプログラミングライフを!

アバター
へろりくしょん
記事: 92
登録日時: 14年前

RE: コンパイラ作成日誌0x2 『まずファイル入力対応』

投稿記事 by へろりくしょん » 11年前

MoNoQLoREATOR さんが書きました:世界中のほぼ全てのコンパイラがそうであるように、ファイルからソースを受け付けるように改変して…みようとしました。
とりあえず適当にやってみたところ、ソースの内容にかかわらず常に"syntax error"と表示されるコンパイラが出来上がりました。
ざらっと見ただけですが。

非終端子 line の定義において、expr の次は改行で終わらなければならない。 と、定義されています。
この言語のソースには改行が含まれていないようですが、これが原因ではないでしょうか。

#define YYERROR_VERBOSE と定義しておく事で、
yyerror() 関数に渡されれる引数に構文エラーの具体的な内容が文字列として渡されるようになりますよ。


また、プログラムの書き方ですが、プログラムにおいては常に構造化を意識するのは当然ですが、
特に処理系に関しては、とにかくがっちりと構造化しておかないと、後で死にます。

言語仕様が複雑化するにつれ、構文木もまた複雑化していき、また、
構文木を生成しながら、ものすごく深くスタックに潜り込んでいく事がしばしば起きます。

深いスタックの途中で、エラーが発生した時に、正しく戻ってくるのは意外と大変です。
構造化が出来ていないと、ここで極めて究明が困難なバグを仕込む事になります。

グローバル変数の使用は禁じ手。 ぐらいに考えてもいいぐらいです。
ましてや、ファイルポインタのグローバル変数化などは、絶対に避けるべきです。
ついでに言うと、字句解析器の中でファイルの読み込みはやるものではありません。


お小言のようで申し訳ないのですが、お小言ついでに、main() 関数の中身です。
yyparse() から帰ってきた後ですが、この時点でファイルポインタは有効なファイルポインタである事が保証されているべきで、
ここは if() 文による場分けではなく、assert() を仕掛けるべき局面です。

後、ポインタの代入ですが、定数0はヌルポインタに読み替えられる事が言語仕様により保証されていますが、
やっぱり、NULL を代入した方が可読性は高いと思いますよ。

三つ子の魂という訳ではありませんが、こういうのは癖になりますので、使い捨てのテストプログラムとは言え、
きちんとやっておいた方がいいんじゃないかなー と思ったり。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

Re: コンパイラ作成日誌0x2 『まずファイル入力対応』

投稿記事 by MoNoQLoREATOR » 11年前

ご返信ありがとうございます。

仰る通り、改行を入れていないことが原因でしょうね。
ただ、これはソースに改行を入れていないことが悪いのではなく、「exprの次は改行で終わらなければならない」という定義が悪く、「exprの次は改行もしくはEOFで終わらなければならない」という定義にすべきですよね。
これはこれから修正します。

エラーについてはまだ手を付けない予定です。
エラー処理について研究する際にYYERROR_VERBOSEを調べてみます。

私も、グローバル変数を使ったのは数年ぶりというくらい、アンチグローバル変数派です。
今回はyyparse関数にパラメーターを渡す方法がわからなかったため(そもそも渡せるのかどうかさえわからなかった)、応急処置としてグローバル変数にしておきました。
bisonが生成したCソースを解析した結果、どうやらパラメーターが渡せそうだということがわかったので、これから試してみます。

私はassertを使わない派です。
強制終了時の動作が不確定(少なくとも私には知識が無い)で、全てのオブジェクトのデストラクタが呼び出されるかどうか、私にはわからないからです。
それに、私には利点が見出せません。
意味合いによる可読性ですか?
if文による場合分けよりも、どのような点で適切なのですか?

私はNULLマクロを使わない派です。
理由は、NULLが宣言されているヘッダファイルをインクルードするかもしくは自分でマクロ定義する必要があり、スタイリッシュではないからです。
また、今回はbisonの仕様が不確定(少なくとも私には知識が無い)で、NULLマクロを使用する前にインクルードやマクロ定義が完了する保障があるかどうかわからなかったこともありますし、なによりNULLマクロによる弊害もあるため使用しませんでした。
というより、使用しない”べき”でしょう。
さらに、その代わりに/*null_ptr*/というコメントを入れています。
これによって、「著しく可読性が下がる」という事態は防いでいると言えると私は考えています。
調べてみると、C++11からはnullptrというキーワードが定義されたのですね。
私は以後これを使うことにします。

アバター
へろりくしょん
記事: 92
登録日時: 14年前

Re: コンパイラ作成日誌0x2 『まずファイル入力対応』

投稿記事 by へろりくしょん » 11年前

MoNoQLoREATOR さんが書きました:私はassertを使わない派です。
強制終了時の動作が不確定(少なくとも私には知識が無い)で、全てのオブジェクトのデストラクタが呼び出されるかどうか、私にはわからないからです。
それに、私には利点が見出せません。
意味合いによる可読性ですか?
if文による場合分けよりも、どのような点で適切なのですか?
まず、デストラクタは呼ばれません。 と、言うよりも assert() に引っかかる時点で、プログラムとして正しい振る舞いではありません。
つまり、そもそもプログラムが正しく走っている限り、if() 文による場分けは必要ないのです。 チェックする必要すらもありません。

それでも、人間ミスを犯すもので、なんらかのバグを仕込んでしまう事もあります。 それをトラップするための assert() ですよ。
それを if() 文で場分けしてしまうのは、ただの対症療法でしかありません。 ここはデバッグを行い根本的な原因を取り除くべきです。
assert() は、プログラムにバグがあるよ。 と、プログラマに教えてあげる為に使います。

今回の件で言えば、この if() 文に到達した時点で、input_fp は、決して NULL であってはいけません。
プログラムの仕様上、input_fp が NULL になる可能性がある。 というのであれば、if() 文で場分けしてもいいと思いますが。

ソースを見た限りではそのような感じではありませんが、どのような原因でこの input_fp がNULL になる事を想定しているのでしょうか。

MoNoQLoREATOR さんが書きました: 私はNULLマクロを使わない派です。
理由は、NULLが宣言されているヘッダファイルをインクルードするかもしくは自分でマクロ定義する必要があり、スタイリッシュではないからです。
また、今回はbisonの仕様が不確定(少なくとも私には知識が無い)で、NULLマクロを使用する前にインクルードやマクロ定義が完了する保障があるかどうかわからなかったこともありますし、なによりNULLマクロによる弊害もあるため使用しませんでした。
というより、使用しない”べき”でしょう。
さらに、その代わりに/*null_ptr*/というコメントを入れています。
これによって、「著しく可読性が下がる」という事態は防いでいると言えると私は考えています。
調べてみると、C++11からはnullptrというキーワードが定義されたのですね。
私は以後これを使うことにします。
スタイリッシュでは無いと言われれば、感性の問題ですので、何も言えませんが。

NULL を使わずに、nullptr を使う。 というのは分かりますが、代わりに定数0を使う。 というのは分かりません。

理由は単純で、NULL マクロを使った時に起きる弊害は、定数0を使っても、まったく同じ弊害を引き起こすからです。
この問題は、私の会社の勉強会でも議題に上がった事がありますが、関数に与える実引数のみ指定の型にキャストした NULL を使い、それ意外は普通に NULL。 という結論に至りました。
これは、会社のコーディングルールになりました。

可読性の低下をコメントで補う。 というのは、実引数でも同じ事をするのでしょうか。 つまり

hoge(0/*null_ptr*/, 0/*null_ptr*/, 0/*null_ptr*/);

とでも書くのでしょうか。 コメント。 書き忘れたりしませんか?