はじめに
この文書は、Forth を知っているユーザーに対し、Paraphrase の概要を知ってもらえるよう解説したものです。
Forth と同じところ
トークンの評価順序
入力された文字列はトークンに分解され、各トークンと同名のワードが存在する場合はワード名として認識し、そのワードに関する処理を行います。同名のワードが存在しない場合は値としての評価に移ります。
スタック
データスタックの他にリターンスタックも備わっています。
Forth と異なるところ
様々な値をスタックにプッシュできます
Forth では、基本的に整数のみをスタックに積むことができました(文字コードやアドレス値などを含む)。Paraphraseでは整数値はもとより浮動小数点値、文字列、配列、リストなどもスタックに積むことができます。以下の例では、浮動小数点値をスタックに積み、計算を行っています。以下、プログラムリストにおける行頭の不等号 > は Paraphrase インタプリタが出力するプロンプトです。
> 1.0 4 / . // 浮動小数点値
0.25 ok.
次の例では文字列とリストを積んでいます(ワード show にて、現在のスタックの様子が表示されます)。
> "hello world" ( alpha beta gamma ) 12345678901234567890 show
+-----------------------------+
TOS-->| bigInt 12345678901234567890 |
| list ( alpha beta gamma ) |
| string hello world |
DS:-------------------------------------
ok.
Paraphrase でユーザーが陽にスタックにプッシュできる値は以下のとおりです(内部的には、これ以外の値も存在しています(例:IP=Instruction Pointer 等))。
- 真偽値(bool)
- 数値(int, long, float, double, 多倍長整数(big-int),多倍長浮動小数点(big-float))
- 文字列
- 配列
- リスト
- シンボル
- ファイル
- ワード(へのポインタ)
- EOC (=Enc Of Channel)
- EOF (=End Of File)
10 進数または 16 進数にのみ対応しています
基本的には 10 進数です。頭に 0x をつければ 16 進数となります。60 進数については、もしかすると今後のバージョンアップにおいて対応するかもしれません。
ワードの定義方法が少し異なります
Forth では、コロンの直後にこれから定義するワード名を記述していました。Paraphrase では TOS にある文字列を新たなワード名とするため、
"定義するワード名" : 定義の内容 ;
という形になります。
例として、スタック上の値を 2 倍する twice というワードの定義は次のようになります:
> "twice" : 2 * ;
ok.
> 12 twice .
24 ok.
インタプリタモードでも if や do ワード等が使用できます
Forth では、if などのフロー制御用ワードはコンパイルモードにて使用する必要がありました。Paraphrase ではこれらのワードが出現すると、自動的に無名ワードをコンパイルするモードに移行し、実行できる状況になり次第、速やかに生成した無名ワードを実行します。そのため、インタプリタモード中においてもストレスなく、これらフロー制御用ワードを利用可能です(厳密には、Paraphrase でもインタプリタモードでは if などのワードは利用できません。あくまでも使い勝手という意味においての話として理解していただければ幸いです)。
> "FizzBuzz.pp" load
ok.
> 1 20 for+ i FizzBuzz . next
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz ok.
即時実行属性はフラグではなく数値
整数値にて実行レベルを管理しています。0 が通常のワード、1 が Forth でいうところの即時実行属性、2 が新設のシンボルモードです。シンボルモードはリストの構築にて使用されます。その他には、コメント文などもリストとして構築されています(コメント終了ワード実行時に破棄されています)。この機構により、コメント終了がワードとして実現できるようになっています。これを活用した簡易文芸的プログラミングが可能です(HTML 文書そのものを Paraphrase のプログラムリストとして活用することも可能)。
入力バッファへのアクセス方法は存在しない
シンボルモードおよび文字列を活用したワード定義の導入により、ワードが入力バッファへ関与することなくワードやコメント機能が実現できるようになりました。その結果として、現バージョンではワードが入力バッファ(およびスキャナ)へ関与・連携することはできません。
DSL(Domain Specific Language) などの実現には、シンボルモードの活用が考えられますが、現バージョンでは DSL への展開については全く考えられてはいません(が、なんらかの方法は提示されるべきかと考えています)。
switch - case は標準で用意されています
ワード switch と case, ->, dispatch を用いて、分岐処理を記述できます。
"FizzBuzz" :
switch
case 15 % 0? -> "FizzBuzz" break
case 5 % 0? -> "Buzz" break
case 3 % 0? -> "Fizz" break
dispatch
;
再帰処理を意識することなくワード定義できます
現在定義中のワード名も辞書への登録処理は終了しているため、再帰呼び出しも特に意識することなく記述可能です。
> "fact" : dup 0? if drop 1 else dup 1 - fact * then ;
ok.
> 5 fact .
120 ok.
小文字ベースです
Forth が現役だった頃とは異なり、ソースコードでは小文字を使用するのが一般的な時代となりました。Paraphrase でも、標準ワードで英文字を使用する場合には一部の例外を除いて小文字を用いることとしています。なお、大文字と小文字は区別されるので、例えば FORHT と Forth というワードがあった場合、それぞれ異なるワードとして区別されます。
標準で並列処理が記述できます
括弧もしくは二重括弧で囲むだけで並列処理が記述できます。ある並列処理から別の並列処理へと情報を渡すパイプ(チャネル)も用意されているので、scatter-gather モデルによる並列計算も可能です。
- 単一の別スレッドで実行:
- 単一の大括弧 [ と ] で囲んで、[ doSomething ] と書けば、 別スレッドでワード doSomething が実行されます。
- 複数のスレッドで実行:
- 二重大括弧 [[ と ]] で囲んで、[[ doSomething ]] と書けば、 複数のスレッドでワード doSomething が実行されます。 デフォルトでは CPU の数分のスレッドが走り出すので、 4 コア 8 (CPU)スレッドの環境であれば、8 個の並列処理が開始されます。 AMD の 2990wx は 32 コア 64 (CPU) スレッドとのことですので、 64 個の並列処理が走ることでしょう(未確認)。
- 普通にメインスレッドで実行:
- 括弧で囲まずにそのまま doSomething と書けば、 インタプリタのメインスレッドにてWord doSomething が実行されます。
(簡単な)最適化が行われます
例えば、0 == を 0? という 1 語のワードに変換するような、のぞき穴最適化が備わっています。
アセンブラは使わず全て C++ にて記述されています
C++17 にて記述されています。gcc および VisualStudio にてコンパイル可能です。アセンブラは使っていないので、内部インタプリタはサブルーチンコール方式にてスレッドを呼び出していきます(C の関数として記述された処理を順次呼び出していきます)。
最後に
Forth でいうところの forget はありません。これは、Paraphrase の辞書が線形リストではなく連想配列を使用しているためです。そのため、ワード間には、定義された時刻による順序関係が存在せず、あるワード以降に定義したワード全てを破棄するーといった処理ができません(ワード単体を忘却する forget というワードは実装予定です)。