時代遅れひとりFizzBuzz祭り、今回は個人的に「偽アセンブラ」呼ばわりしているCASL IIだ。CASL IIは私が生まれて初めて本格的に触ったプログラミング言語の1つで*1、2012年の年頭にあたり初心を忘れがちなここ最近の自分の気を引き締めるべくFizzBuzzしてみることにした。
CASL IIは前々回のbashの正反対に位置する言語だが、私の中では両方とも重要な道具だと認識している。
bashはUIとしてもスクリプト言語としても常用している。日々のちょっとした作業用のツールを書く時の候補の1つでもある。つまり実際にコードを書いて動かす用の言語として扱っている。
一方でCASL IIはそもそもCOMET IIという仮想ハードウェアの為のアセンブラなので、コードを書いたところでターゲットとなる計算機が無い*2。シミュレータの類は存在するもののWindowsやLinux上で実用的なツールを構築できるような処理系(というかシミュレータ)は皆無だ*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
ENDCASL 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互換のボードを作った会社があったが、さっぱり売れなかったらしい」という噂を聞いたのだが、あれは本当だったのだろうか? 今なら大学の研究室あたりで「VHDLやFPGAでCOMET II」的なネタをやっている所がありそう。
*3:CASL IIの仕様にINマクロやOUTマクロがあるので、使いやすいインタプリタがあればフィルタの類なら書けそうな気がする。実際に書こうものなら発狂するだろうけど。
*4:スタートアップルーチンとか。
*5:組み込みのハード寄りの人ならともかく、単なる教養としてアセンブラを通して低水準の世界を味わいたい人にとっては重荷になると思う。