XT
普通は小文字でxtと書くが、これはexecution token(エグゼキューション・トークン)の頭文字である(英語ではex...の頭文字にeではなくxを採ることが普通なのは一般論)。直訳すれば「実行標識」である。数値なのだが、これでワードを識別できる。各ワードについた固有のタグである。イメージとしていえば関数ポインタのようなものである。しかし、関数ポインタとは違って、実装上は必ずしもポインターではないし、関数以外でもパブリックなワードであれば全て、例えば、大域変数などもxtを持っている。
xtは基本的には数値であってデータであるが、データ処理プロセスを意味するデータである。つまり、実行可能ワード(サブルーチン、関数、プロシージャー、他)をデータとして扱う手段を提供する。xtは普通の数値としてスタックに置かれたり、変数に格納されたり、取り出されたりすることもできるし、必要ならば実行することができる(つまり、その値をスタックに置いたからといって自動的に[実行]と解釈されたりすることはない。)。したがって、例えば、変数にxtを格納してランタイム中にその値を入れ替えることによって実行内容を動的に変更したり、リストに複数のxtを格納しランタイムに選択することで実行されるワードを切り替えるようなこともできるわけである。数値演算処理することには意味はない場合が多いが、xtは通常の数値と同様に自由に扱うことができ、例えば、あるワードが、その入力や出力として、実行可能ワードを受け取ったり返したりすることもできるようになるのである。いわばメタワードである。
xtは伝統的には、ワードの間接ポインタだったようである。間接スレッディング方式であれば、それを実行するのは標準的な解釈実行手順であるし、あるワードのxtをディクショナリ内の他のワードの定義内容リストに格納することは、まさにその呼び出しをコンパイルすることに対応する。しかし、現在では、間接スレッディグ方式を採用しているgforthでさえも、xtは必ずしも間接ポインタであるとはいえない(一つのワードにxtが複数あるようである)。それでも何らかのコードアドレスである環境は多いようであるが、例えばMopsでは、その値自体はメモリーアドレスでもない。したがって、xtを間接ポインタないし関数ポインタと考えることは、もはやできないのであって、規格上の機能を果たすこと以上の想定は避けなければならない、といわれている。
xtから、それに対応するワードの様々な情報が得られることになっている。まずは、ワードからそのxtをとる方法を述べる。
' (Tick:ティック)
形態のゆえに説明が書きにくいこの1文字ワードは、ティックと呼ばれている。アポストロフィー記号、あるいは、単引用符の閉じる方、右上から少し左下に払う方の上付き点である。この1文字もワードである。これに続くワードのxtをとる。これもワードであるから、次のワードとの間には空白が必要である。この1文字ワードのティックは実行状態で使用されることが想定されたワードである。
' ( "<spaces>name" -- xt )
解釈実行環境で、
' aWord
とすれば、スタック上にaWordのxtが積まれる。
しかし、ティックをワード定義内に書いても、その呼び出しがコンパイルされるだけで、次のワードのxtを実行時に返してくれるわけではない。
: word1 ' aword ; \ word1を実行しても、awordのxtは取れない。
上のword1を実行すればawordのxtがスタックに残ることを期待するかも知れないが、そうはならない。ティックが実行され、awordが実行されるだけである。しかし、そのせいでかえって面白いことが起こる。つまり、word1が実行されたときに、ティックが実行されて、その次のワード — word1を実行しているソースコード上の次のワードである — のxtをスタックに残すのである。そして、そのxtはawordを実行するときのスタック入力とすることができるのである。これは、簡単な構文を自分で作るときに利用できる。例えば、Mopsでは(おそらく、NEONでも)、メソッドセレクターがオブジェクトからクラスデータをとってメソッドを束縛するために、この機構を利用してきた(iMopsではこの仕組みは変更したが)。
[']
上の「思った通りにならない」例に対して、その思った通りの効果を得るには、この[']を用いる。ワード定義中において続くワードのxtを採り、それを実行時にスタックに置く。
ティックを角括弧で囲んだものである。ブラケット・ティックと呼ぶ。
こちらは逆に、ワード定義内(コンパイル状態)で使用することが前提とされ、解釈実行環境で使っても機能しない。
['] compilation: ( "<spaces>name" -- ) runtime: ( -- xt )
であるから、
: word2 ['] aword ;
としたときには、word2を実行すると、awordのxtがスタックに置かれ、aword自体は実行されない。次のように、
: word3 ['] aword next-word ;
とすれば、word3実行時にはnext-wordのスタック入力としてawordのxtが渡されることになる。
EXECUTE
xtは単なる数値データであって、関数ポインタと違って、それをスタックに置いたからといってそのワードの呼び出しを意味するわけではない。呼び出して実行したいときには、xtをデータスタックのトップに置いた上でEXECUTE実行する。そのxtに対応するワードが実行されることになるのであるから、データスタックのxtの下には、そのワードのための引数が必要に応じておかれなければならない。実行が終わった後は、そのxtに対応するワードの出力が(あれば)データスタック上に残る。
EXECUTE ( i×x xt -- j×x )
EXECUTEは、渡されたxtに対応するワードの実行セマンティクスを実行する。要するにそのワードの定義内容が実行されるのである。
したがって、
: word1 ['] aword EXECUTE ;
: word2 aword ;
のword1とword2は同じ内容になる。前者はクドいだけであるが、ランタイムにスタックにxtを渡せばよいため、可変的に利用できるのである、とクドく説明してみる。
Forthの標準規格からは、上でxtをとったawordは、変数でもよいということになる。というのは、変数は、その実行セマンティクスが、そのデータ領域アドレスや数値をスタックに置く、という内容のワードとして定義されているからである。他方、局所変数(locals)については、xtは取れないことになっている。
これに対して、Mopsでは、EXECUTEできるのは実行可能ワードに限られており、局所変数はもちろん、変数や定数についてもエラーとなる。変数のxtをEXECUTEしたいということは、ほとんどないことであるから、あまり制限としては感じないが、そのような違いがある点は注意が必要である。
Forthは"型無し"言語とされている。しかし、Mopsのワードには、クラスはもちろんのこと、通常のものにも実は型がある。
静的型付けであるが、黙示なだけである。多くの他のforthも設計上は同様なのではないかと思われる。
変数を数値と同一視することを前提として、forthの変数には型がないというが、forth変数はワードであり、内容数値と同等ではない。
そのワードとしての型はforthオブジェクトとしての低レベル型なわけである。
型が無いといえるとすれば、主にデータスタックの各セルのことに言及する場合であろう。
しかし、私見だが、データスタックのセルを変数と見ることができるかどうか疑わしい。
データスタックはいわば局所コンテキストのようなものと個人的には考えている。
補遺(DEFERに関連して)
DEFER! DEFER@
先に述べた
DEFERで定義されたワードに関する、IS及びACTION-OFの構文は、あまりforth的ではない(ソースコード内のパースを好まない人もいる)。そこで「forth的」語順で同様のことを実現するのが、DEFER!とDEFER@である。機能としては、DEFER!はISに、DEFER@はACTION-OFに対応する。使用法は、
DEFER myAction
' + ' myAction DEFER! \ myActionの内容が + になる
' myAction DEFER@ \ myActionの現在の内容のxtがスタックに積まれる
のようになる。myActionについても、そのxtをパラメターとして渡すことになるわけである。引数の順番としては、!や@の類推から考えればわかりやすい。
ワードデータを取る
xt経由でワードに関するデータを取る方法がいくつかある。便宜上、ここで説明する。
>BODY
直接または間接にCREATEを用いて生成されたワードのxtから、そのデータ域の開始アドレスを取るのが>BODYである。間接にCREATEを用いているのは、VARIABLEとVALUEである。
>BODY ( xt -- a-addr )
VARIABLE変数においては、そのxtに>BODYを実行することと、その変数名を書くこととが等価である。したがって、あまり有用な局面は無い。VALUE変数は通常そのデータフィールドの位置を知る方法がないので、デバッグなどのときには有用な局面はあるかも知れない。
後の頁で説明するCREATE-DOES>で定義されたワードのプライベートデータ域に対して利用するのが本来の意図であろう。しかし、そこでもデバッグが主な用途になるであろう。
CREATEで生成されたのではないワードのxtに用いた場合には、有意義な値は返せない。結果は環境によりそれぞれ異なるが、Mopsでは無意味な値を返すだけである。
なお、forth規格にはないが、Mopsには他にもいくつかxtからワードデータを取るワードが定義されている。他のforth環境でも定義されているものはあるかも知れない。基本的にはデバッグ等にしか利用できないが。
>NAME
このワードは、xtを受け取り、辞書内の対応するワードの名前文字列の格納個所を返す。
>NAME ( xt -- c-addr )
名前文字列は、カウンテド文字列(counted string)の形式で格納されている。これはPascal文字列とも呼ばれるもので、最初の1バイトに文字列全体のバイト数が格納され、次のバイトから文字列が格納される形式である。他のページでも述べるが、この形式の文字列をforth型のaddr len文字列に変換するワードとして
COUNTが定義されている。すでにcountedな文字列にCOUNTというのは奇妙ではあるが、歴史的理由でそうなっているようである。
当然のことながら、名前のないxtについては意味のない値を返す。
.ID
このワードは、名前文字列の格納場所というに止まらず、その内容をコンソールウィンドウに印字する。
.ID ( xt -- )
これも当然ながら、名前のないxtに関しては何もしない。
次は、
最終更新:2019年01月16日 00:40