私は、Forthに大きく基づいて、独自の連結言語を作成しています。
コンパイルワードCREATE
とDOES>
がどのように機能し、どのように実装されているか(実行時にForthのランタイム環境の状態が正確にどのように変化するか)を理解するのに少し問題があります。
一般的なビューを提供する次のリソースを読みましたが、それらの使用方法についてのみであり、システムがそれらを実装する方法については説明していません。
これら2つの単語の動作に関する次のことは、私には不明確です。
CREATE
は、入力ストリームから次の(スペースで区切られた)Wordを取得し、そのWordの新しい辞書項目を作成します。CREATE
は、新しいディクショナリ項目に何かを記入しますか?CREATE
は(スタック上で)何を返しますか?CREATE
とDOES>
の間の単語に何か特別なことはありますか?DOES>
作成されたWordの実行時の動作を「埋めます」。DOES>
は入力として何を消費しますか?17 CREATE SEVENTEEN ,
などのコードスニペットでは、DOES>
は使用されません。 DOES>
がオーバーライドする「デフォルトの動作」の種類はありますか?もちろん、これらのさまざまな不明確さは、何が起こっているのかを理解するのが困難であり、かなり複雑に見えるこれらの概念を、アセンブリのような低レベル言語で簡単な方法でどのように実装できるかというコア問題から生じます。
CREATE
およびDOES>
はどのように正確に機能しますか?
だから、私はゲームに少し遅れますが、これらの質問(特にDOES>について)は、Forthの初心者でもある私をも不思議に思っていました。これが私が学んだことと私がそれをどのように実装したかです:
[TL; DR: "CREATE"は、Wordを単純なデフォルトの動作にします。 「DOES>」は呼び出し元に戻りません。代わりに、戻りアドレスを使用して、「goto」を最新の定義に入れます。]
CREATEはスタックから何も取得せず、何も返しません。入力からWordを解析し、そのための辞書エントリを作成します。これは、新しく作成されたWordのコードを、整列されたアドレスをスタックにプッシュして単純に返す標準のボイラープレートコードで埋めます(後続の "、"(カンマ)呼び出しがデータで埋められるのと同じ整列されたアドレス)。私のシステムでは、CREATE NewVar
などの生成されたコードは次のようになります。
NewVar: Push_data next_addr
return
next_addr:
したがって、(初期化された)VARIABLEを次のように定義できます。
: VARIABLE CREATE 0 , ;
または、疑似マシンコードでは:
VARIABLE: call CREATE
Push_data 0
call comma
return
VARIABLE NewVar
のようなものを言うと、NewVarは "Push_data/return"を実行するWordになります。次に、0 ,
は、NewVarがスタックに配置するアドレスにゼロを格納します。これは、コードスニペットに示されている「next_addr」です。 NewVar @
や42 NewVar !
などを実行すると、その場所の読み取りと書き込みが行われます。
コンパイルに関して、CREATEとDOES>の間の単語、またはDOES>の後の単語についてさえ、(少なくとも私のシステムでは)特別なことは何もありません。定義がCREATEとDOES>を使用するWordは通常コンパイルされ、コンパイルされたコードでDOES>が「呼び出される」ことを確認します。 DOES>が行う特別なことは次のとおりです。最後に作成されたWordのコードの場所を検出し、「return」命令を「jump」命令で上書きします。宛先はリターンスタック上のアドレスです。 DOES>ルーチンの。このアドレスは、DOES>が呼び出されるたびにリターンスタックからポップされ、「ジャンプ」命令を作成するために使用されます。次にDOES>が呼び出し元に戻ろうとすると、実際にはDOES>が含まれているWordを呼び出した人に戻ります...コードの残りの部分には戻りません。 DOES>の実装は次のようになります。
DOES>: [find second opcode of latest definition]
popr ; like "R>"
[overwrite opcode with a "jump" to TOS value]
return
したがって、次のようにVALUEを定義すると、
: VALUE VARIABLE DOES> @ ;
私たちが得るものは次のようなものです:
VALUE: call VARIABLE
call DOES>
call fetch <-- return address of call to DOES>
return
コードは、上記のVARIABLEの定義を呼び出し、次に、CREATEを呼び出して新しいエントリを作成します。しかし、DOES>を呼び出すと、DOES>は上記の戻りアドレスをポップし、NewVarの定義をその場所にジャンプするように調整します。これにより、以前と同じように、データスタックに "next_addr"をプッシュするようにNewVarが作成されます。ジャンプしてfetchを呼び出します。これにより、DOES>の呼び出しで終了するようにVALUEが実行されます。 DOES>が戻ると、VALUEの残りの定義(@ ;
)ではなく、VALUEの呼び出し元に戻ります。
CREATEは即時のWordではないことに注意してください。 VARIABLEの定義はCREATE 0 ,
でしたが、VARIABLEの定義中にCREATEが検出されるため、「0」という名前のWordは作成されません...定義に焼き付けられるだけです。代わりに、VARIABLEが実際に実行されるときに、CREATEは入力から次のWordを取得して、新しい定義を作成しようとします。
また、DOES>は、最近定義されたWordについて多くを想定していることにも注意してください。 「リターン」オペコードを熱心に検索することもできましたが、代わりに、CREATEが新しいWordを作成する方法を知っているため、その定義に固定オフセットを使用しました。私はスペックに頼っています。これは、「[最新の定義]がCREATE ...で定義されていない場合、あいまいな条件が存在する」と述べています。私のシステムでは、その「あいまいな条件」は、一部のWordが2番目の命令として「ジャンプ」オペコードを取得することです。
入力として何を消費しますか?
独自の戻りアドレスを使用し、最新の定義を指すグローバル変数も使用します。
DOES>がオーバーライドするある種の「デフォルトの動作」はありますか?
はい。 CREATEが行うデフォルトの動作( "Push_data/return")です。
質問に順番に答えます。
CREATE
は、新しい空のデータスペースを割り当てる場合があります。次に、新しいディクショナリエントリの「データフィールド」をHERE
に設定し、実行セマンティクスでその「データフィールド」の値をプッシュします。 ' foo >BODY
は、foo
がfoo
で作成されたと想定して、CREATE
の「データフィールド」を返します。 CREATE
はスタックに何も返しません(または消費しません)。 CREATE
とDOES>
の間の単語については、特別なことは何も起こりません。 ANS Forthは、コンパイルコンテキストでDOES>
が行うことを定義し、CREATE
で作成された定義での操作のみを許可しますが、 同じ定義内で発生する必要はありません 。
上記の結果の1つはHERE CREATE foo foo =
が真である場合とそうでない場合がありますが、CREATE foo foo HERE =
は常に真である必要があります。
ANS ForthのDOES>
:DOES>
に関する質問への回答は、コンパイルコンテキストでのみ意味があります。 DOES>
は、実行トークンを返さず、代わりに定義された最後のWordを更新することを除いて、; :NONAME
と似た動作をします(CREATE
で定義されていると想定)。コンパイル時に、基本的に定義されているコードの不透明な実装定義の表現である「colon-sys」を消費します。その定義を終了し、新しいものを作成します。標準から引用すると、DOES>
"[r] eplace [s]以下の名前の実行セマンティクスを持つ最新の定義の実行セマンティクス[...] 「デフォルトの動作」は「データフィールド」の値をプッシュすることです。つまり、与えられたセマンティクスは「データフィールド」の値をプッシュすることから始まるので、概念的にはDOES>
が拡張のために実行セマンティクスを開くようなものです。実際に定義を拡張するのは、DOES>
に続く単語のコンパイルセマンティクスであるため、「拡張のために開く」と言います。
したがって、Forthについて理解しておくべき重要なことが1つあります。拡張された「制御」構造はありません。 Forthプログラムのセマンティクスは、一度にWordで処理することで理解できます。代わりに、いくつかの(実質的にグローバルな)状態を変更することにより、単語が互いに相互作用します。これは、インタープリターモード(コンパイルモードへの切り替えなど)、辞書、入力バッファー、またはさまざまなスタックの1つです。
Forthコンパイルの基本的なセマンティクスでは、「colon-sys」は、いくつかのコードの終わりへのポインタにすぎません。リテラルのコンパイルセマンティクスは、そのブロックにPushステートメントを追加し、ポインターを前方に移動することです。通常のWordのコンパイルセマンティクスは、ブロックに呼び出しステートメントを追加し、ポインターを前方に移動することです。次に、;
は、returnステートメントをブロックに追加し、「colon-sys」をスタックからポップします。辞書エントリは、コードブロックへのポインタにすぎません。 CREATE
の解釈セマンティクスは、新しい辞書エントリを作成し、コードをPush HERE
に追加して戻ることです。 DOES>
は、最後に定義されたディクショナリエントリのコードブロックを検索し、コードブロックへのポインター、つまりそれがプッシュする「colon-sys」をreturnステートメントにポイントします(上書きされるため)。次の単語のコンパイルセマンティクスは、通常どおりコードブロックを更新します。 ANS Forthが課す制限の多くは、単純なバンプアロケーターの使用を許可することです(個別のコードおよびデータスペースを想定)。たとえば、ネストされた定義のコードは最終的に外部定義のコードに埋め込まれるため、ネストされたコンパイルは許可されません。同様に、DOES>
は割り当てられたコードスペースの最後にあるため、最後に定義された定義でのみ機能します。
本をダウンロードしてください:Thinking Forth here ... https://sourceforge.net/projects/thinking-forth/files/reprint/rel-1.0/thinking-forth.pdf/download?use_mirror=gigenet&download= =
より明確な説明がForth、IncのWebサイトにあります。 https://www.forth.com/starting-forth/11-forth-compiler-defining-words/
これは、定義する言葉である定数のフォースの定義です。
:定数作成、> DOES @;
CREATEは、実行時に入力ストリームを「解析」して、CONSTANTの後に入力ストリームから次のWordをキャプチャし、CONSTANTの後に見つかった新しいWordを辞書に作成します。次に、CREATEと> Doesの間のコードが実行されます。この例では、16ビット値がメモリに格納されます。次に、CONSTANTが作成した新しいWordを実行すると、> DOESに続くコードが実行されます。したがって、16ビットの数値がデータスタックに返されます。
他の言語では、このような新しいコンパイラー関数を作成できません。 Cはそうではなく、#defineはカウントされません...
Forthで新しい言語を作成するのは非常に簡単です。フォースはよく呼ばれます:この機能のためのメタ言語。