watermint.org - Takayuki Okazaki's note

発掘昔のコード(1996年, C++): コンパイラと実行環境

ファイルを整理していたところ、高校生の頃に作ったコードが出てきたので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実行環境を作る予定でしたが仕様がそれなりに大きく間に合わないと判断してより簡易的なスクリプト言語の実装という方向性になったのだと記憶しています。