時代遅れひとり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 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互換のボードを作った会社があったが、さっぱり売れなかったらしい」という噂を聞いたのだが、あれは本当だったのだろうか? 今なら大学の研究室あたりで「VHDLやFPGAでCOMET II」的なネタをやっている所がありそう。
*3:CASL IIの仕様にINマクロやOUTマクロがあるので、使いやすいインタプリタがあればフィルタの類なら書けそうな気がする。実際に書こうものなら発狂するだろうけど。
*4:スタートアップルーチンとか。
*5:組み込みのハード寄りの人ならともかく、単なる教養としてアセンブラを通して低水準の世界を味わいたい人にとっては重荷になると思う。