[Piet]ASCIIコード表

Pietでプログラミングしていると文字出力のためにASCIIコード表が欲しくなります。表を作ること自体は難しくありません。でもめんどくさい。

そういう面倒なことはプログラムにお任せするのが良いと思います。というわけでPietで書いてみました(なぜ)

概要

調べてみるとASCIIコードの印字可能文字は、16進数で 0x20-0x7Fの範囲にあるそうで、とりあえずこの 範囲の表ができれば問題ありません。

出力されるASCIIコード表は次のようなものにします。

|' ' (32)|'!' (33)|'"' (34)|'#' (35)|'$' (36)|'%' (37)|'&' (38)|''' (39)|'(' (40)|')' (41)|'*' (42)|'+' (43)|',' (44)|'-' (45)|'.' (46)|'/' (47)|
|'0' (48)|'1' (49)|'2' (50)|'3' (51)|'4' (52)|'5' (53)|'6' (54)|'7' (55)|'8' (56)|'9' (57)|':' (58)|';' (59)|'<' (60)|'=' (61)|'>' (62)|'?' (63)|
|'@' (64)|'A' (65)|'B' (66)|'C' (67)|'D' (68)|'E' (69)|'F' (70)|'G' (71)|'H' (72)|'I' (73)|'J' (74)|'K' (75)|'L' (76)|'M' (77)|'N' (78)|'O' (79)|
|'P' (80)|'Q' (81)|'R' (82)|'S' (83)|'T' (84)|'U' (85)|'V' (86)|'W' (87)|'X' (88)|'Y' (89)|'Z' (90)|'[' (91)|'\' (92)|']' (93)|'^' (94)|'_' (95)|
|'`' (96)|'a' (97)|'b' (98)|'c' (99)|'d'(100)|'e'(101)|'f'(102)|'g'(103)|'h'(104)|'i'(105)|'j'(106)|'k'(107)|'l'(108)|'m'(109)|'n'(110)|'o'(111)|
|'p'(112)|'q'(113)|'r'(114)|'s'(115)|'t'(116)|'u'(117)|'v'(118)|'w'(119)|'x'(120)|'y'(121)|'z'(122)|'{'(123)|'|'(124)|'}'(125)|'~'(126)|''(127)|

文字をシングルクォーテーション'でくくり、カッコでASCIIコードの数値を10進数で示します。

表全体は垂直バー|で区切って表現します。Pietでシンプルに表を出力するならこんなもんでしょう。

1行は16文字とします。 1行めが文字コード0x20-0x2Fに対応、2行めが文字コード0x30-0x3Fに対応、・・・とします。

プログラムのフロー

  1. 表の縦方向のループカウンタyを用意する
  2. 縦方向ループ開始
  3. 表の横方向のループカウンタxを用意する
  4. 横方向ループ開始
  5. 表示する文字コードを計算(x+16*y
  6. 垂直バーを表示(|
  7. 文字を表示('文字'
  8. 文字コードを表示((文字コード)
  9. xを更新(x<-x+1
  10. x<16なら「横方向ループ開始」に戻る
  11. 垂直バーと改行文字を出力する(次の行へ)
  12. yを更新(y<-y+1
  13. y<8なら「縦方向ループ開始」に戻る
  14. 終了

プログラム

まずは作ったプログラムと自動生成したPietソースコードを示します。プログラムは長いので、見たい方は下の文字をクリックして開いてください。

クリックしてプログラムをみる

push2
:loopY
push1 dup sub

:loopX
dup push3 push2 roll
dup push4 push1 roll
push2 dup mul dup mul mul add push1
goto:out_bar
:out_bar_return1
pop goto:out_char
:out_char_return
goto:out_num
:out_num_return
pop push1 add dup push3 push5 mul gt
if::loopX
push2 goto:out_bar
:out_bar_return2
pop
goto:out_NL
:out_NL_return
push2 push1 roll push1 add dup
push3 push2 dup add add gt
if:end:
push2 push1 roll
pop
goto:loopY

:out_char
dup
push2 dup dup dup dup push1 add add mul mul mul push1 sub dup
push3 push1 roll outc outc outc
goto:out_char_return

:out_bar
push2 dup dup dup mul mul dup mul mul push2 dup mul sub outc
:out_bar_return
dup push1 sub if::out_bar_return1
dup push2 sub if::out_bar_return2
end

:out_num
dup
push2 dup dup push1 add add mul dup mul push1 sub gt
if:out_num_out:
push1 goto:out_space
:out_space_return1
pop
:out_num_out
dup
push2 dup dup mul mul push2 push3 add mul
dup push1 add push3 push1 roll
outc outn outc
goto:out_num_return

:out_space
push2 dup dup mul dup mul mul outc
:out_space_return
dup push1 sub if::out_space_return1
end

:out_NL
push2 push5 mul outc
goto:out_NL_return

:end
end

ASCII表を出力するPietコード(6倍拡大)

これをnpietで実行すると次のようになります

> ./npiet -cs 6 asciix6.png 
|' ' (32)|'!' (33)|'"' (34)|'#' (35)|'$' (36)|'%' (37)|'&' (38)|''' (39)|'(' (40)|')' (41)|'*' (42)|'+' (43)|',' (44)|'-' (45)|'.' (46)|'/' (47)|
|'0' (48)|'1' (49)|'2' (50)|'3' (51)|'4' (52)|'5' (53)|'6' (54)|'7' (55)|'8' (56)|'9' (57)|':' (58)|';' (59)|'<' (60)|'=' (61)|'>' (62)|'?' (63)|
|'@' (64)|'A' (65)|'B' (66)|'C' (67)|'D' (68)|'E' (69)|'F' (70)|'G' (71)|'H' (72)|'I' (73)|'J' (74)|'K' (75)|'L' (76)|'M' (77)|'N' (78)|'O' (79)|
|'P' (80)|'Q' (81)|'R' (82)|'S' (83)|'T' (84)|'U' (85)|'V' (86)|'W' (87)|'X' (88)|'Y' (89)|'Z' (90)|'[' (91)|'\' (92)|']' (93)|'^' (94)|'_' (95)|
|'`' (96)|'a' (97)|'b' (98)|'c' (99)|'d'(100)|'e'(101)|'f'(102)|'g'(103)|'h'(104)|'i'(105)|'j'(106)|'k'(107)|'l'(108)|'m'(109)|'n'(110)|'o'(111)|
|'p'(112)|'q'(113)|'r'(114)|'s'(115)|'t'(116)|'u'(117)|'v'(118)|'w'(119)|'x'(120)|'y'(121)|'z'(122)|'{'(123)|'|'(124)|'}'(125)|'~'(126)|''(127)|

最後のASCIIコード127は実は印字できない文字なので、 ここだけ垂直バー|がずれているのはご愛嬌です。

プログラムの構造解説

さて、プログラムにはコロン「:」で始まる行がありますが、 それが概ねプログラムの処理のひとかたまりを表します。

それらがどのような順で実行されるかを示したものが下図になります。

プログラムの処理順

詳しく解説します。

プログラム開始地点

まず、プログラムの開始地点は「:1」というブロックです。 そこから表の縦方向(行)のループの開始地点「:loopY」と、横方向(列)のループの開始地点「:loopX」を通ります。

プログラム開始地点

この間、スタックにはループカウンタの役割を果たす値がプッシュされます。

文字コード計算と出力

次に、「表示する文字コードを計算」から「文字コードを表示」までの処理です。次の部分にあたります。

表示処理(1文字)

例えば、文字コードが100(英小文字d)のときは、この処理で次の文字列が出力されます。

|'d'(100)

各ブロックの処理は次のとおりです。

ブロック名 処理説明
:loopX 文字コードの計算(X+16*Y
:out_bar 垂直バー|の出力
:out_bar_return 次の処理の決定
:out_bar_return1 (次の処理)
:out_char 文字出力(例:'d'
:out_char_return (次の処理)
:out_num, :out_num_out 文字コードの出力(例:(100)

改行判定〜改行しない場合

次に、「:out_num_return」というブロックで改行判定をします。

まだ1行ぶんの出力が終わっていないなら改行不要と判定されます。この場合は、横方向のループカウンタxに1を足して、「:loopX」に戻り、 次の文字の出力を続けます。

同じ行で処理を続ける場合

このループで表の1行が出来上がっていきます。 ただし、右端に垂直バーが出ないことに注意です。

|'`' (96)
|'`' (96)|'a' (97)
|'`' (96)|'a' (97)|'b' (98)
|'`' (96)|'a' (97)|'b' (98)|'c' (99)
|'`' (96)|'a' (97)|'b' (98)|'c' (99)|'d'(100)
|'`' (96)|'a' (97)|'b' (98)|'c' (99)|'d'(100)|'e'(101)
...

改行判定〜改行する場合

1行ぶんの表示が終わった場合は、改行します。 ただし、改行の前に表の右端を閉じるため垂直バー|を出力します。 垂直バーを出力する処理は「:out_bar」ブロックで一度書いたので、使い回すことにします。 その後、ループカウンタを更新(yに1を足し、xを0にする)して、横方向のループを再開します。

改行処理は次の部分で行なっています。

次の行に移る場合

ブロック名 処理説明
:out_num_return 文字コード出力後の処理先兼、改行判定
:out_bar 垂直バー|の出力
:out_bar_return 次の処理の決定
:out_bar_return2 (次の処理)
:out_NL 改行文字出力
:out_NL_return (次の処理)

改行する前に、「:out_num_return」ブロックから「:out_bar」に入り、垂直バー|を出力します。

同じ「:out_bar」の処理でも、「:loopX」から入った時と、「:out_num_return」から入った時とで、処理ルートを変える必要があります。そのために「:out_bar」に入る直前にスタックにフラグをプッシュしておき、その値に応じて行き先を変えます。

プログラムをみると、「:loopX」と「:out_num_return」の最後はいずれもgoto:out_barです。 その直前で、「:loopX」ではpush1を、「:out_num_return」ではpush2を実行して、フラグをプッシュしています。 「:out_bar_return」では、フラグが1なら「:out_bar_return1」に進み、フラグが2なら「:out_bar_return2」に進むように設計されているため、処理ルートを変えることができます。

Pietはスタックが唯一のメモリですので、同じ処理を使い回す場合は、フラグを使って処理順序を変えます。

終了判定

「:out_NL_return」ブロックは、プログラムの終了判定もしています。ここでは、縦方向ループカウンタyの値をチェックし、7を超えたら終了、「:end」に進みます。

終了

おまけ〜表の見栄えを良くする

ここまでの説明に出てこなかった下図の部分の補足です。

表の幅の補正

この部分は、表の1マスのサイズを揃えるために、 半角スペースを出力する処理をしています。

この処理が抜けると、下に示すように、 文字コードの数値が2桁か3桁かによって表のマスの長さが変わり、垂直バーの位置がずれてしまいます。 (右端までスクロールするとわかりやすいです。申し訳ないですが、スマホだとわからないかもしれません・・・。)

|' '(32)|'!'(33)|'"'(34)|'#'(35)|'$'(36)|'%'(37)|'&'(38)|'''(39)|'('(40)|')'(41)|'*'(42)|'+'(43)|','(44)|'-'(45)|'.'(46)|'/'(47)|
|'0'(48)|'1'(49)|'2'(50)|'3'(51)|'4'(52)|'5'(53)|'6'(54)|'7'(55)|'8'(56)|'9'(57)|':'(58)|';'(59)|'<'(60)|'='(61)|'>'(62)|'?'(63)|
|'@'(64)|'A'(65)|'B'(66)|'C'(67)|'D'(68)|'E'(69)|'F'(70)|'G'(71)|'H'(72)|'I'(73)|'J'(74)|'K'(75)|'L'(76)|'M'(77)|'N'(78)|'O'(79)|
|'P'(80)|'Q'(81)|'R'(82)|'S'(83)|'T'(84)|'U'(85)|'V'(86)|'W'(87)|'X'(88)|'Y'(89)|'Z'(90)|'['(91)|'\'(92)|']'(93)|'^'(94)|'_'(95)|
|'`'(96)|'a'(97)|'b'(98)|'c'(99)|'d'(100)|'e'(101)|'f'(102)|'g'(103)|'h'(104)|'i'(105)|'j'(106)|'k'(107)|'l'(108)|'m'(109)|'n'(110)|'o'(111)|
|'p'(112)|'q'(113)|'r'(114)|'s'(115)|'t'(116)|'u'(117)|'v'(118)|'w'(119)|'x'(120)|'y'(121)|'z'(122)|'{'(123)|'|'(124)|'}'(125)|'~'(126)|''(127)|

これを防ぐため、文字コードが2桁の場合は、文字出力('c'など)の後ろに半角スペース' 'を1文字出力してから、文字コードの出力((99)など)をしています。これで垂直バーの位置ずれを防ぐことができます。

おわりに

今回は、プログラムの流れを図示しながら解説してみました。 ブログの更新を1週間サボってこれらの図を半自動で作成できるツールを作っていました。 おかげで説明する側としても労力がだいぶ減りましたし、 多少なりともわかりやすくなっていれば良いと思います。

今回はスタックの細かい変化は省きましたが、 Pietでプログラミングするときにはスタックの変化も追っていく必要があります。 今後はスタックの変化も図にできたらいいなあと思いつつ、 図示用ツールを作成中です。

次回は何を題材にしようかちょっと迷ってますが、 算数・数学チックな話になるのではないかと思います。 (素数判定とか、分数計算とか)

ではまた。