発掘昔のコード(1996年, C++): コンパイラと実行環境
16th January 2021ファイルを整理していたところ、高校生の頃に作ったコードが出てきたので25年ぶりに実行してみました。当時、郵便配達のアルバイトで購入したBorland C++を使って作っています。C++やオブジェクト指向についても図書館に通い少しこなれてきた頃に作ったもので、産業フェアというイベントに出展した記憶があります。プログラムは大きく分けて二つあり、BASICを参考にした簡単なスクリプト言語をバイトコードに変換するコンパイラと、MS-DOS画面ながらもマルチウインドウの仮想マシンです。
高校の図書室にあったコンパイラの解説本やスタックマシーンについて説明している本を参考にしつつ、当時話題のJavaやバイトコードといったエッセンスを混ぜて作ったのを思い出しました。コンパイラの部分をみてみると、実装逆ポーランド記法などへの変換などもがんばって実装しているのが懐かしいです。
void
Compile::ReversePolishNotation( int argc , char **argv )
{
struct lsElement
{
int r ; // 優先順位
char* symbol ; // 識別子
int output ; // 出力
} ;
lsElement elements[] =
{
{ 0 , "'" , bc_nop } // NULL
, { 1 , "and" , bc_and }
, { 1 , "or" , bc_or }
, { 1 , "xor", bc_xor }
, { 2 , "<" , bc_lt }
, { 2 , "<=" ,bc_le }
, { 2 , "==" ,bc_eq }
, { 2 , "!=" ,bc_ne }
, { 2 , ">" , bc_gt }
, { 2 , ">=" , bc_ge }
, { 3 , "+" , bc_add }
, { 3 , "-" , bc_sub }
, { 4 , "*" , bc_mul }
, { 4 , "/" , bc_div }
, { 4 , "mod" , bc_mod }
} ;
const int StackSize = 256 ;
// '(' は 100
// としてスタックに積むことにします
const int vLeft = 100 ;
int ss[ StackSize ] ;
int sp = 0 ;
int i ;
int j ;
int f ; // 一致するものがあったかどうかのフラグ
int elementnum = sizeof( elements ) / sizeof( elements[ 0 ] ) ;
DataId id ;
ss[ sp ] = 0 ;
for ( i = 0 ; i < argc ; i++ )
{
f = 0 ;
for ( j = 0 ; j < elementnum ; j++ )
{
if ( strcmp( argv[ i ] , elements[ j ].symbol ) == 0 )
{
if ( ss[ sp ] == vLeft )
{
sp++ ;
ss[ sp ] = j ;
}
else
{
if ( elements[ ss[ sp ] ].r >= elements[ j ].r )
{
id.value = 0 ;
csPut( elements[ ss[ sp ]].output , id ) ;
ss[ sp ] = j ;
}
else
{
sp++ ;
ss[ sp ] = j ;
}
}
f = 1 ;
break ;
}
}
if ( !f )
{
if ( strcmp( argv[ i ] , "(" ) == 0 )
{
sp++ ;
ss[ sp ] = vLeft ;
continue ;
}
if ( strcmp( argv[ i ] , ")" ) == 0 )
{
if ( ss[ sp ] != vLeft )
{
id.value = 0 ;
csPut( elements[ ss[ sp ]].output , id ) ;
}
sp-- ;
if ( sp < 0 )
sp = 0 ;
continue ;
}
if ( isalpha( argv[ i ][ 0 ] ))
{
id = VarId( argv[ i ] ) ;
csPut( bc_push , id ) ;
}
else
{
long v = atol( argv[ i ] ) ;
id.PutType( 0x01 ) ;
id.PutVal( v ) ;
csPut( bc_push , id ) ;
}
}
}
while ( sp != 0 )
{
if ( ss[ sp ] != vLeft )
{
id.value = 0 ;
csPut( elements[ ss[ sp ]].output , id ) ;
}
sp-- ;
}
}
実行対象となるプログラムはカレンダーを表示するプログラム、素数の計算、フィボナッチ数列など4つほどでしたが、当時単体テストすらまともに実施しておらず、サンプルコードの問題なのかコンパイラの問題なのか、ランタイムの問題なのかデバッグは困難を極めあまり複雑なコードは時間切れで作れなかった記憶があります。
' LeftScript Sample Program
' (c) by Takayuki Okazaki 1996
' $Id: prime.ls 1.1 1996/11/03 17:53:09 Okazaki Exp Okazaki $
print "素数を求める"
e = 1000 ' 何処まで求めるか
i = 2
c = 1
print c , "個目の素数は" , i , "です"
c = c + 1
for i = 3 to e
y = 0
for j = 2 to i - 1
if i mod j == 0 then
y = 1
endif
next
if y == 0 then
print c , "個目の素数は" , i , "です"
c = c + 1
endif
next
print "1 から" , e , "までの整数のうちの素数は以上です"
実行環境は擬似マルチタスクで、各プログラムのバイトコードを1つ実行するたびに次のプロセスの実行に移ります。一応、キー入力などの実行命令も作っていたのでIO待ちなどの状態管理もしていました。仮想マシンのクラス定義をみてみると、スタックメモリはメインメモリの制約からかファイル上にマッピングしていたようです。
class ByteComputer : public UserWin
{
private :
char ls_filename[ asd_BufSize ] ;
char cs_filename[ asd_BufSize ] ;
char ds_filename[ asd_BufSize ] ;
FILE* stack ;
FILE* ds ;
FILE* cs ;
long sp ;
DataId io_port ;
int status ;
void Push( DataId x ) ;
DataId Pop() ;
DataId GetVar( DataId x ) ;
void Input() ;
public :
ByteComputer( const char *filename ) ;
~ByteComputer() ;
virtual int InputChar( int c ) ;
virtual procStruct GenUser() ;
} ;
コンパイラと実行環境合わせて全体で5000行ほどのプログラムです。記憶が正しければ当初は情報処理技術者試験で使われていたCASLアセンブリ言語のコンパイラとCOMET実行環境を作る予定でしたが仕様がそれなりに大きく間に合わないと判断してより簡易的なスクリプト言語の実装という方向性になったのだと記憶しています。