Literal
とその仲間たちは、 データ値を現在の定義にコンパイルします。
他のワードを現在の定義にコンパイルするワードを記述することもできます。 例えば:
: compile-+ ( -- ) \ compiled code: ( n1 n2 -- n ) POSTPONE + ; : foo ( n1 n2 -- n ) [ compile-+ ] ; 1 2 foo .
これは : foo + ;
と同等です(確認するには see foo
としてください)。
この例では何が起こっているのでしょうか? Postpone
は、 +
のコンパイル機能(compilation
semantics)を compile-+
にコンパイルします。 その後、 テキスト・インタープリターは compile-+
を実行し、 + のコンパイル機能(compilation semantics)を実行します。 これにより、+
(の実行機能(execution semantics)) が foo
にコンパイルされます23。
postpone
( "name" – ) core “postpone”
name の コンパイル機能(compilation semantics)をコンパイルする。
compile-+
のようなコンパイル・ワード(compiling
words)のコンパイルは通常即実行ワード(または即実行同様)であるため、 それらを実行するためにインタプリタ状態に切り替える必要はありません。
最後の例をそれに応じて変更すると、 以下のようになります:
: [compile-+] ( compilation: --; interpretation: -- ) \ compiled code: ( n1 n2 -- n ) POSTPONE + ; immediate : foo ( n1 n2 -- n ) [compile-+] ; 1 2 foo .
場合によっては、 複数のワードを POSTPONE する必要があることに気づくでしょう。 このような各ワードの前に POSTPONE
を置くのは面倒なので、 Gforth ではより便利な構文 ]] ... [[
を提供しています。 これにより、
[compile-+]
を以下のように記述できるようになります:
: [compile-+] ( compilation: --; interpretation: -- ) ]] + [[ ; immediate
]]
( – ) gforth-0.6 “right-bracket-bracket”
postpone 状態に切り替え: すべてのワードと認識器(recognizers)は、 その前に postpone
があるかのように処理されます。 [[
が認識されると、 postpone 状態は終了します。
角括弧の珍しい方向はその機能を示しています。 ]
が即時実行(インタープリター状態)からコンパイルに切り替えるのと同じように、
]]
はコンパイルから postpone 状態(つまり、 コンパイル機能のコンパイル)に切り替えます。 逆に、 [[
は
postpone 状態からコンパイル状態に切り替えます。 これは、 コンパイル状態から即時実行(インタープリター状態)に切り替える [
に似ています。
]]
... [[
の本当の利点は、 POSTPONE するワードがたくさんある場合に明らかになります。 たとえば、
ワード compile-map-array
(see Advanced macros) は、
以下のようにさらに短く書くことができます:
: compile-map-array ( compilation: xt -- ; run-time: ... addr u -- ... ) \ at run-time, execute xt ( ... x -- ... ) for each element of the \ array beginning at addr and containing u elements {: xt: xt :} ]] cells over + swap ?do i @ xt 1 cells +loop [[ ; : sum-array ( addr u -- n ) 0 [ ' + compile-map-array ] ;
see sum-array
すると、 以下のコードが表示されます:
: sum-array #0 over + swap ?do i + #8 +LOOP ;
]]
...[[
に加えて、 この例では他の機能もいくつかお披露目しています:
xt:
を指定する) defer フレーバーのローカル変数 xt
を使用しています。
]]
...[[
内でローカル変数 xt に遭遇すると、 ローカル変数 xt を compile,
します。
]]
...[[
内でリテラル 1
を使用します。 これにより、 1
が postpone
(延期)されます。 つまり、 compile-map-array
の実行時にコンパイルされます。
compile-map-array
が実行されると、 1 cells
がコンパイルされ、 Gforth
の定数折りたたみによって #8
に最適化(optimize)されます。
注意: s\"
などのワードのパースは postpone 指定時にはパースされないため、 ]]
...[[
内ではパースされないことに注意してください。 s\" mystring\n"
の代わりに、 文字列認識器(string
recognizer)を使用して、 ]]
...[[
内で動作する "mystring\n"
で記述することができます。これは ]]
...[[
内で機能します。 同様に、 パース・ワード [']
についても、 `
で始まる認識器(recognizer)表記で記述することができます。
ただし、 あなたが s\"
を使用したい場合(または、 認識器(recognizer)置換がないパース・ワードがある場合)、
以下のようにコンパイル状態に切り替えることで実行できます:
]] ... [[ s\" mystring\n" ]] 2literal ... [[
標準 Forth での ]]
と、 その仲間の定義は、 compat/macros.fs で提供されます。
即時コンパイル・ワード(immediate compiling words)は、 他の言語(特に Lisp)のマクロに似ています。 C言語などのマクロとの重要な違いは以下のとおりです:
postpone
などを使用して定義されたマクロは、 文字列よりも高いレベルで言語を扱います。 名前との結び付け(name
binding)はマクロ定義時に行われるため、 C言語のマクロで発生する可能性のある名前の衝突の落とし穴を回避できます。 もちろん、 Forth
は自由主義の言語(liberal language)であり、 以下のようなテキスト解釈マクロ(text-interpreted
macros)を使用して自分自身を攻撃することもできます
: [compile-+] s" +" evaluate ; immediate
マクロ使用時に名前を結びつける(bind)だけでなく、 evaluate
を使用すると、 あなたの定義は
state
-smart (see state-smartness) になります。
マクロで数値をワードにコンパイルすることが必要な場合があります。 これを行うためのワードは literal
ですが、
postpone
する必要があるため、 literal
のコンパイル機能(compilation
semantics)はマクロがコンパイルされる時ではなくマクロの実行時に効果を発揮します:
: [compile-5] ( -- ) \ compiled code: ( -- n ) 5 POSTPONE literal ; immediate : foo [compile-5] ; foo .
マクロにパラメーターを渡して、 マクロを現在の定義にコンパイルする必要がある場合があります。 パラメーターが数値の場合は、
postpone literal
を使用できます(他の値の場合も同様)。
コンパイルされるワードを渡したい場合、 通常の方法は、実行トークンと compile,
を渡すことです:
: twice1 ( xt -- ) \ compiled code: ... -- ... dup compile, compile, ; : 2+ ( n1 -- n2 ) [ ' 1+ twice1 ] ;
compile,
( xt – ) core-ext “compile-comma”
xt で表される機能(semantics)を現在の定義に追加します。 結果のコード断片が実行されると、 xt が
execute
されたのと同一の振る舞いをします。
Gforth で利用可能な代替方法では、 コンパイル機能をパラメーター(compilation semantics)として渡すことができる、 コンパイル・トークンを使用します(see Compilation token)。 以下は、 上記と同じ例にこの方法を使ったものです:
: twice ( ... ct -- ... ) \ compiled code: ... -- ... 2dup 2>r execute 2r> execute ; : 2+ ( n1 -- n2 ) [ comp' 1+ twice ] ;
この例では、 2>r
と 2r>
により、 実行(execute)されるコンパイル機能(compilation
semantics)がデータ・スタックに影響を与える場合でも、 twice
が確実に機能するようにします(訳注: 2dup ( ct ct
) 2>r ( ct r:ct ) execute ( ?? r:ct ) 2r> ( ?? ct ) execute ( ??? ) 。 最初の
execute でデータ・スタックのTOSがどうなろうとも、 2つ目の execute のために ct を tos に与えるため。 ct は
2セル単位なので、 2dupで複製、 2>r ... 2r> で ct を1つ退避となる)
これらのワードを使用して完全な定義を定義することもできます。 これは、 does>
を使用する代わりの方法を提供します(see User-defined Defining Words)。 たとえば以下の代わりに
: curry+ ( n1 "name" -- ) CREATE , DOES> ( n2 -- n1+n2 ) @ + ;
以下のように定義することができます
: curry+ ( n1 "name" -- ) \ name execution: ( n2 -- n1+n2 ) >r : r> POSTPONE literal POSTPONE + POSTPONE ; ; -3 curry+ 3- see 3-
n1 にアクセスするために >r : r>
というシーケンスが必要です。 なぜなら、 :
はデータ・スタックに
colon-sys をプッシュし、 それより下にある全てのモノにアクセスできなくなるためです。
ワードを定義するこの方法は、 does>
を使用するよりも便利な場合もあれば、 そうでない場合もあります(see Advanced does> usage example)。 この方式の利点の 1 つは、 コンパイラーは、 literal
でコンパイルされた値が固定されているのに対して、 create
されたワードに関連付けられたデータは変更可能なことを認識しているため、
より適切に最適化できることです。
[compile]
( compilation "name" – ; run-time ? – ? ) core-ext “bracket-compile”
古いワード(lLegacy word)です。 代わりに postpone
を使用してください。 name
がデフォルト以外のコンパイル機能(compilation semantics)を持つ場合は、 postpone
と同様に機能します。
name がデフォルトのコンパイル機能を持つ(つまり、 通常のワードである)場合、 [compile] name
をコンパイルすることは、 name をコンパイルすることと同じです(つまり、 この場合 [compile]
は冗長です)。
最近の RFI の回答では、 ワードのコンパイルはコンパイル状態でのみ実行する必要があるとしているため、 この例はすべての標準システムで動作することは保証されませんが、 ちゃんとした(decent)システムであれば動作します