Forth

Forthについてのメモ書き

本記事は
言語実装 Advent Calendar 2017
19日目向けの投稿です。

gyaboと申します。どうも学術よりのカレンダーだったようで、とても緊張しています。

Forthについて少し勉強したので書きます。
ついでにお試し処理系をC/C++言語で書きます。

Forthをご存知ですか?

Forth forth, FORTH
※どう書いてもいいらしいです。

兎に角、早速簡単にForthを使ってみましょう。

Forth はスタック指向であり逆ポーランド記法(RPN)

ex) 1 + 2を計算してprintf(印字)してください

[Forthで書くと]

1 2 + .

説明します。Forthはスタックを持っています。いろいろなものを積めます。
スタックの状態を以下に図示します。

※\は行末までコメントの意味。

1   \ 1  1をスタックに積む。
1 2 \ 2  2をスタックに積む。
3   \ +  +を計算する
    \ .  3 と出力される。. によってスタックの値は取り出されて空っぽになる

ex) HelloWorldしてください

すいません、わざとサブルーチン使います。

[Forthで書くと]

: HELLO ." HelloWorld"  CR ;
HELLO

意味を同様にコメントで書いていきます。

: \ サブルーチン宣言開始。次の"ワード"(後述)はサブルーチンの名前

." \ 文字列出力

HelloWorld" \ 文字列自身

CR \ printf("\n");

; \ サブルーチン宣言終了。"ディクショナリ"(後述)に登録される

HELLO \ サブルーチン呼び出し

サブルーチンも定義できるんですね。

こんな感じで、あらゆるメモリ操作、やりたいことのために、一時的に何かしらの保存領域としてスタックを使ったり、
どこかの関数やサブルーチンに値を受け渡しするときにスタックの仕掛けを使います。
もちろん分岐時の評価にもスタックを使います。
Forth自身の組み込み関数がスタックを使わないなら、スタックを通さなくても良くなる認識で、
いろんな処理系があるようです。

ex)1 + 2の結果を2回printfしてください
スタックのコピーを使います。

1 2 + DUP . .

. (印字)はスタックを破棄するので、事前にduplicate(DUP)します。

その他、単に生メモリにアクセスしたりなどもあったりします。足りない処理はとにかく自分で書きます。

Forthの文献で出てくるキーワード

スタック(stack)
計算するためのスタック。あらゆる計算はスタックで行われます。

ワード(word, words)
Forthが解釈する何かしらの最小単位です。

ディクショナリ(dictionary)
組み込み処理だったり登録したサブルーチン処理が登録されているところです。
ワードをディクショナリから検索して、登録されてたらそれを評価し、その繰り返しがForthの処理の基本です。

魅力 シンプル

勉強してて面白かったのは、Forthの処理系はとても小さいものが多く、またForthは明確な文法が存在しないようです。

魅力 シンプルなのでお試し実装が大量にあって勉強になる

※参考文献参照。pythonでのForth実装とか一杯あります。

魅力 Forthの処理系がやってることがシンプル

Wikipediaを抜粋してかみ砕くと、Forthがやってることは以下の通りです。

1) インタプリタはユーザ入力デバイス(この場合ファイルでも何かしらのメモリ領域でも何でもいい)から入力された行を読み

2) それから区切り文字としての空白を使って単語に構文解析される

3) インタプリタがワードを見つけると、ディクショナリ (dictionary) からそのワードの検索を試みる

4) もしそのワードが見つかれば、インタプリタはワードに関連付けられたコードを実行

5) もしワードを発見することができないなら、ワードを数だと仮定して数値への変換を試み、それをスタックにプッシュ

6) 変換が成功していれば、インタプリタは入力システムからの構文解析を継続する

7) 辞書の参照と数値への変換の両方が失敗した場合、インタプリタはそのワードが認識できないというエラーメッセージを出す

これを書くとForthのサブセットが完成するようです。

サブセットを実装してみた

なんというか手が完璧になまってたので、サブセットを実装しました。

tf.cpp
https://gist.github.com/kumaashi/6d4c83b635fdd996fd67bb8f887bec43

VC2015 64bitで動作確認しています。

以下にあるような見慣れないのはサポートしていません。

http://wiki.laptop.org/go/Forth_stack_operators
entryuserprimであとはひたすら拡張していくだけ、という認識です。
※ほんまかいな

付録 スレッデッドコード

Wikipediaによると、スレッデッドコード(英: threaded code)は、
プログラミング言語処理系におけるコード生成手法のひとつで
呼出すべきサブルーチンのアドレスを羅列する、というものです。
いろいろスレッデッドコードの手法があるようですが、Wikipediaのサンプルコードがよくできていたので解説します。
(少し変形)

thread:  ;実行すべき命令列
  &pushA
  &pushB
  &add
  ...

; ↓ここから実行開始

start:
  ip = &thread ;ipってレジスタにthreadのアドレスを格納
top:
  jump *ip++   ;pushAにジャンプしてipを次に進める


pushA:         ;Aをスタックに積んでくれるサブルーチン
  *sp++ = A    ;Aをスタックに積む
  jump top     ;topに強制ジャンプ

pushB:         ;Aをスタックに積んでくれるサブルーチン
  *sp++ = B    ;Bをスタックに積む
  jump top     ;topに強制ジャンプ

add:           ;スタックのトップとトップ-1を足してスタックに積む
  *sp++ = *--sp + *--sp
  jump top

参考文献

◆Wikipedia Forth
https://ja.wikipedia.org/wiki/Forth

◆FORTH の情報まとめてみた 2016 年版
https://qiita.com/ryos36/items/d2d50f9803cddd091905

◆6 行の Lua プログラムで、Forth の内部インタプリタを作る
https://qiita.com/iigura/items/855954ad000fe1da6c59

◆西尾泰和のはてなダイアリー : 最小限Forth + ワード定義機能付きForth
http://d.hatena.ne.jp/nishiohirokazu/20110401/1301667130

◆Forthを作るのじゃ | TECHSCORE BLOG
http://www.techscore.com/blog/2014/01/28/forth%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%98%E3%82%83/

◆Wikipedia : スレッデッドコード
https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%87%E3%83%83%E3%83%89%E3%82%B3%E3%83%BC%E3%83%89

◆[Factor] LOL -> FORTH -> Factor に出会う
http://basicwerk.com/blog/archives/1619

終わりに

FactorというFORTHの後釜的な処理系があるようです。
遊んだことがないので、今度遊んでみようと思います。
間違いなどあったら指摘ください。

以上です。
Let's enjoy FORTH!