星野が作ったインタプリタ(ip.g01)の上で動くプログラムを作る事を目標とします。
そのインタプリタはefg01で動くので、結局作ったアプリも(間接的に)efg01の上で動くことになります。
ちゃんとしたチュートリアルはそのうちhideyosiさんが作られるようです。
また、K-tanさんも以前にGUIGUI01/memoシリーズでC言語やアセンブリでのg01アプリの作り方を書かれています。
インタプリタ上で動くプログラムなんか興味無い!という方はぜひそちらをご覧ください。
efg01とip.g01を手に入れてください。
ip.g01はhttp://osask.net/w/513.htmlから入手できます。
後はip.g01とefg01を同じフォルダに入れておいてください。
古今東西、いかなるプログラミング言語であっても伝統的に入門時はHello, world!と表示するプログラムを書くことになっています。
この講座でもその伝統に従って、最初はHello, world!と表示するプログラムをHQ9+で書きたいと思います。
H
上のような内容にしたテキストファイルをhello.txtという名前で保存します。
このプログラムを実行したいときは、コンソールを開いて次のように打ち込みます。
プロンプト>efg01 ip02 file:hello.txt type:10
これで無事にHello, world!プログラムを作ることが出来ました。
もし、上のように打ち込んでもHello, world!と表示されなければ、それまでのところで何か失敗をしているはずです。
もうお分かりかもしれませんが、HQ9+でHello, world!と表示するには、Hという命令を実行させます。
このように初心者に非常に分かりやすく、覚えやすい文法を採用しているのがHQ9+の特徴です。
なお、もしHello, world!と二回表示するプログラムを作りたければ、
HH
という内容のファイルを実行させます。
Hello, world!と表示するだけでは飽きてしまったと思いますので、今回はファイル出入力の一環としてHQ9+プログラムが書かれたファイルの中身を表示させてみようと思います。
まずは、プログラムを以下に示します。
Q
これを実行させると、確かにQと表示されます。
HQ9+では、自分自身のファイルをオープンして内容を読み込み、それを表示する命令が用意されています。
これがQという命令で、もし仮に二回HQ9+のプログラムが書かれたファイルの内容を表示させたければ、
と書かれたファイルを実行します。
これでは、「QQ」というファイルの中身を二回表示するので、
QQ QQ
と表示されます。
これまで作ってきたプログラムはどれも実用的とは言えませんでした。
そこで、今回は99 Bottles of Beerという英語の歌の歌詞を表示させるプログラムを作りたいと思います。
これが有れば、何かの機会に99 Bottles of Beerを歌わないといけなくなったときに、すぐに歌詞を思い出すことが出来ます。
では、今回のプログラムです。
9
これを実行すると、かなり長い歌詞が表示されます。
このように、HQ9+では9という命令を実行させることで、99 Bottles of Beerの歌詞を表示させることが出来ます。
これまで見てきたように、HQ9+は非常にシンプルで覚えやすいプログラミング言語です。
この言語の特徴は変数、という物の扱いにも現れています。
変数とは、簡単にいえば色々な物を入れておく箱の事ですが、HQ9+では特に宣言をしなくても変数を使う事が出来ます。
つまり、最初から0で初期化された変数が一つ用意されているわけです。
この変数の値を1増加させるときには、+という命令を使います。
+++
上のようなHQ9+プログラムは、最初から用意されている変数に1を3回足すので、変数の中身が3になります。
まずは、次のようなプログラムを実行させてみます。
This is HQ9+ program !
これを実行すると、
Hello, world! This is HQ9+ program ! 99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. (中略) No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall.
と表示されます。(もしかしたら長すぎて上のほうがこうンソールからはみ出すかもしれません。)
HQ9+では、H、Q、9、+、以外の全ての文字がコメントとして無視されます。
よって、上のプログラムは
HQ9+
という内容とみなされて実行されます。
勿論、HQ9+でも一つのプログラムの中で複数の種類の命令を使う事が出来るので、上記のような結果となるわけです。
最後に、もうひとつプログラムを紹介します。
#include<stdio.h> void main(void){ printf("Hello,world!"); return; }
このプログラムも、コメントの部分を無視すると以下のようになります。
H
よって、このプログラムはHello, world!と表示します。
HQ9++とは、C言語に対するC++のように、HQ9+と互換性を保ちながら、オブジェクト指向を取り入れた言語です。
新たに++という命令が追加されています。
この命令は、変数に2を足し、さらにオブジェクトのインスタンスを生成します。
HQ9++で書かれたプログラムをip01.g01で実行するときは、
プロンプト>efg01 ip02 file:ファイル名 type:11
とコマンドプロンプトに打ち込みます。
HQ9++
というプログラムを実行すると、
Hello, world! HQ9++ 99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. (中略) No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall.
と表示され、プログラム終了時の変数の値は2で、オブジェクトのインスタンスが生成されています。
ただし、情報隠蔽の原理に従って、このオブジェクトへのアクセスはできない様になっているようです。
HQ9F+も、HQ9+と互換性のある言語のひとつです。
HQ9F+で書かれたプログラムをip02.g01で実行するときは、
プロンプト>efg01 ip02 file:ファイル名 type:12
とコマンドプロンプトに打ち込みます。
新たにFという命令が加えられています。
この命令は、0から命令が実行された時の変数の値の数まで、一つずつ数を数えていき、その数が3で割れればFizzを、5で割れればBuzzを、3でも5でも割れればFizz Buzzを、3でも5でも割れなければその数自身を表示させます。
具体的には、
++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ F
というプログラムは、
Fizz Buzz 1 2 Fizz 4 Buzz (中略) Fizz 97 98 Fizz Buzz
と表示されます。
また、お分かりだと思いますが、HQ9F+では、Fもコメントではなく命令として扱われるのでご注意ください。
前回はHQ9+系統のプログラミング言語を使ってプログラムを作りました。
しかし、HQ9+は初心者用の言語なので、コンピュータが本来処理できる計算の、ほんの一部しか実行できません。
これに対して、今回扱うBrainf*ck系統のプログラミング言語はコンピュータが行える全ての計算が可能である、とされています。
最初のうちはちょっと難しく感じるかもしれませんが、頑張ってください。
伝統は大切にするべきなので、いきなりですがHello, world!と表示するプログラムを以下に示します。
++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++. ++++++++++ ++++++++++ +++++++++. +++++++. . +++. ---------- ---------- ---------- ---------- ---------- ---------- -------. ---------- --. ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ +++++++. --------. +++. ------. --------. ---------- ---------- ---------- ---------- ---------- ---------- -------.
かなり意味不明だと思いますが、これから少しずつ説明しますのでご安心を。
Brainf*ckは仮想的なコンピュータの機械語、ととらえることが出来ます。
しかも、機械語としては非常に高い可読性を持っています。
その仮想機械は一つのレジスタと、1024バイトのメモリを持っています。
(この1024という数字は星野が勝手に決めた物であり、処理系によって変化します。)
レジスタもメモリも最初は0で初期化されています。
機械語は全部で8種類あります。
+ レジスタが指しているメモリのアドレスにある値を1増加させます。 - レジスタが指しているメモリのアドレスにある値を1減少させます。 > レジスタの値を1増加させます。 < レジスタの値を1減少させます。 [ レジスタが指しているメモリのアドレスにある値が0なら、対応する]の直後にジャンプします。 ] レジスタが指しているメモリのアドレスにある値が0以外なら、対応する[にジャンプします。 . レジスタが指しているメモリのアドレスにある値を、文字コードとみなして出力します。 , レジスタが指しているメモリのアドレスに、入力された文字のコードを代入します。
上記以外の文字はコメントとみなされて全て無視されます。
まず、先にあげたソースの解説をしようと思います。
++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++. ++++++++++ ++++++++++ +++++++++. +++++++. . +++. ---------- ---------- ---------- ---------- ---------- ---------- -------. ---------- --. ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ +++++++. --------. +++. ------. --------. ---------- ---------- ---------- ---------- ---------- ---------- -------.
1~2行目の3番目のブロックまででメモリの0番地の値を72にして、出力させています。
この72はHの文字コードです。
3行目の1番目のブロックまでで、メモリの0番地の値にさらに29を足して、eの文字コードである101を作り、出力しています。
(中略)
9行目の3番目のブロックまでで、メモリの0番地の値から67を引いて!の文字コードである33を作り、出力しています。
ただ、このやり方はかなりカッコ悪いので、もう少し短いソースでHello, world!と表示させてみます。
++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++. ++++++++++ ++++++++++ +++++++++. +++++++. . +++.> ++++++++++ ++++++++++ ++++++++++ ++++++++++ ++++. ---------- --.< ++++++++. --------. +++. ------. --------.> +.
前のソースが373文字(スペース、改行を除く)だったのに対して、今回は217文字にまで減らせました。
(それでもHQ9+の1文字には全然届きませんが…)
3行目の4番目のブロックまでは前回と同じです。
ここで、>命令が実行されています。
これによって、レジスタはメモリの1番地をさすようになります。
メモリの1番地の値はこのとき0ですが、これを4行目の4番目のブロックまでで,の文字コードである44にして出力します。
5行目の1番目のブロックでスペースを出力した後、レジスタの値を1減らしています。
これで再びレジスタはメモリの0番地をさすようになり、そこにはoの文字コードである111が入っています。
今度はそれをいじって文字を出力していき、最後にもう一回1番地の値をいじって!を出力させて終了です。
少しはましになりましたが、まだまだカッコイイとは言えないソースです。
もうちょっと工夫を加えてみます。
+++++++ [>++++++++++<-] >++. ++++++++++ ++++++++++ +++++++++. +++++++. . +++.> ++++ [>++++++++++<-] >++++. ---------- --.<< ++++++++. --------. +++. ------. --------.>> +.
これで152文字にまで減らすことが出来ました。
ソース自体も、[や]が加わったことでBrainf*ckらしいカッコよさが出てきました。
今までは72を作るときに、1+1+1+1+(中略)+1=72と考えていました。
この考えを捨てて、10*7+2=(1+1+1+1+1+1+1+1+1+1)*(1+1+1+1+1+1+1)+1+1=72として計算したのが上のソースです。
まず最初に、0番地の値を7にします。
0番地の値は7なので[は素通りして、>でレジスタは1番地をさします。
1番地の値に10を足した後、<で0番地へもどり、1を引きます。
それでもまだ値は6なので[へもどり、さらに1番地に10を足します。
また、0番地の値を1減らしますが、まだ5なので[へもどります。
これを繰り返すと、1番地の値が70になったときに0番地の値が0になり、ようやく]の次へ進みます。
それに2を足すので72になり、無事にHが出力されます。
2行目の最後でoを出力した後も、同じ要領で4回ループさせ、3番地の値を40にします。
それに4を足して44にし、,を出力します。
4行目と5行目の最後が<<と>>になっているのは、2回目のループでカウンタとして使った2番地を飛び越えるためです。
最後に、Brainf*ckのカッコよさが最大限に発揮されたHello, world!を書きます。
+++++++++ [>++++++++>+++++++++++>+++++<<<-] >. >++.+++++++..+++. >-.------------. <++++++++.--------.+++.------.--------. >+.
119文字、という事は、最初のプログラムの1/3以下という事です。
簡単に解説すると、最初に0番地をカウンタにして9回ループさせます。
このループで1番地が72、2番地が99、3番地が45になります。
後は、1番地を使ってHを、2番地を使って、e、l、l、o、w、o、r、l、dを、3番地を使って,、 、!を出力させるだけです。
g01アプリをabcdwを使って学んだ人なら誰でも知っているechoプログラムを製作します。
とはいいつつも、ものすごく簡単なので説明は省きます。
+[>,.<]
これさえあれば、一時的にコンソールにメモをすることが出来ます。
同じくabcdwでおなじみのcharsプログラムです。
0x20~0x7eの文字コードを持つ文字を出力させます。
これも、最終段階のHello, world!が理解できた人なら難なく理解できると思います。
+++++++++ [>++++++++++>++++<<-] >+++++ >----< [>.+<-]
色々と最適化の余地があるので、考えてみてください。
K-tanさんがGUIGUI01/memoシリーズで作ったプログラムの一つである、1~10の合計を数えるプログラムをBrainf*ckで作っていきたいと思います。
++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ + .
これは誰でも思いつくと思います。
これを実行すると、7と表示されます。7の文字コードは55なので、1~10の合計は55であることが分かります。
しかし、この方法だと55回+を打ち込まないとこのプログラムは作れないので、実行しなくても何回+を打ち込んだか数えればいい話です。
それはBrainf*ck的には勿論のこと、一般常識に照らし合わせてみても全然カッコ良くないので、ループを使って数えさせてみます。
++++++++++ [[>+>+<<-]>-[<+>-]<] >>.
ちょっと複雑なループですが、要は1~10の合計を数えさせているだけなので、じっくりと読んでみてください。
さきほどのプログラムに比べればだいぶ良くなりましたが、答えは55なのに7と表示されるのがあまりカッコよくありません。
しかし、Brainf*ckで55と表示させるのはかなり難しいので、1~10の合計の数だけ1を表示して、1行ずらして10ごとに0~9の数字を表示させてみます。
ただ、文字コードを全て暗記している人にとっては、わざわざ1を数えるよりも7と表示されたほうがいいかもしれないので、7と表示する部分も残しておきました。
++++++++++ [[>+>+<<-]>-[<+>-]<] >>. >++++++++++.++++++++++++++++++++++.------------------------- [>+>+++++++<<-] < [>>?>.<<<-] >++++++++++. >>>>+++++ [>++++++++++>++++++<<-] >-- >++ >++++++++++ [><<<.+>>>+++++++++[><<<.>>><-]<-]
かなり汚いプログラムですいません。
書いているときは全く問題なかったのですが、今改めて見ると書いた自分でも「なんじゃこりゃ?」状態です。
しかし、それがBrainf*ckの醍醐味だと思うので気にしないことにします。
なお、プログラム中の?を.に変えれば、一部のOSでは1~10の合計と同じ回数音が鳴ります。
うるさくしても問題ない環境で試してみてください。
最後に、これまでの集大成として、1~10の合計を文字コードとみなして表示する前にASCIIcodeと表示させたいと思います。
これでいきなり7と表示されても戸惑う事が少なくなるはずです。
++++++++++ [>++++++>++++++++>++++++++++>+++++++++++<<<<-] >+++++.>+++.<++.++++++..>>-.>+.<+.+.>> ++++++++++ [[>+>+<<-]>-[<+>-]<] >>. >++++++++++.++++++++++++++++++++++.------------------------- [>+>+++++++<<-] < [>>?>.<<<-] >++++++++++. >>>>+++++ [>++++++++++>++++++<<-] >-- >++ >++++++++++ [><<<.+>>>+++++++++[><<<.>>><-]<-]
?を.に変えると音が鳴るのは今回も変わりません。
このレベルのプログラムが問題なく読めて、作れるようであれば、十分変人だと思います。
MONAmonaはBrainf*ck系統の言語です。
ip02.g01でMONAmonaで書かれたプログラムを実行するには、
プロンプト>efg01 ip02 file:ファイル名 type:1
と入力します。
MONAmonaでは>、<、+、-、,、.、[、]、がそれぞれM、O、N、A、m、o、n、a、に置き換えられます。
つまり、
M ポインタを1進める O ポインタを1戻す N ポインタの指す所の値を1増やす A ポインタの指す所の値を1減らす m ポインタの指す所へ値を入力 o ポインタの指す所の値を出力 n ポインタの指す所の値が0だったら対応するaまでジャンプ a ポインタの指す所の値が0でなかったら対応するnまでジャンプ
となります。
MONAmonaでは、M、O、N、A、m、o、n、a、以外の文字は全てコメントとして無視されます。
以下に、MONAmonaでのHello, world!を示します。
NNNNNNNNN +++++++++ nMNNNNNNNNMNNNNNNNNNNNMNNNNNOOOAa [>++++++++>+++++++++++>+++++<<<-] Mo >. MNNoNNNNNNNooNNNo >++.+++++++..+++. MAoAAAAAAAAAAAAo >-.------------. ONNNNNNNNoAAAAAAAAoNNNoAAAAAAoAAAAAAAAo <++++++++.--------.+++.------.--------. MNo >+.
勿論右側は無視されます。
BrainCrashはBrainf*ckを拡張した言語です。
新たにビット演算を行う命令が加えられました。
また、0バイトでHello, world!プログラムが書ける言語としても有名です。
ip02.g01でBrainCrashで書かれたプログラムを実行するには、
プロンプト>efg01 ip02 file:ファイル名 type:2
と入力します。
以下に、BrainCrashの追加機能を示します。
追加命令
| レジスタが指しているメモリのアドレスにある値と、その次のアドレスにある値をOR計算し、 レジスタが指しているアドレスの次のアドレスに値を入れます。 また、レジスタの値は一つ進められます。 & レジスタが指しているメモリのアドレスにある値と、その次のアドレスにある値をAND計算し、 レジスタが指しているアドレスの次のアドレスに値を入れます。 また、レジスタの値は一つ進められます。 ~ レジスタが指しているメモリのアドレスにある値をビット反転(NOT計算)させます。 レジスタの値は変化しません。 ^ レジスタが指しているメモリのアドレスにある値と、その次のアドレスにある値をXOR計算し、 レジスタが指しているアドレスの次のアドレスに値を入れます。 また、レジスタの値は一つ進められます。
開始、終了時の挙動
処理開始時、メモリにはHello, world!に相当する72,101,108,108,111,44,32,119,111,114,108,100,33が0番地よりこの順で積まれます。 終了時、現在レジスタが指し示すアドレスの値が0になるまでレジスタを1づつ進めて出力します。
よって、BrainCrashでHello, worldを書くと、以下のようになります。
Let us write Hello world !
何かしら英文が書かれていますが、全部コメントなので無視されます。
これを実行すると、まずメモリにHello, world!に当たる文字コードが0番地から順に積まれ、レジスタは0番地を示しています。
そして、プログラムが何も書かれていないので、このまま終了処理に移行し、Hから順に文字が出力されつつレジスタの値が増加していきます。
!に当たる33を出力し、レジスタの値が1増加すると、レジスタが指し示すアドレスの値が0になるので、そこで処理が終了します。
BrainCrashのインタプリタでBrainf*ckのプログラムを動かす際には、
[>] Brainf*ckのプログラム [>]
とします。
ただし、ip.g01ではtypeの値を0にすればBrainf*ckのプログラムとして実行できるので、通常は使う事のないテクニックです。
前回までは、難解プログラミング言語と呼ばれる言語でプログラミングを行っていました。
やはりそれらの言語では実用性が高いとは言えません。
今回扱うeasy Forthは立派な実用言語であるForthを少し変更を加えた実用言語です。
これでip.g01でのプログラミングもかなり楽になるはずです。
ちなみに、easy Forthの「easy」は処理系にとって処理しやすいという意味で、初心者向け、という意味ではありません。
Forth自体が十分簡単な言語ですので、お間違えの無いように。
今回もHello, world!と表示するプログラムから解説していきます。
: main "!" "d" "l" "r" "o" "w" " " "," "o" "l" "l" "e" "H" PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT ;
以上を実行すると、Hello, world!と表示されます。
easy Forthでは、プログラムはワードと呼ばれる物の集まりで出来ています。
このプログラムでは「:」「main」「"!"」「PUT」「;」などがワードです。
ワードとワードの間は一つ以上のスペースか改行で区切られている必要が有ります。
最初の「:」というワードは新たに自分でワードを定義することを宣言するワードです。
このように、easy Forthでは、最初から定義されているワード以外に自分でワードを定義することが出来ます。
「:」の次にワードが新たに定義するワードの名前となり、「;」までがワードの内容となります。
ここでは、mainというワードを定義していますが、インタプリタは実行を開始すると、このmainという名前のワードを探し、そこから処理を始めます。
そのため、easy Forthでは、必ずmainというワードを定義することが必要になります。
さて、ワードの中身ですが、二行目に"!"、"d"、"l"…というワードが有ります。
easy Forthでは、スタックと呼ばれる靴下のような物を使って色々な処理をするのですが、この"と"で文字が囲まれたワードはその文字のデータをスタックの中に放り込みます。
つまり、二行目が終わった段階で靴下の中身は以下のようになります。
(左側が靴下の口です。)
------------------------------+ H e l l o , w o r l d ! | ------------------------------+
Hello, world!を逆の順番でスタックに入れていったので、スタックの奥に!がきます。
三行目のPUTというワードは、スタックから一つデータを取り出して、文字として出力するワードです。
勿論靴下は物を入れたり出したりする穴は一つしかないので、最後に入れたHから、順番に出力されていきます。
このようにして、mainはHello, world!を出力するワードとして定義され、それがインタプリタによって実行されて、Hello, world!が表示されたわけです。
しかし、いちいちこんな書き方をしていたのでは、めんどくさいです。
以下に、ちょっとめんどくさくなくなったHello, world!を示します。
: main "!dlrow ,olleH" PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT PUT ;
PUTの連続がカッコ悪いのは変わりませんが、だいぶんまともに見えます。
easy Forthでは、もし"と"の間の文字が二つ以上なら、一つ一つに分解して一つずつスタックに入れてくれます。
そのため、最初の"!dlrow ,olleH"は"!" "d" "l" "r" "o" "w" " " "," "o" "l" "l" "e" "H"と解釈されるわけです。
つぎに、PUTの連続を何とかしたいと思います。
: main 0 "!dlrow ,olleH" print ; : print BGN DUP WHL PUT RPT DRP ;
mainの次に、0というワードが有ります。
これは、スタックに0を積むワードです。
また、"!dlrow ,olleH"の次にprintというワードが有ります。
これは、最初から定義されているワードではなく、二行目で定義しているワードです。
ここで、処理が二行目に移ります。
二行目では、C言語で言うところのwhile文が使われています。
easy Forthでの繰り返し処理は、以下のように書きます。
BGN ワード列(1) WHL ワード列(2) RPT
まず、ワード列(1)が実行されて、WHLに到達した時点でスタックから一つデータが取り出されます。
そのデータが0ならRPTの次まで処理をジャンプします。
そのデータが0以外ならばワード列(2)を実行し、BGNに戻り、以上の処理を繰り返し行います。
今回ワード列(1)のところにあるのはDUPだけです。
このDUPはスタックから一つデータを取り出し、それを二回スタックに入れます。
つまり、スタックの一番上のデータを複製します。
最後のDRPは、スタックから一つデータを取り出し、それを捨てるワードです。
では、print全体の処理を見てみます。
まず最初にワード列(1)のDUPで、スタックの一番上にあるHが複製されます。
次にWHLで複製されたHが取り出されます。
Hの文字コードは72なので、0以外である為、ワード列(2)のPUTが実行されます。
PUTは、スタックからHを取り出し、表示します。
もう一度BGNの次のDUPが行われてeが複製され、0以外なので、表示されます。
これを繰り返して、!まで無事表示されます。
!を表示してBGNに戻ると、DUPで今度は0が複製されます。
そして、WHLで0が取り出され、0なのでPUTを行わずにRPTの次へ処理が移ります。
スタックの中に残った0をDRPで掃除します。
次は、ワードの定義が終了した事を示す;が有るので、printワードを呼び出したmainワードに処理が戻る、という流れです。
これでだいぶんカッコ良くなりましたが、Hello, world!が逆向きなのが気に食わないので、これを何とかします。
: main 0 "Hello, world!" print ; : print invert BGN DUP WHL PUT RPT DRP ; : invert VAR pointer VAR256 words 0 pointer ! 256 BGN DUP WHL 1 - DUP words + 0 SWP ! RPT DRP BGN DUP WHL words pointer @ + ! pointer @ 1 + pointer ! RPT 0 words pointer @ + ! 0 pointer ! BGN words pointer @ + @ WHL words pointer @ + @ pointer @ 1 + pointer ! RPT ;
とりあえず、Hello, world!は正しい向きになりました。
色々と新しいワードが出ていますが、これを今説明するのは難しいので、後回しにさせてください。
とりあえずこれで、printとinvertをきちんと定義すれば、0 "文字列" print とするだけで文字列が表示できるようになりました。
いきなりですが、easy Forthを使って計算をさせて見たいと思います。
: main 0 "11 + 3 = " print 11 3 + . "\n" PUT 0 "11 - 3 = " print 11 3 - . "\n" PUT 0 "11 * 3 = " print 11 3 * . "\n" PUT 0 "11 / 3 = " print 11 3 / . "\n" PUT 0 "11 % 3 = " print 11 3 % . "\n" PUT ; : print invert BGN DUP WHL PUT RPT DRP ; : invert VAR pointer VAR256 words 0 pointer ! 256 BGN DUP WHL 1 - DUP words + 0 SWP ! RPT DRP BGN DUP WHL words pointer @ + ! pointer @ 1 + pointer ! RPT 0 words pointer @ + ! 0 pointer ! BGN words pointer @ + @ WHL words pointer @ + @ pointer @ 1 + pointer ! RPT ;
これを実行すると、11+3、11-3、11*3、11/3、11%3の結果がそれぞれ表示されます。
(*は掛け算、/は小数点以下切り捨ての割り算、%は割り算の余り)
まず、0 "11 + 3 = " printのところは前回までの内容なのでいいと思います。
invertの中身は今回も説明しません。もう少し待って下さい。
2~6行目の最後の"\n" PUTはちょっと変に思われた方もいるかもしれません。
\nというのは改行を表す特別な文字で、この二文字で一文字として扱われます。
そのため、PUT一回で出力することが出来、これが出力されると改行されます。
さて、最後に残ったprintと"\n"の間の説明です。
代表して、2行目で説明します。
まず、11というワードが実行され、スタックに11が積まれます。
次に、3というワードが実行され、スタックに3が積まれます。
そして、+というワードでは、スタックから二つデータを取り出し、その二つを足して答えをスタックに積みます。
これで、11+3の答えである14がスタックに積まれるわけです。
最後に、.というワードは、スタックから一つデータを取り出し、それを数値として出力します。
ここでは、14が出力されます。
3~6行もほぼ同じです。割る方、割られる方、引く方、引かれる方に注意してください。
スタックの一番上のデータで(を)、二番目のデータを(から)割る(引く)します。
easy Forthでは、勿論if文も使う事が出来ます。
以下にサンプルを示します。
: main 100 RND DUP . 0 " + " print 100 RND DUP . 0 " = " print + , "\n" PUT = IF 0 "That's true !" print ELS 0 "That's false !" print THN ; : print invert BGN DUP WHL PUT RPT DRP ; : invert VAR pointer VAR256 words 0 pointer ! 256 BGN DUP WHL 1 - DUP words + 0 SWP ! RPT DRP BGN DUP WHL words pointer @ + ! pointer @ 1 + pointer ! RPT 0 words pointer @ + ! 0 pointer ! BGN words pointer @ + @ WHL words pointer @ + @ pointer @ 1 + pointer ! RPT ;
これを実行すると、足し算の問題が出題されます。
これに、正しい答えを入力してEnterを押すとThat's true !と、間違った答えを入力してEnterを押すとThat's false !と表示されます。
では、プログラムを見ていきます。
2行目にRNDというワードが二回出ています。
このワードは、スタックから一つデータを取り出し、0~取り出したデータ-1までの数で、ランダムに一つ選んでスタックに積みます。
今回はどちらも100が直前に積まれているので、0~99の間で、ランダムに数が積まれます。
(ちなみに、このランダムな数の事を乱数と言います。)
それらの数はそれぞれDUPで複製され、片方は.でそれぞれ出力されています。
" = "が出力された後、二つの乱数は足されています。
そして、,というワードがきます。
このワードは、このプログラムを実行している人に数字を入力してもらうワードです。
Enterで入力を終了します。
数字以外はもちろん入力できず、入力された物は数字として処理されます。
今回はこのワードで、それまでに表示した足し算の式の答えを入力してもらいます。
2行目が終わった時点で、スタックの中には、一番上に入力してもらった数値が、二番目に足し算の答えが入っています。
3行目では、いきなり=というワードが実行されます。
このワードはスタックから値を二つ取り出し、その二つの値が等しければ1を、等しくなければ0をスタックに積みます。
ここでは、足し算の答えと入力された答えが同じなら1が、違えば0がスタックに積まれます。
そしてついにIFです。
easy Forthでは、IFというワードは以下のように使います。
IF ワード列(1) ELS ワード列(2) THN
IFというワードが実行されるとスタックからデータが一つ取り出され、その値が0以外ならワード列(1)を、0ならワード列(2)を実行します。
もし、値が0の時何もしないのであれば、
IF ワード列(1) THN
とすることもできます。
今回は、答えが正解ならワード列(1)が、不正解ならワード列(2)が実行されます。
最後に、=と似たような意味を持つワード群を書いておきます。
= スタックの1番目のデータが2番目のデータと等しければ1を、そうでなければ0をスタックに積みます < スタックの1番目のデータが2番目のデータより小さければ1を、そうでなければ0をスタックに積みます > スタックの1番目のデータが2番目のデータより大きければ1を、そうでなければ0をスタックに積みます <= スタックの1番目のデータが2番目のデータより小さいか等しければ1を、そうでなければ0をスタックに積みます >= スタックの1番目のデータが2番目のデータより大きいか等しければ1を、そうでなければ0をスタックに積みます <> スタックの1番目のデータが2番目のデータと等しくなければ1を、そうでなければ0をスタックに積みます
一般用コメント一覧