#What's this
超次元難解プログラミング言語Befungeの拡張版、Funge-98の最終仕様ドキュメントを見つけたので日本語に訳してみます。これを読んで一人でもFungeを始める人がいたらいいなぁと思います。
元記事:http://quadium.net/funge/spec98.html
誤訳等ありましたら編集リクエストを投げていただけると助かります。
なお、この文章によって発生する事柄について責任は負いません。
#本文
Chris Pressey,1998年9月11日
明確化のための改訂:1998年9月30日
##序論
###Fungeとは?
Fungesは、所定のトロポジカルパターンと次元数によってプログラムが表現されるプログラミング言語です。
Funge-98は、現在においてFungesの公式プロトタイプ規格であり、Befunge-93の言語定義を更新したBefunge-96を改良したBefunge-97を一般化したFunge-97が更に進化したものです。
Funge-98は、3つの実在し、かつ公認されたプログラミング言語(Unefunge, Befunge, Trefunge)のclass(一種?グループ?)であり、様々な次元や多様なトポロジーのものをいくらでも記述するためのパラダイムを提供しています。
もっともポピュラーなFungeはBefungeで、2次元的に記述し、Cartesian Lahey-Space(Befunge-93でのみCartesian Torus)トポロジーに基づいています。他のCartesian Lahey-SpaceトポロジーによるFungeには、Unefunge(1次元的に記述)やTrefunge(3次元的に記述)があります。すべてのFungeの命令がすべてのFungesにおいて適切とは限らないため、本ドキュメント内ではポイントを明確にするためにBefungeとの比較がよく用いられます。
###このドキュメントについて
これは最終ドキュメントです。この文書の情報は公式な認可を受けており、Funge言語ファミリーの"公式な"技術仕様として支持者に承認を得ています。
このドキュメントは、いかなる年代・種別のFungeにも精通していない方に適しています。
##Fungeの仮想マシン
Fungeに与えられたコードおよびデータは、二つの場所のいずれかに保存することができます(cellと呼ばれます):
- Funge-Space―――Fungeの次元性、トポロジー、そしてタイリングパターンに適当な行列であり、トポロジカルネットの各ノードはセルを含みます。
- Befunge-93におけるstackや、Funge-98におけるstack stack―――どちらにせよthe stackと呼ばれることが多く、last-in, first-out (LIFO)のセルスタックとしてアクセスされます。
Befunge-93は、符号付きの32ビットスタックセルと、符号無しの8ビットFunge-Spaceセルを定義しています。一方Funge-98においては、スタックとFunge-Spaceセルは同サイズの符号付き整数として扱われます。
正確にどのようなサイズにするかは実装者に委ねられています。32ビットが一般的です。16ビットと8ビットのバージョンは、Fungeの別バリエーションとして議論されています。32ビット以上でも構いません。重要なのは、スタックセルのメモリサイズがFunge-Spaceセルのそれと等しいことです。
###Funge-Space
Befunge-93においては、Funge-Spaceは横に80セル、縦に25セルに制限されています。一方、Funge-98プログラムにはそのような制限はありません。Funge-98インタプリタは、理想的にはセルサイズと同じアドレッシングレンジを持つことが望ましいでしょう。すなわち、Funge-98の32ビットでの実装においては、それぞれの座標インデックスに32ビットの符号付き整数を使用します。
このような大きく、典型的なアドレッシングレンジにおいて、Funge-98-Spaceは一般的に、コンパイラの舞台裏のメカニズムによって動的に割り当てられると考えられています。もちろんそうである必要はありませんが、実際にはそうなっています。
したがって、ストレージメカニズムは、実行中のプログラムにどのようにFunge-Spaceを提供するかについて、一貫して信頼できるものでなければなりません。また、Funge-98プログラムは、Funge-Spaceに入れたすべてのコードとデータが、このメカニズムを通して消えないことを信頼できるべきです。
Befunge-93、Funge-98の双方で使われている座標マッピングは、スクリーングラフィックスや表計算ソフトで使用されている"コンピューターストレージ"座標系を反映しており、通常の直交座標系のスタンダードな数学的表現と比較すると、これは上下が逆さまになっています。
(アスキーアート図解を引用)
Befunge-93 32-bit Befunge-98
========== =================
0 x 79 |-2,147,483,648
0+-------------+ |
| | x
| -----+-----
y| -2,147,483,648 | 2,147,483,647
| |
| y|2,147,483,647
24+
###stack stack
The Funge stack stackは、典型的なセルLIFOスタックのLIFOスタックです。Befunge-93においては、1つだけのスタック(the stackと呼ばれます)で可能な操作は二つだけです:セルをスタックの一番上にpushする操作と、セルをスタックの一番上からpopする操作です。
ただし、Funge-98の場合、the stackとはstack stackの一番上のstackを指します。プッシュやポップの操作はスタック上でも可能ですが、スタック全体をプッシュしたりポップすることも可能なのです。
また、the stack(つまりstack stackの一番上のスタック)からセルを取り除き、完全に空にするFunge-98命令もあります。
もしもプログラムがセルが空の時にスタックからセルをポップしようとしたとしても、エラーは発生せず、あたかも0をポップしたかのように動作します。
このドキュメントにおいては、短いスタックは一般的に、左から右に表記され、下から上の意味を持ちます。ドキュメントの記載での一番左の値は、一番下の値でもあり、スタックに一番最初にプッシュされた値です。長いスタックは上から下、つまり正確に表記されます。
###Fungeソースのファイルフォーマット
Befunge-93のソース(プログラム)のファイル名は、一般的な規則では拡張子.bf
で終わります。Funge-98のソースファイル名が何で終わるかについて強制的な規則はありません(例えば、ファイル名が.c
で終わるC-Befungeポリグロットを簡単に記述することも可能です)が、.b98
はBefunge-98のソースにはいいチョイスと言えるでしょう―――――"標準的な"サンプルプログラムはこの接尾辞を使います。
Befunge-93のソースファイルは、印字可能なASCII文字と後述する行末コントロールのみを含むプレーンテキストファイルです。
Funge-98のソースファイルは、Funge文字で構成されています。Funge-98の文字セットはBefunge-93で使用されているASCIIサブセットをオーバーレイしており、127を超える文字が含まれる場合があります。(文字がマルチバイトで保存されるシステムでは255を超える場合もあります。―――が、2,147,483,647を超えることはありません)
Fungeの文字セットは、'ディスプレイ非依存'なのです。つまるところ、例えば文字#417はシステムFooではスクイグルに見え、システムBarではhappy faceに見えるかもしれませんが、Fungeにとってはどのように見えるかに関わらず、常に同じ'文字#417'を意味するのです。
言い換えるのならば、特定のコンピューターまたはOSにおいてFunge文字がどう見えるかは、完全にそのコンピューターやOSに依存しているのです。しかしながら、一般的に印字可能と認識されていない文字が、Funge-98にとって特別な意味を持つこともあります:
- 0~31:"ASCII制御文字"(現在は10のみが、行末を表すと定義されています)
- 32~126:"ASCII印字可能文字"(すべてが入力/出力であり、固定幅です)
- 127:"削除制御文字"^1
- 128~2bil:"拡張印字可能文字"(マシン及びフォントの固有文字です)
Befunge-93においては、各行が現在のOSの"行末"文字で終了し、これは改行(10)(Linux)、キャリッジリターン(13)(MacOS)、キャリッジリターン改行(13,10)(MS-DOS)1のいずれかです。
しかしFunge-98では、どのOSを使用しているかにかかわらず、次のシーケンスが行末マーカーとしてインタプリタに認識されるのが理想的です:
- 改行(10)
- キャレッジリターン(13)
- キャレッジリターン改行(13,10)
もしインタプリタが三種類の行末マーカーのすべてをサポートできないならば、そのインタプリタのドキュメントに明記する必要があります。
プログラムがロードされると、行末マーカーがFunge-Spaceに表示されなくなります。
Befunge-93では、各行に"行末"マーカーの前に最大80文字までの有効文字を含ませることができ、この行を最大25行までソースファイルに含ませることができます。Befunge-98にはそのような制限はなく、ユーザーは矛盾がない限り、好きなだけ文字数と行数を使えることを合理的に期待できます。
ロードの前、Funge-Spaceの各セルにはスペース(32)の文字が含まれています。これらのデフォルトの内容は、プログラムソースのロード時、ソース内の文字によって上書きされます。しかしながら、プログラムソース内のスペース文字は、Funge-Space内の何かを上書きすることはできません。このことは、i
"Input File"命令の使用によって、重複したファイルを読み込む際に重要になります。
ソースファイルはFunge-Spaceの原点から始まります。後続の文字列はx座標をインクリメントし、後続の行はy座標を(存在するならば)インクリメントし、x座標を0にリセットします。Unefungeの後続の行は単純に最初の行に付け足され、ソースファイルの末尾にはその(一行の)行の末尾が表示されます。行末マーカーは決してFunge-Spaceにコピーされることはありません。
Trefunge-98においては、改ページ(12)文字はz座標をインクリメントし、x座標とy座標を0にリセットします。
##コード:プログラムの流れ
###命令ポインタ
命令ポインタ/instruction pointer(IP)は、実行中のBefungeプログラムの"現在位置"を示すベクトル(座標の集合)と考えることが可能です。これは、他のプログラミング言語やプロセッサにおける命令ポインタ(またはプログラムカウンタ/program counter(PC))と同じ機能を持ちます―――現在実行中の命令がどこにあるかを示す機能です。
他のほとんどの言語やマシン(仮想/現実を問いません)では、IP/PCはランダムなジャンプを伴う一次元的な一方向移動に制限されています。しかしFungeでは、IPはデルタ/deltaと呼ばれるまた別のベクトルを記録しています。毎ティック、IPは現在の命令(すなわち、IPが指し示す位置にある命令)を実行し、そのデルタベクトルを位置ベクトルに加算して、新しい位置に移動します。
プログラムの開始時、Funge-98ではBefunge-93と同様に、IPは常に原点で、デルタを(1,0)にした状態で始まります。原点はBefungeでは(0,0)、Unefungeでは(0)、Trefungeでは(0,0,0)です。
二次元には、次のような用語があります。
IPのデルタが(0,-1)(南)、(1,0)(東)、(0,1)(北)、(-1,0)(西)のいずれかであれば、それは縦横/cardinallyに移動していると言われます。これはチェスでのルークの動き方と同じで、実のところこれがBefunge-93におけるIPが動く唯一の方法です。
デルタが0以外のIPは移動中/movingと考えられ、デルタが0のIPは停止中/stoppedと言われます。縦横に移動していない移動中のIPはすべて飛翔中/flyingと言われます。
###命令
標準命令はすべて1文字であり、ASCII 32(スペース)からASCII 126(~)までの範囲です。Fungeには複数文字にわたる命令はありません。
IPは毎ティック命令を実行します。実行するのは、そのIPの現在位置にある命令です。その後、IPはデルタによって新しい位置に移動します。
A
からZ
までのすべての命令は、最初はr
"反転"命令のように動作します。しかし、他の命令はこれらの命令にセマンティクスを動的に割り当て、FungeプログラマはユニークID(またはフィンガープリント)でタグ付けされた標準及び独自の命令セットのライブラリを使用可能にします。
けれども、Funge-98 インタプリタは、ASCII 127以上またはASCII 0以下のプロプライエタリな命令を公開することもできます。
これらの理由によりFungeインタプリタは、実装されていない命令(Unefungeにおける|
のような命令を含みます)に遭遇したとき、少なくとも実装されていない命令を実行せよと言われたことをユーザーに知らせるオプションを提供し、場合によってはこのファイルは不適切な言語またはバージョンである可能性があることをユーザーに伝えなければなりません。
それ以外の場合、実装されていない命令は、r
が実行されたかのように動作しなければならず、スタックに触れてはいけません。未定義または未実装なすべての命令は、実装されていないと見なすべきです。
また、t
(分裂)、=
(実行)、i
(ファイル入力)、そしてo
(ファイル出力)の命令は、様々な理由で別個のインタプリタでは利用できない場合があり、日常的に縛られています。(つまり、r
と同じように動作しています)しかし、これらは実行に失敗した際にr
のように動作することもあります。これらの命令がサポートされているのか確かめるためには、1y
を実行し、それによって生じるセルを調べてみてください。
###方向転換
IPのデルタを変更するには、少しの命令が不可欠です。>
"東に行く"命令では東に、<
"西に行く"命令では西に、それぞれIPの方向が変わります。これらの指示は、すべてのFungeにおいて有効です。
^
"北に行く"命令はIPを北に、v
"南に行く"命令は南に移動させます。これらの命令は、Unefungeでは利用できません。
h
"高く"命令はIPを上に移動させ(デルタを(0,0,1)に設定し)、l
"低く"命令はIPを下に移動させ(デルタを(0.0.-1)に設定し)ます。これらの命令は、UnefangeやBefungeでは実行できません。
?
"どこかに行く"命令は、IPを使用中の次元数に応じたランダムな方位にIPを移動させます:例えばUnefungeは東または西であり、Befungeでは東、西、南、または北です。
以下の命令はBefunge-93では利用できませんが、Funge-98では利用可能です。
]
"右に曲がる"命令と[
"左に曲がる"命令は、それらに遭遇したIPのデルタを90度回転させます。この回転は、常にz軸を中心とします。これらの命令は、Unefungeでは使用できません。
この命令のどちらがどちらに曲がるのか覚えるためには、自転車のシートの上でハンドルを見下ろしている自分をイメージしてください:
(Markdownの仕様上表が再現できないので画像で)
r
"反転"命令は、IPのデルタを-1倍します。二次元においては、これはIPのデルタをz軸で反射することに相当します。
x
"絶対ベクトル"命令は、スタックからベクトルをポップし、IPデルタに設定します。
スタック上では、格納は下から上に行われるため、Befungeでは、x
(及び他のすべてのベクトルポップ命令)はdyを呼び出す値をポップし、その次にdxを呼び出す値をポップして、デルタを(dx,dy)に設定します。
###ラッピング
Befunge-93は、IPが境界から外れた(Funge-Spaceのマップから外れた)時、空間をトーラスのように扱うことで処理します。もしIPが西の端から外れたなら、同じ列の東の端に再出現し、もし南の端から外れたなら、同じ列の北の端に再出現するのです。その逆も同様です。
様々な理由によって、Funge-98ではトロイダルラッピングが問題となっています。その代わりに私たちは、Funge-Spaceが任意のサイズを持ち、IPを飛べるようにするような、より柔軟な環境で、より一貫した結果が得られる特殊なラッピング技術を使うことにしました。これは、同一行ラッピング/same-line wrappingと呼ばれています。
同一行ラッピングはいくつかの方法で記述可能ですが、これが包含する重要な考え方は次の通りです:間に介在する命令によってIPのデルタや位置が変更されなければ、IPは常にラップし、結局はラップする前の命令に戻ります。
同一行ラッピングの数学的形容は、特殊なトポロジカル空間を定義するLahey-spaceラッピングとして知られています。これは、一般的にプログラマーよりもトポロジストや数学者の方が関心を向けていることでしょう。ここでは取り上げませんが、完全を期すために付録に含まれています。同一行ラッピングのアルゴリズムは、バックトラックラッピングとして記述することができます。これは、FungeプログラマーよりもFungeインタプリタの実装者が関心を持つことでしょうが、プログラマーが理解できるように、ラッピングがどんな風に動作するかを正確に記述しているので、ここではそれを含め解説します。
IPは、空白と、コードと既知のアドレス指定可能空間の終端の間に移動しようとすると、バックトラックします。これは、そのデルタが逆になり、すべての命令を無視(実行せずにスキップ)することを意味します。このようにして移動すると、コードの前に再び空白以外の何もないときに、コードのもう一方の'端'を見つけます。そしてもう(元のデルタを復元するために)一度180度反射され、命令の無視を停止します。その後、実行は正常に再開され、ラップは完了します。
この時点で、IPがいるのが同じ行であることが容易にわかります―――これがこの名前の由来です。(ラッピングプロセスから予想されるように、これはマルチスレッドに関連して何ティックも要することは絶対に無いことに注意してください)
同一行ラッピングは、Befunge-93のトロイダルラッピングとの下位互換性があるという利点があります。また、IPデルタの飛翔中(非cardinal状態)や、プログラムのサイズが変わった時にも安全に動作します。
述べたように、デフォルトの状態では、Funge-Spaceのすべてのセルにスペース(32)文字が含まれています。(これは殆どのBefunge-93インタプリタに当てはまりますが、オリジナルでは異なります)
Befunge-93では、命令として解釈された場合、スペースは"何もしない"つまりnopとして扱われます。インタプリタは何も実行せず、我が道を行きます。
Funge-98では、技術的にはFunge-98が"時間を掛けずに"任意の数のスペースを処理することを除いて、ほとんど同じように動作します。これは、Funge-Spaceで複数のIPを同時に使用している(マルチスレッド)場合に重要になります。Funge-98において、明示的なnop命令にはz
を使ってください。
スペースは、文字列モードと呼ばれるインタプリタの特別モードでも(Funge-98においては)特殊なプロパティを持ちます。これは後ほど、また読むことになります。
###流れのコントロール
#
"トランポリン"命令は、パス内の次にIPが通ろうとしているFunge-Spaceセルの1つ先のセルにIPを移動させます。2
@
"停止"命令は、現在のIPをキルします。非並行FungeにはIPは一つしかありません。いずれにせよもうIPが生き残っていない場合、プログラムはその後no error(エラーコード0を返します)で終了します。
以下の命令及びマーカーはBefunge-93では利用できませんが、Funge-98では利用可能です。
;
"飛び越え"マーカーにより、IPは再び;
マーカーに到達するまでの間、後続のすべての命令をスキップします。これはスペースと同様に、実行には0ティックしか要さないため、サブルーチン、コメント、そしてサテライトコードを;
マーカーで囲むことで、それらをマルチスレッドと絶縁させることが可能です。
;
マーカーは、ちょうどスペースのように、真に、ティックを要して何かを行うという意味で実行されることはありません。
j
"跳躍"命令は、スタックから値をポップし、その数だけセルを飛び越えます。例えば、2j789
というプログラムは9
を出力し、空のスタックを残します、負の数もj
の正規の引数であり、04-j@
が無限ループになるようなものです。
q
"終了"命令は、Funge-98でのみ、(並行FungeにおけるアクティブなIPの数に関係なく)プログラム全体を直ちに終了させます。また、スタックからセルをポップし、OSへのFungeインタプリタの戻り値として使用します。
殆どのOSは、あなたの戻り値のうち、符号無しの最下位バイトのみを受け取ることに注意してください。ただしセル全体を返すことも可能であり、その場合OSは処理できるだけの量を解釈し、OSが要求するように符号付きまたは符号無しとしてそれを扱います。
k
"繰り返し"命令は、スタックからn個の値をポップし、Funge-Space内におけるパスでのIPの次の命令を(これはスペースや;
などのマーカーではいけないことに注意してください)n回実行します。これにかかるのは、並行処理においては1ティックだけです。
一部の命令は、0を渡さない限りk
のコンテキスト内ではあまり意味をなさないことに注意してください。例えば、最初に^
を実行した後に何度k
が^
を実行しても、結果は変わりません。しかし、k
に0を渡せば、^
命令は実行されません。これは価値のある行動になりえます。
また、k
は#32のスペースや、;
などの命令を実際に実行することはないことに注意してください。
###意思決定
!
"論理的否定"命令は、スタックから値をポップし、その値の論理的否定となる値をプッシュします。もし値が0なら1をプッシュし、値が0でなければ0をプッシュします。
`
"より大きく"命令は、スタックを2回ポップし、2番目のセルが1番目のセルより大きければ1を、それ以外の場合は0をプッシュします。3
Fungeには方向性による'if'ステートメントのような命令があります。_
"東西If"命令は、スタックから値をポップし、それが0ならば>
命令のように、そうでなければ<
命令のように振舞います。
|
"南北If"命令は、スタックから値をポップし、その値が0ならばv
命令のように、0でなければ^
命令のように振舞います。また、|
命令はUnefungeには存在しません。
m
"高低If"(middleのmと考えてください)命令は、スタックから値をポップし、0ならl
命令のように、でなければh
命令のように振舞います。また、m
命令はUnefungeやBefungeでは利用できません。
##データ:セルのクランチング
###整数
0
"0をプッシュ"から9
"9をプッシュ"命令は、それぞれ0から9までの値をスタックにプッシュします。Funge-98では、a
~f
もそれぞれ10~15をプッシュします。
+
"加算"命令は、スタックから2つの値をポップし、その整数加算の結果をプッシュします。
*
"乗算"命令は、スタックから2つの値をポップし、その整数乗算の結果をプッシュします。このようにして、9(または15)を超える数値をスタックにプッシュできます。
例えば、値123をスタックにプッシュしたければ、次のように書きます。
99*76*+
または
555**2-
また、Fungeでは次のような算術も使用できます:
-
-
"減算"命令は、スタックから2つの値をポップし、2番目の値から1番目の値を引く形の整数減算の結果をプッシュします。 -
/
"除算:商"命令は、スタックから2つの値をポップし、2番目の値を1番目の値で割る形の整数減算の商をプッシュします。(Funge-98では0による除算の結果は0になりますが、Befunge-93では除算の結果をどうしたいかユーザーに尋ねるようになっていることに注意してください) -
%
"除算:剰余"命令は、スタックから2つの値をポップし、2番目の値を1番目の値で割る形の整数減算の剰余をプッシュします。0で割ったことによる余りは、0による除算と同じルールに従いますが、どちらかの引数が負の場合、結果は処理系定義/implementation-definedです。
###文字列
"
"文字列モード切替"命令は、文字列モード/stringmodeと呼ばれるFungeインタプリタの特別なモードを切り替えます。
文字列モードでは、IPが遭遇するすべてのセル("
とFunge-98ではスペースを除く)は命令としてではなく、Funge文字として解釈されます。後続の"
は、文字列モードを再びオフにします。
Funge-98の文字列モードでは、スペースは"SGML-style"として扱われます。つまり、連続したスペースが処理されるときは、1ティックだけ、1つのスペースをスタックにプッシュします。これは Befunge-93 との小さな後方非互換性を導入しています――――文字列モード中に複数のスペースやラップを持つプログラムは、Funge-98 で同じように動作するように変更する必要があります。
"hello world"
"hello world"
"hello world"
"hello "::"world"
Funge-98には、'
"文字読み込み"命令もあります。これは、次に遭遇するセル (位置 + デルタ)のFunge文字値をスタックにプッシュし、をスタックにプッシュし、デルタを位置に追加し(#
のように)、文字をスキップします。(0ティック)例えば、以下の 2 つのスニペットは同じ機能を実行し、Qをプリントしています。
"Q",
'Q,
s
"文字格納"命令は、'
命令の鏡写しです。これは、代わりにスタックから値をポップし、(位置+デルタ)に書き込みます。
いくつかの命令は、引数としてスタック上のFunge文字列を要求します。これらの文字列の標準的なフォーマットは0"gnirts"と呼ばれています―――つまり、NULL終端の文字列で、スタックの先頭が文字列の先頭、末尾がNULL終端です。
###スタック操作
$
"ポップ"命令は、スタックからセルをポップして破棄します。
:
"複製"命令は、スタックからセルをポップし、それを2回プッシュすることで複製します。
\
"交換"命令は、スタックから2つのセルをポップし、最初に2つ目のセルを、その次に1つ目のセルをプッシュします。
n
"スタッククリア"命令(Befunge-93にはありません)は、スタックを完全に消去します。(スタックが空になるまでセルをポップして破棄します)
###stack stack操作
stack stackは透過的にスタックをオーバーレイします―――つまり、Funge-98におけるstack stackのトップスタックは、Befunge-93の単独スタックと同じように扱われます。Fungeプログラマーは、Funge-98の{
、}
、そしてu
命令を使わない限り、これらの違いに気づくことはありません。
しかし、stack stack上の異なるスタックで作業するときは、それらに二つの名前を付けると便利です:top of stack stackまたはTOSS、そしてTOSSの直下にあるsecond on stack stackまたはSOSSです。
{
"ブロック開始"命令は、セルをポップしてnとし、stack stackの先頭に新しいスタックをプッシュし、SOSSからTOSSにn個の要素を転送し、そして、ストレージオフセットをベクトルとしてSOSSにプッシュし、新しいストレージオフセットをIPが次に実行する位置に設定します。(ストレージオフセット <- (位置+デルタ))これらの要素をブロックとしてコピーするため、順序は保存されます。4
SOSSがk個の要素を含むとして、k < nの場合、k個の要素は最上位の要素として転送され、残りの最下位の要素(n-k個)は0値のセルで埋められます。
nが0の場合、要素の転送は起こりません。
nが負の場合、|n|個の0がSOSSにプッシュされます。
対応する}
"ブロック終了"命令は、スタックをポップしてnとし、ストレージオフセットに割り当てられたSOSSからベクトルをポップし、TOSSからSOSSにn個の要素を(ブロックとして)転送し、stack stackからトップスタックをポップします。
}
"ブロック終了"のための要素の転送/transferは、要素が転送される方向を除いて、すべての点で{
"ブロック開始"の要素の転送と類似しています。"Transfer"はここでは"移動"の意味で使われており、"コピー"ではありません。元のセルは削除されます。
nが0の場合、要素の転送は起こりません。
nが負の場合、(オリジナルの)SOSSから|n|個のセルがポップされます。
{
は現行のTOSSを新しいSOSSにします。
}
は現行のSOSSを新しいTOSSにします。
{
は、別のスタックに使用可能なメモリがこれ以上ない場合にr
のように振る舞うことがあります。{
は、そうしなければstack stackのアンダーフローが発生する場合(すなわち、stack stackにスタックが一つしかない場合)に、r
のように振舞います。
これらの命令の実用的な使い方は、プロシージャまたはFungeコードの他のブロックを"絶縁"し、"局限"することです。
u
"スタック・アンダー・スタック"命令は、カウント/countをポップし、その数だけのセルをポップ-プッシュ・ループでSOSSからTOSSに転送します。言い換えれば、転送中に順番が保持されず、逆向きになります。
もしSOSSがない(TOSSだけがスタックとしてある)場合は、u
はr
のように振舞う必要があります。
カウントが負の場合、|カウント|個のセルがTOSSからSOSSに転送されます。(同様にポップ-プッシュ・ループです)
カウントが0の場合、何も起こりません。
##メディア:通信とストレージ
###Funge-Spaceでの記憶
g
"取得"、p
"設置"命令は、それぞれFunge-Spaceにデータやコードを保存したり、取得したりするために使用されます。
Befunge-93では、g
はベクトルをポップし(つまり、yをポップした後xをポップし)、(x,y)の座標を持つBefunge-Spaceセルにある値(文字)をスタックにプッシュします。p
はベクトルをポップしたあと値をポップし、その値で(x,y)にあるFunge-Spaceセルを上書きします。
Funge-98では、各IPはストレージオフセット/storage offsetと呼ばれる追加のベクトルプロパティを持っており、初期状態ではこのベクトルは原点に設定されているため、Befunge-93をエミュレートするように動作します。g
とp
の引数は同じですが、Funge-Spaceの絶対位置ではなく、ストレージオフセットとの相対的なセルを参照します。
###標準入力/出力
.
"10進数出力"および,
"文字出力"命令は、それぞれ数値やFunge文字・制御の出力を行います。これらは、スタックからセルをポップし、数値またはFunge文字の形式で標準出力に送信します。これは通常、インタプリタが対話型ユーザーターミナルに表示するものです。
文字番号10を出力すると、標準出力では改行表示されます。
数値の出力は、10進数の後ろにスペースが連続する形で出力されます。
これらの命令は、何らかの理由で標準出力に失敗が生じた場合、r
のように動作します。
&
"10進数入力"および~
"文字入力"命令は、それぞれ数値やFunge文字・制御の出力を行います。。これらの命令はそれぞれプログラムを中断し、ユーザーが標準入力に数値またはFunge文字形式の値を入力するまで待ちます。そして、その値をスタックにプッシュします。
文字番号10の入力は、ユーザーがキーボードの'Enter'またはそれに類するキーを押したことを示しています。
10進数入力は、10進数の数字として正しい文字に遭遇するまで文字を読み込んでは破棄し、遭遇したらその数字から10進数を読み取ります。入力文字が数字でなくなるか、セルオーバーフローを引き起こすまで繰り返されます。
標準入力/出力は、通常、何らかのインタラクティブなユーザーターミナルに表示されますが、その必要はなく、多くのOSがリダイレクトをサポートしています。ファイル終端やファイルエラーの場合、&
及び~
はr
のように動作します。
###ファイル入力/出力
ファイルのI/Oは、i
"ファイル入力"命令とo
"ファイル出力"命令によって行われ、Funge-98でのみ利用可能です。
i
はファイル名としてNULL終端の*0"gnirts"文字列をポップし、その後にフラグセル、どこで操作するかを示すベクトルVaが続きます。もしファイルが読み込み目的で開けるならば、VaによってFunge-Spaceに挿入された後、直ちに閉じられます。そして、対応するo
命令の適当な引数であるVaとVbの二つのベクトルがスタックにプッシュされます。もしもファイルを開くのに失敗した場合は、この命令はr
のように振舞います。
i
は、Fungeソースコードに限らず、任意のファイルをロードできる点と、原点に限らず、任意の場所を指定可能なことを除けば、全ての点においてメインのFungeソースコードをロードするプロシージャに似ています。
また、フラグセルの最下位ビットがハイ/highの場合、i
はファイルをバイナリファイルとして扱います。つまり、次元カウンタがリセットされ、インクリメントされる代わりに、EOL・FFシーケンスがFunge-Spaceに格納されます。
O
は最初に、ファイル名として使用するためにNULL終端の0"gnirts"*文字列をポップします。次にフラグセルをポップし、その次に空間内の最小点(領域の中で最小の数値座標を持つ点です。Befungeのコンテキストでは左上隅として知られています)を示すベクトルVaと、矩形(Trefungeでは矩形プリズム等)のサイズを示すベクトルVbをポップします。ファイル名として指定したファイルが書き込み目的で開けるならば、VaからVa+VbまでのFunge-Spaceの矩形の内容がそこに書き込まれ、直ちに閉じられます。さもなくば、この命令はr
のように振舞います。
これらの両方の命令によってポップされた最初のベクトルは、ストレージオフセットに対する相対的な物とみなされます。(当然のことながら、サイズベクトルVbはVaに対する相対的な物です)
2次元以上の環境では、(3,3,3)のように2次元以上のサイズを指定しても、感覚的/sensicalな結果が得られる保証はないことに注意してください。
またフラグセルの最下位ビットがハイ/highの場合、o
はファイルを線形テキストファイルとして扱います。つまり、各EOLの前のスペースや、EOFの前のEOLは書き出されません。結果として得られるテキストファイルは、外観は同じであり、占有するストレージスペースはより少なくなります。
###システムの実行
=
"実行"命令は、スタックから文字列をポップし、それを実行しようとします。どこ用に実行するかは実装依存です。しかし、実装はいくつかの標準化された文字列の解釈方法のうち一つをサポートしている場合があり、どのルーチンを使用するのかは、y
に問い合わせることで知ることができます。一般的なメソッドには、Cのsystem()コールのように扱う方法や、MacOSのようなプラットフォームにおいてAppleScriptのように文字列を扱う方法などがあります。
実行後、失敗値/a failure valueがスタックにプッシュされます。もしこの値が0ならば、全てが期待通りに進んだことになります。そうでなければ、実行されたプログラムのリターンコードである可能性があります。いずれにせよ、プログラムの実行の試み、またはプログラム自体が不成功に終わったことを示しています。
###システム情報検索
Funge-98でのみ利用可能なy
"システム情報取得/Get SysInfo"命令は、スタックから値を1つポップし、値が0または負の場合、y
は、Fungeインタプリタ、OS、そしてコンピューターについて、知りたいことよりも遥かに多くの情報を教えてくれます(そのため、通常はすぐ後にn
命令が続きます)
y
によって取得された情報の各セルは、現在のIP、現在の環境(つまるところ、現在のIPのインタプリタ)、もしくはグローバル環境(どのインタプリタで実行されているかに関わらず、同じFunge-Spaceにある全てのIPの環境)のどれかです。。
正でない引数でy
を実行したとき、スタックにはさらに多くのセルが含まれます。(上から下へとリストアップされています):
-
フラグを含む1セル(環境)
- 最下位ビット0(0x01):
t
が実装されていればhigh(これは並行Fungeか?) - ビット1(0x02):
i
が実装されていればhigh - ビット2(0x04):
o
が実装されていればhigh - ビット3(0x08):
=
が実装されていればhigh - 最上位ビット4(0x10):バッファなしの標準 I/O(
getch()
など)が有効な場合はhigh、通常のバッファ付きの各種 (scanf("%c")
など) が使用されている場合はlow - 更なる上位ビット:未定義、Funge-98ではすべてlowにする必要があります。
- 最下位ビット0(0x01):
-
1セルあたりのバイト数(グローバルenv/global env)を含む1セル
- 別名:セルサイズ。通常は4ですが、2や8、本当に本当に大きなサイズ/really really large、無限大などの可能性もあります。
-
実装のハンドプリントを含む1セル(環境)
-
実装のバージョン番号を含む1セル(環境)
- バージョン番号名にポイントが含まれている場合は削除されます。v2.01 == 201, v1.03.05 = 10305, v1.5g = 1507です。バージョン番号には'パーソナライゼーション'を示すため、数字以外のものを使用しないでください―――代わりとしてハンドプリントを使用します。
-
オペレーティング・パラダイムIDを含む1セル(グローバル環境)
- 0 = 利用できません。
- 1 = C言語のsystem()呼び出しによる動作と同等です。
- 2 = 特定のシェルやプログラムによる解釈と同等です。このシェルやプログラムはインタプリタによる指定が可能ですが、理想的にはインタプリタユーザーがカスタマイズできるようにすることが望ましいです。このパラダイムの下で実行されるFungeプログラムは、
=
に渡された文字列をどのプログラムが解釈することを期待しているか文書化すべきです。 - 3 = そのFungeインタプリタを起動したのと同じシェルによる解釈と同等です。インタプリタがこのパラダイムをサポートしている場合は、これを利用することで、Befungeソースを実行中のユーザーは、どのシェルを使って
=
命令を実行するかを簡単に選択することができます。 - この値は、プログラムが
=
がどういった挙動をするか合理的に把握するために含まれています。ここに示されている値は、公開時に入手可能な最も基本的なセットに過ぎません。さらなるオペレーティング・パラダイムへの画期的な前進については、Registry5を参照してください。
-
パスの区切り文字を含む1セル(グローバル環境)
-
i
とo
のファイル名のパスの区切り文字を示します。
-
-
ベクトルごとのスカラ数を含む1セル(グローバル環境)
- 別名:次元数。Befungeなら2、Unefungeなら1、Trefungeなら3。
-
現在のIPのユニークIDを含む1セル(ip)
- 並列Fungeの場合のみ有効です。IDは、このIPを現在IPリストに存在する他の全IPと区別します。
-
現在のIPのユニークチーム番号を含む1セル(ip)
- NetFungeやBeGladでのみ重要になります。
-
現在IPのBefunge-Spaceでの位置を含む1ベクトル(ip)
-
現在IPのBefunge-Spaceでのデルタを含む1ベクトル(ip)
-
現在IPのFunge-Spaceでのストレージオフセットを含む1ベクトル(ip)
-
原点を基準とした、非空間セルを含む最小点を含む1ベクトル(環境)
-
最小点を基準とした、非空間セルを含む最大点を含む1ベクトル(環境)
- これら二つのベクトルは、プログラムソース全体をテキストファイルに出力するため
o
命令に与えるときに便利です。
- これら二つのベクトルは、プログラムソース全体をテキストファイルに出力するため
-
現在の((年-1900)256256)+(月*256)+(日)を含む1セル(環境)
-
現在の(時256256)+(分*256)+(秒)を含む1セル(環境)
-
IPが現在使用しているスタックの総数(stack stackのサイズ)を含む1セル(ip)
-
各スタックのサイズを含み、TOSSからBOSSまでをリストアップしたsize-of-stack-stackセル(ip)
- スタックサイズは、
y
がスタックにプッシュを開始する前に測定されます。
- スタックサイズは、
-
一連のコマンドライン引数を含む、追加の二重NULLで終了するそれぞれがNULLで終了する1連の文字(文字列)のシーケンス(環境)
- これは、分離された引数はNULL文字列になることがありますが、連続した二つの引数はNULL文字列になりません。
-
それぞれNULLで終わる文字列群の、更にNULLで終わる纏まり。環境変数が含まれます。^5
- 各変数の設定書式は
NAME=VALUE
です。
- 各変数の設定書式は
y
に正の引数が与えられた場合、これらすべてのセルは、引数が正でない場合と同様にスタックにプッシュされます。しかし、y
はその後、(上から数えて)引数番目のスタックセルを一時的な場所にコピーし、その後、スタックにプッシュしたセルをすべて削除します。そして、その一時的なコピーをスタックにプッシュします。例えば3y
は、ハンドプリントだけがスタックにプッシュされたかのように振舞います。
この動作の興味深い副作用は、y
がスタックにプッシュするセル数を超える引数を渡した場合、y
が実行されるより前にスタック上に存在したデータを'選択/pick'命令として動作させることができるという点です。
##スケール:拡張とカスタマイズ
Funge-98は機能的に完全にカスタマイズ可能で、り、拡張性があり、重要なプログラムを、ディレクティブなしに、移植性のある形で記述可能です。フィンガープリント・メカニズムは、フィンガープリントされた拡張機能の形式で、事実上無制限のライブラリを定義可能にします。ハンドプリント・メカニズムにより、プログラムは実行されているインタプリタを正確に識別可能です。
###ハンドプリント
ハンドプリントは、Funge-98の特定の実装(インタプリタ、コンパイラ等々)を一意に識別するビット列です。
これらは、既知のインタプリタのバグを知っており、そのインタプリタ上でポータブルソースを実行する際に、そのバグを回避したいと考えているFunge-98プログラムにのみ使われるべきです。(Features(特徴?)一方、機能はフィンガープリントであるべきです。すべてが適切に実装されていれば、現在のハンドプリントは無関係であるべきです。設計の決定時には、常にハンドプリントよりもフィンガープリントを優先して使用するべきです―――ハンドプリントは、とても理想的とは言えません。
ハンドプリントは通常、インタプリタのリビジョン間では同一です。ハンドプリントにはy
によってバージョン番号が付きます。
###フィンガープリント
Funge-98の拡張及びカスタマイズは、いわゆる"フィンガープリント・メカニズム"によって実現されています。Befunge-93にはそのようなメカニズムは存在しません。
より正確に言うならば、フィンガープリントとは命令A
〜Z
が何をするかに一時的に割り当てられるルーチンのライブラリ(フィンガープリント拡張)を示す固有のIDコードです。複数のフィンガープリントがロードされていると、オーバーラップ及びオーバーロードしてしまうことがあるので、オブジェクト指向的な継承も可能です。
一般的には、これらの新しいセマンティクスや命令は、それらをロードしたIPによってのみ利用可能であり、そのIPにのみ適用されます。フィンガープリント仕様自体は、この規則に例外を設けることができますが、それらが何であるかを明確に指定しなければなりません。
与えられた拡張機能のセマンティクスは、一般的にインタープリタに直接コード化されています。しかし、動的にロード可能な形式で利用できない理由もありません。とはいえそのような形式の利便性の高い標準フォーマットはありません。
セマンティクスが実行コードを含むディスクファイルから動的にロードされるのか、Fungeインタプリタにハードワイヤードされるのかの違いは、Fungeプログラマーの視点からすれば意味はありません。
ですが、Fungeプログラマと潜在的なFunge拡張機能ライター双方のために、フィンガープリントされた拡張機能には2つのクラスがあることを説明しておきましょう。その2つのクラスとは、基礎となるFunge仮想マシンの動作を変更し、かつ/または/and/orリエントラントではないもの(フェラル拡張/feral extensions)と、自己完結型でありリエントラントなもの(テーム拡張/tame extensions)です。
主な違いは、フェラル拡張機能は、それを認識していないFungeインタプリタに単純、あるいは簡単に"落とされる/dropped into"することができないという点です。フィンガープリントされた拡張機能を公開用に指定するときは、可能な限りテーム拡張機能にしてください―――プロプライエタリなインタープリタ機能のプログラム制御など、特定のセマンティクスでの目的を達成するために、フェラルなフィンガープリントの仕様が避けられないことも多々あります。
(
"ロード・セマンティクス"命令は、与えられたフィンガープリントのセマンティクスをA
からZ
までの命令のいくつかまたはすべてにロードします。次に、カウントセルをポップします。ポップする各セルについて、一時的な値(初期値は0です)を 256 倍し、セルの値を加算します。
このようにして(
はフィンガープリントをビルドします。 このメカニズムにより、0x452e472eのような巨大な指紋を、".G.E"4( ... )
のようなプリント可能なASCIIで表現することが簡単になり、すべての指紋の媒体として ASCII を使用する必要がなくなります。
次に、(
はこのフィンガープリントを使って、A
からZ
までの命令のセマンティックのセットを探してロードします。もしFunge実装が、与えられたフィンガープリントの正しいライブラリを見つけることができない場合、(
はr
のように動作します。しかし、セマンティック・ライブラリのロードが成功した場合、新しいフィンガープリントである1がスタックにプッシュされます。(後続の)
命令によって受け入れられるようにするためです)
対応する)
"アンロード・セマンティクス"命令は、与えられたフィンガープリントのセマンティクスを、A
~Z
のいずれかまたはすべてから(それまでにそのフィンガープリントがロードされたことがなかったとしても)アンロードします。そしてカウントをポップし、まったく同じ方法でフィンガープリントをビルドします。
(
)
は、Perlで言う"use x ... no x"のようなものです。インタプリタライターやインタプリタカスタマイザは、フィンガープリントに好きなことをさせることができます。また、それらは好きなようにフィンガープリントを実装可能ですが、フィンガープリントとフィンガープリントユーザーの間の'契約/contract'として幾つかの一般的なルールに従わねばなりません。
- フィンガープリントは、その仕様として明確に文書化されている必要がある場合を除き、
A
~Z
以外の命令のセマンティクスに影響を与えるべきではありません。 - ロードされたとき、
A
とB
のセマンティクスを実装したフィンガープリントは、次のように動作するはずです:
save(A); bind(A, myAsemantic()); save(B); bind(B, myBsemantic());
When unloaded, the same fingerprint should act something like
restore(B); restore(A);
- アンロードされると、同じフィンガープリントは以下のように動作します。
restore(B); restore(A);
- ここで'bind'は、現在のFunge-98プログラムでの命令の実行を変更することを意味します。
- ここで'save'は、命令のセマンティクスを後で使用するためセーブする(スタックにプッシュする)ことを意味します。
- ここで'restore'は、最後にセーブされたバージョンから命令のセマンティクスを呼び出す(スタックからポップする)ことを意味します。
これにより、フィンガープリントを「オーバーローディング」することができます。3つのフィンガープリントがインタプリタに実装されているとして、
-
D
を'破壊/Destroy'、R
を'復元/Restore'と実装したE.G.
-
S
を'送信/Send'、R
を'受信/Receive'、E
を'暗号化/Encrypt'と実装したTEST
-
S
を'スライス/Slice'、D
を'ダイス/Dice'、P
を'ピュレ/Puree'と実装したWORK
これらを使えば、Fungeプログラマは次のようなコードを作成できるはずです:
".G.E"4( ... "TSET"4( ... "KROW"4( ... S ... ) ... ) ... )
ここで、S
命令はWORK
フィンガープリントの'スライス/Slice'命令を示しており、TEST
の'送信/Send'命令ではありません。
しかし、こう書かれていたならどうでしょうか:
"KROW"4( ... "TSET"4( ... ".G.E"4( ... S ... ) ... ) ... )
S
は、E.G.
にはS
がないためTEST
の'送信/Send'に、E.G.
に存在するD
命令は'破壊/Destroy'を返します。しかし、P
はTEST
にもE.G.
にも定義されていないため、WORK
の'ピュレ/Puree'を使用することになるのです。
###Funge Central Registry
Funge Central Registryは、http://www.cats-eye.com/funge/registry/ 6にあるオンラインデータベースです。独自のFunge-98実装や拡張機能を開発してリリースする前に、必要なハンドプリントとフィンガープリントをすべてここに登録してください。
このシステムは、世界中でのフィンガープリントやハンドプリント同士の衝突を減らし、その独自性を確保し、そして現存する既知のすべてのハンドプリントとフィンガープリントの迅速な参照を可能とするために設置されています。
Befungeに標準ライブラリはありません。Funge-98インタプリタはフィンガープリントをなんでもかんでも実装する必要はありません。Funge-98の"標準"ライブラリは、"公式に認可された"拡張機能に過ぎず、Registryではフィンガープリントでカタログ化されています。拡張機能は永遠に蓄積されていくので、"標準"フィンガープリントのリストはRegistryを参照してください。
##付録
###命令のクイックリファレンス
/
モディファイア―:
-
98
:Funge-98特有で、Befunge-93にはありません。 -
2D
:最低でも二次元が必要です。(Unefungeにはありません) -
3D
:最低でも三次元が必要です。(Unefunge、Befungeにはありません) -
c
:並列Funge。y
をチェックして、これらの命令が実装済みかどうか確認してください。 -
f
:ファイルシステムFunge。y
をチェックして、これらの命令が実装済みかどうか確認してください。
並列Funge-98
Befunge-93ではマルチスレッド実行をすることができませんが、Funge-98のスーパーセットである並列Funge-98/Concurrent Funge-98では、IPリスト/IP listと呼ばれる任意の数の同時実行される命令ポインタのリストが定義されます。並列Funge-98ではIPはスレッドとも呼ばれ、それぞれの位置、デルタ、スタックを持っています。
並列Funge-98のインタプリタは、ティックを順次生成する内部かつ想像上のクロック/clockを持っていると考えることもできます。各IPが遭遇した命令は、IPがリストに出現した順に処理され、各IPはそのデルタに指定された通りに移動します。
このリストは常に繰り返し、順次、同じ方向に処理されます。IPが削除された時にリストも削除され、どちらにせよいずれ実行される予定のリスト上の次のIPの命令が、実際に実行されます。
IPを追加生成するには、t
"分裂"命令を使用します。これにより、現在のIPは複製され、その複製IPはリストに追加され、次に親IPが実行されるより前に実行されます。
このように子IPがFunge-Spaceに渡されると、その位置、ストレージオフセット、スタックは、すべて親IPからそのままコピーされます。ただし、デルタは親IPとは逆になります。
@
"停止"命令は、現在のIPを停止します。もし現在のIPだけがIPリストの中で動いていた場合、プログラムは終了しますそうでなければ、リストの先頭部分が"下に移動/shifts down"し、リストの次のスレッドが実行されます。
###Lahey-Space
Lahey-SpaceはFunge-98で使われている空間数学モデルです。
Lahey-Spaceでは、線は原点から始まらねばならず、どんな向きを選んでも最終的には原点に戻らないといけません。もし反対に進めば、反対からやはり原点に到達します。
Lahey-lineをいくら走っても、最終的には同じLahey-lineに沿って、同じ方向に向かっているが、同時に別の方向から来ている状態になるでしょう。これは、最終的には毛色は繰り返されるということを意味し、(Fungeの用語では)無限の空間を一貫してラッピングすることを意味します。
さて、半径1の球体が原点から1単位上にあることを想像してみましょう。そして、原点より2単位上にある―――つまり、球体の上に置かれている平面を想像してください。
一つ気付くかもしれませんが、原点から平面上の任意の一点まで線を引くと、球体と正確に二度―――一度は原点で、もう一度は固有の場所で―――交差します。このようにして、平面を球体に一意にマッピングできます。
さて、平面上の点Aから同じく平面上の点Bまでの各法線はLahye-lineに変換可能であり、我々のモデルにおいては、球面上の点A'と、同じく球面上の点B'の両方を含む球面の円弧として見ることができます。
この円弧は、その後、球体を'ぐるり'と包み込むようにすることで、完全な円に拡張することができます。
###その他のトポロジー
前述の通り、Fungeはプログラミング言語のファミリーであり、多くの親戚や子孫がいます。このドキュメントではデカルトをカバーするFungeのみを扱っています。Honefunges(hex-netトポロジー)、Kleinefunges(クラインの壺)などの他のFungeも可能です。
しかし、高度なFunges/Advanced Fungesでは、複雑なトポロジーに対してtorodial spaceやLahey-spaceのどちらも十分に見つけられない可能性もあるため、この仕様では数学的方法によってラッピングの振舞いを定義するための言語を提供しています。
このようにしてwrapping function W()を定義可能です:
W(x,y) = (x<0 -> x:=79, x>79 -> x:=0, y<0 -> y:=24, y>24 -> y:=0)
これはBefunge-93のためのものです。複雑なトポロジは、独自のラッピング関数を定義できます。これらの関数が、問題となっているAdvanced Fungeのドキュメントで厳密かつ明確な指定を受けていれば、ユーザーの混乱を避けることができるため、非常にお勧めです。