GAME80コンパイラ解説 (2003/08/05)
GAME80コンパイラの作者の中島聡さんにGAME80 コンパイラのソースを掲載する許可を頂きました。 プログラム言語の仕組みに興味がある方にとって非常に参考になると思います。
GAME80コンパイラは、約5.5キロバイト強と非常にコンパクトなソースで記述されています。 コンパイラがこのようなサイズで作成できる理由は、GAME IIIという言語が単純な文法でありながら密度の高い記述ができることにあります。コンパイラがターゲットとする言語自身で書かれていることもGAME80コンパイラの特徴です。自分で自分自身をコンパイルできることは、言語の改善や拡張が容易であり、GAME80コンパイラ以後に多くのマシン用のGAMEコンパイラが開発された理由にもなっています。GAME IIIの文法は GAME86 または VTL系言語の歴史 を参考にして下さい。
このコンパイラはGAME言語のプログラムソースからインテルの8080という8ビットのCPUをターゲットとしたコードを生成します。インテルの8080の機械語がコンパイラのソースに直接埋め込まれていますが、 8080の命令セットは、例えば半導体コレクション展示会場(MCS-80)を参考にして下さい。
80386以降のCPUでLinux上で動作する32ビット版のコンパイラはrvtl コンパイラを参照してください。(2006/5/3 追加)
以下のリストは昔に記事を読んで打ち込んだもので、実行して確認していません。ミスがあるかもしれませんが、その場合はご容赦下さい。GAME言語のソースは、慣れないと記号ばかりで読みにくいと思いますが、GOTOが「#=」、GOSUBが「!=」、RETURNが「]」、IFが「;=」であることを意識しておけば読めるようになると思います。
GAME80コンパイラソース
中島 聡,GAME80コンパイラ, ASCII, アスキー,1979(7), p28-29より引用
10** GAME80 COMPILER **
20 "GAME PROGRAM FROM" G=?
30 "START ADRRESS" S=?
40 "WORK AREA FROM" L=?+4
50 L(-2)=G A=S
60 P=1 !=100 L(-1)=A
65 A=S D=$8D06
70 G=L(-2) P=2 !=100
80 "PROGRAM SIZE: " ?=A-S "(" ??=S "-" ??=A-1 ")" /
90 "END" #=-1
100** 1-2 PASS ROUTINE **
110 O=0
200** TOP OF LINE **
210 V=G:0)*256+G:1)
220 ;=G:0)>=$80 L(O)=$7FFF L(O+1)=$8603 A:0)=$C3 A=A+3 A(-1)=$8603 ]
225 ?(5)=V .=5 ??=A >=5 ??=G /
230 ;=P=1 L(O)=V L(O+1)=A O=O+2
240 G=G+3
250 ;=G:-1)<>" " #=900
300** TOP OF STATEMENT **
310 N=G:0)
350 ;=N="#" Q=$C3 #=1000
360 ;=N="!" Q=$CD #=1000
370 ;=N="]" #=1100
380 ;=N="/" G=G+1 Z=$8C83 #=2910
390 ;=N=""" #=2300
400 ;=N="?" #=2500
410 ;=(N>="A")*(N<="Z") #=5000
420 ;=N=";" #=5500
430 ;=N="@" #=5550
440 ;=N="." G=G+2 Z=$8B58 #=2900
450 ;=N=">" G=G+2 !=500 Z=K #=2910
460 ;=N="$" G=G+2 Z=$8B85 #=2900
470 ;=N="'" Z=$8D52 #=5010
480 ;=N=" " #=700
490 #=20000
500 K=0 ;=G:0)="$" G=G+1 #=530
510 E=G:0)-$30 ;=(E<0)+(E>9) ]
520 K=K*10+E G=G+1 #=510
530 E=G:0)-($30*(G:0)<"A"))-($37*(G:0)>"9"))
540 ;=(E<0)+(E>15) #=560
550 K=K*16+E G=G+1 #=530
560 ;=G:-1)<>"$" ]
570 #=4750
600 A:0)=$D5 A=A+1
610 ;=A:-2)=$D1 A=A-2
620 ]
650 A:0)=$EB A=A+1
660 ;=A:-2)=$EB A=A-2
670 ]
700** END OF STATEMENT **
710 ;=G:0)=0 G=G+1 #=200
730 ;=G:0)=" " G=G+1 #=710
740 #=300
900** REM **
920 ;=G:-1)>0 G=G+1 #=900
930 #=200
1000 ;=G:1)<>"=" #=1200
1010 G=G+2 !=1400
1020 #=700
1100 G=G+1 A:0)=$C9 A=A+1 #=700
1200 G=G+1 !=4000 A:0)=$7D A=A+1 G=G+1 Q=$CA
1210 A:0)=$3D A=A+1 !=1400
1220 ;=G:0)="," G=G+1 #=1210
1230 #=700
1400 ;=G:0)="-" G=G+1 !=500 K=$7FFF #=1500
1410 !=500
1500 A:0)=Q A=A+3 ;=P=1 ]
1510 J=-2 @ J=J+2 @=(L(J)>=K)
1520 ??=L(J+1) /
1530 A(-1)=L(J+1) ]
2300 A:0)=$C3 A=A+1 I=2
2400 Q=A+2 @ A:I)=G:I-1) I=I+1
2410 @=(G:I-1)=""") A(0)=A+I
2420 G=G+I A=A+I A:0)=$3E
2430 A:1)=I-2 A:2)=$32 A=A+5 A(-1)=$847A
2440 A:0)=$21 A=A+3 A(-1)=Q
2445 A:0)=$22 A=A+3 A(-1)=$847B
2450 A:0)=$CD A=A+3 A(-1)=$FA52
2460 #=700
2500 ;=G:1)<>"=" #=2600
2510 G=G+2 Z=$8C26 #=2900
2600 ;=G:1)<>"?" #=2700
2610 G=G+3 !=3000 A(0)=$42D5 A:2)=$CD A=A+5 A(-1)=$8C32
2620 A:0)=$D1 A=A+1 Z=$8C31 #=2910
2700 ;=G:1)<>"$" #=2750
2710 G=G+3 Z=$8C31 #=2900
2800 ;=G:1)<>"(" #=20000
2810 G=G+1 !=4000 A:0)=$4D A=A+1 G=G+1 Z=$8B9C
2820 A:0)=$C5 A=A+1 !=3000 A:0)=$C1 A=A+1 #=2910
2900 !=3000
2910 A:0)=$CD A=A+3 A(-1)=Z #=700
2950 !=3000 !=650 ]
2960 A:0)=$E5 A=A+1 !=3000
2970 A:0)=$E1 A=A+1 ]
2999** EXPRESSION **
3000 !=4000 A:0)=$EB A=A+1
3200 H=G:0) G=G+1
3210 ;=H="-" !=4000 H=$8A03 #=3500
3220 ;=H="*" !=4000 H=$8A0F #=3500
3230 ;=H="/" !=4000 H=$8A2C #=3500
3240 ;=H="=" !=4000 H=$C2 #=3610
3250 ;=H="<" #=3400
3260 ;=H=">" #=3450
3270 ;=H="<" #=3300
3280 G=G-1 ]
3300 !=4000 A(0)=$EB19 A=A+2 #=3200
3400 ;=G:0)=">" G=G+1 !=4000 H=$CA #=3610
3410 ;=G:0)=">" G=G+1 !=4000 H=$FA #=3600
3420 !=4000 H=$F2 #=3610
3450 ;=G:0)="=" G=G+1 !=4000 H=$FA #=3610
3460 !=4000 H=$F2 #=3600
3500 A:0)=$CD A=A+3 A(-1)=H #=3200
3600 A:0)=$EB A=A+1
3610 A:0)=$CD A=A+3 A(-1)=$8A03
3620 ;=H/16=$C A:0)=$B3 A=A+1
3630 A:0)=$11 A=A+3 A(-1)=0
3640 A:0)=H A=A+3 A(-1)=A+1
3650 A:0)=$1C A=A+1 #=3200
4000 H=G:0) G=G+1
4010 ;=(H>="0")*(H<="9")+(H="$") #=4210
4020 ;=H=""" #=4200
4030 ;=H="&" K=L(-1) #=4220
4040 ;=(H<"A")+(H>"Z") #=4500
4050 ;=(G:0)>="A")*(G:0)<="Z") G=G+1 #=4050
4060 H=(H-"A")*2+D
4070 ;=G:0)=":" #=4300
4080 ;=G:0)="(" #=4350
4090 A:0)=$2A A=A+3 A(-1)=H ]
4200 K=G:0) G=G+2 #=4220
4210 G=G-1 !=500 ;=G:-1)="$" ]
4220 A:0)=$21 A=A+3 A(-1)=K ]
4300 !=4400 A(0)=$6ED1 A(1)=$26 A=A+4 #=4550
4350 !=4400 A(0)=$D119 A(1)=$237E
4360 A(2)=$6F66 A=A+6 #=4550
4400 X=H !=600 G=G+1 !=3000
4410 A:0)=$2A A=A+3 A(-1)=X A:0)=$19 A=A+1 ]
4500 ;=H<>"(" #=4600
4510 !=600 !=2950 A:0)=$D1 A=A+1
4550 ;=G:0)<>")" #=20000
4560 G=G+1 ]
4600 ;=H<>"'" #=4700
4610 A(0)=$C5D5 A=A+2 !=4000
4620 !=650 A:0)=$CD A=A+3 A(-1)=$889E
4630 !=650 A(0)=$D1C1 A=A+2 ]
4700 ;=H<>"%" #=4800
4710 !=4000
4720 A:0)=$2A A=A+3 A(-1)=$8D4E ]
4750 A:0)=$CD A=A+3 A(-1)=$8C96
4760 A:0)=$6F A=A+3 A(-1)=$26
4780 ]
4800 ;=H="-" !=4900 H=$8A5B #=4920
4810 ;=H="+" !=4900 H=$8872 #=4920
4820 ;=H="#" !=4900 H=$8881 #=4920
4830 ;=H="?" !=600 H=$8904 #=4920
4890 #=20000
4900 !=600
4910 !=4000 !=650 ]
4920 A:0)=$CD A=A+3 A(-1)=H
4930 A(0)=$D1EB A=A+2
4940 ]
5000 Z=2*(G:0)-"A")+D
5005 ;=(G:1)>="A")*(G:1)<="Z") G=G+1 #=5005
5010 ;=G:1)="=" #=5300
5020 ;=G:1)=":" #=5150
5030 ;=G:1)="(" #=5100
5090 #=20000
5100 !=5200 A:0)=$19 A=A+1 #=5310
5150 !=5200 !=2960 A:0)=$73 A=A+1 #=5600
5200 G=G+2 !=3000 ;=G:0)<>")" #=20000
5210 A:0)=$2A A=A+3 A(-1)=Z
5220 A:0)=$19 A=A+1 G=G+2 ]
5300 G=G+2 !=2950 A:0)=$22 A=A+3 A(-1)=Z #=5600
5310 !=2960
5320 A:0)=$73 A:1)=$23 A:2)=$72 A=A+3
5330 #=5600
5500 ;=G:1)<>"=" #=20000
5510 G=G+2 !=3000
5520 A=A-1 ;=(A:0)=$1C)*(A(-1)=A+1) #=5800
5530 A=A+3 A(-1)=$B27B
5540 Q=$CA K=V+1 !=1500 #=700
5550 G=G+1 ;=G:0)="=" #=5700
5560 A:0)=$11 A=A+3 A(-1)=0
5570 #=5620
5600 ;=G:0)<>"," #=700
5610 G=G+1 !=3000
5620 A:0)=$CD A=A+3 A(-1)=A A:0)-$D5 A=A+1
5630 #=700
5650 G=G+1 ;=G:0)="=" #=5700
5700 Z=G:1) G=G+1 !=3000
5710 !=650
5720 ;=(Z>="A")*(Z<="Z") A:0)=$22 A=A+3 A(-1)=2*(Z-"A")+D
5730 A(0)=$D5D1 A:2)=$CD
5740 A=A+5 A(-1)=$8A03 A:0)=$D1 A=A+1
5760 A:0)=$E1 A:1)=$FA A(1)=A+6
5770 A:4)=$E5 A:5)=$E9
5780 A=A+6 #=700
5800 A=A-6 Q=A:3) K=V+1 !=1500 #=700
20000 "ERROR" #=-1
解説
コンパイラはある言語から別の言語へ翻訳するプログラムです。GAME80コンパイラは、GAME言語のプログラムの命令文を1つずつインテル8080のマシン語のパターンに置き換えていきます。
GAME80コンパイラのソースを解読するポイントは、コンパイルするソースを G:0) で1文字づつ参照していること、生成するマシン語は A:0) で1バイトを、A(0) で2バイトを書き込んでいるところです。ソースの読み込みとコード生成に使われる変数Aも変数Gも配列というよりポインタとして使用されていて、A:0)=x A=A+3 A(-1)=y のようにAの示すアドレスを変更して書いていきます。BASIC というよりむしろ C に近い書き方です。
行番号10から90までがメインルーチンです。最初にコンパイルされるGAME言語のソースが格納されている先頭アドレス(変数G)、生成するマシン語を格納する先頭アドレス(変数S)、ワークエリアの先頭アドレス(変数L)をコンソールから取得します。その後、GAME言語のソースを2回読み出してコンパイルを実行します。
GAME80コンパイラはソースを読みながらマシン語の命令を生成しますが、GOTO文(#=1000等)などの飛び先アドレスが前方(まだ読んでいない行番号)の場合には飛び先アドレスを決められないため、ソースを2回読み込みます。1回目で命令を生成しながら行番号と生成された対応するプログラムのアドレスとの対応表を作成します。2回目では1回目と同様に命令を生成しますが、飛び先アドレスは行番号との対応表から確定した値を書き込むことができます。このようにソースを2回読むことで完全なマシン語のプログラムを生成します。行番号とアドレスの表は配列変数 L の領域を使って、行番号とアドレスを交互に格納しています。
行100-490はメインルーチンから2回呼び出されて、各パスでソース中の命令をマシン語に翻訳しています。コンパイルされるGAME言語のソースは、2バイトの行番号に続いてテキストで書かれた行、行末を表す 0 を1行としてメモリに格納されています。全体の処理の流れは次のようになります。
- 現在の行番号を変数Vに格納
- 行番号が負ならばアドレステーブルの行番号を$7FFF、アドレスを$8603として登録、$8603番地へのJMP命令を生成(終了:GAME80インタプリタへ制御を戻す)
- 1パス目ならばアドレステーブル(L)に登録して、インデックス(変数O)を更新
- 読み込みソース位置(G)を行先頭に設定、コメント(行番号の直後が空白でない)ならその行を読み飛ばす(行900-930)
- 命令の種類に従って分岐してコード生成(行300-490)
- 行末でなければ 5. を繰り返す(行740)
- 行末なら次の行の最初に読み込み位置(G:0))を設定(行710-730)して、1. に戻る
個々の命令に関する処理へは行300-480のIF文によって分岐しています。分岐先で対応するマシン語のパターンを生成しています。
| 行番号 | 処理内容 |
|---|---|
| 500 | 数字文字列を数値に変換 |
| 700 | 次の命令に移る。行末ならば行200へ |
| 900 | コメント行を読み飛ばす |
| 1000 | 制御系 |
| 1100 | リターン文 |
| 2300 | 文字列出力 |
| 2500 | 数値出力 |
| 3000 | 式 |
| 4000 | 項 |
| 5000 | 変数、1バイト配列、2バイト配列への代入 FOR文 |
| 5300 | 代入文 |
| 5500 | IF文 |
| 5550 | DO文 |
| 5600 | FOR文 |
| 5700 | UNTIL文 |
| 20000 | エラー処理 |
入出力と四則演算は GAME80インタープリタの入出力ルーチンをサブルーチンコールして利用しています。変数の格納領域のインタープリタの領域を使用します。機能とサブルーチンのアドレスの対応は次のとおりです。
| 機能 | アドレス |
|---|---|
| ホットスタート | $8603 |
| 改行出力 | $8C83 |
| TAB出力 | $8B58 |
| 1文字出力 | $8B85 |
| 1文字入力 | $8C96 |
| 乱数生成 | $889B |
| 16進数出力 | $8C31, $8C32 |
| 減算 | $8A03 |
| 乗算 | $8A0F |
| 除算 | $8A2C |
| 右詰数値出力 | $889B |
| 数値出力 | $FA52:TK-80BSのROM内ルーチン |
| 変数格納領域先頭 | $8D06 |
行番号380-470、3210-3230付近でサブルーチンコール命令($CD)に続けて、目的とするサブルーチンアドレスを格納した変数Zの値を書き込んでサブルーチンコールを実行するコードを生成しています。
教科書的に解説すると、「コンパイラは、字句解析、構文解析、意味解析、コード生成、最適化といった部分から構成される」という理解するには遠い道のりに思えますが、GAME80コンパイラは200行弱です。 独自の言語を作成する気になってきませんか?
GAME言語が発表されてから半年ほどでコンパイラを作成した中島さんは当時高校生だったそうです。驚きです。
参考文献
- 大西 博, GAME, ASCII, アスキー,1978(7), p66
- 大西 博, GAME, ASCII, アスキー,1978(8), p42
- 大西 博, GAME, ASCII, アスキー,1978(9), p68
- 大西 博, GAME, ASCII, アスキー,1978(10), p66
- 高岡洋一,GAME80インタプリタ, ASCII, アスキー,1979(1), p38
- 中島 聡,GAME80コンパイラ, ASCII, アスキー,1979(7), p26