はじめに
こんにちは、麻菜結です。前々から自作のプログラミング言語を作ってみたくて、それがそこそこ形になったので共有する記事です。ソースコードも公開していますので、「人生で一度はプログラミング言語作りたいよな」と考えている方はご参考になれば幸いです。
nouzen
どんな言語なのかをかいつまんでいうと、「Brainf**kとForthにインスパイアされた、スタック指向なgoto言語」です。よろしくお願いいたします。
環境
環境はWindows10
とPython
です。Python3系
が用意出来れば多分動くんじゃないでしょうか。
Windows10
Python 3.9.0
スタック指向な言語とは?
Forth言語に慣れ親しんだ方はここ飛ばしてください。
スタック指向な言語とは、Forth
に代表されるようなプログラミングパラダイムを持つ言語です。特徴は何といっても記法でしょう、下の足し算を行うプログラムを見てください。
2 3 + .i
普通なら2 + 3
と記述しそうなものですが、スタック指向な言語nouzen
ではこれが正しい記法です。これで何が行われているのかを見ていきましょう。
まず、データ構造のスタックがわかっていなければいけません。スタックのWikipedia を見てみます。
スタックは、コンピュータで用いられる基本的なデータ構造の1つで、データを後入れ先出し(LIFO: Last In First Out; FILO: First In Last Out)の構造で保持するものである。
2つの基本操作プッシュ(push)とポップ(pop)を持つ。Pushは指定されたノードをスタックの先頭(トップ)に追加し、既存のノードはその下にそのまま置いておく。Popはスタックの現在のトップのノードを外してそれを返す。
スタック、日本語で言うと積み重ねたものとか干し草の山などの意味がありますが、積み重ねられた山に対して、また新たに積み重ねる(Push)か、一番上からとってくる(Pop)かの操作を行うことができる構造です。
それではもう一度プログラムを見てみます。
2 3 + .i
プログラムは左から解釈していきます。
トークン | 意味 | スタックの状態 | |
---|---|---|---|
1 | 2 | 数値 2 をスタックにPush | 2 |
2 | 3 | 数値 3 をスタックにPush | 2, 3 |
3 | + | スタックから2回Pop、その値を加算してスタックにPush | 5 |
4 | .i | スタックからPop、その値を数値として出力 | 空 |
このように実行されます。この言語のメリットは、難しい構文解析を用いなくても他の高級な言語と遜色ないぐらい計算をすることができるからでしょう。また、この記法は日本人にとってメリットが存在すると言われています。
日本人は2+3
をするときに「2と3を足して」と言うと思いますが、スタック指向な言語はそのような考え方をそのまま書き下したような記述がすることができます。
また、もう少し詳しい方なら「逆ポーランド記法じゃん」と思うかもしれません。逆ポーランド記法でプログラミングできる言語、それがForth言語、ならびにそれにインスパイアされたnouzenなのです。
Brainf**k要素
Brainf**k的な要素には、条件分岐やループなどの時に現れます。
括弧記号()
[]
{}
にはgoto的な挙動が割り振られています。
( )
:括弧はじめが実行されたとき、スタックからポップ、その値が0
以外だったら、括弧の中の処理を実行。0
だったら対応する括弧閉じへジャンプする。
1 ( "True" .s )
0 ! ( "False" .s )
# ! 真偽反転
[ ]
:無条件ジャンプ。括弧はじめが実行されると、対応する括弧閉じへジャンプする。
1 .i [ 2 .i ] 3 .i
# 13と出力
{ }
:ループ。括弧閉じが実行されたとき、対応する括弧はじめへジャンプする。つまり何もしなければ無限ループになる。
{ "Infinite Loop" .s }
これらを組み合わせて、具体的にはループ内で条件分岐を行い、条件分岐の括弧の中でループの外にジャンプするするような書き方をすれば、
5 { _
_ .i # Loop Content print countdown
! ( [ ) 1 - } ]
# 543210
こんな感じでfor
文のようなものがプログラミングできます。もし無限ループになってしまっても、一定回数実行したらプログラムが勝手に止まるようになっているので、
limit over execute token
安心ですね。
実行方法
上で示したリポジトリのアクセスしてプログラムをローカルにダウンロードします。
> git clone https://github.com/hempgreens/nouzen
ダウンロードしたディレクトリに移動します。sampleディレクトリにサンプルプログラムが入っています。
> python nouzen.py sample/helloworld.nz
マニュアルはmanual.mdです。マークダウン形式で記述されています。
Hello World!
じゃあまずはHello World
したいですよね。文字列はダブルコーテーション"
で囲むことで定義することが出来ます。.s
は文字の直後に書くことでその文字を出力することが出来ます。
"Hello World\n" .s
適当な名前で保存して実行してみてください。サンプルプログラムの拡張子は.nz
になっていますが、ただのテキストファイルなのでなんでも大丈夫です。
次にFizzBuzz
をやってみましょう。
### do $ i $ lim i { $ i i _ lim < ! ( [ )
### loop 1 + } ]
: cr '\n' . ; # 改行
31 1 #do
i 15 % ! ( "FizzBuzz" .s [ ) # goto fb
i 3 % ! ( "Fizz" .s [ ) # goto fb
i 5 % ! ( "Buzz" .s [ ) # goto fb
i .i
] ] ] # fb
cr
#loop
-
###
で文字列置換マクロを使うことが出来ます。やっぱdo
loop
があるとForth感でますね。
#do
#loop
のように書くことでその場所にマクロ展開をすることが出来ます。 -
: ;
はサブルーチン定義です。いわゆる関数のように使うことが出来ます。 -
$
は変数宣言です。スタックからポップして、その値で初期化します。 -
#
はコメントです。改行までのコメントが#
で、コメントアウトは##
で囲むことで出来ます。 -
%
は割り算した余りをスタックにプッシュします。
それでは次にフィボナッチ数列の出力してみましょう。この言語特有のやりかたで簡潔に書くことが出来ます。
: swap $ t1 $ t2 t1 t2 ;
: over $ t1 $ t2 t2 t1 t2 ;
: fibo swap over + _ .i cr ;
: cr 10 . ;
1 1
fibo
fibo
fibo
fibo
fibo
# 2
# 3
# 5
# 8
# 13
基本はfibo
サブルーチンが本体です。こんなに小さくかけるなんてすごいですね。
@iigura 様の記事を参考にさせていただきました。「最短(?)のフィボナッチ数列計算プログラム」
おわりに
お疲れ様でした。説明が雑な所が多々ありますが、雰囲気で読んでください。もしバグやマニュアル読んでも分からんところがあったら、コメントで優しく教えてください。
これで、「自作のプログラミング言語ですか?まあForthっぽい言語なら作ったことありますよ。」という実績を解除することが出来たので満足です。これからもちょこちょこ改修したりこれで作ったプログラムを紹介したりするかもしれませんね。
ここまで読んでくださってありがとうございました。機会があればまた記事を読んでくださるとうれしいです。
いや、それにしても驚きました。本体のプログラムを作ったのは4日ほどだけど、仕様をつくるまで1年以上練っていたので、あっさり作れてしまった事に驚きました。みなさんもプログラミング言語を自作してにやにやしてみては?