最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

← 先頭のページに戻る

続Tiny Rubyコンパイラの作成(2)

実は、前にデカルト言語で作成したTiny Rubyコンパイラには、ある弱点があります。

それは何かと言いますと、入力したソースにエラーがあった場合に、どこで何がエラーの原因か、さっぱり分からないことです。

試しに以下のようなエラーを含むTiny Rubyのソースを実行してみます。


a=1+2
if a a > 0
        print a
end

どこがエラーかわかりますか?

if文のところが変な条件式になっていますね。

Tiny Rubyコンパイラで実行してみます。 ソースはtre.rbとして保存したとします。


$  descartes  tinyruby  tre.rb
inputfile tre.rb
outputfile tre.c
result --
        <compile>
-- unknown

結果がunknownとなるだけで、どこがエラーなのかは、この結果からは残念ながらわかりません。

これでは、あまりにも使い難いので改善していきましょう。

1. x述語

コンパイルのエラー処理にはx述語を使って対応します。

ちょっと分かりにくいのですが、後に示す実例を見ながらこの項目を見直すとすぐに分かると思います。

x述語は特殊なデカルト言語の組み込み述語です。


<x 述語...>
	通常は何も行わない。
	バックトラックしたときに引数の述語を実行する。

この述語は、通常は実行しても、何も実行せずtrueで成功します。つまり、何も実行せず、何も起らないで、素通りします。

このままでは、何の役に立つのかわかりませんね。

しかし、x述語は、自身が実行された後の述語の実行が失敗しunknownとなったときに真の効力を発揮します。

デカルト言語では、述語が失敗してunknown状態になると、前の述語に戻って、別の選択肢を使って述語の実行をやり直します。その述語の実行が失敗すればさらに、もう一つ前の述語を再実行しようとします。つまり、成功するまで遡って述語を再実行しようとします。この動作をバックトラックと言います。

そして、x述語にバックトラックしてくると、引数に指定された述語が実行されるのです。

つまり、後ろの述語が失敗した場合にx述語は引数に指定された処理を実行します。

今回のコンパイラのエラーの場合には、エラーが起きそうな場所の前に、x述語を置きましょう。x述語の引数にエラーメッセージを表示する処理を付けておけば、タイムリーにエラーメッセージを表示させることができます。

ちなみに、なぜx述語かと言いますと、失敗したとき(×なとき)に動作する述語であるから、x述語と名付けました。我ながら安直ですね。

2. line述語

エラーの場合に何が起きたのか、メッセージを表示するのには、x述語でなんとかなります。

しかし、どこでエラーが起きたのかはわかりません。 そんな状況に対応するために、line述語を作りました。


::sys <line 変数>
        openr述語でオープンした入力ファイルの現在読み込まれた位置までの行数を
         変数に設定する。


line述語を使うことにより、エラーが発生するまでに読み込まれたファイルの位置までの行数がわかります。エラーメッセージの表示に、この行数を表示すれば、エラー発生場所が分かるという寸法です。

3. エラーメッセージの出力プログラム

以下のような形式でエラーメッセージを出力させることにしましょう。


error : 行番号:エラーメッセージ

行番号は、入力されたソースの中のどの何行目でエラーが起きたのかを示します。

エラーメッセージを出力するerrormsg述語は以下のようになります。


<errormsg #x>
                ::sys <line #n>
                <warn "error : " #n ":" #x>
                <exit>
                ;

エラーの発生した場所で、<errormsg エラーメッセージ> として呼び出すと、行番号の情報を付けてエラー出力します。

エラー出力するには、デカルト言語の組み込み述語であるwarn述語を使います。print述語は標準出力に出力されますが、warn述語は標準エラー出力に出力されるところが異なります。

エラーメッセージを出力した後は、そこで処理を打ち切るために<exit>述語を呼び出します。

4. エラーメッセージの追加

まず、if文にエラーメッセージを追加します。


<if文>          "if"                    <x <errormsg "if文にエラー">>
                                        <printf "if (">
                <条件式>                <printf ") {" <\_n>>
                <プログラム>
                [
                        "else"          <x <errormsg "else文にエラー">>
                                        <printf "} else {" <\_n>>
                        <プログラム>
                ]
                "end"                   <printf "}" <\_n>>
                ;

"if"の後の<x <errormsg "if文にエラー">>と、"else"の後の<x <errormsg "else文にエラー">>がエラーメッセージの表示部分です。

"if"や"else"のようなトークンを超えてしまった後にエラーになった場合に、メッセージを出力しています。たとえば、"if"を超えて構文解析が進んだ後に、解析結果がエラーになるということは、バックトラックして"if"の前に戻ってもエラーのままでしかならないからです。"else"も同様です。

しかし、"end"はif文の構文の最後であるため、エラーメッセージの出力はつけません。"end"の後にはエラーになるような構文が無いため、"end文にエラー"となる可能性がないためです。

while文も同様です。


<while文>       "while"                 <x <errormsg "while文にエラー">>
                                        <printf "while (">
                <条件式>                <printf ") {" <\_n>>
                <プログラム>
                "end"                   <printf "}" <\_n>>
                ;

"while"トークンの後にエラーメッセージの表示を追加します。

"end"トークンの後には、エラーメッセージ表示は追加しません。

次はtimes文です。


<times文>       (<NUM #n> | <変数 #n>)
                "." "times"             <x <errormsg "timesにエラー">>
                                        <printf "{int ct; for (ct=0; ct<">
                                        <printf #n>
                                        <printf "; ct++) {" <\_n>>
					
                "do"
                <プログラム>
                "end"                   <printf "}}" <\_n>>
                ;

"times"トークンの後にエラー表示を追加します。 "do"トークンは、"times"トークンとの間にエラーになる要因が無いのでエラー表示はつけません。 もしも"do"トークンが無い場合には、"timesにエラー"とエラー表示されることになります。


次は、print文です。


<print文>
               "print"                  <x <errormsg "print文にエラー">>
                <表示項目> {","  <表示項目>}
                ;

今でと同様に、"print"トークンにエラー表示をつけます。

表示項目については、エラー表示は無くても良いでしょう。ここでは今回は特にエラー表示は行わないことにします。


<表示項目>
                  <STRINGS #s>          <printf 
                                            'printf("%s", "' #s '");' 
                             <\_n>>
                | <NUM #n>             <printf 
                         'printf("%d ", ' #n ');' 
                             <\_n>>
                | <変数 #v>             <printf 
                         'printf("%d ", ' #v ');' 
                             <\_n>>
                ;

代入文は、"="トークンにエラー表示を付けておきましょう。


<代入文>        <変数 #v>
                "="                     <x <errormsg "代入文にエラー">>
                                        <printf #v>
                                        <printf " = ">
                <数式>                  <printf ";" <\_n>>
                ;

条件式のエラーも検出できるようにしましょう。


<条件式>                                <x <errormsg "条件式にエラー">>
                <数式>
                (">=" | ">" | "==" | "!=" | "<=" | "<")
                                        <GETTOKEN #op>
                                        <printf #op>
                <数式>
                ;

さて、これで主要な構文の要素について、エラーを検出して表示できるようになりました。

しかし、実はこれではまだ不十分なのです。

何が不十分か分かるでしょうか。 そうです。 if文、while文、times文、print文および代入文のエラーは、これまでのエラー処理の追加で対応できます。しかし、そのいずれでもない構文のエラーが検出できないのです。 例えば以下のような場合は、コンパイルは失敗しますが、どこにエラーがあるのか分からないのです。


abc print "hello"

printまで、構文解析が辿り付くことなく、先頭の無意味なabcがコンパイルできずに失敗します。

そこで、このような構文エラーのためのエラー処理を追加します。

まず、if文、while文、times文、print文および代入文でなかった場合に、行番号を保存しておきます。


<実行文>                                <SKIPSPACE>
                                        <setVar exline ::sys <line #l>>
                (<if文> | <while文> | <print文> | <times文> | <代入文>)
                ;

SKIPSPACE述語は、余分な空白や改行を読み飛ばし、確実に処理の先頭に位置づけるために使用しています。 setVar述語は、現在の行位置をexline変数に設定しています。

構文のエラーは、<プログラム>が失敗してバックトラックしたときに処理します。


<プログラム>                            <x <exline #l>
                                                <errormsg #l "構文エラー">>
                {[<実行文> { ";" <実行文>}] (<コメント> | <CR>)}
                ;

exline述語で保存された行番号を取り出し、errormesg述語で表示します。 errormsg述語に行番号を引数とするものを追加します。


<errormsg #n #x>
                <warn "error : " #n ":" #x>
                <exit>
                ;

これで、一通りのエラー処理を追加しました。完成です。

個別の処理だけを見ていると分かりにくいので以下に、全部の処理をまとめて乗せます。


<compile>
	::sys<args #x>
	::sys<nth #inputfile #x 1>
	::sys<suffix #outputfile #inputfile c>
	<print inputfile #inputfile>
	<print outputfile #outputfile>
	::sys<openw #outputfile
		::sys<openr #inputfile <TinyRuby>>>
	;


<TinyRuby>				<print "#include <stdio.h>">
					<print "int main() {">
					<print "int a,b,c,d,e,f,g,h,i,j,k,l,m;">
					<print "int n,o,p,q,r,s,t,u,v,w,x,y,z;">
					<setVar exline 1>
		<プログラム> <EOF>	<print "exit(0);">
					<print "}">
		;

<プログラム>				<x <exline #l>
						<errormsg #l "構文エラー">>
		{[<実行文> { ";" <実行文>}] (<コメント> | <CR>)}
		;


<実行文>				<SKIPSPACE>
					<setVar exline ::sys <line #l>>
		(<if文> | <while文> | <print文> | <times文> | <代入文>)
		;


<if文>          "if"	 		<x <errormsg "if文にエラー">>
					<printf "if (">
		<条件式>		<printf ") {" <\_n>>
		<プログラム> 
		[ 
			"else"	  	<x <errormsg "else文にエラー">>
			 		<printf "} else {" <\_n>>
			<プログラム> 
		] 
		"end"			<printf "}" <\_n>>
		;

<while文>       "while"  		<x <errormsg "while文にエラー">>
			 		<printf "while (">
		<条件式> 		<printf ") {" <\_n>>
		<プログラム> 
		"end"			<printf "}" <\_n>>
		;

<times文>       (<NUM #n> | <変数 #n>)
		"." "times"	 	<x <errormsg "timesにエラー">>
			 		<printf "{int ct; for (ct=0; ct<">
					<printf #n>
					<printf "; ct++) {" <\_n>>
		"do"
		<プログラム> 
		"end"			<printf "}}" <\_n>>
		;

<print文>
	       "print"	  	 	<x <errormsg "print文にエラー">>
		<表示項目> {","  <表示項目>}
		;

<表示項目>
		  <STRINGS #s>		<printf 'printf("%s", "' #s '");' <\_n>>
		| <NUM #n>		<printf 'printf("%d ", ' #n ');' <\_n>>
		| <変数 #v>		<printf 'printf("%d ", ' #v ');' <\_n>>
		;

<代入文>        <変数 #v>
		"="  	 		<x <errormsg "代入文にエラー">>
					<printf #v>
					<printf " = ">
		<数式>			<printf ";" <\_n>>
		;

<数式>          <expradd>;

<expradd>       <exprmul> 
		{ "+" 			<printf "+">
			<exprmul> 
		| "-" 			<printf "-">
			<exprmul> 
		}
		;

<exprmul>       <exprID> 
		{ "*" 			<printf "*">
			<exprID> 
		| "/" 			<printf "/">
			<exprID> 
		}
		;

<exprID>        "+" <exprterm> 
		| "-" 			<printf "-">
			<exprterm> 
		| <exprterm>
		;

<exprterm>      "(" 			<printf "(">
			<数式> 
		")" 			<printf ")">
		| <NUM> 		<GETTOKEN #n>
					<printf #n>
		| <変数 #v>		<printf #v>
		;

<条件式> 		  	 	<x <errormsg "条件式にエラー">>
		<数式> 
		(">=" | ">" | "==" | "!=" | "<=" | "<") 
					<GETTOKEN #op>
					<printf #op>
		<数式>
		;

<変数 #x> 
		<RANGE #x a z>
		;

<コメント>	"#" <SKIPCR>
		;

<errormsg #x>
		::sys <line #n>
		<warn "error : " #n ":" #x>
		<exit>
		;
<errormsg #n #x>
		<warn "error : " #n ":" #x>
		<exit>
		;

?<compile>;

5. エラープログラムのコンパイル

以下のプログラムをコンパイルしてみましょう。


 
if a a > 0
        print b
        z = b
else
        print c
end

if文の条件式が変ですね。

tre1.rbという名前で保存して、Tiny Rubyでコンパイルしてみます。


$ descartes tinyruby.pl tre1.rb
inputfile tre1.rb
outputfile tre1.c
error : 2:条件式にエラー
result --
        <compile>
-- true

コンパイルしてみると、ちゃんと2行目の条件式にエラーがあると表示されています。

今度は次のようなプログラムをコンパイルします。どこが変でしょうか?


a=1+2
if  a > 0
q       print a
end

tre2.rbという名前で保存してコンパイルしてみます。


$ descartes tinyruby.pl tre2.rb
inputfile tre2.rb
outputfile tre2.c
error : 3:構文エラー
result --
        <compile>
-- true

3行目が変な構文になってました。

如何でしょう?

なんとかエラーの場所と原因が特定できるようになりましたね。