最近の更新 (Recent Changes)

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

Wikiガイド(Guide)

サイドバー (Side Bar)

← 先頭のページに戻る

Tiny Pythonコンパイラの作成(その2)

以下にTiny Pythonのコード全体を示します。


<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 <TinyPython>>>
	;


<TinyPython>				<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>
		{
					<SKIPSPACE>
		  			<INDENT 0>
		  <プログラム> 
		}
					<SKIPSPACE>
					<x <errormsg "indent error">>
		  			<INDENT 0>
		<EOF>			<print "exit(0);">
					<print "}">
		;

<プログラム>
		  <制御文>
		| 
		  <プログラム文>
		;

<プログラム文>
		[<実行文> { ";" <実行文>}] [<コメント>] <CR>;

<ブロック>
		":"			<INDENT #n0>
					<SKIPSPACE>
					<INDENT #n>
					<print "{">
		{
					<SKIPSPACE>
					<INDENT #n>
		  <プログラム>
		}
					<SKIPSPACE>
					<INDENT #n0>
					<x <errormsg "indent error">>
					<print "}">
		;

<制御文>	( <if文> | <while文> | <for文> )
					<setVar exline 
						<_ = ::sys <line #l>+1>>;

<実行文>	( <print文> | <代入文>)
					<setVar exline 
						<_ = ::sys <line #l>+1>>;


<if文>          "if"	 		<x <errormsg "if文にエラー">>
					<printf "if (">
		<条件式>		<printf ") ">
		<ブロック> 
		[ 
			"else"   	<x <errormsg "else文にエラー">>
			 		<printf "else " <\_n>>
			<ブロック> 
		] 
		;

<while文>       "while"  		<x <errormsg "while文にエラー">>
			 		<printf "while (">
		<条件式> 		<printf ") " <\_n>>
		<ブロック> 
		;

<for文>       "for"
				 	<x <errormsg "for文にエラー">>
		<変数 #i>
		"in" "range" "("
		       (<NUM #n> | <変数 #n>)
		")"
			 		<printf "{"  <\_n>>
					<printf "int " #i ";" <\_n>> 
					<printf "for (" #i "=0; " #i " < ">
					<printf #n>
					<printf "; " #i "++) " <\_n>>
		<ブロック> 
					<printf "}" <\_n>>
		;

<print文>
	       "print"	  	 	<x <errormsg "print文にエラー">>
		<表示項目> 
			{","  		<printf 'printf(" ");' <\_n>>
			  <表示項目>}
					<printf 'printf("\n");' <\_n>>
		;

<表示項目>				<printf 'printf('>
		(
		   <STRINGS #s>		<printf '"%s","' #s '"'>
		 | 
					<printf '"%d", '>
		   <数式>
		)
					<printf ');' <\_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> 
		<SKIPSPACE>
		<RANGE #x a z>
		;

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

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


?<compile>;



1. Tiny Pythonの実行

前項で示したTiny PythonコンパイラをファイルtinyPythonとして保存してください。 ここでは、例としてPythonのソースとして、fib.pyを例題として使います。

fib.pyには以下を保存しておいてください。


l = 0
m = 1

print 0, l
print 1, m

for i in range(10):
  n = l+m
  print i+2,n
  l = m
  m = n

実行します。

1. デカルト言語の引数にtinyPythonのプログラムとコンパイルするPythonソースを指定します。


   $ descartes tinyPython fib.py

2. ソースに指定したPythonソースの拡張子を c に変換したC言語のソースが出力されている はずです。 それを、Cコンパイラでコンパイルします。


   $ gcc fib.c -o fib

3. 出来上がったプログラムを実行してみてください。


$ ./fib
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
11 89

4. 試しに本家ホンモノのPythonで実行してみます。


$ Python fib.py
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
11 89

同じ結果が出ました。

2. Tiny Pythonのコンパイル出力

「1. Tiny Pythonの実行」で、fib.pyをコンパイルした結果出力であるfib.cの中身を見てみ

ましょう。


#include <stdio.h>
int main() {
int a,b,c,d,e,f,g,h,i,j,k,l,m;
int n,o,p,q,r,s,t,u,v,w,x,y,z;
l = 0;
m = 1;
printf("%d", 0);
printf(" ");
printf("%d", l);
printf("\n");
printf("%d", 1);
printf(" ");
printf("%d", m);
printf("\n");
{
int i;
for (i=0; i < 10; i++)
{
n = l+m;
printf("%d", i+2);
printf(" ");
printf("%d", n);
printf("\n");
l = m;
m = n;
}
}
exit(0);
}


インデントが無く読みづらいですが、確かにC言語のソースに見えます。

タブを適切に入れてインデントを直してみます。


#include <stdio.h>
int main()
{
    int a, b, c, d, e, f, g, h, i, j, k, l, m;
    int n, o, p, q, r, s, t, u, v, w, x, y, z;
    l = 0;
    m = 1;
    printf("%d", 0);
    printf(" ");
    printf("%d", l);
    printf("\n");
    printf("%d", 1);
    printf(" ");
    printf("%d", m);
    printf("\n");
    {
        int i;
        for (i = 0; i < 10; i++) {
            n = l + m;
            printf("%d", i + 2);
            printf(" ");
            printf("%d", n);
            printf("\n");
            l = m;
            m = n;
        }
    }
    exit(0);
}


どうでしょうか、元のTiny Pythonのソースが、C言語のソースに変換されているのが分かるで

しょうか。

Tiny Pythonのソースの説明の詳細は次項より行います。

3. x述語

ここからはまず、ソース上で使われている特有の組み込み述語について、まず、説明していきましょう。

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

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


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

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

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

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

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

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

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

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

4. line述語

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

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


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


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

3. 引数、サフィックス変換、入力ファイルと出力ファイル

コンパイラ本体である<TinyPython>は以下の処理から起動されます。


<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 <TinyPython>>>
	;


順に説明していきましょう。

1. args述語は、コマンドラインの引数を変数#xに設定してます。

2. 次のnth述語はargsで得た引数の2つ目(nthでは最初の要素が0から始まる)を取り出して入力ファイル#inputfileに設定しています。

3. suffix述語は#inputfileの拡張子をcに変換して、出力ファイル#outputfileに設定します。

4. printで入力ファイルと出力ファイルを画面に表示します。

5. openw述語で出力ファイルをオープンし、openr述語で入力ファイルをオープンして、コンパイラ本体であるTinyPython述語を呼び出します。

6. ?<compile>で、上の処理を実行しています。

4. Tiny Pythonのメイン処理

Tiny Pythonのメイン処理を以下に示します。


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


1) まず、C言語に変換されたときに必要なヘッダファイル"stdion.h"をインクルードします。

2) 次に、main関数を定義します。このmain関数内の処理として、Tiny Pythonの処理が C言語に変換されて書き込まれます。

3) 変数としてaからzを定義しておきます。事前に定義しておくことにより、途中で 新たに使用された変数にも対応することができます。

4) 行番号を1に初期化しておきます。

5) ユーザに実際に書かれたプログラムの処理がここで処理されます。

6) ここからは後処理です。インデントが元に戻っているか確認して終了します。

5. ブロック

ここでは、ブロックによるインデント(字下げ)の処理について説明します。 python特有の機能であります。


<ブロック>
		":"			<INDENT #n0>
					<SKIPSPACE>
					<INDENT #n>
					<print "{">  //(1)
		{
					<SKIPSPACE>
					<INDENT #n>
		  <プログラム>
		}
					<SKIPSPACE>
					<INDENT #n0>
					<x <errormsg "indent error">> //(2)
					<print "}"> // (3)
		;


前の「Tiny Pythonコンパイラの作成」で説明した処理に、(1), (2) および(3)の処理が追加されています。

(1)と(3)は、ブロックの処理の開始と終了を処理し、それをC言語のブロックである"{", "}"に変換して出力しています。

(2)の機能はインデントが途中で誤っていたときにエラーを出力するためのx述語の処理です。

6. 行番号

行番号は、制御文または実行文を一つ実行するたびに一つずつ増やしていきます。


<制御文>	( <if文> | <while文> | <for文> )
					<setVar exline 
						<_ = ::sys <line #l>+1>>;

<実行文>	( <print文> | <代入文>)
					<setVar exline 
						<_ = ::sys <line #l>+1>>;

行番号は、exlineというグローバル変数に設定されます。

7. if文

if文は、 「if 条件式 : ブロック内の処理」を、「if (条件式) { ブロック内の処理 }」に変換します。


<if文>          "if"	 		<x <errormsg "if文にエラー">>
					<printf "if (">
		<条件式>			<printf ") ">
		<ブロック> 
		[ 
			"else"   	<x <errormsg "else文にエラー">>
			 		<printf "else " <\_n>>
			<ブロック> 
		] 
		;


x述語で、ifおよびelseの構文を認識した後にエラーが発生した場合、エラーメッセージを表示するようにしています。

printf述語で、実際のコンパイルの出力を行います。

ブロックは、前の項「5. ブロック」で示したように、「 {ブロックの内容} 」に変換されて出力されるのです。

8. while文

while文は、 「while 条件式 : ブロック内の処理」を、「while (条件式) { ブロック内の処理 }」に変換します。


<while文>       "while"  		<x <errormsg "while文にエラー">>
			 		<printf "while (">
		<条件式> 		<printf ") " <\_n>>
		<ブロック> 
		;


if文の場合と同様に、whileの構文を認識した後にエラーが発生した場合、エラーメッセージを表示するようにしています。

ブロックの処理も同様で、インデントによるブロックを、{}で括られた処理に変換されます。

9. for文

for文は、 「for 変数 in range(繰り返し数) : ブロック内の処理」を、「for (変数=0; 変数<繰り返し数; 変数++) { ブロック内の処理 }」に変換します。


<for文>       "for"
				 	<x <errormsg "for文にエラー">>
		<変数 #i>
		"in" "range" "("
		       (<NUM #n> | <変数 #n>)
		")"
			 		<printf "{"  <\_n>>
					<printf "int " #i ";" <\_n>> 
					<printf "for (" #i "=0; " #i " < ">
					<printf #n>
					<printf "; " #i "++) " <\_n>>
		<ブロック> 
					<printf "}" <\_n>>
		;

ちょっと複雑に見えますが、実はwhile文とほとんど同じような処理になっているのが分かるでしょうか。

forの構文を認識した後にエラーが発生した場合、エラーメッセージを表示するようにしています。

「in range(繰り返し数)」の構文を認識した後に、C言語のfor文を合成して組み立てます。 繰り返し数としては、数字か変数が指定できます。

ブロックの処理はif文やwhile文と同じように、{ブロック}の形に展開されるのです。

10. print文

print文は、カンマで括られたprintの構文を、C言語のprintf()の並びに変換します。


<print文>
	       "print"	  	 	<x <errormsg "print文にエラー">>
		<表示項目> 
			{","  		<printf 'printf(" ");' <\_n>>
			  <表示項目>}
					<printf 'printf("\n");' <\_n>>
		;

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

表示される項目には、文字列や数式も表示できるように変換します。

文字列は、printf()のフォーマット"%s"の引数となるように変換されます。

数式の場合は、フォーマット"%d"の引数となるように変換されます。

11. 代入文

代入文は、変数 = 数式を変換します。


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


ほぼそのままC言語に変換するのですが、最後に';'(セミコロン)が付くのが異なります。

12. 数式

数式は、そのままC言語に変換されるのですが、正しい数式の構文でありエラーがないことを確認しなければなりません。 そのために、以下の処理で構文解析を行います。


<数式>          <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>
		;


演算子の優先順位により処理する述語が異なります。

expradd述語は、+, -演算子を処理します。

exprmul述語は、*, /演算子を処理します。

exprID述語は、単項演算子の+, -演算子を処理します。例えば、+1とか-xのような数字や変数の前に付く+, -の符号をあらわす演算子です。

exprterm述語は、()で括られた式や、数字または変数を処理します。

この呼び出しの順番は、演算子の優先順位の低いものから、高いものへの順位に従います。 優先度の最も高いものが、exprterm述語で処理され、もっとも低い+, -がexpraddで処理されます。

それぞれの述語の呼び出し関係を追ってみてください。

13. 条件式

条件式は、if文の判定条件やwhile文のループ条件に使用します。


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


数式を、比較演算子ではさんだカッコウです。 よって、演算子としての優先度は、最も低くなるのです。

比較演算子を求めるには、GETTOKEN述語を使っていますが、この述語は直前にマッチした構文要素を返すものです。 本当は、各演算子の後にprintf述語で該当する演算子を出力してもよいのですが、処理が煩雑になるので、 ここでは、GETTOKEN述語でまとめて演算子を取得して、printf述語で出力しています。

14. 変数

変数は、英数字の1文字で表されます。


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

「4. Tiny Pythonのメイン処理」で示したとおり、英字変数はすべてmain関数の最初の部分で事前に定義されています。 そのため、ここでは特にC言語側で再定義しなくてもそのままでよいのです。 英字1字であることをRANGE述語で確認するだけで、OKです。

実際の変数名の処理は、前の項「12. 数式」で示したexprterm述語で変数として出力して処理されます。

15. コメント

コメントについては、C言語に変換する処理もなく、そのまま#から行末の改行までスキップするだけです。


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


SKIPCR述語は、行末までスキップさせるための、組み込み述語です。

16. エラーメッセージ

errormsg述語は、エラーが発生した場合に、エラーの発生箇所と原因を表示するものです。


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


引数が1つのものと、2つのものの2種類が用意されています。

引数が1つのものは、表示するメッセージを引数として受け取ります。 エラーの発生した場所については、line組み込み述語により、構文解析の実行中のカレント行を取得します。

引数が2つのものは、エラーが発生した行番号とエラーメッセージを受け取ります。

どちらもwarn組み込み述語により、行番号とエラーメッセージを表示した後に、exit述語により実行を打ち切ります。

17. 起動

最後に、全体のプログラムを呼び出します。


?<compile>;

この行により、compile述語が呼び出されます。ソース上では一番上に書いてあります。

コンパイラ本体である<TinyPython>は以下の処理から起動されます。


<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 <TinyPython>>>
	;

順に説明していきましょう。

1. args述語は、コマンドラインの引数を変数#xに設定してます。

2. 次のnth述語はargsで得た引数の2つ目(nthでは最初の要素が0から始まる)を取り出して入力ファイル#inputfileに設定しています。

3. suffix述語は#inputfileの拡張子をcに変換して、出力ファイル#outputfileに設定します。

4. printで入力ファイルと出力ファイルを画面に表示します。

5. openw述語で出力ファイルをオープンし、openr述語で入力ファイルをオープンして、コンパイラ本体であるTinyPython述語を呼び出します。

6. ?<compile>で、上の処理を実行しています。

これですべての処理の説明が終わりました。