時代遅れひとりFizzBuzz祭り CASL II編

時代遅れひとりFizzBuzz祭り、今回は個人的に「偽アセンブラ」呼ばわりしているCASL IIだ。CASL IIは私が生まれて初めて本格的に触ったプログラミング言語の1つで*1、2012年の年頭にあたり初心を忘れがちなここ最近の自分の気を引き締めるべくFizzBuzzしてみることにした。

CASL IIは前々回のbashの正反対に位置する言語だが、私の中では両方とも重要な道具だと認識している。

bashはUIとしてもスクリプト言語としても常用している。日々のちょっとした作業用のツールを書く時の候補の1つでもある。つまり実際にコードを書いて動かす用の言語として扱っている。

一方でCASL IIはそもそもCOMET IIという仮想ハードウェアの為のアセンブラなので、コードを書いたところでターゲットとなる計算機が無い*2。シミュレータの類は存在するもののWindowsLinux上で実用的なツールを構築できるような処理系(というかシミュレータ)は皆無だ*3。しかしアセンブラの経験が皆無な私にとってはC言語の薄皮の下の世界を考える時に頼りになるツールというか記号だ。つまり考えるための道具だ。

今回はLinux上でFizzBuzzしようということで、IPA公式(?)のCASL IIシミュレータであるJavaCASL2 Ver.2.0を使用した。

まず最初のバージョン。アセンブラ未経験なこともあってサブルーチンに値を引き渡す時にどのレジスタを使うべきか悩んだ。とりあえずGR7などの後ろ側(?)のレジスタで値を渡すようにした。

MAIN    START
        LAD     GR1,1       ; counter (1 to 100)
LOOP    LAD     GR2,0       ; print type (0: number, 1:Fizz, 2:Buzz, 3:FizzBuzz)
        LAD     GR3,0       ; output length (0 or 4 or 8)
CHKFIZ  LD      GR5,GR1
        LAD     GR6,3
        CALL    DIVL
        CPL     GR5,=0
        JNZ     CHKBUZ
        LAD     GR2,1,GR2
        LAD     GR3,4,GR3
CHKBUZ  LD      GR5,GR1
        LAD     GR6,5
        CALL    DIVL
        CPL     GR5,=0
        JNZ     PRNFIZ
        LAD     GR2,2,GR2
        LAD     GR3,4,GR3
PRNFIZ  CPL     GR2,=1
        JNZ     PRNBUZ
        ST      GR3,LEN
        OUT     FIZ,LEN
        JUMP    EOL
PRNBUZ  CPL     GR2,=2
        JNZ     PRNFB
        ST      GR3,LEN
        OUT     BUZ,LEN
        JUMP    EOL
PRNFB   CPL     GR2,=3
        JNZ     PRNNUM
        ST      GR3,LEN
        OUT     FIZBUZ,LEN
        JUMP    EOL
PRNNUM  LAD     GR4,NUM
        LD      GR7,GR1
        CALL    ITOA
        ST      GR7,LEN
        OUT     NUM,LEN
EOL     CPL     GR1,=100
        JZE     FIN
        LAD     GR1,1,GR1
        JUMP    LOOP
FIN     RET
FIZ     DC      'Fizz'
BUZ     DC      'Buzz'
FIZBUZ  DC      'FizzBuzz'
NUM     DS      3
LEN     DC      1
;
; ITOA - convert integer to string (logical)
;   GR4  set address
;   GR5  reserved
;   GR6  reserved
;   GR7  set number, return length
ITOA    PUSH    0,GR1
        LD      GR1,GR4
ILOOP   LD      GR5,GR7
        LAD     GR6,10
        CALL    DIVL
        ADDL    GR5,='0'
        ST      GR5,0,GR1
        LAD     GR1,1,GR1
        CPL     GR7,=0
        JNZ     ILOOP
        SUBL    GR1,GR4
        LD      GR5,GR4
        LD      GR6,GR1
        CALL    REVM
        LD      GR7,GR1
        POP     GR1
        RET
;
; REVM - reverse memory
;   GR5  set start address
;   GR6  set length
REVM    PUSH    0,GR1
        PUSH    0,GR2
        ADDL    GR6,GR5
RLOOP   SUBL    GR6,=1
        CPL     GR5,GR6
        JPL     RFIN
        JZE     RFIN
        LD      GR1,0,GR5
        LD      GR2,0,GR6
        ST      GR1,0,GR6
        ST      GR2,0,GR5
        LAD     GR5,1,GR5
        JUMP    RLOOP
RFIN    POP     GR2
        POP     GR1
        RET
;
; DIVL - divide x by y (logical)
;   GR5  set x, return mod
;   GR6  set y
;   GR7  return div
DIVL    LAD     GR7,0
DLOOP   CPL     GR5,GR6
        JMI     DFIN
        SUBL    GR5,GR6
        LAD     GR7,1,GR7
        JUMP    DLOOP
DFIN    RET
        END

CASL IIの命令は非常にシンプルというかむしろシンプル過ぎて、除算や乗算の命令すら存在しない。なので最初に除算用のサブルーチンDIVLを書いた。

その次に、OUTマクロで値を表示する際に数値を文字列に変換する必要があるのでサブルーチンITOAを書こうとし、書いている途中でサブルーチンREVMが欲しくなったので追加した。

メインルーチンは最後だ。

ところでメインルーチン部分にてこんな風に定数を確保している。

FIZ     DC      'Fizz'
BUZ     DC      'Buzz'
FIZBUZ  DC      'FizzBuzz'

この書き方だと16ワード必要になるのだが、8ワードにできないだろうか? 'FizzBuzz' という1つの定数だけ用意し、Fizzと表示したい場合は先頭4ワードを、Buzzの場合は後ろの4ワードを使うのだ。

もし仮に、

FIZ     DC      'Fizz'
BUZ     DC      'Buzz'

この書き方で連続する8ワードの領域に 'FizzBuzz' と値が確保されるのなら、以下のようなコードでも動作する。わざわざ分ける理由は、OUTマクロにて出力したい文字のあるメモリ領域をラベルで指定しなくてはならないからだ。

MAIN    START
        LAD     GR1,1       ; counter (1 to 100)
LOOP    LAD     GR2,0       ; print type (0: number, 1:Fizz, 2:Buzz, 3:FizzBuzz)
        LAD     GR3,0       ; output length (0 or 4 or 8)
CHKFIZ  LD      GR5,GR1
        LAD     GR6,3
        CALL    DIVL
        CPL     GR5,=0
        JNZ     CHKBUZ
        LAD     GR2,1,GR2
        LAD     GR3,4,GR3
CHKBUZ  LD      GR5,GR1
        LAD     GR6,5
        CALL    DIVL
        CPL     GR5,=0
        JNZ     PRNNUM
        LAD     GR2,2,GR2
        LAD     GR3,4,GR3
PRNNUM  CPL     GR2,=0
        JNZ     PRNBUZ
        LAD     GR4,NUM
        LD      GR7,GR1
        CALL    ITOA
        ST      GR7,LEN
        OUT     NUM,LEN
        JUMP    EOL
PRNBUZ  ST      GR3,LEN
        CPL     GR2,=2
        JNZ     PRNFIZ
        OUT     BUZ,LEN
        JUMP    EOL
PRNFIZ  OUT     FIZ,LEN     ; 'Fizz' or 'FizzBuzz'
EOL     CPL     GR1,=100
        JZE     FIN
        LAD     GR1,1,GR1
        JUMP    LOOP
FIN     RET
FIZ     DC      'Fizz'
BUZ     DC      'Buzz'
NUM     DS      3
LEN     DC      1
;
; ITOA - convert integer to string (logical)
;   GR4  set address
;   GR5  reserved
;   GR6  reserved
;   GR7  set number, return length
ITOA    PUSH    0,GR1
        LD      GR1,GR4
ILOOP   LD      GR5,GR7
        LAD     GR6,10
        CALL    DIVL
        ADDL    GR5,='0'
        ST      GR5,0,GR1
        LAD     GR1,1,GR1
        CPL     GR7,=0
        JNZ     ILOOP
        SUBL    GR1,GR4
        LD      GR5,GR4
        LD      GR6,GR1
        CALL    REVM
        LD      GR7,GR1
        POP     GR1
        RET
;
; REVM - reverse memory
;   GR5  set start address
;   GR6  set length
REVM    PUSH    0,GR1
        PUSH    0,GR2
        ADDL    GR6,GR5
RLOOP   SUBL    GR6,=1
        CPL     GR5,GR6
        JPL     RFIN
        JZE     RFIN
        LD      GR1,0,GR5
        LD      GR2,0,GR6
        ST      GR1,0,GR6
        ST      GR2,0,GR5
        LAD     GR5,1,GR5
        JUMP    RLOOP
RFIN    POP     GR2
        POP     GR1
        RET
;
; DIVL - divide x by y (logical)
;   GR5  set x, return mod
;   GR6  set y
;   GR7  return div
DIVL    LAD     GR7,0
DLOOP   CPL     GR5,GR6
        JMI     DFIN
        SUBL    GR5,GR6
        LAD     GR7,1,GR7
        JUMP    DLOOP
DFIN    RET
        END

このコードはJavaCASL2では問題なかったが、他のシミュレータでも大丈夫か否かは不明だ。

それにしてもさすがCASL II、単なるFizzBuzzだというのに予想以上に大仕事になった。ここまで手間が掛かったのはバッチファイルとGNU m4以来だ。バッチファイルの時はそのプログラマブル度の低さと環境変数の展開がらみの微妙な挙動に苦戦し、GNU m4の時はC言語を始めとする一般的な手続き型スタイルの言語との違いに悩んだ。今回は機能の低水準さとシンプルさ故に他の言語では標準でカバーされている機能を自作しなくてはならない点に起因する苦労だった。

CASL IIはプログラマ視点で低水準な世界の雰囲気を味わう分には手軽な言語ではないかと思う。本来は実機と本物のアセンブラを使うべきなんだろうけど、ちょっとしたボードであっても実際に動かすには色々な手順*4が必要で面倒だし*5、普及しているアーキテクチャが入門向けとも限らない*6。低水準の世界にどっぷり浸かる必要があるならそういった現実ならではの汚い部分に向き合わなくてはならないけど、そうでないならCASL IIの箱庭ぐらいが丁度良いのではないだろうか?

もっとも極めても直接的に役に立つことはない言語だという点は留意しておくべきだけど。

*1:ちなみにもう1つはJavaだが、すっかり忘れてしまった。

*2:2005年ごろに「その昔COMET II互換のボードを作った会社があったが、さっぱり売れなかったらしい」という噂を聞いたのだが、あれは本当だったのだろうか? 今なら大学の研究室あたりで「VHDLFPGAでCOMET II」的なネタをやっている所がありそう。

*3:CASL IIの仕様にINマクロやOUTマクロがあるので、使いやすいインタプリタがあればフィルタの類なら書けそうな気がする。実際に書こうものなら発狂するだろうけど。

*4:スタートアップルーチンとか。

*5:組み込みのハード寄りの人ならともかく、単なる教養としてアセンブラを通して低水準の世界を味わいたい人にとっては重荷になると思う。

*6:x86なんて拡張の拡張で老舗旅館の迷路さ並ではなかろうか?