このマニュアルは、 標準 Forth 言語の高速で移植可能な実装である Gforth (バージョン 0.7.9_20240418, April 18, 2024)用です。 これはリファレンス・マニュアルとして機能しますが、 Forth の概要と Forth チュートリアルも含まれています。
Authors: Bernd Paysan, Anton Ertl, Gerald Wodni Copyright © 1995, 1996, 1997, 1998, 2000, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover texts being “A GNU Manual,” and with the Back-Cover Texts as in (a) below. A copy of the license is included in the section entitled “GNU Free Documentation License.”
(a) The FSF’s Back-Cover Text is: “You have freedom to copy and modify this GNU Manual, like GNU software. Copies published by the Free Software Foundation raise funds for GNU development.”
POSTPONE
Literal
CREATE
CREATE..DOES>
CREATE..DOES>
to
and defer@
compile,
Const-does>
Gforth プロジェクトの目標(goal)は 標準Forth(Standard Forth)のための標準の模範(model)を開発することです。 これは以下のように、 幾つかの副目標に落とし込めます:
上記の目標を達成するには Gforth は以下のようにするべきです
私達はこれらの目標を達成できているでしょうか? Gforth は、 Forth-94 (ANS Forth) と Forth-2012 標準に準拠しています。 内部データ構造の一部 (特にヘッダー) は時間の経過とともに変更されているため、 Gforth は安定しているモデルとは見なさていません。 確かにまだ事実上の標準にはなっていませんが、 かなり人気があるようです。 以前のモデルとの類似点と相違点がいくつかあります。 いくつかの強力な機能がありますが、 まだ私たちが想像していたもの全てを備えているわけではありません。 私たちは確かに実行速度の目標を達成しました(see Performance)1。 Gforth は無料で、多くのマシンで利用できます。
(Gforthの安定性)Gforth の以前のバージョンで動作するプログラムは、 新しいバージョンでも動作するはずです。 ただし、 以下のようないくつかの注意点があります:
Gforth の内部データ構造 (コンパイル済みコードの表現を含む) は、 文書化されていない限り、 バージョン間で変更される可能性があります。
さらに、 私達は、 標準のワード (つまり、 標準のワードセットにある名前のワード)と、 永続的な Gforth
拡張機能として文書化されたワード(ワードセット名が gforth
または gforth-<version>
であるワード)(see Notation 参照)は、 保持する義務があると考えています。
他のワードは新しいリリースでは削除される可能性があります。
とりわけ、 あなたは locate
を使用するか、 または、 Gforth のソース・コードを調べることでワードを見つけることができ、
スタック効果コメントの直後のコメントでワードセットを確認できます。 そのワードにワードセットがない場合、 それは内部実装であり、
将来のバージョンでは削除される可能性があります。 ワードセットが gforth-experimental
または
gforth-internal
または gforth-obsolete
の場合、
そのワードは将来のバージョンで削除される可能性があります。
あなたが永続的としてマークされていない特定のワードを使用したい場合、 私達にご連絡ください。 そのワードを永続的ワードとして追加することを検討します(または、このワードに代わる代替案を提案する場合もあります)。
メモ: Gforth のマニュアル・ページ(man page)は、 結局のところ、 この章の内容から自動生成されています。
イメージの作成に関する関連情報については Image Files を参照してください。
Gforth は 2 つの部分から成ります。 実行可能「エンジン」(gforth
または gforth-fast
という名前)と、イメージ・ファイルです。 Gforth を開始するには、 通常は gforth
とするだけです。 これにより、
デフォルトのイメージ・ファイル gforth.fi が自動的にロードされます。 他の多くの場合、 デフォルトの Gforth
イメージは以下のように呼び出されます:
gforth [file | -e forth-code] ...
これにより、 ファイルの内容や、 (訳注:コマンドラインに記述された、) Forth コードが、 指定の順序で通訳(interpret)されます。
gforth
エンジンに加えて、 gforth-fast
というエンジンもあります。 これは高速ですが、
表示されるエラー・メッセージ(see Error messages)の情報が少なく、 そして、 一部のエラーのキャッチ(特に、
スタック・アンダーフローや整数除算エラーなど)が遅れて発生するか、 あるいはまったく発生しません。 デバッグ済みの、
パフォーマンスが重要なプログラムに対して使用するべきです。
さらに、 gforth-itc
というエンジンがあり、 下位互換性が必要な状況(see Direct or Indirect Threaded?)で役に立ちます。
一般に、 コマンドラインは以下のようになります:
gforth[-fast] [engine options] [image options]
エンジン・オプションは、 コマンド・ラインの他の部分より前に指定する必要があります。 それらは以下のとおりです:
--image-file file
¶-i file
デフォルトの gforth.fi の代わりに、 指定の Forth イメージ・ファイルをロードします(see Image Files)。
--appl-image file
¶指定のイメージ・ファイルをロードし、 これ以降のコマンドライン引数は(エンジンのオプションとして処理するのではなく)、
すべてそのイメージが処理する為に残します。 これは、 gforthmi --application ...
で構築された、 Unix
上で実行可能なアプリケーション・イメージを構築する場合に便利です。
--path path
¶-p path
デフォルトである環境変数 GFORTHPATH
、 またはインストール時に指定されたパス(例:
/usr/local/share/gforth/0.2.0:.)と作業ディレクトリ .
の代わりに、
指定のパスを使用してイメージ・ファイルと Forth ソース・コード・ファイルを検索します。 パスは ‘:’
で区切られたディレクトリのリストとして指定されます(以前のバージョンには他の OS 用に ‘;’ がありましたが、 Cygwin は現在は
/cygdrive/<letter>
のみを受け入れ、 かつ、 私達は OS/2 や MS-DOS のサポートは終了してしまったため、
どこでも全部 ‘:’ になりました)。
--dictionary-size size
¶-m size
イメージで指定されているデフォルト(通常は 256K)を使用する代わりに、 Forth
ディクショナリー用のスペースに指定サイズ(size)のスペースを割り当てます。
このオプションと、下記のオプションのサイズ(size)指定は整数と単位で構成されます(例: 4M
)。 単位は b
(バイト)、 e
(elementの略。セル単位)、 k
(キロバイト)、 M
(メガバイト)、
G
(ギガバイト)、 T
(テラバイト)のいずれかになります。 単位が指定されていない場合 e
が使用されます。
--data-stack-size size
¶-d size
イメージで指定されているデフォルト(通常は 16K) を使用する代わりに、
データ・スタックに指定のサイズ(size)のスペースを割り当てます。サイズ(size)指定は整数と単位で構成されます(例:
4M
)。 単位は b
(バイト)、 e
(elementの略。セル単位)、 k
(キロバイト)、
M
(メガバイト)、 G
(ギガバイト)、 T
(テラバイト)のいずれかになります。
単位が指定されていない場合 e
が使用されます。
--return-stack-size size
¶-r size
イメージで指定されたデフォルト(通常は 15K)を使用する代わりに、 リターン・スタックに指定のサイズ(size)のスペースを割り当てます。
サイズ(size)指定は整数と単位で構成されます(例: 4M
)。 単位は b
(バイト)、 e
(elementの略。セル単位)、 k
(キロバイト)、 M
(メガバイト)、 G
(ギガバイト)、
T
(テラバイト)のいずれかになります。 単位が指定されていない場合 e
が使用されます。
--fp-stack-size size
¶-f size
イメージで指定されているデフォルト(通常は 15.5K)を使用する代わりに、
浮動小数点スタックに指定のサイズ(size)のスペースを割り当てます。この場合、単位指定子 e
は浮動小数点数を参照します。
サイズ(size)指定は整数と単位で構成されます(例: 4M
)。 単位は b
(バイト)、 e
(elementの略。セル単位)、 k
(キロバイト)、 M
(メガバイト)、 G
(ギガバイト)、
T
(テラバイト)のいずれかになります。 単位が指定されていない場合 e
が使用されます。
--locals-stack-size size
¶-l size
イメージで指定されているデフォルト(通常は 14.5K)を使用する代わりに、 ローカル・スタック(locals
stack)に指定サイズ(size)のスペースを割り当てます。 サイズ(size)指定は整数と単位で構成されます(例:
4M
)。 単位は b
(バイト)、 e
(elementの略。セル単位)、 k
(キロバイト)、
M
(メガバイト)、 G
(ギガバイト)、 T
(テラバイト)のいずれかになります。
単位が指定されていない場合 e
が使用されます。
--vm-commit
¶通常 Gforth は、 ディクショナリーとスタックに十分な仮想メモリー(virtual memory)がない場合でも起動しようとします(OS
がサポートしていれば MAP_NORESERVE
を使用します)。 したがって、
あなたは非常に大きなディクショナリーやスタックを要求することができ、 利用可能なのを超える仮想メモリーを使用しない限り、
すべて問題ありません(ただし、それを超えて使用すると、 プロセスが強制終了(kill)されます)。 このオプションを使用すると、 OS
のデフォルトの割り当てポリシーを使用するようになります。 とりわけ、 オーバーコミットしない OS (Solaris など) の場合、 これは、
大きなディクショナリーやスタックを要求することはできませんし、 要求すべきではないことを意味しますが、 しかし、 Gforth
が正常に起動する事に成功したならば、 メモリー不足によって強制終了(kill)させられることはありません。
--help
¶-h
コマンドライン・オプションに関するメッセージを出力します
--version
¶-v
バージョンを出力して終了(exit)
--debug
¶起動時のデバッグに役立ついくつかの情報を出力します。
--offset-image
¶それ以外の場合に使用される位置とはわずかに異なる位置でディクショナリーを開始します (データ再配置可能イメージ(data-relocatable images)の作成に役立ちます see Data-Relocatable Image Files)。
--no-offset-im
¶ディクショナリーを通常の位置で開始します。
--clear-dictionary
¶イメージをロードする前に、 ディクショナリー内の全てのバイトを 0 に初期化します(see Data-Relocatable Image Files)。
--die-on-signal
¶通常、 Gforth はほとんどのシグナル(例えば、 ユーザー割り込みの SIGINT や、 セグメンテーション違反 SIGSEGV)を Forth の
THROW
に変換することで処理します。 このオプションを使用すると、 Gforth
はそのようなシグナルを受信すると終了(exit)します。 このオプションは、
(最初のシグナルから回復する前に別のシグナルが発生するなど、)エンジンやイメージがひどく壊れている可能性がある場合に役立ちます。 このオプションは、
そのような場合の無限ループを回避します。
--no-dynamic
¶--dynamic
レプリケーション(replication)を伴う動的スーパー命令(dynamic superinstructions)を無効または有効にします(see Dynamic Superinstructions)。
--no-super
¶動的スーパー命令(dynamic superinstructions)を無効にし、 動的レプリケーション(dynamic replication)のみを使用します。 これは、 スレッド化コード(threaded code)にパッチを適用する場合に便利です(see Dynamic Superinstructions)。
--ss-number=N
¶エンジンにコンパイル済みの最初の N 個の静的スーパー命令(static
superinstructions)のみを使用します(デフォルトでは全てを使用します。 注意: gforth-fast
のみのオプションです)。 このオプションは、 静的スーパー命令(static
superinstructions)のパフォーマンスへの影響を測定するのに役立ちます。
--ss-min-codesize
¶--ss-min-ls
--ss-min-lsu
--ss-min-nexts
指定のメトリックを使用して、 静的スーパー命令の選択(static superinstruction
selection)をするためにプリミティブまたは静的スーパー命令のコストを決定します。 Codesize
はプリミティブまたは静的スーパー命令のネイティブ・コード・サイズ、 そして、 ls
はロードとストアの数、 そして、 lsu
はロードとストアと更新の数、 そして、 nexts
は(動的スーパー命令を考慮しない)ディスパッチの数です。 ここで、
すべてのプリミティブまたは静的スーパー命令のコストは 1 です。 デフォルトでは、 動的コード生成を使用する場合は codesize
、
それ以外の場合は nexts
です。
--ss-greedy
¶このオプションは、 静的スーパー命令のパフォーマンスへの影響を測定するのに役立ちます。 デフォルトでは、 静的スーパー命令の選択には最適な最短パス・アルゴリズム(shortest-path algorithm)が使用されます。 --ss-greedy を使用すると、 そのアルゴリズムは、 現在検討中の静的スーパー命令以降は静的スーパー命令に結合されないと想定するように変更されます。 --ss-min-nexts を使用すると、 その時点で利用可能な最長のスーパー命令を常に選択する貪欲なアルゴリズムと同じ結果が生成されます。 たとえば、スーパー命令 AB と BCD がある場合、 シーケンス A B C D に対して、 最適アルゴリズムは A BCD を選択し、 貪欲アルゴリズムは AB C D を選択します。
--print-metrics
¶静的スーパー命令の選択中に使用されるいくつかのメトリックを出力します。 code size
は、
動的に生成されたコードの実際のサイズです。 Metric codesize
は、静的スーパー命令の選択によって確認できるコードサイズ・メトリック(codesize metrics)の合計で、 code size
とは異なります。 これは、 すべてのプリミティブと静的スーパー命令が動的に生成されるコードにコンパイルされるわけではないことと、
マーカーがあるためです。 他のメトリクスは ss-min-... オプションに対応します。
このオプションは、--ss-... オプションの効果を評価するのに役立ちます。
上記にて説明したように、 デフォルト・イメージである gforth.fi のイメージ固有のコマンドライン引数は、 一連のファイル名と、
指定した順序で通訳(interpret)される -e forth-code
オプションで構成されます。 -e
forth-code
または --evaluate forth-code
オプションは Forth
コードを評価(evaluate)します。 このオプションは引数を 1 つだけ取ります。 あなたが、もし、 さらに多くの Forth
ワードを評価したい場合は、 ワードを引用符で囲むか、 -e
を複数回使用する必要があります。
コマンド・ラインの処理後に(対話モードに入るのではなく、)終了(exit)するには、 コマンド・ラインに -e bye
を追加します。
Forth プログラムではコマンド・ライン引数を処理することもできます(see OS command line arguments)。
複数のバージョンの Gforth がインストールされている場合、 gforth
は最後にインストールされたバージョンを呼び出します。
gforth-<version>
は特定のバージョンを呼び出します。 あなたの環境に環境変数 GFORTHPATH
がある場合、 --path
オプションを使用してこの環境変数をオーバーライドできます。
起動時、 イメージ・オプションを処理する前に、 環境変数 GFORTH_ENV
で指定されたユーザー初期化ファイル、
またはその環境変数が設定されていない場合は(存在する場合、) ~/.config/gforthrc0 がインクルードされます。
GFORTH_ENV
が「off
」の場合は何もインクルードしません。 すべてのイメージ・オプションを処理した後、
ブート・メッセージを出力する直前に、 オプション --no-rc
が指定されていない限り、 ホーム・ディレクトリのユーザー初期化ファイル
~/.config/gforthrc がインクルードされます。
警告レベルは以下のように設定できます
-W
¶警告(warnings)をオフにする
-Won
¶警告(warnings)をオンにする(レベル 1)
-Wall
¶初心者向け警告をオンにする(レベル 2)
-Wpedantic
¶細かい構文的な警告(pedantic warnings)をオンにする(レベル 3)
-Werror
¶警告をエラーとして出す(レベル 4)
bye
または (行の先頭で、) Ctrl-d または (--die-on-signal
オプションを指定して
Gforth を起動した場合、) Ctrl+C を入力すると、 Gforth を終了できます。 Gforth を終了すると、
あなたの定義とデータはすべて破棄されます。 Gforth を終了する前にシステムの状態を保存する方法については、 Image Files
を参照してください。
bye
( – ) \ gforth を正常に終了する。
Gforth には、 シンプルなテキストベースのオンライン・ヘルプ・システムがあります。
help
( "rest-of-line" – ) gforth-1.0 “help”
名前が指定されていない場合は、 基礎的なヘルプが表示されます。 ドキュメント・ノード名に ‘::‘ が続く場合、 ノードの最初を表示します。
ワードの名前が指定されている場合、 そのワードのドキュメントが存在する場合はそのドキュメントを、 存在しない場合はソース・コードを表示します。
g
を使用して、 help
で表示された場所でエディタに入ります。 Help
は現在の位置をマークするため、
n
と b
を使用してテキストの詳細を表示したり、 g
を使用してエディターでドキュメントにアクセスしたりできます(see Locating source code definitions)。
authors
( – ) gforth-1.0 “authors”
著者のリストを表示
license
( – ) gforth-0.2 “license”
ライセンス声明を出力します
Gforth は、 テキスト・インタープリターに入力したすべての行を記録するヒストリ・ファイルを維持しています。 このファイルはセッションをまたいで保存され、 コマンドラインの再呼び出し機能を提供するために使用されます。 Ctrl-P を繰り返し入力すると、このセッション(または前のセッション)から古いコマンドを連続して呼び出すことができます。 コマンドライン編集機能の完全なリストは以下のとおりです:
bye
使用すると正常に終了します)。
Ctrl-d
も使えます) カーソル位置の文字を削除します。
編集中、 表示可能な文字はカーソル位置の左側に挿入されます。 行は常に(「上書きモード」(overstrike)ではなく、)「挿入モード」(insert)です。
Unix システムでは、 ヒストリ・ファイルは デフォルトでは $HOME/.local/share/gforth/historyです2。 以下のコマンドを使用して、 ヒストリ・ファイルの名前と場所を確認できます:
history-file type \ Unix-class systems history-file type \ Other systems history-dir type
あなたが長い定義を手入力した場合、 テキスト・エディターを使用してヒストリ・ファイルから Forth ソース・ファイルに貼り付け、 後で再利用できます。
Gforth はヒストリ・ファイルのサイズを決して削減しないため、 あなたは必要に応じて定期的にヒストリ・ファイルのサイズを削減する必要があります。
Gforth は以下の環境変数を使用します:
GFORTHHIST
– (Unix系のみ) ヒストリ・ファイル .gforth-history のパスを指定。
デフォルト: $HOME/.local/share/gforth/history
.
GFORTHPATH
– gforth イメージ・ファイルと Forth ソースコード・ファイル
を検索するときに使用するパスを指定(通常 ‘.’ 、現在の作業ディレクトリ)。 パス区切り文字は ‘:’ です。
/usr/local/share/gforth/1.0:. みたいなのが典型です。
LANG
– LC_CTYPE
参照
LC_ALL
– LC_CTYPE
参照
LC_CTYPE
– Gforth の起動時にこの環境変数が “UTF-8” を含んでいる場合、 Gforth は内部で文字列に
UTF-8 エンコーディングを使用し、 UTF-8 エンコーディングでの入力を期待し、 UTF-8 エンコーディングで出力を生成します。
それ以外の場合、 エンコーディングは 8 ビットです(see Xchars and Unicode)。 この環境変数が設定されていない場合、
Gforth は LC_ALL
を調べ、 それも設定されていない場合は LANG
を調べます。
GFORTHSYSTEMPREFIX
– C言語 の system()
に渡す前に system
の引数に何を付加するかを指定します。 デフォルト: Windows では "./$COMSPEC /c "
で、他の OS では
""
です。 このプレフィックスとコマンドは直接連結されるため、 間にスペースが必要な場合はプレフィックスに追加してください。
GFORTH
– gforthmi によって使用されます(See gforthmi)。
GFORTHD
– gforthmi によって使用されます(See gforthmi)。
TMP
, TEMP
- (Unix系以外) ヒストリ・ファイルの場所として暗黙に使用されます。
すべての Gforth 環境変数は、 設定されていない場合、 デフォルトで適切な値になります。
Gforth を Unix系にインストールすると、 デフォルトでは以下の場所にファイルがインストールされます:
configure
のオプションを使用すると、 インストール先に違う場所を選択できます(どんなオプションがあるかは
configure --help
してください)。
Gforth は、他の場所で作成されたパイプラインを使用できます(以下で説明します)。 独自にパイプラインを作成することもできます(see Pipes)。
Gforth にパイプライン入力する場合、 プログラムは stdin
から read-file
または
read-line
を使用して読み取る必要があります(see General files)。 Key
は入力の終わりを認識しません。 accept
のようなワードは入力をエコーするため、
通常はパイプラインからの読み取りには役に立ちません。 あなたは、
(Forthのテキスト・インタープリターはパイプライン入力を通訳(interpret)しようとしてしまうため、)Forth内のコマンド・ラインを使用する機会はなく、
OSのコマンド・ライン・オプションを使用して Forth プログラムを呼び出す必要があります。
あなたは type
、 emit
、 cr
などでパイプラインへ出力できます。
もう一方の端で既に閉じられているパイプラインに書き込むと、 Gforth は SIGPIPE シグナル(「パイプが壊れた」シグナル)を受け取ります。
Gforth はこれを例外 broken-pipe-error
に変換します。 あなたのアプリケーションがその例外をキャッチしない場合、
システムはその例外をキャッチして、 通常は黙って終了(exit)します(Forth コマンド・ラインで作業している場合を除き、
エラー・メッセージを出力して終了します)。 これは通常、 望ましい動作です。
この振る舞いが気に入らない場合は、 自分で例外をキャッチし、 対応する必要があります。
ここで、 パイプライン内で使用できる Gforth の呼び出しの例を以下に示します:
gforth -e ": foo begin pad dup 10 stdin read-file throw dup while \ type repeat ; foo bye"
この例では、 入力をそのまま出力にコピーするだけです。 この例を含む非常に単純なパイプラインは以下のようになります:
cat startup.fs | gforth -e ": foo begin pad dup 80 stdin read-file throw dup while \ type repeat ; foo bye"| head
Gforth の stderr
出力に関連するパイプラインは機能しません。
Gforth を CGI スクリプトやシェルスクリプトで使用する場合、 起動速度が問題になる場合があります。 libc-2.7 を搭載した 64 ビット
Linux 2.6.27.8 上の 3GHz Core 2 Duo E8400 では、 gforth-fast -e bye
でユーザー時間は 13.1 ミリ秒、システム時間は 1.2 ミリ秒かかります(gforth -e bye
は、
下記で説明するオプションの一部を組み込んでいるため、 ユーザー時間は約 3.4 ミリ秒、 システム時間は 1.2 ミリ秒で起動がより高速になります)。
起動速度が問題になる場合は、 以下の改善方法を検討してください。 または、 起動の数を減らす方法(Fast-CGI の使用など)を検討することもできます。 以下の最初の手順では、 (コンパイル時間を含む)実行時間を犠牲にして起動時間を短縮するため、 それで利益が得られるかどうかはあなたのアプリケーションでのこれらの時間のバランスによって決まることに注意してください。
Gforth の起動速度に影響を及ぼす簡単な手順は、 実行時間(run-time)を増加させながらイメージの読み込み時間(image-loading time)を短縮する多数のオプションを使用してみることです。
最初に試す必要があるのは --ss-number=0 --ss-states=1
です。
このオプションは実行時の高速化が比較的わずかなのに、 起動時にかなりの時間がかかるためです。 gforth-fast
--ss-number=0 --ss-states=1 -e bye
には、 ユーザー時間が約 2.8 ミリ秒、 システム時間が約 1.5
ミリ秒かかります。
次のオプションは --no-dynamic
です。 これは実行時間に大きな影響を及ぼします(いくつかのプラットフォームでは約 2
倍です)が、 それでも起動速度が若干速くなります。 gforth-fast -- ss-number=0 --ss-states=1
--no-dynamic -e bye
は、 約 2.6 ミリ秒のユーザー時間と 1.2 ミリ秒のシステム時間を消費します。
起動速度を向上させるための次のステップは、 データ再配置可能イメージ(data-relocatable
image)を使用することです(see Data-Relocatable Image Files)。 これにより、
イメージ内のコードの再配置コストが回避されます(ただし、 データの再配置コストは回避されません)。 イメージは、 使用している特定のバイナリ(つまり、
gforth
、 gforth-fast
、さらには特定のビルド)に固有であることに注意してください。
./gforth-fast
で動作するデータ再配置可能イメージを作成するには、 GFORTHD="./gforth-fast
--no-dynamic" gforthmi gforthdr.fi
とします(これには --no-dynamic
が必要です。そうしないとこのイメージは機能しません)。 そして、 gforth-fast -i gforthdr.fi ... -e
bye
で実行します(上記で説明したフラグは、 再配置可能なコードでのみ機能するため、ここでは重要ではありません)。
gforth-fast -i gforthdr.fi -e bye
では、 ユーザー時間は約 1.1 ミリ秒、 システム時間は約 1.2
ミリ秒かかります。
さらにもう 1 つのステップは、 再配置不可イメージ(non-relocatable image)を使用することで、
すべての再配置コストと書き換え時コピー(copy-on-write;COW)コストの一部を回避することです(see Non-Relocatable Image Files)。ただし、 アドレス空間のランダム化が行われているオペレーティング・システム(最近の Linux 等ではこれがデフォルト)や、
その他の理由でディクショナリーが移動した場合(OS カーネルの変更や、 ライブラリー更新などの場合)には機能しないという欠点があるため、
あまりお勧めできません。再配置不可イメージ(non-relocatable image)を作成するために gforth-fast
--no-dynamic -e "savesystem gforthnr.fi bye"
とします(ここでも --no-dynamic
が必要です)。 そして、 gforth-fast -i gforthnr.fi ... -e bye
として実行します(ここでも、
上記で説明したフラグは重要ではありません)。 gforth-fast -i gforthdr.fi -e bye
はユーザー時間は約
0.9 ミリ秒、 システム時間は約 0.9 ミリ秒かかります。
実行するスクリプトに大量のコードが含まれている場合は、 起動時のコンパイルのコストを避けるために、 それをイメージにコンパイルすると有益な場合があります。
この章と Introduction(see An Introduction to Standard Forth) の違いは、 このチュートリアルの方がハイペースで、 かつ、 あなたがコンピューターをさわれるときに読む必要があり、 かつ、 より多くの内容をカバーしていますが、 しかし、 Forth システムがどのように機能するかについては説明してい無いことです。
このチュートリアルは、 標準に準拠した任意の Forth でご利用できます。 Gforth 固有の機能はすべてその旨の目印が付けられており、 別の Forth を使用する場合はその部分はスキップできます。 このチュートリアルでは、 Forth のすべての機能について説明するわけではありませんが、 あなたが Forth を使い始めて、 Forth で使用できる機能についていくつかのアイデアを得るには十分です。 このチュートリアルが終わったら、 マニュアルの残りの部分をお読みください。
このチュートリアルの使用目的は、 あなたがコンソールの前に座って作業を進め、 例を見てその結果を予測してから、 自分で試してみるというものです。 結果が期待どおりでない場合は、 何が起こっているのかを理解できるように(類似の例を試すなどして)理由を調べます。 いくつか課題も出題してあります。
このチュートリアルでは、 あなたが以前にプログラミングをしたことがあり、 例えば、 ループとは何か、とかを理解していることを前提としています。
あなたが Gforth を開始するには、その名前をタイプします:
gforth
これにより、 対話モードに入ります。 bye
と入力すると Gforth を終了できます。 Gforth では、 bash と同様に、
コマンド・ラインを編集し、 カーソル・キーを使用してコマンド・ライン・ヒストリ(履歴)にアクセスできます。
word は、任意の文字のシーケンスです(空白(white space)を除く)。 ワードは空白(white space)で区切られます。 たとえば、 以下の各行には正確にただ 1 つのワードが含まれています:
word !@#$%^&*() 1234567890 5!a
初心者によくある間違いは、 必要な空白を省略することです。 その結果、 ‘Undefined word’ のようなエラーが発生します。そのため、 このようなエラーが表示された場合は、 必要な場所に空白を入れてあるかどうかを確認してください。
." hello, world" \ correct ."hello, world" \ gives an "Undefined word" error
Gforth および他のほとんどの Forth システムは、 大文字と小文字の違いを無視します(大文字と小文字は区別されません)。 つまり、 ‘word’ は ‘Word’ と同一です。 あなたのシステムで大文字と小文字が区別される場合は、 ここに示されているすべての例を大文字で入力する必要がある場合があります。
Forth は、 自分の足を撃つような馬鹿げた事を妨げたりはしません。 以下のように Gforth をクラッシュさせるいくつかの方法を試してみましょう:
0 0 ! here execute ' catch >body 20 erase abort ' (quit1) >body 20 erase
最後の 2 つの例は、Gforth (および他のほとんどのシステム) の重要な部分を破壊することが保証されているため、 (Gforth
が自動的に終了していない場合、) この後は Gforth を終了させたほうがよいでしょう。 一部のシステムでは、 外部から gforth
を強制終了する必要がある場合があります(例: Unix系 では kill
を使用します)。
これらの行が何を行うのか、 なぜクラッシュが発生するのかは後ほど分かります。
クラッシュを発生させる方法がわかったので(そして、 それは大したことがない事がわかったので)、 今度は意味のあるプログラムを作成する方法を学ぶとしましょう。
Forth の最も明々白々な機能はスタックです。 数値を入力すると、 その数値がスタックにプッシュされます。 .s
を使用してスタックの内容を表示できます。
1 2 .s 3 .s
.s
はスタックの最上位(top-of-stack)が一番右になるように表示します。 つまり、 数値は入力時に表記されたとおりに
.s
出力に表れます。
.
を使用してスタックの最上位要素を出力できます。
1 2 3 . . .
一般に、 ワードはスタック引数を消費します(.s
は例外です)。
研究課題(assignment):
5 6 7 .
の後、 スタックには何が含まれていますか?
+
や -
や *
や /
や mod
というワードは、 常に頂上から見て 2
つのスタック項目に作用します:
2 2 .s + .s . 2 1 - . 7 3 mod .
-
や /
や mod
のオペランドは、 対応する中置式と同じ順序になります(これが Forth
における一般的なケースです)。
ワードの順序によって評価の順序とオペランドが明確に決定されるため、 括弧は不要です(そして、 括弧は使用不可です):
3 4 + 5 * . 3 4 5 * + .
研究課題(assignment): 上記の Forth コードに対応する中置式はどうなるでしょうか? また、
6-7*8+9
を Forth 表記で記述してください3。
符号を変更するには、 以下のように negate
を使用します:
2 negate .
研究課題(assignment): ‘-(-3)*4-5‘ を Forth に変換してみましょう。
/mod
は /
と mod
の両方を実行します。
7 3 /mod . .
詳しくはこちらを参照ください: Arithmetic
スタック操作ワードはスタックのデータを並べ替えます。
1 .s drop .s 1 .s dup .s drop drop .s 1 2 .s over .s drop drop drop 1 2 .s swap .s drop drop 1 2 3 .s rot .s drop drop drop
上記は最も重要なスタック操作ワードです。 以下のように2つペアでスタック項目を操作する亜種もあります:
1 2 3 4 .s 2swap .s 2drop 2drop
さらに 2 つ、 以下のスタック操作ワードがあります:
1 2 .s nip .s drop 1 2 .s tuck .s 2drop drop
研究課題(assignment):
nip
とtuck
を他のスタック操作ワードの組み合わせに置き換えてみましょう。以下の結果になるスタック操作を考えてみましょう Given: How do you get: 1 2 3 3 2 1 1 2 3 1 2 3 2 1 2 3 1 2 3 3 1 2 3 1 3 3 1 2 3 2 1 3 1 2 3 4 4 3 2 1 1 2 3 1 2 3 1 2 3 1 2 3 4 1 2 3 4 1 2 1 2 3 1 2 3 1 2 3 4 1 2 3 1 3
5 dup * . \ 5^2
研究課題(assignment):
17
を複数回書かずに、 Forth で 17^3 と 17^4 を書いてみましょう。 また、 スタック上の 2 つの数値 (a と b、 スタック頂上を b とする) を想定し、(a-b)(a+1)
を計算する Forth コードを作成してみましょう。
こちらも参照してください: Stack Manipulation
Forth コマンド・ラインでの作業は、 1 行の例や短い 1 回限りのコードの場合には便利ですが、 あなたは編集や永続化に便利なように、 ソース・コードをファイルに保存したいと思うかもしれません。 お気に入りのエディター(なお、 Gforth には Emacs サポートがあります see Emacs and Gforth)を使用して file.fs を作成し以下のようにします
s" file.fs" included
こうすると、 file.fs ファイルを Forth システムにロードします。 私達は Forth ファイルの拡張子には ‘.fs’ を使っています。
以下のようにいくつかのファイルをロードして、 Gforth を簡単に開始できます:
gforth file1.fs file2.fs
これらのファイルのロード中にエラーが発生した場合、 Gforth は終了(terminate)しますが、 Gforth 内で
INCLUDED
中にエラーが発生すると、 通常は Gforth コマンド・ラインになります。 Forth システムを毎回開始させれば、
あなたの以前の試行の結果に影響されることなく、 毎回クリーンな開始が可能になります。
私達は多くの場合、 すべてのテストをファイルに入れて、 それからそのコードをロードし以下のようにしてテストを実行します
gforth code.fs tests.fs -e bye
(多くの場合、 Emacs内 で C-x C-e を使用してこのコマンドを実行します。) -e bye
により、 テスト後に
Gforth が確実に終了(terminate)するので、 面倒なくこのコマンドを再度開始できます。
このアプローチの利点は、 プログラムが変更されるたびにテストを簡単に繰り返すことができ、 変更によって生じたバグを簡単に発見できることです。
こちらも参照してください: Forth source files
\ これはコメントです。これは行末で終わります ( もう一つのコメント。 これはこのように閉じ丸括弧で終わります: ) .s
\
と (
は通常の Forth ワードであるため、 その後ろのテキストと空白(white
space)で区切る必要があります。
\This gives an "Undefined word" error
最初にあらわれる )
が (
で始まるコメントを終了するため、 「(
型のコメント」を入れ子(nest)にすることはできません。 そして、 ( ... )
を使用して )
を含むテキストをコメントアウトすることはできません4。
私達は、 説明テキストや、 1 行以上のコードのコメントアウトに \
コメントを使用します。 (
コメントは、
スタック効果やスタックの内容を説明したり、 コードの下位部分をコメントアウトしたりするために使用します。
Emacs モード gforth.el (see Emacs and Gforth) は、 C-x \
でリージョンをコメントアウトし、 C-u C-x \ でリージョンのコメントを解除し、 \
でコメントされた領域を
M-q でフィル(fill)します。
こちらも参照してください: Comments
コロン定義(Colon Definitions)は、 他のプログラミング言語のプロシージャや関数に似ています。
: squared ( n -- n^2 ) dup * ; 5 squared . 7 squared .
:
はコロン定義を開始します。 その名前は squared
です。 それに続くコメントは、
そのスタック効果について説明しています。 dup *
というワードは実行はされませんが、 定義にコンパイルされます(compiled
into the definition)。 ;
はコロン定義を終了します。
新しく定義されたワードは、 他の定義での使用を含め、 他のワードと同様に使用できます:
: cubed ( n -- n^3 ) dup squared * ; -5 cubed . : fourth-power ( n -- n^4 ) squared squared ; 3 fourth-power .
研究課題(assignment):
nip
やtuck
やnegate
や/mod
のコロン定義を他の Forth ワードで記述し、 それらが機能するかどうかを確認してみましょう (ヒント: 最初にオリジナルであなたの作成したテストコードをテストして結果を確認してから、 その後あなたが定義して、 再度テストして結果を比較しましょう)。 ‘redefine’ メッセージに驚かないでください。 これらは単なる警告です。 (訳注: redefine; 同じ名前のワードを定義したという警告。 置き換えではなく追加となる。 これ以降の通訳(interpret)・コンパイルは新しい方のワードを参照するが、 通常は新しい方のワード定義後も、 既にコンパイル済みのワードは古い方のワードを参照しつづける。 詳しくはディクショナリー等の項目を参照)
こちらも参照してください: Colon Definitions
see
: を使用してコロン定義を逆コンパイルできます:
see squared see cubed
Gforth では、 see
は実行可能コードからソース・コードを再構築したものを示します。 ソースには存在するが、
実行可能コードには存在しない情報(コメントなど)は失われます。
最初から義済みのワードを逆コンパイルすることもできます:
see . see +
慣例により、 定義名の後のコメントはスタック効果を説明します。 ‘--’ の前の部分は、 定義の実行前のスタックの状態、 つまりコロン定義に渡されるパラメータを説明します。 ‘--’ の後ろの部分は、 定義の実行後のスタックの状態、 つまり定義の実行結果です。 スタック・コメントには、 定義がアクセス、 または、変更する、 (訳注: 定義の外部から見える、 )スタック項目のみを記述します。
たとえ スタック効果が ( -- )
(訳注: スタックに何の効果も及ぼさない)であっても、 あなたは、
すべての定義に正しいスタック効果を記述するべきです。 また、 より複雑なワードには説明的なコメントを追加する必要があります(通常、 これは
:
の後ろに続けます)。 これを行わないと、 あなたコードの行動は解読不能になります(なぜなら、 とある定義を理解するためには、
全ての定義を辿るハメになるから)。
研究課題(assignment): 例えば
swap
のスタック効果は、x1 x2 -- x2 x1
のように記述できます。 同様に、-
やdrop
やdup
やover
やrot
やnip
やtuck
のスタック効果を書いてみましょう。 ヒント: 書けたら、 このマニュアルに書いてあるスタック効果と合っているかどうかチェックしましょう(see Word Index)。
プログラマーは、 コロン定義内のさまざまな場所に、 その場所のスタックの内容を説明するコメント(スタック・コメント)を挿入することがあります。 つまり、 スタック効果コメントの最初の部分のようなものです。 例えば以下のようなのです
: cubed ( n -- n^3 ) dup squared ( n n^2 ) * ;
この場合、 ワードが十分に単純であるため、 スタック・コメントは割と余計です。 あなたが、 読みやすさを高めるためにそのようなコメントを追加することが良いと思った場合、 ワードをいくつかの単純なワードにファクタリング(因数分解)することも検討する必要があります(see Factoring)。 ファクタリングにより、 通常はスタック・コメントが不要になります。 ただし、 あなたが結果としてリファクタリングしないことに決めた場合は、 そのようなコメントがある方が、 無いよりも良いです。
標準や、 このマニュアルや、 多くのプログラムの、 スタック効果およびスタック・コメント内のスタック項目の名前は、 Fortran やハンガリアン記法と同様に、 型プレフィックスによって型を指定します。 最も頻繁に使用されるプレフィックスは以下のとおりです:
n
符号付き整数
u
符号なし整数
c
文字(character)
f
二値フラグ(Boolean flags)。 つまり false
または true
a-addr,a-
セル・アライメント・アドレス
c-addr,c-
文字(char)アライメント・アドレス(注意: Windows NT では文字(char)は 2 バイトになる場合があることに注意)
xt
実行トークン(Execution token)。 セルと同一サイズ
w,x
セル(cell)。 セルには整数またはアドレスを含めることができます。 通常は 32ビットまたは 64 ビットまたは 16 ビットを必要とします(プラットフォームと Forth システムによって異なります)。 cell は一般には machine word として知られていますが、 Forth では word という用語はすでに別の意味を持っています。
d
符号付き2倍長整数(signed double-cell integer)
ud
符号無し2倍長整数(unsigned double-cell integer)
r
浮動小数点数(Float)(FP スタック上に置かれる)
より完全なリストは Notation にあります。
研究課題(assignment): あなたがここまでで記述したすべての定義に対してスタック効果コメントを記述してみましょう。
Forth では、 演算子の名前はオーバーロードされません。 したがって、 異なる型に対する同様の演算には異なる名前が必要です。 たとえば、
+
は整数の加算をしますが、 浮動小数点数を加算するには f+
を使用する必要があります。 以下のプレフィックスは、
さまざまな型の関連する演算によく使用されます:
(none)
符号付き整数
u
符号なし整数
c
文字(character)
d
符号付き2倍長整数(signed double-cell integer)
ud, du
符号無し2倍長整数(unsigned double-cell integer)
2
2 つのセル(必ずしも 2倍長整数(double-cell numbers)とは限りません)
m, um
単一セルとダブル・セルの混合操作
f
浮動小数点(注意: スタック・コメントでは、 ‘f’ はフラグを表し、 ‘r’ は FP 数値を表すことに注意してください。 また、 リテラル FP 数値には指数部分を含める必要があります see Floating Point)。
符号付きのバリエーションと符号なしのバリエーションに違いがない場合(例えば +
の場合)、
プレフィックスのないバリエーションのみが存在します。
Forth は、 コンパイル・モード時もインタープリター・モード時も型チェックを実行しません。 以下のように間違った操作を行うと、 データが正しく通訳(interpret)されません:
-1 u.
あなたが、 これまで型チェック言語しか使用したことがなく、 型チェックがいかに重要であるかを聞いたことがある場合でも、 パニックに陥る必要はありません。 著者の経験(および他の Forth 利用者(Forthers)の経験)では、 Forth コードの型エラーは通常(慣れてしまえば)簡単に見つかります。 プログラマの警戒心が高まると、 ほとんどの型エラーに加えて、 より困難なエラーも発見される傾向があります。 型システムを回避する必要がまったくないため、 ほとんどの状況では型チェックがないことが利点であるようです(Forth に型チェックを追加するプロジェクトは普及していません)。
あなたが長ったらしい定義を書こうとすると、 スタックの内容を追跡するのがすぐに困難になることがわかります。 したがって、優れた Forth プログラマは短い定義 (たとえば 3 行)のみを記述する傾向があります。 意味のある短い定義を見つける技術は、 (多項式の因数分解(factoring polynomials)と同様に)ファクタリング(factoring)として知られています。
よくファクタリングされたプログラムには、 追加の利点もあります。 つまり、小さくて一般的なワードは、 大きくて特殊なワードよりもテストとデバッグが容易で、より再利用性に富みます。
したがって、 あなたが、 スタック管理に問題を抱えてるなら、 コードを記述するときにワードに意味のある要素を定義し、 それらの観点からワードを定義するようにしてください。 たった 2 つのワードしかしか含まれないような定義でも、 多くの場合役に立ちます。
上手なファクタリングは簡単ではなく、 コツを掴むにはある程度の練習が必要です。 しかし、経験豊富な Forth プログラマーであっても、 すぐには適切な解決策を見つけられないことが多く、 プログラムの書き直し時に見つかるのです。 したがって、 すぐに良い解決策が思い浮かばなくても、 絶望しないで試し続けてください。(訳注: 参考: 拙訳 Thinking Forth 第6章 ファクタリング https://thinking-forth-ja.readthedocs.io/ja/latest/chapter6.html)
他の言語では、 関数(function)のパラメーターに任意の順序を使用できます。 また、 結果は 1 つだけなので、 結果の順序を扱う必要もありません。
Forth (および他のスタック・ベースの言語、 たとえば PostScript)では、 定義のパラメーターの順序と結果の順序が重要であり、 適切に設計する必要があります。 一般的なガイドラインは、 ワードの実装が複雑になる場合でも、 ワードがほとんどの場合に簡単に使用できるようにスタック効果を設計することです。 いくつかの具体的なルールは以下のとおりです:
.
)。
-
)。
!
(「ストア」 see Memory)は、
通常、 格納されている値よりも計算が簡単であるため、 スタック頂上にアドレスがあるのを期待します(多くの場合、 アドレスは単なる変数です)。
open-file
のようなファイル・ワードは、スタック頂上にエラー・コードを返します。 これは、 通常、 throw
によってすぐに消費されるためです。
さらに言えば、 他の結果に対して何かを行う前に、 エラー・コードをチェックする必要があります。
これらのルールは一般的なガイドラインに過ぎません。 ワードを使いやすくするという全体的な目標を見失わないでください。 例えば、 慣習のルールが計算長のルールと衝突する場合、 ワードがあまり使われない場合は慣習を優先するかもしれません。 一方、 ワードが頻繁に使われる場合は、 計算長のルールを優先するかもしれません(だからといって頻繁に使うと、 計算長のルールを破るコストが非常に高くなりますし、 頻繁に使うことで非慣習的な順序を覚えてしまいがちになります)。
コロン定義内でローカル変数(local)を定義できます:
: swap { a b -- b a } b a ; 1 2 swap .s 2drop
(あなたの Forth システムがこの構文をサポートしていない場合は、 最初に compat/anslocal.fs をインクルードしてください)
この例では、 { a b -- b a }
がローカル変数定義です。 スタックから 2 つのセルを取得し、 スタック頂上を
b
に入れ、 その次のスタック要素を a
に入れます(訳注: a b はスタック上から取り除かれる)。 --
は }
で終わるコメントを開始します。 ローカル変数の定義後、 ローカル変数の名前を使用すると、 その値がスタックに積まれます。
コメント部分(-- b a
)は省略できます:
: swap ( x1 x2 -- x2 x1 ) { a b } b a ;
Gforth では、 コロン定義内の任意の場所に複数のローカル変数定義を含めることができます。 対照的に、 標準Forthのプログラムでは、 コロン定義ごとにローカル変数定義を 1 つだけしか持つことができず、 そのローカル変数定義は制御構造の外側にある必要があります。
ローカル変数を使用すると、 スタックの問題に遭遇することなく、 少し長い定義を書くことができます。 ただし、重要な、ファクタリング技能を身につけるための演習として、 ローカル変数を使用せずにコロン定義を記述してみることをお勧めします。
研究課題(assignment): ここまでのあなたの定義をローカル変数を使って書き換えてみましょう。
こちらも参照下さい: Locals
Forth では、 コロン定義内でのみ制御構造を使用できます。 if
構造は以下のようになります:
: abs ( n1 -- +n2 ) dup 0 < if negate endif ; 5 abs . -5 abs .
if
はスタックからフラグを取得します。 フラグがゼロ以外 (true) の場合は、 その次のコードが実行されます。 そうでない場合は、
endif
(または else
) の後から実行が続けられます。 <
は、 頂上から 2
つのスタック要素を比較し、 フラグを生成します。
1 2 < . 2 1 < . 1 1 < .
実は、 endif
の標準Forthでの名前は then
です。 このチュートリアルでは、 endif
を使用した例を示します。 なぜなら、 then
が異なる意味を持つ他のプログラミング言語に慣れている人々にとっては、
endif
を使用する方が混乱が少ないからです。 システムに endif
がない場合は、 以下のように定義します
: endif postpone then ; immediate
あなたはオプションで else
部分を使用できます:
: min ( n1 n2 -- n ) 2dup < if drop else nip endif ; 2 3 min . 3 2 min .
研究課題(assignment):
else
部分を付けずにmin
を記述してみましょう。 (ヒント:nip
の定義は何ですか?)。
こちらも参照下さい: Selection
false フラグはすべてのビットがクリアです(整数として通訳(interpret)される場合は 0)。 正規化された true フラグは、
すべてのビットがセットされています(2の補数の符号付き整数として -1)。 多くの文脈(例: if
)では、ゼロ以外の値はすべて true
フラグとして扱われます。
false . true . true hex u. decimal
比較ワードは正規化フラグを生成します:
1 1 = . 1 0= . 0 1 < . 0 0 < . -1 1 u< . \ 型エラー。 u< は -1 を 大きな符号なし数として扱ってしまいます -1 1 < .
Gforth は、接頭辞 0 u d d0 du f f0
(または「 接頭辞なし」)との比較 = <> < > <= >=
のすべての組み合わせをサポートします。 これらの組み合わせの一部のみが標準Forthです (詳細については、標準Forth または
Numeric comparison または Floating Point または Word Index
を参照してください)。
and or xor invert
を正規化フラグ用の演算子として使用できます。 実際には、 これらはビット単位演算です:
1 2 and . 1 2 or . 1 3 xor . 1 invert .
0<>
を使用して ゼロ/非ゼロ フラグを正規化フラグに変換できます(そして、 その途中で 0=
を使用して ゼロ/非ゼロ
フラグの余数(complement)を取ります。 実際、 正規化フラグでは invert
の代わりに 0=
を使用するのが一般的です)。
1 0= . 1 0<> .
0<>
無しでも if
で ゼロ/非ゼロ をテストすることはできますが、 ゼロ/非ゼロ 値を and or
xor
と組み合わせる場合、 and or xor
はビット単位での操作なので、 0<>
を使用する必要がある場合があります。 最も単純で、 エラーが少なく、 おそらく最も明確な方法は、 これらすべての場合に 0<>
を使用することですが、 場合によっては、 使用する 0<>
の数を減らすこともできます。 以下にいくつかのスタック効果を示します。
fc は正規化フラグ(canonical flag)を表し、 fz は ゼロ/非ゼロ を表します(すべての fc は
fz としても機能します):
or ( fz1 fz2 -- fz3 ) and ( fz1 fc -- fz2 ) and ( fc fz1 -- fz2 )
したがって、 以下のようなコードの場合:
( n1 n2 ) 0<> and if
これは、 n1 and n2 (n1 かつ n2)がゼロ以外であるかどうかをテストし、 イエスの場合は if
の後のコードを実行します。
n1 を ゼロ/非ゼロ として扱い、 0<>
を使用して n2 を正規化フラグに変換します。 and
は fz を生成し、
それは if
によって消費されます。
正規化フラグの全ビットセット機能とブール演算のビット単位の演算を使用して、 if
を回避することもできます:
: foo ( n1 -- n2 ) 0= if 14 else 0 endif ; 0 foo . 1 foo . : foo ( n1 -- n2 ) 0= 14 and ; 0 foo . 1 foo .
研究課題(assignment):
min
をif
無しで書いてみましょう。
こちらも参照下さい: Boolean Flags, Numeric comparison, Bitwise operations
無限ループは非常に単純です:
: endless ( -- ) 0 begin dup . 1+ again ; endless
(Gforth の場合、) Ctrl-C を押してこのループを終了します。 begin
は実行時に何も行わず、
again
は begin
にジャンプします。
任意の場所に 1 つの出口があるループは以下のようになります:
: log2 ( +n1 -- n2 ) \ logarithmus dualis of n1>0, rounded down to the next integer assert( dup 0> ) 2/ 0 begin over 0> while 1+ swap 2/ swap repeat nip ; 7 log2 . 8 log2 .
実行時には while
はフラグを1つ消費します。 フラグが 0 の場合、 repeat
の後ろへ飛んで実行が継続されます。
フラグがゼロ以外の場合、 実行は while
の後ろから継続されます。 Repeat
は、 again
と全く同じように、 begin
に戻ります。
Forth には、 1+
など、多数の 組み合わせ/省略形 があります。 しかし、 2/
は 組み合わせ/省略形
ではありません。 これは引数を 1 ビット右にシフトし、 Gforth の (Gforth 0.7 以降の) /
と同様、
常に負の無限大方向に向かって小数点以下を丸める除算(フロア除算)と見なされますが、 他の多くの Forth システムの /
とは異なります。
-5 2 / . \ -2 or -3 -5 2/ . \ -3
assert(
は標準Forthのワードではありませんが、 Gforth 以外のシステムでも
compat/assert.fs を含めることで取得できます。 それが何をするかは、 以下のように試してみることで確認できます。
0 log2 .
以下は、 最後に出口があるループです:
: log2 ( +n1 -- n2 ) \ logarithmus dualis of n1>0, rounded down to the next integer assert( dup 0 > ) -1 begin 1+ swap 2/ swap over 0 <= until nip ;
Until
はフラグを消費します。 フラグがゼロの場合は begin
から実行が継続され、 それ以外の場合は
until
の後から実行が継続されます。
研究課題(assignment): 最大公約数を計算する定義を書いてみましょう。
こちらも参照ください: Simple Loops
: ^ ( n1 u -- n ) \ n = the uth power of n1 1 swap 0 u+do over * loop nip ; 3 2 ^ . 4 3 ^ .
U+do
(あなたの Forth システムにない場合は compat/loops.fs をインクルードしてください)は、
( u3 u4 -- )
、 つまり、 スタックから 2 つの数値を取得し、 u+do
と loop
の間のコードを u3 - u4
回実行します( u3 - u4 < 0
の場合はまったく実行しません)。
ループ開始ワードのスタック効果を見れば、スタック効果の設計ルールが機能していることがわかります。 ループの開始値は終了値と比べて定数であることが多いため、 開始値はスタック頂上(top-of-stack)に渡されます。
i
を使用して、 ループ・カウンターにアクセスできます:
: fac ( u -- u! ) 1 swap 1+ 1 u+do i * loop ; 5 fac . 7 fac .
+do
もあります。 これは符号付きの数値を期待します(それはループに入るかどうかを決定するために重要です)。
研究課題(assignment): n 番目のフィボナッチ数を計算するための定義を記述してみましょう。
増分として 1 以外も使用できます:
: up2 ( n1 n2 -- ) +do i . 2 +loop ; 10 0 up2 : down2 ( n1 n2 -- ) -do i . 2 -loop ; 0 10 down2
こちらも参照ください: Counted Loops
通常、 定義の名前はその定義内に表れません。ただし、 それ以前の(同名の)定義は普通に表れます(訳注: 下記例は再帰呼出しではなくて、 ’/’ の古いバージョンを呼び出しているに過ぎない事に注意。同じ名前の古いバージョンが存在しなければエラーになるが、存在すればエラーにならないので注意。):
1 0 / . \ "Floating-point unidentified fault" in Gforth on some platforms : / ( n1 n2 -- n ) dup 0= if -10 throw \ report division by zero endif / \ old version ; 1 0 /
再帰定義の場合は、 recursive
(非標準) または recurse
を使用できます:
: fac1 ( n -- n! ) recursive dup 0> if dup 1- fac1 * else drop 1 endif ; 7 fac1 . : fac2 ( n -- n! ) dup 0> if dup 1- recurse * else drop 1 endif ; 8 fac2 .
研究課題(assignment): n 番目のフィボナッチ数を計算するための再帰定義を記述してみましょう。
こちらも参照ください (間接再帰に関してもコチラ): See Calls and returns
EXIT
は、 現在の定義をすぐに終了(exit)します。 EXIT
の実行前に、
(残ったループ・カウンタ達を全て取り除くために、)ネストしたカウンタ付きループの数だけ UNLOOP
を実行する必要があります(訳注:
下記例は1重のループなので unloop が1つ。 2重ループになったら unloop unloop と2つ必要):
: ... ... u+do ... if ... unloop exit endif ... loop ... ;
LEAVE
は、 (LEAVE
から見て)最も内側にあるカウンタ付きループを直ちに終了します:
: ... ... u+do ... if ... leave endif ... loop ... ;
こちらも参照ください: Calls and returns, Counted Loops
データ・スタックに加えて、 Forth には 2 番目のスタックであるリターン・スタックもあります。 ほとんどの Forth システムは、プロシージャ呼び出しの戻りアドレスをそこに保存します(したがって、 リターン・スタックという名前が付けられています)。 プログラマもリターン・スタックを利用することができます。
: foo ( n1 n2 -- ) .s >r .s r@ . >r .s r@ . r> . r@ . r> . ; 1 2 foo
>r
はデータ・スタックから1つの要素を取得し、 それをリターン・スタックにプッシュします。 逆に、 r>
は1つの要素をリターンからデータ・スタックに移動します。 r@
は、リターン・スタック頂上のコピーをデータ・スタックにプッシュします。
Forth プログラマーは通常、 データ・スタックのみを使用すると複雑すぎて、 かつ、 ファクタリングやローカル変数が選択肢に無い場合、 データを一時的に保存するためにリターン・スタックを使用します。
: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 ) rot >r rot r> ;
定義のリターン・アドレスとカウント付きループのループ制御パラメーターは通常、 リターン・スタック上に存在するため、 コロン定義またはカウント付きループでリターン スタックにプッシュしたすべての項目を、定義の終了やループの終了の前にリターン・スタックから取得する必要があります。 ある定義の外側やループの外側でリターン・スタックにプッシュしたアイテムに、 ループの定義内からアクセスすることはできません。
リターン・スタック項目の数を間違えると、 通常はクラッシュします:
: crash ( n -- ) >r ; 5 crash
ローカル変数の使用とリターン・スタックの使用を混在させることはできません(標準Forthの場合。 Gforth では問題ありません)。 ただし、 これらは同一の問題の解決なので、 問題にはなりません。
研究課題(assignment): リターン・スタックを使用して、 ここまでにあなたが書いた定義をより良い方法で書き直すことができるでしょうか?
こちらも参照ください: Return stack
以下使用してグローバル変数 v
を作成できます:
variable v ( -- addr )
v
は、スタックにメモリー内の、とあるセルのアドレスをプッシュします。 このセルは variable
によって予約されています。 !
(ストア)を使用してスタックからこのセルに値を保存し、
@
(フェッチ)を使用して値をメモリーからスタックにロードできます:
v . 5 v ! .s v @ .
dump
を使用すると、 メモリーの生のダンプ(メモリー・ダンプ)を確認できます:
v 1 cells .s dump
Cells ( n1 -- n2 )
は、 n1 個のセル が占めるバイト数(より一般的にはアドレス単位(address
units)(aus))を与えます。 そして、 あなたは、 さらに多くのメモリーを予約することもできます:
create v2 20 cells allot v2 20 cells dump
変数のようなワード v2
を作成し、 20 個の初期化されていないセルを予約します。 v2
によってプッシュされたアドレスは、 これら 20 個のセルの先頭を指します(see CREATE
)。 あなたは、 アドレス演算を使用して、
これらのセルにアクセスできます:
3 v2 5 cells + ! v2 20 cells dump
,
を使用してメモリーを予約 かつ 初期化できます:
create v3 5 , 4 , 3 , 2 , 1 , v3 @ . v3 cell+ @ . v3 2 cells + @ . v3 5 cells dump
研究課題(assignment): 最初のセルは
addr
、 次のセルはaddr cell+
などとして、u
個のセルの値の合計を計算する定義vsum ( addr u -- n )
を記述してみましょう。
variable
と create
の違いは、variable
がセルを割り当てる(allots)ことと、
標準 Forth では変数(variable)に追加のメモリーを割り当てることができないことです。
新しいワードを作成せずにメモリーを予約することもできます:
here 10 cells allot . here .
最初の here
は(最初の here
時点の)ディクショナリー領域の後のアドレスをプッシュし、
メモリー領域の開始アドレスとして使い、 2 番目の here
は(2 番目の here
時点の)ディクショナリー領域の後のアドレスをプッシュします。 この開始アドレスはどこかに保存する必要があります。 そうしないと、
あなたはこのメモリー領域を再度見つけるのが困難になります。
Allot
はディクショナリー・メモリーを管理します。 ディクショナリー・メモリーには、 Gforth や他のほとんどの Forth
システムの、 ワードなどのシステムのデータ構造が含まれています。 これはスタックのように管理されます。
あなたは以下のようにして割り当て(allot
)したメモリーを解放できます:
-10 cells allot here .
注意: その合間に新しいワードを作成した場合、 それを実行できないことに注意してください(allot
で作成されたメモリーが、
もはやディクショナリー「スタック」の頂上ではなくなるため)。
その代わりに、 allocate
や free
を使用すると、 任意の順序でメモリを解放できます:
10 cells allocate throw .s 20 cells allocate throw .s swap free throw free throw
throw
はエラー(メモリー不足など)を処理します。
また、 ガベージ・コレクター なら、 メモリーを明示的に解放(free
)する必要がなくなります。
こちらも参照ください: Memory
スタック上では、 数と同様に文字がセルを占めます。 メモリー内では文字は独自のサイズ(ほとんどのシステムでは 8 ビットのバイト値)であるため、 メモリー・アクセスには文字独自のワードが必要です:
create v4 104 c, 97 c, 108 c, 108 c, 111 c, v4 4 chars + c@ . v4 5 chars dump
スタック上の文字列の推奨される表現は addr u-count
です。ここで、addr
は文字列の最初の文字のアドレスで、
u-count
は文字列の文字数です。
v4 5 type
以下を使用すると文字列定数を取得できます
s" hello, world" .s type
s"
と文字列の間にスペースがあることを確かめてください。 s"
は通常の Forth ワードであり、 空白(white
space)で区切る必要があります(スペースを削除するとどうなるかを試してみましょう)。
ただし、 この s"
のインタープリターでの使用(interpretive use)は非常に制限されています。 文字列は、
s"
が次に呼び出されるまでの間だけ存在します(一部の Forth システムはこれらの文字列を複数保持しますが、
普通は未だ限られた寿命です)。
s" hello," s" world" .s type type
あなたは定義内で s"
を使用することもでき、 (定義が続く限り、)その結果の文字列は永久に存続します:
: foo s" hello," s" world" ; foo .s type type
研究課題(assignment):
Emit ( c -- )
はc
を(数値ではなく)文字として出力します。 これを使ってtype ( addr u -- )
を実装してみましょう。
こちらも参照ください: Memory Blocks
多くのプロセッサでは、 @
と !
を使用してセルにアクセスする場合、
メモリー内でセルをアライメントする必要があります (プロセッサがアライメントを必要としない場合でも、
アライメントされたセルへのアクセスは高速です)。
Create
は here
(つまり、 次の割り当てが行われる場所、
そしてcreate
されたワードが指す場所)でアライメントします。 同様に、
allocate
によって生成されたメモリーはアライメントしたアドレスから始まります。 アライメントしたアドレスに cells
の数値 を足すと、 次のアライメントされたアドレスが生成されます。
ただし、 char+
および chars
を含むアドレス演算では、セルにアライメントしていないアドレスが作成される可能性があります。 Aligned ( addr -- a-addr
)
は、 その次のアライメントせされたアドレスを生成します:
v3 char+ aligned .s @ . v3 char+ .s @ .
同様に、 align
は here
を次のアライメントされたアドレスに進めます:
create v5 97 c, here . align here . 1000 ,
注意: プログラムを移植可能にしたい場合は、 プロセッサがそれらを必要としない場合でも、 アライメントれたアドレスを使用する必要があることに注意してください。
こちらも参照ください: Address arithmetic
Forth の浮動小数点(floating-point)(FP)の、 数値と算術演算は、 ほぼ期待された通りに機能しますが、 特筆すべき点がいくつかあります:
最初の点は Forth に固有のものではありませんが、 非常に重要で、 まだ広く知られていないため、 ここで言及します。 浮動小数点数は実数(real)ではありません。 実数(real)が持ち、 あらゆる種類の数値に期待される多くの性質(算術法則など)は、 浮動小数点数には当てはまりません。 浮動小数点演算したい場合、 浮動小数点演算の問題とその回避方法について学ぶ必要があります。 良い出発点は David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys 23(1):5−48, March 1991 です(訳注: https://docs.oracle.com/cd/E19957-01/806-4847/ncg_goldberg.html これが合ってるかどうか不明。一部文字化けあり2024/06現在)。
Forth ソース・コードでは、リテラル浮動小数点数には指数が必要です(例: 1e0
)。これは、 1e
のように短く書くことも、 +1.0e+0
のように長く書くこともでき、 その間にはさまざまなバリエーションがあります。 その理由は、
歴史的な理由により、 Forth は小数点のみ(例: 1.
) の数を 2 セル整数を示すものとして通訳(interpret)するためです。
例:
2e 2e f+ f.
リテラル浮動小数点数のもう 1 つの要件は、 現在の基数が 10 進数であることです。 16 進数の 1e
は整数として通訳(interpret)されます:
Forth には、Forth-2012 に準拠した浮動小数点数用の別個のスタックがあります。 このモデルの利点の 1 つは、
浮動小数点数にアクセスするときにセルが邪魔にならないこと、 またその逆も同様であることです。 Forth には、 浮動小数点スタック(FP
スタック)を操作するためのワードのセットがあります: fdup fswap fdrop fover frot
や (非標準の、)
fnip ftuck fpick
FP 算術ワードには F
という接頭辞が付きます。 通常の f+ f- f* f/ f** fnegate
のほか、 他の、
関数用の多数のワード (例: fsqrt fsin fln fmin
) があります。 期待されるワードの 1 つが f=
ですが、 f=
は標準にはありません。 浮動小数点数の計算結果は通常不正確であるため、 正確な比較は通常間違いであり、
近似的な比較を使用する必要があります。 残念ながら、この目的のための標準のワードである f~
は適切に設計されていないため、 Gforth
では f~abs
と f~rel
も提供しています。
そしてもちろん、 メモリー内の浮動小数点数にアクセスするためのワード(f@ f!
)や、 アドレス演算用のワード(floats
float+ faligned
)もあります。 メモリ内の IEEE 書式の単精度および倍精度数にアクセスするために、 sf
および
df
プレフィックスを付けた、 これらのワードのバリエーションもあります。 その主な目的は、
外部浮動小数点数データ(ファイルから読み取られた、 またはファイルに書き込まれるデータなど)にアクセスすることです。
以下は、 ドット出力ワード(dot-product word)とその使用例です:
: v* ( f_addr1 nstride1 f_addr2 nstride2 ucount -- r ) >r swap 2swap swap 0e r> 0 ?DO dup f@ over + 2swap dup f@ f* f+ over + 2swap LOOP 2drop 2drop ; create v 1.23e f, 4.56e f, 7.89e f, v 1 floats v 1 floats 3 v* f.
研究課題(assignment): 二次方程式を解くプログラムを作成してみましょう。 次に、 Henry G. Baker, You Could Learn a Lot from a Quadratic, ACM SIGPLAN Notices, 33(1):30−39, January 1998 を読んで、 そのプログラムを改善できるかどうか確認してください。 最後に、 元のバージョンと改良されたバージョンで異なる結果が生成されるテスト・ケースを探しましょう。
こちらも参照ください: Floating Point; Floating point stack; Number Conversion; Memory Access; Address arithmetic.
このセクションでは、 Forth 内でファイルを使用する方法について簡単に説明します。 それは 5 つの簡単なステップに分かれています:
こちらも参照ください: General files
s" foo.in" r/o open-file throw Value fd-in
s" foo.out" w/o create-file throw Value fd-out
使用可能なファイル・モードは、 読み取り専用アクセスの場合は r/o 、 読み取り/書き込み アクセスの場合は r/w 、書き込み専用アクセスの場合は
w/o です。 必要に応じて、 読み取りと書き込みの両方のファイルを r/w で開くこともできます。 すべてのファイル用ワードはエラーコードを返します。
ほとんどのアプリケーションでは、 throw
を使用してエラー・コードを外部のエラー・ハンドラーに渡すのが最善(best)です。
開いたり(opening)割り当てたり(assigning)するためのワードが必要な場合は、 以下のように定義します:
0 Value fd-in 0 Value fd-out : open-input ( addr u -- ) r/o open-file throw to fd-in ; : open-output ( addr u -- ) w/o create-file throw to fd-out ;
使用例:
s" foo.in" open-input s" foo.out" open-output
256 Constant max-line Create line-buffer max-line 2 + allot : scan-file ( addr u -- ) begin line-buffer max-line fd-in read-line throw while >r 2dup line-buffer r> compare 0= until else drop then 2drop ;
read-line ( addr u1 fd -- u2 flag ior )
は、 addr からのバッファーに最大 u1
バイトを読み取り、 読み取ったバイト数と、 ファイルの終わりに達した場合に false になるフラグと、 エラーコードを返します。
compare ( addr1 u1 addr2 u2 -- n )
は 2 つの文字列を比較し、 両方の文字列が等しい場合は 0
を返します。 最初の文字列の方が字句的に大きい(lexically greater)場合は正の数値を返し、 2
番目の文字列の方が字句的に大きい(lexically greater)場合は負の数値を返します。
このループは、 まだ見たことがないですよね。 このループは出口が2つあります。 while
はスタック上にある読み取ったバイト数で終了するため、 それを個別にクリーンアップする必要があります。 そのクリーンアップする部分は
else
の後にあります。
使用例:
s" The text I search is here" scan-file
: copy-file ( -- ) begin line-buffer max-line fd-in read-line throw while line-buffer swap fd-out write-line throw repeat drop ;
fd-in close-file throw fd-out close-file throw
これも、同様に、 定義に組み込むことができます:
: close-input ( -- ) fd-in close-file throw ; : close-output ( -- ) fd-out close-file throw ;
研究課題(assignment):
copy-file
を変更して、 2 行目が一致するまでコピーするようにするにはどうすればよいでしょうか? セクションの開始行と終了行を指定して、 テキスト・ファイルのセクションを抽出するプログラムを作成できますか?
ワードがコンパイルされる時と、 通訳(interpret)される時では異なる振る舞いをします。 たとえば、 +
について考えてみましょう:
1 2 + . : foo + ;
これらの 2 つの振る舞いは、 コンパイル機能(compilation semantics)とインタプリタ機能(interpretation
semantics)として知られています。 通常のワード(例: +
)の場合、 コンパイル機能は、 現在定義中のワード(上記の例では
foo
)にインタープリター機能を追加します。 つまり、 後で foo
が実行されると、 +
のインタープリター機能(interpretation semantics)(つまり、2 つの数値の加算)が実行されます。
ただし、 if
のような制御フロー・ワードなど、 デフォルト以外のコンパイル機能を持つワードが存在します。
immediate
を使用すると、 最後に定義されたワードのコンパイル機能をインタープリター機能と等しくなるように変更できます:
: [FOO] ( -- ) 5 . ; immediate [FOO] : bar ( -- ) [FOO] ; bar see bar
デフォルト以外のコンパイル機能をもつワードだと知らしめる 2 つの慣習は、 名前を括弧で囲む(より頻繁に使用される)ことと、 名前をすべて大文字で記述する(あまり使用されない)ことです。
if
などの一部のワードについては、 インタープリター機能を使用するのは通常間違いであるため、 それらを
compile-only
としてマークし、 インタープリター機能を使用すると警告が表示されます。
: flip ( -- ) 6 . ; compile-only \ but not immediate flip : flop ( -- ) flip ; flop
この例では、 最初に flip
のインタープリター機能を使用します(警告が表示されます)。 flip
の 2
番目の使用では、 コンパイル機能を使用します(警告は表示されません)。 この例では、 compile-only
が実行時(run-time)ではなくテキスト・インタープリター時に評価される属性であることもわかります。
テキスト・インタープリターには 2 つの状態があります。 インタープリター・モード(interpret)は、 遭遇したワードのインタープリター機能(interpretation semantics)を実行します。 コンパイル・モードでは、 これらのワードのコンパイル機能(compilation semantics)が実行されます。
特に、 :
はコンパイル状態に切り替え、 ;
はインタープリター状態に戻します。 これらには、
状態を切り替えるだけの効果である ]
(コンパイル状態に切り替える) と [
(インタープリター状態に切り替える)
が含まれています。
: xxx ( -- ) [ 5 . ] ; xxx see xxx
これらの角括弧(brackets)は、 上記の命名慣習の源でもあります。
こちらも参照ください: Interpretation and Compilation Semantics
' word
は、 ワードの実行トークン(execution token)(XT)を提供します。 XT は、
ワードのインタープリター機能(interpretation semantics)表すセルです。 これは execute
で実行できます:
' + .s 1 2 rot execute .
XT は C の関数ポインターに似ています。 ただし、 パラメーターがスタックで渡されるため、もう少し柔軟になります:
: map-array ( ... addr u xt -- ... ) \ addr で始まり u 個の要素を含む配列のすべての要素に対して \ xt ( ... x -- ... ) を実行します { xt } cells over + swap ?do i @ xt execute 1 cells +loop ; create a 3 , 4 , 2 , -1 , 4 , a 5 ' . map-array .s 0 a 5 ' + map-array . s" max-n" environment? drop .s \ 下記比較初期値用に整数最大値を得る a 5 ' min map-array . \ 最初の要素は、 environment? で取得した整数最大値と比較
生成するより消費する要素が 1 つ多いワードの XT に対して map-array を使用できます。 理論的には、他の XT でも使用できますが、 スタック効果は配列のサイズに依存するため、理解するのが困難です。
XT はセルサイズであるため、 メモリーに保存し、 他のセルと同様にスタック上で操作できます。 compile,
を使用して XT
をワードにコンパイルすることもできます:
: foo1 ( n1 n2 -- n ) [ ' + compile, ] ; see foo1
compile,
は標準ではコンパイル機能(compilation semantics)がないため、 上記は標準ではないけれども、 良い
Forth システムでは動作します。 うまくいかなかったモノついては、 以下を使用してください
: [compile,] compile, ; immediate : foo1 ( n1 n2 -- n ) [ ' + ] [compile,] ; see foo1
'
は、 デフォルトでコンパイル機能(compilation semantics)を持つワードです。
そのワードのインタープリター機能(interpretation semantics)を実行すると、 その次のワードを構文解析(parse)します。
: foo ( -- xt ) ' ; see foo : bar ( ... "word" -- ... ) ' execute ; see bar 1 2 bar + .
コンパイル中にワードを解析(parse)し、 その XT をコンパイルして、 実行時にスタックにプッシュされるようにしたいことがよくあります。
[']
はこれを行います:
: xt-+ ( -- xt ) ['] + ; see xt-+ 1 2 xt-+ execute .
多くのプログラマーは、 '
とそれが解析(parse)するワードを 1 つの単位として認識し、 コンパイル時に [']
のように動作することを期待し、 実際の動作に混乱する傾向があります。 あなたがもしそうなら、 Forth システムは '
を 1
つの単位として捉えているだけであり、
それが解析(parse)ワードであるとはまったく考えていないことを覚えておいてください(この問題でプログラマーの便宜を図る試みは、 通常、
さらにひどい落とし穴につながります。
State
-smartness—Why it is evil and How to Exorcise it)。
XT の作成および実行は、 インタープリターの状態には影響を受けないことに注意してください。 つまり、 コンパイル状態で '
を実行した場合でも、 インタープリター機能(interpretation semantics)が得られます。 そして、 そこでの状態が何であれ、
execute
は XT によって表されるコード(つまり、'
で生成された XT
の場合はインタープリター機能(interpretation semantics))を実行します。
こちらも参照ください: Tokens for Words
throw ( n -- )
は、 n がゼロでない限り例外を引き起こします。
100 throw .s 0 throw .s
catch ( ... xt -- ... n )
は execute
と同様に動作しますが、 例外をキャッチし、
スタック上に例外の数値(または XT が例外なしで実行された場合は 0)をプッシュします。 例外があった場合、 スタックの深さは
catch
の実行直前と同一です。
.s 3 0 ' / catch .s 3 2 ' / catch .s
研究課題(assignment):
catch
の代わりにexecute
を使用して同じことを試してみましょう。
throw
は、常に動的に直近にこの throw
を囲んでいる(定義の) catch
にジャンプします、
たとえそのジャンプを達成するために複数の呼び出しレベルを飛び越す必要がある場合でもです:
: foo 100 throw ; : foo1 foo ." after foo" ; : bar ['] foo1 catch ; bar .
多くの場合、 定義が例外によって終了した場合でも、 定義を終了したときに値を復元することが重要です。 以下のようにしてみるのはどうでしょうか:
: ... save-x ['] word-changing-x catch ( ... n ) restore-x ( ... n ) throw ;
しかし、 これでも、 たとえば、catch
と restore-x
の間を実行中にユーザーが Ctrl-C
を押すなどしたら安全ではありません。
Gforth は、そのような場合に対して安全な代替例外処理構文(alternative exception handling syntax)
try ...restore ... endtry
を提供します。 try
と endtry
の間のコードに例外があった場合、 スタックの深さが復元され、 例外数値がスタックにプッシュされ、 restore
の直後から実行が続行されます。
以下は、 上記のコードと同等の、 より安全なコードです
: ... save-x try word-changing-x 0 restore restore-x endtry throw ;
こちらも参照ください: Exception Handling
これまでに出てきた :
や create
や variable
は定義ワードです。
これらは他のワードを定義します。 Constant
はもう一つの定義ワードです:
5 constant foo foo .
variable
や constant
でも接頭辞 2
(2倍長セル) や f
(浮動小数点)
を使用することができます。
あなた独自の定義ワードを定義することもできます。 例:
: variable ( "name" -- ) create 0 , ;
また、 単にアドレスを生成する以外のことを行うワードを作成する定義ワードを定義することもできます:
: constant ( n "name" -- ) create , does> ( -- n ) ( addr ) @ ; 5 constant foo foo .
上記の constant
の定義は does>
で終了します。 つまり、 does>
は ;
を置き換えるのですが、 他のことも行います。 最後に定義されたワードを変更して、 ワード本体(body)のアドレスをプッシュし、 呼び出されるたびに
does>
の後のコードを実行します。
上の例では、 constant
は ,
を使用して foo
の本体に 5 を格納します。
foo
が実行されると、 本体のアドレスがスタックにプッシュされ、 (does>
の後のコードにより、)そこから 5
がフェッチされます。
does>
の脇のスタック・コメントは、 does>
の後のコードのスタック効果ではなく、
定義されたワードのスタック効果です(違いは、 does>
の後のコードは does>
の脇のスタック・コメントには書いてない、 ワード本体(body)のアドレスを期待している点です)。
これらの定義ワードを使用すると、 (他の)定義ワードが関係する場合にファクタリングを行うことができます。 たとえば、 フィールド・オフセットは常にアドレスに加算するものですが、 その代わりに以下を定義します
2 cells constant offset-field1
この offset-field1 は以下のように使います
( addr ) offset-field1 +
ここで、 あなたは以下のような定義ワードをを定義できます
: simple-field ( n "name" -- ) create , does> ( n1 -- n1+n ) ( addr ) @ + ;
フィールド・オフセットの定義と使用は以下のようになります:
2 cells simple-field field1 create mystruct 4 cells allot mystruct .s field1 .s drop
does>
の後のコードを実行せずに、 そのワードで何かをしたい場合は、 >body ( xt -- addr )
を使用すれば create
したワードの本体(body)にアクセスできます:
: value ( n "name" -- ) create , does> ( -- n1 ) @ ; : to ( n "name" -- ) ' >body ! ; 5 value foo foo . 7 to foo foo .
研究課題(assignment): (
abort
の XT の先頭に、) XT を格納するワードを作成する、defer ( "name" -- )
を定義し、 実行時に XT をexecute
で実行するようにしてみましょう。 間接再帰はdefer
の応用の 1 つです。
こちらも参照ください: User-defined Defining Words
Forth には配列を定義するための標準ワードはありませんが、 アドレス演算に基づいて自分で配列を構築できます。 配列とレコードを定義するためのワードを定義することもできます(see Defining Words)。
Forth の初心者がワードの定義について学ぶときに最初に着手するプロジェクトの 1 つが配列定義ワード(ことによっては n 次元配列)です。 さぁあなたもやってみましょう。 あなたはそこから何かを学ぶでしょう。 ただし、 後からこれらのワードをほとんど使用しないことがわかってもがっかりしないでください(不適切に使用するとさらに悪いことになります)。 著者はまだ有用な配列ワードのセットを見つけられていません。 ニーズがあまりにも多様で、 (定義ワードを単純に使用した結果である、)名前付きのグローバル配列は、 柔軟性が十分ではないことがよくあります(たとえば、 どのようにしてパラメーターを渡すか、など)。 同様のプロジェクトのもう 1 つは、 文字列の処理に役立つワードのセットです。
その一方、 レコード・ワードには便利なセットがあり、 compat/struct.fs で定義されています。 これらのワードは
Gforth で事前定義されています。 これらについては、 このマニュアルの他の場所で詳しく説明されています(see Structures
参照)。 上記の simple-field
の例は、 このパッケージのフィールドの簡略化されたバリエーションです。
POSTPONE
¶POSTPONE
(訳注: (期限が定まってる)延期)を使用すると、 (そのワードのインタプリタ機能(interpretation
semantics)をコンパイルする代わりに、) そのワードのコンパイル機能(compilation semantics)をコンパイルできます:
: MY-+ ( Compilation: -- ; Run-time of compiled code: n1 n2 -- n ) POSTPONE + ; immediate : foo ( n1 n2 -- n ) MY-+ ; 1 2 foo . see foo
foo
の定義中に、 テキスト・インタープリターは MY-+
のコンパイル機能(compilation
semantics)を実行し、 そのコンパイル機能が +
のコンパイル機能を実行します。 つまり、 +
を
foo
内にコンパイルします。
この例では、 コンパイル機能(compilation
semantics)とコンパイルされたコードのスタック効果について個別のスタック・コメントも表示します。 デフォルトのコンパイル機能を持つワードの場合、
通常、 これらのスタック効果は表示されません。 これらのワードのコンパイル機能のスタック効果は常に ( -- )
であり、
コンパイルされたコードのスタック効果はインタープリター機能(interpretation semantics)のスタック効果です。
注意: この方法でコンパイル機能(compilation semantics)を実行する場合、 インタプリタの状態には影響を受けないことに注意してください。 あなたはそれを対話的(interpretively)に実行することもできます。 例:
: foo2 ( n1 n2 -- n ) [ MY-+ ] ; 1 2 foo2 . see foo2
ただし、 これが常に機能するとは限らない、良くない Forth システムがいくつかあるため、 この方法は 1999 年に非標準となりました。
POSTPONE
を使用する別の例を以下に示します:
: MY-- ( Compilation: -- ; Run-time of compiled code: n1 n2 -- n ) POSTPONE negate POSTPONE + ; immediate compile-only : bar ( n1 n2 -- n ) MY-- ; 2 1 bar . see bar
ENDIF
は以下の方法で定義できます:
: ENDIF ( Compilation: orig -- ) POSTPONE then ; immediate
研究課題(assignment):
2dup
と同等のコンパイル機能(compilation semantics)を持つMY-2DUP
を作成しますが、 コンパイルされるのはover over
になるようにしてみましょう。
Literal
¶数値を POSTPONE
することはできません:
: [FOO] POSTPONE 500 ; immediate
代わりに LITERAL (compilation: n --; run-time: -- n )
を使用します:
: [FOO] ( compilation: --; run-time: -- n ) 500 POSTPONE literal ; immediate : flip [FOO] ; flip . see flip
LITERAL
は、 コンパイル時(コンパイル機能(compilation semantics)が実行される時)に数値を消費し、
実行時(コンパイルされたコードが実行されるとき)にそれをプッシュします。 LITERAL
のよくある使用法は、
コンパイル時に計算された数値を現在のワードにコンパイルすることです:
: bar ( -- n ) [ 2 2 + ] literal ; see bar
研究課題(assignment): 上記の例を
: bar ( -- n ) [ 2 2 + ]L ;
と記述できるような]L
を定義してみましょう。
Execution Tokens の map-array
について再検討してみましょう。 map-array
は execute
を頻繁に実行しますが、 これは一部の Forth
実装では比較的高価な操作です。 compile,
と POSTPONE
を使用すると、 これらの
execute
を削除し、 直接実行されるワードを含むワードを生成できます:
: 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 } POSTPONE cells POSTPONE over POSTPONE + POSTPONE swap POSTPONE ?do POSTPONE i POSTPONE @ xt compile, 1 cells POSTPONE literal POSTPONE +loop ; : sum-array ( addr u -- n ) 0 rot rot [ ' + compile-map-array ] ; see sum-array a 5 sum-array .
コードの生成には Forth の機能を最大限に活用できます。 以下に、 コードがループ内で生成される例を示します:
: compile-vmul-step ( compilation: n --; run-time: n1 addr1 -- n2 addr2 ) \ n2=n1+(addr1)*n, addr2=addr1+cell POSTPONE tuck POSTPONE @ POSTPONE literal POSTPONE * POSTPONE + POSTPONE swap POSTPONE cell+ ; : compile-vmul ( compilation: addr1 u -- ; run-time: addr2 -- n ) \ n=v1*v2 (inner product), where the v_i are represented as addr_i u 0 postpone literal postpone swap [ ' compile-vmul-step compile-map-array ] postpone drop ; see compile-vmul : a-vmul ( addr -- n ) \ n=a*v, where v is a vector that's as long as a and starts at addr [ a 5 compile-vmul ] ; see a-vmul a a-vmul .
この例では compile-map-array
を使用していますが、 代わりに map-array
を使用することもできます(是非試してみてください)。
この手法を使用すると、 巨大な行列を効率的に乗算できます。 行列の乗算では、 一方の行列のすべての行ともう一方の行列のすべての列を乗算します。 1 行のコードを 1 回生成し、 それをすべての列に使用できます。 この手法の唯一の欠点は、 生成されたコードによって消費されたメモリーを完了時に開放するのが面倒なことです(さらに複雑な場合は移植可能ではありません)。
このセクションは Gforth 固有です。 スキップしても構いません。
' word compile,
はインタープリター機能(interpretation semantics)をコンパイルします。
デフォルトのコンパイル機能(compilation semantics)を持つワードの場合、 これはコンパイル機能を実行するのと同じです。
他のワードのコンパイル機能(インタープリター機能を持たない if
などのワード)を表すために、 Gforth
にはコンパイル・トークン(CTと略します。 2つのセルで構成)と、 ワード comp'
と、 ワード [comp']
の概念があります。 execute
を使用して、 CT によって表されるコンパイル機能を実行できます:
: foo2 ( n1 n2 -- n ) [ comp' + execute ] ; see foo2
postpone,
を使用して、 CT によって表されるコンパイル機能をコンパイルできます:
: foo3 ( -- ) [ comp' + postpone, ] ; see foo3
[ comp' word postpone, ]
POSTPONE word
と同等です。 comp'
は、
インタープリター・モード用のコードを持たないワードに対して特に役立ちます:
' if comp' if .s 2drop
こちらも参照ください: Tokens for Words
ディクショナリー(辞書)は、 allot
でメモリーを割り当てることができる、 単なるメモリー領域ではなく、
複数のワード・リスト(wordlist)上にある Forth ワード達も含まれています。 ワード・リスト内のワードを検索するとき、 概念的には、
最も新しいワードから検索を開始し、 古いワードに向かって進みます(実際には、 最近のほとんどのシステムはハッシュ・テーブルを使用します)。 つまり、
古いワードと同一の名前のワードを定義すると、 新しいワードが古いワードを隠します。
どのワードリストがどの順序で検索されるかは、 検索順序スタック(the search order)によって決まります。 order
で検索順序を表示できます。 最初に検索されるワードリストから順に検索順序を表示し、 その次に、 新しく定義されるワードを含むワードリストを表示します。
wordlist ( -- wid )
を使用して、 新しい空のワード・リスト(wordlist)を作成できます:
wordlist constant mywords
Set-current ( wid -- )
は、 新しく定義されたワードを入れるワード・リストを設定します(the current
wordlist):
mywords set-current order
このワード・リストは wordlist
を使用して匿名で作成されたため、 Gforth は mywords
のワード・リスト名を表示しません。
get-current ( -- wid)
で現在のワード・リストを取得できます。 現在のワード・リストに影響を与えずに、
指定のワード・リストに何かを入れたい場合、 通常は以下のようにします:
get-current mywords set-current ( wid ) create someword ( wid ) set-current
検索順序スタック(the search order)は set-order ( wid1 .. widn n -- )
で記述し、
get-order ( -- wid1 .. widn n )
で読み取ることができます。 ( n を除いて) もっとも TOS
側のワード・リストが最初に検索されます。
get-order mywords swap 1+ set-order order
ええ、 order
の出力内のワードリストの順序は、 スタック・コメントや .s
の出力とは逆になっているため、
直感的ではありません。
研究課題(assignment):
>order ( wid -- )
を定義して、 最初に検索されるワード・リスト(wordlist)としてwid
を検索順序スタック(the search order)に追加してみましょう。previous ( -- )
を定義してみましょう。 これは、 最初に検索されたワードリスト(wordlist)を検索順序スタックから削除するものです。 定義したら、 境界条件を試してみましょう(クラッシュや、 そこから抜け出すことが困難または不可能な状況がいくつか見られる事でしょう)。
検索順序スタック(the search order)は、 Modula-2 モジュールや、 C++ の名前空間と同様の機能を提供するための強力な基盤です。 ただし、 この方法でプログラムをモジュール化しようとすると、 デバッグや 再利用/ファクタリング に関しては、 (大規模なプロジェクトの経験はありませんけれども、)著者の経験では利点を上回る欠点があります。 他の言語/プログラミング環境では、 デバッグや再利用がそれほど強力ではないため、 これらの欠点はそれほど目立ちません。
こしらも参照ください: Word Lists
この章とチュートリアル(see Forth Tutorial)との違いは、 急ぎ足のチュートリアルと違ってじっくり腰を据えて、 チュートリアルではカバーしきれていない、 Forth の内部を詳しく説明していることです。 それはさておき、 この章で取り上げる内容はチュートリアルに比べはるかに少ないので、 パソコンを使わずに読むのに適しています。
このマニュアルの主な目的は、 Gforth を文書化することです。 ただし、Forth は広く知られている言語ではなく、 最新の教材が不足しているため、 入門用の教材を提供する価値があると思われます。 巷の Forth 関連情報その他の情報源については Other Forth-related information を参照下さい。
このセクションの例は、 どの標準 Forth でも動作するはずです。 示されている出力は Gforth を使用して生成されました。 各例では、
Gforth が生成する正確な出力を再現しようとしています。 あなたが例を試してみれば(そして、あなたはそうするべきです)、 入力すべき内容は
like this と示され、 Gforth の応答は like this
で示されます。 唯一の例外は、 例で
RET が示されている場合、 これは「Enter」キー(機種によりReturnキー)を押す必要があることを意味します。 残念ながら、
このマニュアルの一部の出力形式では this と this
の違いを表示できないため、
例を試してみるのが困難かもしれません(ただし、 不可能ではありません)。
Forth は珍しい言語です。 インタープリターとコンパイラーの両方を含む対話型開発環境を提供します。 Forth のプログラミング・スタイルでは、 問題をいくつかの要素に分割することが推奨されます 小さな断片を作成し(ファクタリング)、 各断片を対話的に開発・テストします。 Forth の支持者は、 従来のプログラミング言語で使用されている編集・コンパイル・テストのサイクルを打ち破ることで、 生産性の大幅な向上につながる可能性があると主張しています。
Forth イメージを呼び出すと、 起動バナーが出力されますが、 他には何も表示されません (システムに Gforth がインストールされている場合は、 gforthRET と入力して今すぐ呼び出してみてください)。 今、 Forth は、 テキスト・インタープリター(Text Interpreter)と呼ばれるコマンド・ライン・インタプリタを実行しています(外部インタープリターとも呼ばれます;訳注: 別途存在する 内部インタープリター(inner interpreter)に対してこう呼ばれる)。 (この章を読み進ればテキスト・インタープリターについて多くのことを学ぶことができます。 詳細については see The Text Interpreter を参照してください)。
明白ではなくて分かりにくいですが、 今や Forth はユーザーの入力を待っています。 数字の 4 と 5 を入力し RET キーを押します:
45RET ok
テキスト・インタープリターは、 次の入力を促すプロンプトを表示するのではなく、 入力行を処理した後に(改行無しで)ステータス・メッセージを出力します。
この場合のステータス・メッセージ(RET (「エンター」(リターン)キー押下)の後の ok
)は、
テキスト・インタープリターがすべての入力を正常に処理できたことを示します。 それでは次に、 不正な文字列を入力してみましょう:
qwer341RET *the terminal*:2: Undefined word >>>qwer341<<< Backtrace: $2A95B42A20 throw $2A95B57FB8 no.extensions
‘Undefined word‘ 以外の文章はシステムによって若干異なる場合がありますが、 意味は同じです。 テキスト・インタープリターがエラーを検出すると、 行に残っているテキストを破棄し、 特定の内部状態をリセットして、 エラー・メッセージを出力します。 エラー・メッセージの詳細な説明については、 Error messages を参照してください。
テキスト・インタープリターは、エンター・キー(リターン・キー)が押されるのを待ち、 その後入力行を処理します。 行頭から開始して、 行をスペースで区切られた文字のグループに分割します。 文字のグループごとに、 以下の順番で、 何かするために計 2 回の試みを行います:
テキスト・インタープリターが文字グループに対して上記のいずれも実行不可能な場合、 その文字グループと行の残りの部分が破棄され、
エラー・メッセージが出力されます。 テキスト・インタープリターがエラーなく行末に到達すると、 ステータス・メッセージ ok
に続いて改行を出力します。
以下は、 テキスト・インタープリターに与えることができる最もシンプルなコマンドです:
RET ok
ここで、 テキスト・インタープリターは、 私たちが要求したことをすべて(何もせずに)エラーなしで実行したため、 すべてが ok
であると表示しました。 今度は少し長いコマンドを試して見ましょう:
12 dup fred dupRET *the terminal*:3: Undefined word 12 dup >>>fred<<< dup Backtrace: $2A95B42A20 throw $2A95B57FB8 no.extensions
エンター・キー(リターン・キー)を押すと、 テキスト・インタープリターが行に沿って動作を開始します:
2
の後のスペースに到達すると、 文字グループ 12
を取得し、
それらを名前ディクショナリーで検索します5。
名前ディクショナリにはこの文字グループに一致するものがないため、それらを数値として処理しようとし、 そして、
数値として処理するのは正常に実行できたので、 (それが何を意味するにせよ) 数値 12 を「スタック上」に置きます。
dup
を実行します(それが何を意味していても)。
fred
を取得します。
名前ディクショナリーで調べますが、見つかりません。 それらを数値として扱おうとしますが、 正当な数値を表すものではありませんでした。
この時点で、 テキスト・インタープリターは諦めてエラー・メッセージを出力します。 エラー・メッセージには、
テキスト・インタープリターが行の処理でどこまで到達したかが正確に示されます。 これは、 特に、 テキスト・インタープリターが最後の文字グループ
dup
に対して何も行おうとしなかったことを示しています。 テキスト・インタプリタがそのワード dup
を検索して、
一度はちゃんと実行されたのですから、 もう一度実行することに何の問題はないはずなのに、 です。
手続き型プログラミング言語(C や Pascal など)では、 プログラムの構成要素は関数(function)やプロシージャ(procedure)です。 これらの関数またはプロシージャは、 明示的なパラメーターを使用して呼び出されます。 たとえば、 C では以下のように記述できます:
total = total + new_volume(length,height,depth);
ここで、 new_volume は別のコード片への関数呼び出し(function-call)であり、 total, length, height, depth はすべて変数です。 length, height, depth は関数呼び出しのパラメーターです。
Forth では、 関数またはプロシージャに相当するのは「定義」(definition)であり、 パラメーターはプログラマから見える共有スタックを使用して定義間で暗黙的に渡されます。 Forth は変数をサポートしていますが、 スタックの存在は、 他のほとんどのプログラミング言語よりも変数が使用される頻度がはるかに低いことを意味します。 テキスト・インタープリターは数値を検出すると、 それをスタックに置きます(プッシュ)。 何種類かのスタックがあり(いくつあるかは実装依存です)、 操作に使用されるスタックは、 実行される操作によって明確に示されます。 すべての整数演算に使用されるスタックは「データ・スタック」と呼ばれ、 これが最も一般的に使用されるスタックであるため、 「データ・スタック」への参照は多くの場合「スタック」と省略されます。
スタックには後入れ先出し(last-in, first-out;LIFO)構成が採用されています。 以下のように打ち込むと:
1 2 3RET ok
これはテキスト・インタープリターに 3 つの数値を (データ)スタック に配置するように指示します。 スタックの振る舞いはトランプの扱いに例えられます。 トランプの箱から、 (スートは何でもいいですが、) エース(1)のカードと2のカードと3のカードをテーブルの上の山に配ります。 3のカードは山の最後のカード (last-in)であり、 山からカードを 1 枚取ると、 あなたがシャッフルとか何もいじってない限り、 取り出すカードは 3のカードになります(first-out)。 スタックから最初に取り出されるであろう(スタック上の)数値はスタック頂上(スタック・トップ;top of stack)と呼ばれ、 多くの場合「TOS」と省略されます。
Forth でパラメーターがどのように渡されるかを理解するために、 定義 +
(「プラス」と発音します) の振る舞いを見てみましょう。
あなたは、 この定義が加算を実行することを知っても驚かないでしょう。 より正確には、 これは 2 つの数値を加算して結果を生成します。 さて、 その 2
つの数値はどこから取得されるのでしょうか? これはスタック頂上から上位 2 つの数値を取り除きます。 その結果はどこに配置されるのでしょうか?
それはスタックです。 あなたは以下のように、 トランプを使って +
の振る舞いを演ずることができます。
トランプの箱はないですが、 Forth の実行中は、 定義 .s
を使用して、 スタックに影響を与えることなく、
スタックの現在の状態を表示できます。 以下の様に打ち込みます:
clearstacks 1 2 3RET ok .sRET <3> 1 2 3 ok
テキスト・インタープリターはワード clearstacks
を検索して実行します。 スタック(データおよび浮動小数点スタック)を整理し、
以前の例の実行によってスタックに残された可能性のあるエントリをすべて削除します。 続けてテキスト・インタプリタは、 3
つの数値をそれぞれ順番にスタックにプッシュします。 最後に、 テキスト・インタープリターはワード .s
を検索して実行します。
.s
を実行すると、「<3>」 (スタック上の項目の合計数) に続いて、スタック上のすべての項目のリストが出力されます。 一番右側の項目が
TOS です。
あなたは今や以下のように打ち込めます:
+ .sRET <2> 1 5 ok
現在スタックには 2 つの項目があり、 (そのうちの項目の1つである)加算の結果は 5 となっていれば正解です。
あなたが引き続きトランプで遊んでいるなら、 この結果に対して 2 回目の足し算を行ってみましょう。 2 枚のカードを手に取り、 その合計が 6 であることを計算し、手に取った2枚のカードをシャッフルして箱に入れ、 箱から 6のカード を探してテーブルに置きます。 これで、 スタックにはアイテムが 1 つだけになりました。 あなたが 3 回目の足し算を実行しようとするとどうなるでしょうか? あなたは、 1枚目のカードを手に取り、 2枚目のカードを手に取ろうとします – ああ! でも 2枚目のカードはありません。 これは「スタック・アンダーフロー」と呼ばれ、エラーとなります。 Forth で同じことを行おうとすると、 多くの場合、 エラーが報告されます(おそらく、 スタック・アンダーフロー(Stack Underflow)エラー、 または、 無効なメモリーアクセス(Invalid Memory Address)エラー)。
スタック・アンダーフローの逆の状況が「スタック・オーバーフロー」です。 これは、 あなたがたが、 スタック用に予約されている有限量のストレージ・スペースがあることを単に受け入れるだけで済みます。 トランプの例えを拡張すると、 沢山の数のトランプのカードがあり、 それらのカードをテーブルに積み上げた場合、 天井にぶつかってしまい、 最終的には別のカードを追加できなくなります。 Gforth を使用すると、 スタックの最大サイズを設定できます。 一般に、 スタック・オーバーフローが発生するのは、 定義にバグがあり、 制御不能にスタック上にデータを生成している場合のみです。
トランプの例えには、 最後にもう 1 つ適用例があります。 トランプを使用してスタックをモデル化した場合、 スタック上のアイテムの最大数は 52 になります(ジョーカーを使用しなかったと仮定して)。 スタック上のアイテムの最大値は13(キング)です。 実際は、 使用できる数値は 1 ~ 13 の正の整数のみ(つまり、 13個の数)です。 (たとえば) 0や27や3.52や-2は使用できません。 そこで、 一部のカードについて考え方を変え、 さまざまな数値に対応できるようにします。 たとえば、ジャックは 0 を表し、 クイーンは -1 を表し、 キングは -2 を表すと見なします。 すると、 表現できる数値の幅は変更されません(合計 13 個の数値のみを表すことができます)が、 -2 から 10 の数値を表す事ができるようになりました。
この例えでは、 制限は 1 つのスタック・エントリが保持できる情報の量であり、 Forth にも同様の制限があります。 Forth では、 スタック・エントリのサイズは「セル」(cell)と呼ばれます。 セルの実際のサイズは実装に依存し、 スタック・エントリが保持できる最大値に影響します。 標準 Forth は少なくとも 16 ビットのセル・サイズを提供し、 ほとんどのデスクトップ・システムは 32 ビットのセル・サイズを使用します。
Forth は型チェックを一切行わないため、 スタック項目を自由に操作したり組み合わせたりできます。 スタック項目を 2
の補数の符号付き整数として扱う便利な方法は、 +
などの標準ワードもやっています。 それゆえ、 以下のように入力できます:
-5 12 + .sRET <1> 7 ok
あなたが、 Forth を電卓にするために、 数値や +
のような定義を使用すると、
それが通常の電卓とはかなり異なることがわかるでしょう。 あなたは 2 + 3 = と入力するのではなく、 2 3 +
と入力する必要があります(ここでは、 結果を確認するには .s
を使用する必要があるという事実は無視してください)。
この違いを説明するために使用される用語は、 電卓は「中置記法」(Infix Notation;パラメーターと演算子が混合されている)を使用するのに対し、
Forth は「後置記法」(Postfix Notation;パラメーターと演算子が分かれている)、 またの名を「逆ポーランド記法」(Reverse
Polish Notation)と呼ばれるものを使用します。
後置記法は最初はわかりにくいように見えるかもしれませんが、 いくつかの重要な利点があります:
これらの主張をさらに詳しく調べるために、 以下の計算について見てみましょう:
6 + 5 * 4 = 4 * 5 + 6 =
あなたが、算数を勉強中の場合、 または算数が非常に苦手な場合は、 最初の答えは 44、 2 番目の答えは 26 になるでしょう。 あなたが算数に少し詳しい人なら、 乗算は加算よりも優先されるという法則を覚えているでしょう。 そして、 どちらの場合も答えは 26 になるでしょう。 答え 44 だと言った人に、 なぜ答えが 26 かを説明するには、 最初の計算を以下のように書き換えることになるでしょう:
6 + (5 * 4) =
あなたが、 もし、 乗算より優先して加算を実行したい場合は、 かっこを使用して強制的に加算させる必要があります。
上記 2 つの計算を電卓で計算する場合、 以下のキーストローク・シーケンスを使用して、 入力ミスしない限り、 おそらく正しい答えが得られるでしょう:
6 + 5 = * 4 = 4 * 5 = + 6 =
Postfix notation is unambiguous because the order that the operators are applied is always explicit; that also means that parentheses are never required後置記法では演算子が適用される順序は常に明示的です。 これは、 括弧が決して必要ないことも意味します。 演算子はアクティブです(演算子を引用する行為によって演算操作が実行されます)。 これにより、「=」が必要なくなります。
計算 6 + 5 * 4 は、 以下の 2 つの同等の方法で(後置表記で)書くことができます:
6 5 4 * + または: 5 4 * 6 +
この表記法に関して注意すべき重要な点は、 数値の順番は変わらないことです。 10 から 2 を減算する場合は、 10 2 -
と入力します。
Forth が後置表記を使用する理由は非常に簡単に説明できます。 これにより実装が非常に単純になり、 パラメーターを渡すためのメカニズムとしてスタックを使用することが自然な流れになります。 これについての別の考え方としては、 すべての Forth 定義がアクティブであると認識することです。 これらは、 テキスト・インタープリターによって検出されると実行されます。 この結果、 Forth の構文は何の努力も必要とせずシンプルになります。
これまで私たちが見てきた例はこまごまとしたものでした。 私たちは Forth を大きめの電卓として使用してきました。 また、 これまでに示した各計算は「1 回限り」のものです – それを繰り返すには、もう一度入力する必要があります6。 このセクションでは、 Forth の語彙(vocabulary)に新しいワードを追加する方法を説明します。
新しいワードを作成する最も簡単な方法は、「コロン定義」を使用することです。 それらがどのように機能するかについて思い悩む前に、 いくつか定義して試してみましょう。 以下の例を入力してみてください。 スペースを正確に反映するように注意してください:
: add-two 2 + . ; : greet ." Hello and welcome" ; : demo 5 add-two ;
この例を打ち込んだら、 すぐ試してみましょう:
greetRET Hello and welcome ok greet greetRET Hello and welcomeHello and welcome ok 4 add-twoRET 6 ok demoRET 7 ok 9 greet demo add-twoRET Hello and welcome7 11 ok
ここで導入した最初の新しいモノは、 :
と ;
というワードのペアです。
これらは、それぞれ新しい定義を開始および終了するために使用されます。 :
の後の最初の単語(word)は、 新しい定義の名前です。
例からわかるように、 定義はすでに定義されているワードから構成されます。 Forth では、 システムの起動時に存在した定義と、 ユーザーが自分で定義した定義とを区別しません。
この例では、 .
(ドット) や、 ."
(ドット・クォート)や、 dup
(デュープ)
というワードも紹介しています。 ドットはスタック頂上から数詞を取得して表示します。 これは .s
に似ていますが、
スタック頂上の項目のみを表示する点と破壊的である点が異なります。 実行後、スタック上にその数値は最早ありません。 数値の前はスペース無しで、
後ろには常に 1 つのスペースが表示されます。 ドット・クォートは、 ワードの実行時に出力される文字列(string)を定義します。 文字列には、
"
を除く任意の印刷可能な文字を含めることができます。 "
には特別な機能があります。 これは Forth
ワードではありませんが、 区切り文字として機能します(区切り文字の仕組みについては次のセクションで説明します)。 最後に、 dup
はスタック頂上の値を複製します。 5 dup .s
と入力して、 dup
がやることを確認してください。
あなたは、 既に、 テキスト・インタープリターが名前を見つけるためにディクショナリーを検索することを知っています。 あなたが上記の例の通りにした場合、
add-two
という定義がすでにあるはずです。 それでは、 この定義に対して、 更に新しい定義を入力して変更してみるとしましょう:
: add-two dup . ." + 2 = " 2 + . ;RET redefined add-two ok
Forth は、私たちがすでに存在するワードを定義しようとしていることを認識し、 それを警告するメッセージを出力しました。 さて、 それでは、 新しい定義を試してみましょう:
9 add-twoRET 9 + 2 = 11 ok
ただし、 ここで実際に行ったことは、 特定の名前を付けて新しい定義を作成することだけです。 同一の名前の定義がすでに存在するという事実は、 (Forth
が警告メッセージを出力することを除いて、)新しい定義の作成方法に何の違いもありません。 add-two
の古い定義はいまだ存在します(これが正しいかどうかを確認するには、 demo
をもう一度試してください)。 add-two
の新しい定義以降に定義するワードは add-two
の新しい定義を利用しますが、 demo
の定義は、 古い定義は
demo
をコンパイルする時点で既に存在していた add-two
の古い定義のバージョンを引き続き使用します。
次のセクションに進む前に、 あなた自身のワードをいくつか定義したり再定義したりしてみましょう。
ここで、 前のセクションの add-two
の定義をもう一度見てみましょう。 テキスト・インタープリターの動作方法に関する知識から、
add-two
を定義しようとしたとき、 私達は以下の結果を予期したかもしれません:
: add-two 2 + . ;RET *the terminal*:4: Undefined word : >>>add-two<<< 2 + . ;
しかし、 これが起こらなかった理由は、 :
の動作方法に関係しています。 :
というワードは 2 つの特別な働きをします。
1つ目の特別な機能は、 テキスト・インタープリターが add-two
という文字を認識できないようにすることです。
テキスト・インタープリターは、 >IN
(to-in;トゥーイン)という変数を使用して、 入力行のどこを追跡するかを保持し、
:
というワードに遭遇すると、 他のワードの場合とまったく同じように動作します。 名前ディクショナリーでそれを検索し、 その xt
を見つけて実行します。 実行される :
は、 入力バッファーを調べてワード add-two
を見つけ、
>IN
の値をその後ろを指すように進めておきます。 次に、 新しい定義の作成に関連するその他の処理を実行します(名前ディクショナリーに
add-two
のエントリを作成する等)。 :
の実行が完了すると、 制御はテキスト・インタープリターに戻ります。
テキスト・インタープリターは、 このトリックにより入力行の一部をスキップしていることに気づきません。
:
のようなワード(>IN
の値を進めて、 テキスト・インタープリターが入力行全体に作用するのを妨げるワード)は、
「構文解析ワード」(parsing words)と呼ばれます。
:
が行う 2 つ目の特別な処理は、 state
と呼ばれる変数の値を変更することです。 これは、
テキスト・インタープリターの振る舞いに影響します。 Gforth が起動するとき、 state
の値は 0 であり、
テキスト・インタープリターはインタープリター状態(interpreting)であると言われます。 (:
で始まる)コロン定義中
、 state
は -1 に設定され、テキスト・インタープリターはコンパイル状態(compiling)と言われます。
この例では、 テキスト・インタープリターは文字列 “2 +
. ;
”。引き続き同じ方法で文字列を文字シーケンスに分割します。ただし、数値 2
をスタックにプッシュする代わりに、 数値
2
を取得する魔法を add-two
の定義に組み込み(コンパイル)、 add-two
が「実行」されたときスタックにプッシュされます。 同様に、 +
と .
の振る舞いも定義にコンパイルされます。
特定の種類のワードはコンパイルされません。 これらのいわゆる「即実行ワード」(immediate word)は、
テキスト・インタープリターがインタープリター状態であるかコンパイル状態であるかに関係なく、 実行されます(今、 直ちに実行されます)。 ;
というワードは即実行ワードです。 定義にコンパイルされるのではなく、 実行されます。 その効果は、 state
の値を 0
に戻すことを含む、 現在の定義を終了することです。
あなたが add-two
を実行すると、 その定義の外で 2 + . RET
と入力した場合とまったく同一の「実行時効果」(run-time effect) が生じます。
Forth では、 すべてのワードまたは数値は以下の 2 つの性質を持ちます:
数値は常に決まった方法で処理されます:
ワードは常にこのような通常の振る舞いをするとは限りませんが、 ほとんどのワードにはデフォルトの機能(default semantics)があり、 以下のように振る舞うことを意味します:
特定のワードの実際の振る舞いは、 ワードの定義時に immediate
や compile-only
というワードを使用することで制御できます。 これらのワードは、 最後に定義されたワードの名前ディクショナリー・エントリにフラグを設定します。
これらのフラグは、 名前ディクショナリーでワードが見つかったときにテキスト・インタープリターによって取得されます。
immediate としてマークされたワードは、 そのインタープリター機能(interpretation semantics)と同じコンパイル機能(compilation semantics)を持ちます。 つまり、 以下のように振る舞います:
ワードを compile-only としてマークすると、 インタープリター状態(interpretation
state)でこのワードを検出したときにテキスト・インタープリターが警告を生成することを意味します。 ('
または [']
を使用して) ワードをティックすると、 警告が生成されます。
compile-only
を使用する必要はありません(多くの実装によって提供されてはいますが、 標準 Forth
の一部でもありません)が、 インタープリター態(interpret state)で正しく振る舞わないワードに compile-only
を適用するのは良いエチケットです(そして予期しない副作用が発生する可能性があります)。 たとえば、 定義内で条件ワード IF
を使用することのみが正当です。 これを忘れて別の場所で使用しようとすると、 (Gforth では) compile-only
としてマークされているため、 テキスト・インタープリターが有用な警告を生成できます。
以下の例は、 即実行ワードと非即実行ワードの違いを示しています:
: show-state state @ . ; : show-state-now show-state ; immediate : word1 show-state ; : word2 show-state-now ;
show-state-now
の定義の後にあるワード immediate
は、そのワードを即実行ワードにします。
これらの定義では、 @
(「フェッチ」と発音します) という新しいワードが導入されています。
このワードは変数の値を取り出し(フェッチし)、 それをスタックに残します。 したがって、 show-state
の振る舞いは、
state
の現在の値を表す数値を出力することです。
word1
を実行すると、 システムがインタープリター状態であることを示す数値 0 が出力されます。 テキスト・インタープリターが
word1
の定義をコンパイルしたときに、 コンパイル機能が現在の定義に実行時コードを追加する show-state
に遭遇しました。 word1
を実行すると、 show-state
のインタプリタ機能(interpretation
semantics)が実行されます。 word1
(つまり show-state
) が実行される時点で、
システムはインタープリター状態です。
word2
の定義を入力した後に RET を押すと、 数値 -1 が出力され、 その後に ok
が表示されるはずです。 テキスト・インタープリターが word2
の定義をコンパイルすると、 即実行ワードである
show-state-now
が検出されたため、 そのコンパイル機能(compilation
semantics)はインタプリタ機能(interpretation semantics)を実行します。
これは直ちにに実行されます(テキスト・インタープリターが次の文字グループ(この例では ;
)の処理に移る前に)。 これを実行すると、
word2
の定義途中の state
の値が表示されます。 -1 を出力するので、
システムがその時点でコンパイル状態であることがわかります。 もし あなたが word2
を「実行」しても何も行いません。
即実行ワードの話題を離れる前に、 前のセクションの greet
の定義における ."
の振る舞いについて検討してみましょう。
このワードは構文解析ワード(parsing word)でもあり、 かつ、 即実行ワードでもあります。 ."
とそのテキストの先頭
Hello and welcome
の間にはスペースがありますが、 welcome
の最後の文字と "
文字の間にはスペースがありません。 これは、."
が Forth ワードであるということです。 テキスト・インタープリターがその
Forth ワード ."
を識別できるように、 ."
の後ろにスペースが必要です。 "
は Forth
ワードではなく、 区切り文字(delimiter)です。 先の例では、 文字列が表示されるときに、 H
の前にも e
の後ろにもスペースがないことを示しています。 ."
は即実行ワードなので、 greet
の定義中に実行されます。
実行されると、 入力行内を前方に走査して区切り文字を探します。 区切り文字が見つかると、 区切り文字の後ろを指すように >IN
を更新します。 また、 いくつかのマジック・コード、 つまりテキスト文字列を出力する実行時コード xtを greet
の定義にコンパイルします。 文字列 Hello and welcome
をメモリーにコンパイルして、 後で出力できるようにします。
その後、 テキスト・インタープリターが制御を取得すると、 入力ストリーム内で次に検出されるワードは ;
であるため、
greet
の定義を終了します。
あなたが Forth コンパイラーを起動すると、 すでに多数の定義が存在しています。 Forth では、 ボトムアップ・プログラミング手法を使って新しいアプリケーションを開発し、 既存の定義に基づいて定義される新しい定義を作成します。 作成した各定義は、 対話的にテストおよびデバッグできます。
この章の例を試したことがあるなら、 あなたは、それらをおそらく手動で入力したことがあるでしょう。 Gforth を終了すると、
あなたが定義したモノは失われてしまいます。 これを回避するには、 テキスト・エディタを使用して Forth ソース・コードをファイルに入力し、
include
を使用してファイルからコードをロードします(see Forth source files)。 Forth
のソース・ファイルは、 あたかも手入力したかのように、 テキスト・インタープリターによって処理されます7。
Gforth は、 プログラム入力 にテキスト・ファイルを使用する、 伝統的な Forth の代替手段もサポートしています(see Blocks)。
ほとんどというほどではないにしても、 多くの Forth コンパイラーと共通して、 Gforth のほとんどは実際には Forth で書かれています。 インストール・ディレクトリ8内のすべての .fs ファイルは Forth ソース・ファイルであり、 あなたは それらを Forth プログラミングの例として拝んで学ぶことができます。
Gforth は、 テキスト・インタープリターに入力したすべての行を記録する履歴(history)ファイルを維持します。 このファイルはセッションをまたいで維持され、 コマンドラインの呼び出し機能を提供するために使用されます。 長い定義を手動で入力した場合は、 テキスト・エディターを使用して履歴ファイルから後で再利用するために Forth ソース・ファイルに貼り付けることができます(詳細については see Command-line editing)。
この章を要約すると:
state
の値を使用して、 探し出したワードのインタプリタ機能(interpretation
semantics)を使用するかコンパイル機能(compilation semantics)を使用するかを選択します。
信じられないかもしれませんが、 あなたがここまで読んで(そして理解していれば)、 Forth システムの内部動作についての基本をほぼすべて知っていることになります。 あなたは今や確実に、 このマニュアルの残りの部分と標準 Forth ドキュメントを読んで理解し、 Forthの一般的な機能と、 特に Gforth が提供する機能について詳しく学ぶのに十分な知識を持っています。 更に恐るべきことに、 あなたは独自の Forth システムを実装するのにほぼ十分な知識を持っていますが、 独自の Forth システムを実装するには時期尚早かもしれません。 その前に、 あなたは Gforth でいくつかのプログラムを書いてみたほうがいいでしょう。
Forth には非常に豊富な語彙があるため、 学ぶにも、 どこから手を付ければいいかわからない場合があります。 このセクションでは、 小さいながらも有用なプログラムを作成するのに十分なワードのセットをいくつか提案します。 このドキュメントのワード索引を使用して各ワードについて詳しく学び、 それを試して、 それを使用して簡単な定義を書いてみてください。 まずは以下のワード達を試してみてください:
+ - * / /MOD */ ABS INVERT
MIN MAX =
AND OR XOR NOT
DUP DROP SWAP OVER
IF ELSE ENDIF ?DO I LOOP
. ." EMIT CR KEY
: ; CREATE
ALLOT ,
SEE WORDS .S MARKER
上記をマスターできたら、 以下に進みましょう:
VARIABLE CONSTANT VALUE TO CREATE DOES>
@ !
これらをマスターしたら、 あなたは、 このマニュアルのすべてに目を通し、 あなたのプログラムに欠けているものを探し出す必要があります。
TODO: すでに完了した内容やマニュアルの他のセクションにリンクされた一連のプログラミング演習を提供したい。 すべての演習に対する回答を、 ディストリビューション内の .fs ファイルで提供するようにしたいです。
整数値をデータ・スタックにプッシュするために、 あなたはソース・コードに数値を書きます。 たとえば 123
です。 数字の連なりの前に
-
を付けると、 負の数値を示すことができます。 たとえば -123
です。
これはコロン定義の内部と外部の両方で機能します。 数値は base
(基数)
の値に従って通訳(interpret)されます(see Number Conversion)。 「数字」は 0
~
9
、a
(10進数の10) ~ z
(10進数の35) です。 ただし、
基数(base)より小さい「数字」のみが認識されます。 変換では大文字と小文字が区別されないため、A
と a
は同じ「数字」になります。
以下のプレフィックスを使用すると、 数値の基数(base)を明示的に指定できます:
#
– 10進数(decimal)
%
– 2進数(binary)
$
– 16進数(hexadecimal)
&
– 10進数(decimal)(非標準)
0x
– 16進数(hexadecimal), if base<33 (非標準)
基数プレフィックスと符号を含む組み合わせの場合、 標準的な順序では基数プレフィックスを最初に配置します(例: #-123
)。 Gforth
は両方の順番をサポートします。
小数点 .
を数値の末尾(または非標準的に、 プレフィックスの前を除く他の場所)に置くと、 2倍長整数double-cell
integer)として扱われます(例: #-123.
または #-.123
(この2つは同一の数値です))。
別のプログラミング言語の経験があるユーザーは、 基数プレフィックスのない、 このような数値(例: -123.
) を見たり書いたりする場合、
その数値が浮動小数点値を表すものと期待する可能性があります。 混乱を早期に解決するために、 Gforth はそのような使用法について警告しています。
警告を回避するには、 常に基数プレフィックスを付けて 2倍長整数(double-cell integer)を記述することをお勧めします(例:
#-123.
)
以下にいくつかの例を示します。 なお、 同値の 10 進数が括弧内に示されています:
$-41
(-65), %1001101
(205), %1001.0001
(145 ; 2倍長整数),
#905
(905), $abc
(2478), $ABC
(2478).
(文字)コード・ポイントの数値を取得するには、 文字を '
で囲みます (例: 'a'
)。 末尾の '
は標準では必須ですが、Gforth では省略できます。 注意: これは非 ASCII 文字でも機能することに注意してください。 多くの用途では、
文字をセルとしてではなく文字列として持つ方が便利です。 文字列の構文については、 以下を参照してください。
Forth の浮動小数点数は、 その指数によって認識されます。 つまり、 1.
は 2倍長整数(double-cell
integer)で、 そして 1e0
は浮動小数点数です。 後者は 1e
に短縮できます(通常は短縮します)。
仮数部(e
や E
より前の部分)と指数の部分の両方に符号(+
を含む)を含めることができます。
仮数部には少なくとも 1 つの数字が含まれている必要があり、 小数点を含めることができます。 指数は空であってもかまいません。
浮動小数点数は仮数と指数の両方に常に 10 進数の基数を使用し、 基数が 10 進数の場合にのみ認識されます。 例: 1e 1e0 1.e
1.e0 +1e+0
(これらは全て同一の数値です)、 +12.E-4
Gforth 拡張機能 (1.0 以降)では、 浮動小数点数をスケーリングされた表記で書くことができます。 オプションで符号、 その次に 1
つ以上の数字を指定し、その後ろに、 主に SI で定義されたスケーリング記号(別名 メトリック・プレフィックス)または %
のうちの 1
つを使用でき、 その後に、 オプションで更に多くの桁を指定できます。 Gforth が受け入れるスケーリング記号の完全なリストは以下のとおりです:
Q
e30
quetta(クエタ)
R
e27
ronna(ロナ)
Y
e24
yotta(ヨタ)
Z
e21
zetta(ゼタ)
X
e18
exa(エクサ)(E
じゃ無いので注意)
P
e15
peta(ペタ)
T
e12
tera(テラ)
G
e9
giga(ギガ)
M
e6
mega(メガ)
k
e3
kilo(キロ)
h
e2
hecto(ヘクト)
d
e-1
deci(デシ)
%
e-2
percent(パーセント)(c
じゃ無いので注意)(訳注: 一般の centi(センチ)
と違うので注意)
m
e-3
milli(ミリ)
u
e-6
micro(マイクロ)(μ
じゃ無いので注意)
n
e-9
nano(ナノ)
p
e-12
pico(ピコ)
f
e-15
femto(フェムト)
a
e-18
atto(アト)
z
e-21
zepto(ゼプト)
y
e-24
yocto(ヨクト)
r
e-27
ronto(ロント)
q
e-30
quecto(クエント)
Gforth の残りのほとんどとは異なり、 スケーリング・シンボルは大文字と小文字が区別されて扱われます。 スケール表記を使用するということは、
スケール記号の代わりに小数点を使用し、 末尾に指数表記を追加することと同じです。 スケール表記の例: 6k5
(6500e),
23%
(0.23e)
文字列を "
で囲んで入力できます(例: "abc"
、"a b"
)。 その結果は、データスタック上の、
文字列の開始アドレスと、 バイト(=char)のカウントです。
文字列内の "
は \
でエスケープする必要があります(例: "double-quote->\"<-"
)。
さらに、 この文字列構文は、 s\"
でサポートされている制御文字を記述するすべての方法をサポートしています(see String and Character literals)。 この文字列構文の欠点は、 標準ではないことです。 標準プログラムの場合は、 "…"
の代わりに s\"
を使用してください。
環境変数を取得するには、 最初に rec-env.fs をロードし、 次に環境変数名を { と } で囲い、 その前に
$
を付けます (例: ${HOME}
)。 結果は、 上で説明した形式のデータ・スタック上の文字列記述子(string
descriptor)になります。 これは "HOME" getenv
と同等です。つまり、
環境変数は実行時(run-time)に解決されます。
ワード名の前に `
を付けることで、 ワードの実行トークン (xt) を取得できます (例: `dup
)。
'
または [']
を使うよりも有利な点は、 コロン定義の内側から外側へ、
またはその逆にコードをコピーして貼り付けるときに、 これらを切り替える必要がないことです。 欠点は、 この構文が標準ではないことです。
ワード名の前に ``
を付けることで、 ワードの名前トークン (nt) を取得できます (例: ``dup
)。
この構文も非標準です。
ワード名を<
と >
で囲むことで、 ワードの本体(body)アドレスを取得できます(例:
<spaces>
)。 ワード名と末尾の >
の間に +
と数値を入れることで、
その本体アドレスから正のオフセットのアドレス(通常はそのワードの本体内のアドレス)を取得することもできます(例:
<spaces+$15>
, spaces+-3
)。 例えば <spaces+$15>
とか
<spaces+-3>
) とすると、 その本体アドレスに数値を足したのを得るでしょう。 この非標準機能は、 ...
の出力をコピーして貼り付けることを可能にするために存在します(see Examining data and code)。
Forth のワードは、 以下にあるように、 Forth テキストのデファクトスタンダード(事実上の標準)となっている表記にて説明されます:
word Stack effect wordset pronunciation
Description
ワード名。
スタック効果(stack effect)は、 before -- after
という表記で記述されます。
ここで、before と after は、 ワード実行前と実行後のスタック・エントリの頂上部を表します。
スタックの残りの部分にはワードは触れません。 スタックの頂上は右端です。 つまり、 スタック・シーケンスはあなたが入力したとおりに書き込まれます。
注意: Gforth は別個の浮動小数点スタックを使用しますが、 統一されたスタック表記法を使用することに注意してください。 また、
リターンスタック効果は「スタック効果」には表示されませんが、 「説明」(Description)に表示されます。 スタック項目の名前は、
項目の型や機能を説明します。 型の説明については、 下記を参照してください。
すべてのワードには、 コンパイル・モードのスタック効果とインタープリター・モードのスタック効果という 2 つのスタック効果があります。 ほとんどのワードのコンパイル・モードのスタック効果は – です。 ワードのコンパイル・モードのスタック効果がこの標準の振る舞いから逸脱している場合、 またはワードがコンパイル・モードに他の通常でない振る舞いを行う場合、 両方のスタック効果が表示されます。 それ以外の場合、 インタープリター・モードのスタック効果のみが表示されます。
また、 コード・テンプレートまたはサンプルでは、 その時点でのスタックの状態表示するコメントが括弧内にある場合があることにも注意してください。
これらのスタックの状態表示には --
はありません。 なぜなら前・後という状況が無いためです。
ワードの発音
ワードセット(wordset)は、 ワードが標準化されたものか、 または環境クエリ文字列であるか、 または Gforth
固有のワードであるかを指定します。 Gforth 固有のワードの場合、 ワードセット名には文字列 gforth
が含まれており、
他のワードセット名は environment
または標準のワードセット(standard word sets)を参照します。
Forth 標準はいくつかのワードセットに分かれています。 理論上、 標準システムはそれらすべてをサポートする必要はありませんが、 実際には、 組み込み用途な超小型のマシン以外の本格的なシステムは、 ほぼすべての標準化されたワードをサポートします(ただし、一部のシステムでは一部のワードセットを明示的にロードする必要があります)。 そのため、 実際のところはワードセットの使用をケチったところでその分移植性が上がる訳ではありません。
Gforth 固有のワードについては、 以下のカテゴリがあります:
gforth
gforth-<version>
我々はこのワードを Gforth で永続的にサポートするつもりであり、 Gforth <version> 以降で有効です(おそらくその <version> 時点ではサポートされていないワードです)。
gforth-experimental
このワードは現在のバージョンで使用できますが、 永続的なワードになるか、 Gforth の将来のリリースで削除されるか分かりません。 あなたのフィードバックをお待ちしています。
gforth-internal
このワードは内部用であり、 サポート対象のワードではないため、 Gforth の将来のリリースでは削除される可能性があります。
gforth-obsolete
このワードは、 Gforth の将来のリリースでは削除される予定です。
そのワードの振る舞いを説明します。
スタック項目の型は、 以下のように、 スタック項目名のはじめの文字が何であるかによって指定されます:
f
¶二値フラグ(Boolean flags)。 つまり false
または true
c
¶Char
w
¶セル。 整数(an integer)やアドレス(an address)も格納可能。
n
¶符号付き整数
u
¶符号なし整数
d
¶符号付き2倍長整数
ud
¶符号無し2倍長整数
r
¶浮動小数点数(Float)(FP スタック上に置かれる)
a-
¶セル・アライメント・アドレス
c-
¶文字(char)アライメント・アドレス(注意: Windows NT では文字(char)は 2 バイトになる場合があることに注意)
f-
¶浮動小数点数アライメントのアドレス
df-
¶IEEE倍精度浮動小数点数アライメントのアドレス
sf-
¶IEEE単精度浮動小数点数アライメントのアドレス
xt
¶実行トークン(Execution token)。 セルと同一サイズ
wid
¶ワード・リストID。セルと同一サイズ
ior, wior
¶セルサイズの入出力結果コード。 Gforth では iors を throw
できます。
f83name
¶名前構造体へのポインター
"
¶(スタック上ではなく、)入力ストリーム内の文字列。 終端文字はデフォルトでは空白(a blank)です。 終端文字が空白でない場合は、
<>
でクォートして表示します。
Gforth では英大文字小文字を区別しません(case-insensitive)。 大文字、 小文字、 または大文字と小文字の混合を使用して、 定義を入力したり標準のワードを呼び出したりできます(ただし、 こちらも参照ください see Implementation-defined options)
標準 Forth では、 標準のワードが完全に大文字で入力された場合にのみ実装が認識する必要があります。 したがって、 標準のプログラムでは、 すべての標準ワードに大文字を使用する必要があります。 あなたの定義したワードには大文字と小文字を自由に使用できますが、 標準のプログラムでは、 ワードを定義したときと同じ大文字と小文字で使用する必要があります。
Gforth は、 cs-wordlist
(英大文字小文字を区別する(case-sensitive)ワード・リスト
see Word Lists) を通じて英大文字小文字の区別をサポートします。
何人かは Gforth を英大文字小文字を区別する(case-sensitive)ように変換する方法を質問しました。 これは悪い考え(bad idea)だとは思いますが、 すべてのワードリストを以下のようなテーブルに変更できます:
' table-find forth-wordlist wordlist-map !
注意: このように、 Gforth を英大文字小文字を区別する(case-sensitive)ように変換した場合、 定義済みのワードは、 定義したときと同一の大文字と小文字の組み合わせで入力する必要があることに注意してください。 大文字と小文字は異なります。 この操作を実行する前に、 それらをあなた好みの大文字と小文字の組み合わせに変換するとよいでしょう(その方法については説明しません。 Gforth を英大文字小文字を区別する(case-sensitive)ように変換することを実行することを検討している場合でも、 すでに定義済みワードのワード名をあなた好みの大文字と小文字の組み合わせに変換するその方法を知っているくらいの Forth システムの知識があったほうがよいと思います)。
Fors は 2 つのスタイルのコメントをサポートしています。 従来の行中コメントである (
と、 その現代的な親戚である、
行末までのコメント \
です。
(
( compilation ’ccc<close-paren>’ – ; run-time – ) core,file “paren”
通常は次の )
までがコメントです。 「)」 が見つかるまで、 パース領域内の後続の文字をすべてパースして破棄します。 対話入力中は、
行末はコメント終了文字としても機能します。 ただし、 ファイル入力の場合はそうではありませんので、
「)」区切り文字のパース中にファイルの終わりに遭遇すると、 Gforth は警告を出します。
\
( compilation ’ccc<newline>’ – ; run-time – ) core-ext,block-ext “backslash”
行末までがコメントです。 ブロックからのロード中を除き、 パース領域内の残りの文字をすべてパースして破棄します。 ブロックからのロード中は、 1行64バイトと見なすので、 その 64 バイト行の残りの文字をすべてパースして破棄します。
\G
( compilation ’ccc<newline>’ – ; run-time – ) gforth-0.2 “backslash-gee”
\
と同等ですが、 ドキュメントの定義コメントに注釈(annotate)を付けるためのタグとして使用されます。
ブール値フラグはセル・サイズです。 すべてのビットがクリアされているセルはフラグ false
を表し、
すべてのビットがセットされているセルはフラグ true
を表します。 フラグをチェックするワード (IF
など) は、
セルの任意のビットがセットされているものを true
として扱います。
true
( – f ) core-ext “true”
定数 – f は全てのビットがセットされたセルです。
false
( – f ) core-ext “false”
定数 – f は全てのビットがクリアされたセルです。
on
( a-addr – ) gforth-0.2 “on”
指定の a-addr の変数の値を true
にセットする。
off
( a-addr – ) gforth-0.2 “off”
指定の a-addr の変数の値を false
にセットする。
select
( u1 u2 f – u ) gforth-1.0 “select”
f が false なら u2 を u として返し、 そうでなければ u1 を u として返す。
Forth の算術演算はチェックを行いません。 つまり、 加算または乗算での整数のオーバーフローについては問い詰められませんが、
運が良ければゼロによる除算については問い詰められることができるかもしれません。 演算子はオペランドの後に記述されますが、
オペランドは元の順序のままです。 つまり、 中置記法での 2-1
は 2 1 -
に対応します。 Forth
はさまざまな除算演算子を提供します。 あなたが、 潜在的に負になりうるオペランドを使用して除算を実行する場合、 実装により振る舞いが異なる
/
や /mod
を使用せず、 たとえば、 /f
や /modf
や fm/mod
を使用します(see Integer division)。
デフォルトでは、 Forth の数値は 1 セルのサイズの1倍長整数です。 扱い方に応じて、 符号付きまたは符号無しにすることができます。 1倍長整数を認識するためにテキスト・インタープリターで使用されるルールについては、 Number Conversion を参照してください。
これらのワードは、 すべて符号付きオペランドに対して定義されていますが、 一部のワードは符号無しの数値に対しても機能します: +
,
1+
, -
, 1-
, *
+
( n1 n2 – n ) core “plus”
1+
( n1 – n2 ) core “one-plus”
under+
( n1 n2 n3 – n n2 ) gforth-0.3 “under-plus”
n3 を n1 に足し込む(n を得る)
-
( n1 n2 – n ) core “minus”
1-
( n1 – n2 ) core “one-minus”
*
( n1 n2 – n ) core “star”
negate
( n1 – n2 ) core “negate”
abs
( n – u ) core “abs”
min
( n1 n2 – n ) core “min”
max
( n1 n2 – n ) core “max”
umin
( u1 u2 – u ) gforth-0.5 “umin”
umax
( u1 u2 – u ) gforth-1.0 “umax”
テキスト・インタープリターが2倍長整を認識するために使用するルールについては、 Number Conversion を参照してください。
数値は 2 の補数演算を使用する Gforth で表されるため、符号付き単精度浮動小数点数を (符号付き)
倍精度浮動小数点に変換するには、最上位セル全体で符号拡張が必要です。 倍精度整数はセルのペアで表され、 上位側のセルが TOS にあります。
符号無しの1倍長整数を2倍長整数に変換するのは簡単で、 0
を TOS にプッシュするだけです。 整数値は Gforth では 2
の補数表現を使うため、 符号付き1倍長整数を(符号付き)2倍長整数に変換するには、 上位側セル全体で符号拡張が必要です。 これは s>d
を使用して実現できます。 このことから分かるとおり、 それが符号無し整数を表しているのか、 符号有り整数を表しているのか分からなければ、
数値を変換できないということです。
これらのワードはすべて符号付きオペランドに対して定義されていますが、 一部のワードは符号無しの数値に対しても機能します:
d+
、d-
s>d
( n – d ) core “s-to-d”
d>s
( d – n ) double “d-to-s”
d+
( ud1 ud2 – ud ) double “d-plus”
d-
( d1 d2 – d ) double “d-minus”
dnegate
( d1 – d2 ) double “d-negate”
dabs
( d – ud ) double “d-abs”
dmin
( d1 d2 – d ) double “d-min”
dmax
( d1 d2 – d ) double “d-max”
m+
( d1 n – d2 ) double “m-plus”
m*
( n1 n2 – d ) core “m-star”
um*
( u1 u2 – ud ) core “u-m-star”
以下にあるように、 除算を扱うために相当な数のワードがあることが分かります。 これらの主な違いは、 符号付き除算の処理にあります。
これらのワードはどのようにして符号付き除算に対応するのでしょうか? (U
プレフィックスが付いたワードでは符号付き除算に対応しません)
商を四捨五入して整数に丸める場合、 負の無限大に向かって丸めるのでしょうか(floored division(床除算)、 接尾辞 F
)、
それとも 0 に向かって丸めるのでしょうか(symmetric division、接尾辞 S
)。 標準では、
ほとんどの標準ワード(/ mod /mod */ */mod m*/
)について、 問題なのは、 各々の実装依存のままであり、
システムごとに異なる選択が行われています。 Gforth では、これらのワードをfloorとして実装します(Gforth 0.7 以降)。
floored division と symmetric division の違いは、 割られる数と割る数の符号が異なり、 かつ、
割られる数が割る数の倍数で無い場合のみです。 以下の表に組み合わせの結果を示します:
floored symmetric 割られる 割る数 余り 商 余り 商 10 7 3 1 3 1 -10 7 4 -2 -3 -1 10 -7 -4 -2 3 -1 -10 -7 -3 1 -3 1
floored と symmetric とが違いを生む一般的なケースは、 様々な符号の被除数(割られる数) n1 が、 同一の正の除数(割る数) n2 で除算される場合です。 同一の正の除数(割る数) n2 で除算される場合、 通常は floored division が必要で、なぜなら、 同一の正の除数(割る数) n2 で除算される場合、 余りは常に正となり、 被除数(割られる数)に応じて符号が変化しないからです。 また、 floored division では、 n1 が n2 増加すると商は常に 1 増加しますが、 symmetric division では、 -n2<n1<n2 の場合は商は増加しません(この範囲では商は 0 です)。
いずれの場合でも、 floored と symmetric とで違いが生ずる数値を除算する場合は、 どのバリエーションが適切であるかを考えてから、
適切な接尾辞を付けた Gforth ワードか、 標準ワードの fm/mod
または sm/rem
のいずれかを使用する必要があります。
1倍長セル同士の除算:
/
( n1 n2 – n ) core “slash”
n=n1/n2
/s
( n1 n2 – n ) gforth-1.0 “slash-s”
/f
( n1 n2 – n ) gforth-1.0 “slash-f”
u/
( u1 u2 – u ) gforth-1.0 “u-slash”
mod
( n1 n2 – n ) core “mod”
n は n1/n2 の余り(modulus)
mods
( n1 n2 – n ) gforth-1.0 “mod-s”
modf
( n1 n2 – n ) gforth-1.0 “modf”
umod
( u1 u2 – u ) gforth-1.0 “umod”
/mod
( n1 n2 – n3 n4 ) core “slash-mod”
n1/n2 を行い、 n3 が余り(modulus)、 n4 が商です(n1=n2*n4+n3)。
/mods
( n1 n2 – n3 n4 ) gforth-1.0 “slash-mod-s”
n3 が余り(remainder)、 n4 が商
/modf
( n1 n2 – n3 n4 ) gforth-1.0 “slash-mod-f”
n3 が余り(modulus)、 n4 が商
u/mod
( u1 u2 – u3 u4 ) gforth-1.0 “u-slash-mod”
u3 が余り(modulus)、 u4 が商
2倍長セルを1倍長セルで割って1倍長セルの結果を得る; これらのワードは、 一部のアーキテクチャー(AMD64 など)では上記ワードとほぼ同じ速度ですが、 他のアーキテクチャ(さまざまな Aarch64 CPU など)でははるかに遅くなります。
fm/mod
( d1 n1 – n2 n3 ) core “f-m-slash-mod”
Floored division: d1 = n3*n1+n2, n1>n2>=0 or 0>=n2>n1.
sm/rem
( d1 n1 – n2 n3 ) core “s-m-slash-rem”
Symmetric division: d1 = n3*n1+n2, sign(n2)=sign(d1) or 0.
um/mod
( ud u1 – u2 u3 ) core “u-m-slash-mod”
ud=u3*u1+u2, 0<=u2<u1
du/mod
( d u – n u1 ) gforth-1.0 “du-slash-mod”
d=n*u+u1, 0<=u1<u; PolyForth スタイルの混合除算
*/
( ( n1 n2 n3 – n4 ) core “star-slash”
n4=(n1*n2)/n3 中間結果は2倍長
*/s
( n1 n2 n3 – n4 ) gforth-1.0 “star-slash-s”
n4=(n1*n2)/n3 中間結果は2倍長
*/f
( n1 n2 n3 – n4 ) gforth-1.0 “star-slash-f”
n4=(n1*n2)/n3 中間結果は2倍長
u*/
( u1 u2 u3 – u4 ) gforth-1.0 “u-star-slash”
u4=(u1*u2)/u3 中間結果は2倍長
*/mod
( n1 n2 n3 – n4 n5 ) core “star-slash-mod”
n1*n2=n3*n5+n4 中間結果(n1*n2)は2倍長、 n4 は剰余、 n5 は商。
*/mods
( n1 n2 n3 – n4 n5 ) gforth-1.0 “star-slash-mod-s”
n1*n2=n3*n5+n4, 中間結果(n1*n2)は2倍長、 n4 は余り(remainder)、 n5 は商
*/modf
( n1 n2 n3 – n4 n5 ) gforth-1.0 “star-slash-mod-f”
n1*n2=n3*n5+n4 中間結果(n1*n2)は2倍長。 n4 余り(modulus)、n5 は商
u*/mod
( u1 u2 u3 – u4 u5 ) gforth-1.0 “u-star-slash-mod”
u1*u2=u3*u5+u4 中間結果(u1*u2)は2倍長。
除算結果を2倍長セル(double-cell)で得ます。 以下のワード群は上記のワード群よりもはるかに遅いです。
ud/mod
( ud1 u2 – urem udquot ) gforth-0.2 “ud/mod”
符号無し2倍長 ud1 を u2 で割る。 結果は、符号なし2倍長の商 udquot と、 1倍長の余り(remainder) urem です。
m*/
( d1 n2 u3 – dquot ) double “m-star-slash”
dquot=(d1*n2)/u3, 中間結果は3倍長です。 ANS Forth では u3 は正の符号付き数値のみです。
環境クエリ(environmental query) floored
を使用すると、 / mod /mod */ */mod
m*/
が floored division か symmetric division かを確認できます(see Environmental Queries)。
整数除算ワードのもう 1 つの側面は、 整数除算ワードのほとんどがオーバーフローする可能性があり、 かつ、
ゼロによる除算が数学的に定義されていないことです。 これらの条件のいずれかに該当した場合に何が起こるかは、
エンジンやハードウェアやオペレーティング・システムによって異なります。 gforth
エンジンは、適切なエラーである -10
(Division by zero;ゼロ除算) または -11 (Result out of range;範囲外の結果) を throw
しようと懸命に試みます、 が、 しかし、 一部のプラットフォームでは -55(浮動小数点未確認エラー;Floating-point
unidentified fault)が throw されます。 gforth-fast
エンジンでは、 不適切な throw
コード(およびエラー・メッセージ)を生成する場合や、 エラーを生成せずに嘘の値のみを生成する場合があります。 つまり、 あなたは、 そのような条件が
throw されることに賭けるべきではありません。 そして、 あなたが迅速なデバッグを行うためには、 gforth
エンジンは
gforth-fast
エンジンよりも多くのエラーをキャッチし、 より正確なエラーを生成します。
(Two-stage integer divison;2段階除算)ほとんどのハードウェアでは、 乗算は除算よりも大幅に高速です。 したがって、 多くの数値を同じ除数(割る数)で除算する必要がある場合は、 通常、 除数の逆数を一度求めて、 その逆数を数値に乗算する方が高速です。 整数の場合、 これは技巧的になってしまうため、 Gforth ではこの作業をこのセクションで説明するワード群にパッケージ化しています。
以下の例から始めるとしましょう。 あなたがセルの配列のすべての要素を同じ数値 n で除算したいとします。 これを素直に実装すると以下のようになります:
: array/ ( addr u n -- ) -rot cells bounds u+do i @ over / i ! 1 cells +loop drop ;
より効率的なバージョンは以下のようになります:
: array/ ( addr u n -- ) {: | reci[ staged/-size ] :} reci[ /f-stage1m cells bounds u+do i @ reci[ /f-stage2m i ! 1 cells +loop ;
この例では、 まず、 逆数データを格納するためのサイズ staged/-size
のローカル・バッファー reci[
を作成します。 次に、 /f-stage1m
は n の逆数を計算し、 reci[
に格納します。 最後に、
ループ内で /f-stage2m
が reci[
のデータを使用して除算の商を計算します。
これにはいくつかの制限があります。 /f-stage1m
では正の除数のみがサポートされます。 u/-stage1m
には
2 以上の数を使用できます。 サポートされていない除数を使用しようとすると、 エラーが発生します。 floored
第2ステージ・ワードの相互(reciprocal)バッファーは /f-stage1m
で初期化し、
符号な無しの第2ステージ・ワードの相互バッファーは u/-stage1m
で初期化する必要があります。
最初のステージと2番目のステージの間で相互バッファーを変更してはなりません。 基本的に、 これはメモリー・バッファーとして扱うのではなく、
最初のステージでのみ変更可能なものとして扱います。 このルールのポイントは、 Gforth
の将来のバージョンではこのバッファーのエイリアスが考慮されないということです。
これらのワードが以下です:
staged/-size
( – u ) gforth-1.0 “staged-slash-size”
u/-stage1m
または /f-stage1m
のバッファーのサイズ。
/f-stage1m
( n addr-reci – ) gforth-1.0 “slash-f-stage1m”
n の逆数を計算し、 サイズ staged/-size
のバッファー addr-reci に格納します。 n<1
の場合、 エラーを出します(throw)。
/f-stage2m
( n1 a-reci – nquotient ) gforth-1.0 “slash-f-stage2m”
Nquotient は、n1 を a-reci で表される除数で除算した結果であり、 /f-stage1m
によって計算されます。
modf-stage2m
( n1 a-reci – umodulus ) gforth-1.0 “mod-f-stage2m”
Umodulus は、 n1 を a-reci で表される除数で割った余り(remainder)で、
/f-stage1m
によって計算されます。
/modf-stage2m
( n1 a-reci – umodulus nquotient ) gforth-1.0 “slash-mod-f-stage2m”
Nquotient は商で、 umodulus は n1 を a-reci
で表される除数で割った余り(remainder)で、 /f-stage1m
によって計算されます。
u/-stage1m
( u addr-reci – ) gforth-1.0 “u-slash-stage1m”
u の逆数を計算し、 サイズ staged/-size
のバッファー addr-reci に格納します。 u<2
の場合、 エラーを出します(throw)。
u/-stage2m
( u1 a-reci – uquotient ) gforth-1.0 “u-slash-stage2m”
Uquotient は、 u1 を a-reci で表される除数で除算した結果であり、 u/-stage1m
によって計算されます。
umod-stage2m
( u1 a-reci – umodulus ) gforth-1.0 “u-mod-stage2m”
Umodulus は、 u1 を a-reci
で表される除数で割った余り(remainder)で、u/-stage1m
によって計算されます。
u/mod-stage2m
( u1 a-reci – umodulus uquotient ) gforth-1.0 “u-slash-mod-stage2m”
Uquotient は商で、 umodulus は a-reci で表される除数で u1
を割った余り(remainder)で、u/-stage1m
によって計算されます。
Gforth は現在、 段階的対称除算(staged symmetrical division)をサポートしていません。
staged/-divisor @
を使用すると、 逆数(のアドレス)から除数を復旧(recover)できます:
staged/-divisor
( addr1 – addr2 ) gforth-1.0 “staged-slash-divisor”
Addr1 は逆数のアドレス、 addr2 は逆数の計算元となった除数を含むアドレスです。
これは、Gforth の逆コンパイラー出力を確認するときに役立ちます。 定数による除算は、 多くの場合、 逆数のアドレスとその後に続く第2ステージ・ワードを含むリテラルにコンパイルされます。
これらのワードを使用した場合のパフォーマンスへの影響は、 アーキテクチャー(ハードウェア除算があるかどうか)と、
特定の実装(ハードウェア除算の速さはどれくらいか)に大きく依存しますが、 これらのワードの相対的なパフォーマンスについてのアイデアを提供するために、
以下を示します。 2 つの AMD64 実装でのマイクロ・ベンチマークの反復ごとのサイクルを以下に示します。 norm
列は通常の除算ワード(例: u/
)を示し、stg2 列は対応する stage2 ワード(例:
u/-stage2m
)を示します:
intel Skylake AMD Zen2 norm stg2 norm stg2 41.3 15.8 u/ 35.2 21.4 u/ 39.8 19.7 umod 36.9 25.8 umod 44.0 25.3 u/mod 43.0 33.9 u/mod 48.7 16.9 /f 36.2 22.5 /f 47.9 20.5 modf 37.9 27.1 modf 53.0 24.6 /modf 45.8 35.4 /modf 227.2 u/stage1 101.9 u/stage1 159.8 /fstage1 97.7 /fstage1
and
( w1 w2 – w ) core “and”
or
( w1 w2 – w ) core “or”
xor
( w1 w2 – w ) core “x-or”
invert
( w1 – w2 ) core “invert”
mux
( u1 u2 u3 – u ) gforth-1.0 “mux”
multiplex(多重化): u3 の各ビットについて、 そのビットが 1 の場合は u1 から対応するビットを選択し、
それ以外の場合は u2 から対応するビットを選択します。 たとえば、 %0011 %1100 %1010 mux
は
%0110
となります。
lshift
( u1 u – u2 ) core “l-shift”
u1 を u ビット左シフトします。
rshift
( u1 u – u2 ) core “r-shift”
u1 (セル) を u ビットだけ右にシフトし、 シフトインされたビットを 0 で埋めます(論理/符号無しシフトです)。
arshift
( n1 u – n2 ) gforth-1.0 “ar-shift”
n1 (セル) を u ビット右にシフトし、n1 の符号ビットからシフトインされたビットを埋めます (算術シフト)。
dlshift
( ud1 u – ud2 ) gforth-1.0 “dlshift”
ud1 (2倍長セル) を u ビット左にシフトします。
drshift
( ud1 u – ud2 ) gforth-1.0 “drshift”
ud1 (2倍長セル) を u ビットだけ右にシフトし、 シフトインされたビットを 0 で埋めます (論理/符号なしシフト)。
darshift
( d1 u – d2 ) gforth-1.0 “darshift”
d1 (2倍長セル) を u ビット右にシフトし、d1 の符号ビットからシフトインされたビットで埋めます (算術シフト)。
2*
( n1 – n2 ) core “two-star”
1 つ左にシフトします。符号なしの数値でも機能します
2/
( n1 – n2 ) core “two-slash”
1 つ右に算術シフトします。 符号付き数値の場合、 これは 2 による floored division になります(/
は必ずしも
floors ではないことに注意してください)。
d2*
( d1 – d2 ) double “d-two-star”
2倍長セルを左に 1 シフトします。 符号なしの数値でも機能します
d2/
( d1 – d2 ) double “d-two-slash”
1 つ右に算術シフトします。 符号付き数値の場合、これは 2 による floored division になります。
>pow2
( u1 – u2 ) gforth-1.0 “to-pow2”
u2 は、u2>=u1 の最小の 2 のべき乗数です。
log2
( u – n ) gforth-1.0 “log2”
N は u の切り捨て2進対数、 つまり最初に設定されたビットのインデックスです。 u=0 の場合は n=-1 です。
pow2?
( u – f ) gforth-1.0 “pow-two-query”
f は、 u が 2 の累乗の場合、 つまり u のビットが 1 つだけセットされている場合に true になります。
ctz
( x – u ) gforth-1.0 “c-t-z”
x の2進数表現で末尾からのゼロの数を数える
他のほとんどの操作とは異なり、 幅の狭いユニットのローテートと幅の広いユニットのローテートを簡単に合成することはできないため、 1倍長セル幅および 2倍幅のセル幅のローテート操作を使用すると、 結果がセル幅に依存することになります。 公開されたアルゴリズムまたはセル幅に依存しない結果の場合、 通常は固定幅のローテート操作を使用する必要があります。
wrol
( u1 u – u2 ) gforth-1.0 “wrol”
u1 の下位側 16 ビットを u ビットだけ左に回転し、 他のビットを 0 にセットします。
wror
( u1 u – u2 ) gforth-1.0 “wror”
u1 の下位側 16 ビットを u ビットだけ右回転し、 他のビットを 0 にセットします。
lrol
( u1 u – u2 ) gforth-1.0 “lrol”
u1 の下位側 32 ビットを u ビットだけ左に回転し、 他のビットを 0 にセットします。
lror
( u1 u – u2 ) gforth-1.0 “lror”
u1 の下位側 32 ビットを u ビットだけ右回転し、 他のビットを 0 にセットします。
rol
( u1 u – u2 ) gforth-1.0 “rol”
u1 のすべてのビットを u ビットだけ左に回転します。
ror
( u1 u – u2 ) gforth-1.0 “ror”
u1 のすべてのビットを u ビットだけ右回転します。
drol
( ud1 u – ud2 ) gforth-1.0 “drol”
ud1 (2倍長セル) のすべてのビットを u ビットだけ左に回転します。
dror
( ud1 u – ud2 ) gforth-1.0 “dror”
ud1 (2倍長セル) のすべてのビットを u ビット右に回転します。
注意: 等しいかどうかを比較するワード(= <> 0= 0<> d= d<> d0= d0<>
) は、
符号付き数値と符号なし数値の両方に対して機能することに注意してください。
<
( n1 n2 – f ) core “less-than”
<=
( n1 n2 – f ) gforth-0.2 “less-or-equal”
<>
( n1 n2 – f ) core-ext “not-equals”
=
( n1 n2 – f ) core “equals”
>
( n1 n2 – f ) core “greater-than”
>=
( n1 n2 – f ) gforth-0.2 “greater-or-equal”
0<
( n – f ) core “zero-less-than”
0<=
( n – f ) gforth-0.2 “zero-less-or-equal”
0<>
( n – f ) core-ext “zero-not-equals”
0=
( n – f ) core “zero-equals”
0>
( n – f ) core-ext “zero-greater-than”
0>=
( n – f ) gforth-0.2 “zero-greater-or-equal”
u<
( u1 u2 – f ) core “u-less-than”
u<=
( u1 u2 – f ) gforth-0.2 “u-less-or-equal”
u>
( u1 u2 – f ) core-ext “u-greater-than”
u>=
( u1 u2 – f ) gforth-0.2 “u-greater-or-equal”
within
( u1 u2 u3 – f ) core-ext “within”
u2<u3 かつ u1 が [u2,u3) にある(u2 < u3 and u2 <= u1 < u3 )、 または u2 >= u3 かつ u1 が
[u3,u2) にない( u2 >= u3 and (u1 not in (u3 <= and < u2)。 これは、
符号なしの数値と符号付きの数値に対して機能します(ただし、 混ぜてはいけません)。 このワードについて考えるもう 1 つの方法は、
数値を循環として考えることです(符号なしの数値の場合は max-u
から 0 まで、 符号付きの数値の場合は max-n
から min-n まで循環します)。ここで、 u2 から u3 までの増加する数値(u3 を除く)の範囲を検討します(u2=u3
の場合は空の範囲を与えます)。 u1 がこの範囲内にある場合、 within
は true を返します。
d<
( d1 d2 – f ) double “d-less-than”
d<=
( d1 d2 – f ) gforth-0.2 “d-less-or-equal”
d<>
( d1 d2 – f ) gforth-0.2 “d-not-equals”
d=
( d1 d2 – f ) double “d-equals”
d>
( d1 d2 – f ) gforth-0.2 “d-greater-than”
d>=
( d1 d2 – f ) gforth-0.2 “d-greater-or-equal”
d0<
( d – f ) double “d-zero-less-than”
d0<=
( d – f ) gforth-0.2 “d-zero-less-or-equal”
d0<>
( d – f ) gforth-0.2 “d-zero-not-equals”
d0=
( d – f ) double “d-zero-equals”
d0>
( d – f ) gforth-0.2 “d-zero-greater-than”
d0>=
( d – f ) gforth-0.2 “d-zero-greater-or-equal”
du<
( ud1 ud2 – f ) double-ext “d-u-less-than”
du<=
( ud1 ud2 – f ) gforth-0.2 “d-u-less-or-equal”
du>
( ud1 ud2 – f ) gforth-0.2 “d-u-greater-than”
du>=
( ud1 ud2 – f ) gforth-0.2 “d-u-greater-or-equal”
浮動小数点数を認識するためにテキスト・インタープリターで使用されるルールについては、 Number Conversion を参照してください。
Gforth には別個の浮動小数点スタックがありますが、 ドキュメントでは一緒に統一した表記が使用されています9。
浮動小数点数は、 不注意な人にとっては多くの不快な驚きをもたらします(たとえば、浮動小数点の加算は結合的(associative)ではありません)。 また、 用心深い人にとってもいくつかの不快な驚きさえあります。 自分が何をしているのか理解していない場合、 または得られる結果が完全に偽物であることを気にしない場合を除き、 これらを使用すべきではありません。 浮動小数点数の問題(およびその回避方法)について知りたい場合は、 David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys 23(1):5−48, March 1991 から始めると良いでしょう(訳注: https://docs.oracle.com/cd/E19957-01/806-4847/ncg_goldberg.html これが合ってるかどうか不明。一部文字化けあり2024/06現在)。
整数と浮動小数点の間の変換:
s>f
( n – r ) floating-ext “s-to-f”
d>f
( d – r ) floating “d-to-f”
f>s
( r – n ) floating-ext “f-to-s”
f>d
( r – d ) floating “f-to-d”
算術演算:
f+
( r1 r2 – r3 ) floating “f-plus”
f-
( r1 r2 – r3 ) floating “f-minus”
f*
( r1 r2 – r3 ) floating “f-star”
f/
( r1 r2 – r3 ) floating “f-slash”
fnegate
( r1 – r2 ) floating “f-negate”
fabs
( r1 – r2 ) floating-ext “f-abs”
fcopysign
( r1 r2 – r3 ) gforth-1.0 “fcopysign”
r3 は r1 から絶対値を取得し r2 から符号を取得します
fmax
( r1 r2 – r3 ) floating “f-max”
fmin
( r1 r2 – r3 ) floating “f-min”
floor
( r1 – r2 ) floating “floor”
次に小さい整数値に向かって丸めます。 つまり、 負の無限大に向かって丸めます。
fround
( r1 – r2 ) floating “f-round”
最も近い整数値に丸めます(訳注: 0.5e – 0, 0.50e – 0, 0.51e – 1, 1.5e – 2, ... ??)
ftrunc
( r1 – r2 ) floating-ext “f-trunc”
0 に向かって丸める(正数でも負数でも)
f**
( r1 r2 – r3 ) floating-ext “f-star-star”
r3 は r1 の r2 乗です
fsqrt
( r1 – r2 ) floating-ext “f-square-root”
fexp
( r1 – r2 ) floating-ext “f-e-x-p”
fexpm1
( r1 – r2 ) floating-ext “f-e-x-p-m-one”
r2=e**r1−1
fln
( r1 – r2 ) floating-ext “f-l-n”
flnp1
( r1 – r2 ) floating-ext “f-l-n-p-one”
r2=ln(r1+1)
flog
( r1 – r2 ) floating-ext “f-log”
常用対数(decimal logarithm)
falog
( r1 – r2 ) floating-ext “f-a-log”
r2=10**r1
f2*
( r1 – r2 ) gforth-0.2 “f2*”
r1 に 2.0e0 を掛けた値
f2/
( r1 – r2 ) gforth-0.2 “f2/”
r1 に 0.5e0 を掛けた値
1/f
( r1 – r2 ) gforth-0.2 “1/f”
1.0e0 を r1 で割った値
ベクトル演算:
v*
( f-addr1 nstride1 f-addr2 nstride2 ucount – r ) gforth-0.5 “v-star”
ドット積(dot-product): r=v1*v2 v1 の最初の要素は f_addr1 にあり、 次の要素は f_addr1+nstride1 というようになります(v2 も同様)。 どちらのベクトルにも ucount の数の要素があります。
faxpy
( ra f-x nstridex f-y nstridey ucount – ) gforth-0.5 “faxpy”
vy=ra*vx+vy
浮動小数点演算の角度はラジアン(radians)で指定します(完全な円は 2πラジアンです)。
fsin
( r1 – r2 ) floating-ext “f-sine”
fcos
( r1 – r2 ) floating-ext “f-cos”
fsincos
( r1 – r2 r3 ) floating-ext “f-sine-cos”
r2=sin(r1), r3=cos(r1)
ftan
( r1 – r2 ) floating-ext “f-tan”
fasin
( r1 – r2 ) floating-ext “f-a-sine”
facos
( r1 – r2 ) floating-ext “f-a-cos”
fatan
( r1 – r2 ) floating-ext “f-a-tan”
fatan2
( r1 r2 – r3 ) floating-ext “f-a-tan-two”
r1/r2=tan(r3) ANS Forth ではそこまで求められてはいないのですが、 おそらくこれが fsincos
の逆になることを意図していて、 gforth ではそのようになっています。
fsinh
( r1 – r2 ) floating-ext “f-cinch”
fcosh
( r1 – r2 ) floating-ext “f-cosh”
ftanh
( r1 – r2 ) floating-ext “f-tan-h”
fasinh
( r1 – r2 ) floating-ext “f-a-cinch”
facosh
( r1 – r2 ) floating-ext “f-a-cosh”
fatanh
( r1 – r2 ) floating-ext “f-a-tan-h”
pi
( – r ) gforth-0.2 “pi”
Fconstant
(定数) – r は値 pi です(π)。 円の面積と直径の比率。
浮動小数点演算に関する特別な問題の 1 つは、 等価性の比較が、 成功するはずなのに失敗することがよくあることです。 このため、 多くの場合、 近似的等価性が好まれます(ただし、 自分が何をしているのかを理解しておく必要があります)。 また、 IEEE NaN の比較があなたの予想とは異なる場合があることにも注意してください。 比較ワードは以下のとおりです:
f~rel
( r1 r2 r3 – flag ) gforth-0.5 “f~rel”
相対誤差を含む近似等価性: |r1-r2|<r3*|r1+r2|
f~abs
( r1 r2 r3 – flag ) gforth-0.5 “f~abs”
Approximate equality with absolute error: |r1-r2|<r3.< 絶対誤差を伴う近似等価性: |r1-r2|<r3
f~
( r1 r2 r3 – flag ) floating-ext “f-proximate”
r1 と r2 が等しいかどうかを比較するための ANS Forth ワードごたまぜ: r3>0 なら f~abs
; r3=0 なら
ビット単位の比較; r3<0 なら fnegate f~rel
f=
( r1 r2 – f ) gforth-0.2 “f-equals”
f<>
( r1 r2 – f ) gforth-0.2 “f-not-equals”
f<
( r1 r2 – f ) floating “f-less-than”
f<=
( r1 r2 – f ) gforth-0.2 “f-less-or-equal”
f>
( r1 r2 – f ) gforth-0.2 “f-greater-than”
f>=
( r1 r2 – f ) gforth-0.2 “f-greater-or-equal”
f0<
( r – f ) floating “f-zero-less-than”
f0<=
( r – f ) gforth-0.2 “f-zero-less-or-equal”
f0<>
( r – f ) gforth-0.2 “f-zero-not-equals”
f0=
( r – f ) floating “f-zero-equals”
f0>
( r – f ) gforth-0.2 “f-zero-greater-than”
f0>=
( r – f ) gforth-0.2 “f-zero-greater-or-equal”
IEEE754 の特別な値は、 たとえばゼロで除算することによって導出できます。 最も一般的なものは、使いやすいように浮動小数点定数として定義されています。
infinity
( – r ) gforth-1.0 “infinity”
浮動小数点数 (正の)無限大(floating point infinity)
-infinity
( – r ) gforth-1.0 “-infinity”
浮動小数点数 負の無限大(-infinity)
NaN
( – r ) gforth-1.0 “NaN”
浮動小数点数 NaN(Not a Number)
Gforth は、 いくつかの個別のスタックを維持します:
drop
( w – ) core “drop”
nip
( w1 w2 – w2 ) core-ext “nip”
dup
( w – w w ) core “dupe”
over
( w1 w2 – w1 w2 w1 ) core “over”
third
( w1 w2 w3 – w1 w2 w3 w1 ) gforth-1.0 “third”
fourth
( w1 w2 w3 w4 – w1 w2 w3 w4 w1 ) gforth-1.0 “fourth”
tuck
( w1 w2 – w2 w1 w2 ) core-ext “tuck”
swap
( w1 w2 – w2 w1 ) core “swap”
pick
( S:... u – S:... w ) core-ext “pick”
実際のスタック効果は x0 ... xu u -- x0 ... xu x0
です。
rot
( w1 w2 w3 – w2 w3 w1 ) core “rote”
-rot
( w1 w2 w3 – w3 w1 w2 ) gforth-0.2 “not-rote”
?dup
( w – S:... w ) core “question-dupe”
実際のスタック効果は次のとおりです: ( w -- 0 | w w )
つまり w がゼロ以外の場合、 dup
が実行されます。
roll
( x0 x1 .. xn n – x1 .. xn x0 ) core-ext “roll”
2drop
( w1 w2 – ) core “two-drop”
2nip
( w1 w2 w3 w4 – w3 w4 ) gforth-0.2 “two-nip”
2dup
( w1 w2 – w1 w2 w1 w2 ) core “two-dupe”
2over
( w1 w2 w3 w4 – w1 w2 w3 w4 w1 w2 ) core “two-over”
2tuck
( w1 w2 w3 w4 – w3 w4 w1 w2 w3 w4 ) gforth-0.2 “two-tuck”
2swap
( w1 w2 w3 w4 – w3 w4 w1 w2 ) core “two-swap”
2rot
( w1 w2 w3 w4 w5 w6 – w3 w4 w5 w6 w1 w2 ) double-ext “two-rote”
fdrop
( r – ) floating “f-drop”
fnip
( r1 r2 – r2 ) gforth-0.2 “f-nip”
fdup
( r – r r ) floating “f-dupe”
fover
( r1 r2 – r1 r2 r1 ) floating “f-over”
fthird
( r1 r2 r3 – r1 r2 r3 r1 ) gforth-1.0 “fthird”
ffourth
( r1 r2 r3 r4 – r1 r2 r3 r4 r1 ) gforth-1.0 “ffourth”
ftuck
( r1 r2 – r2 r1 r2 ) gforth-0.2 “f-tuck”
fswap
( r1 r2 – r2 r1 ) floating “f-swap”
fpick
( f:... u – f:... r ) gforth-0.4 “fpick”
実際のスタック効果は r0 ... ru u -- r0 ... ru r0
frot
( r1 r2 r3 – r2 r3 r1 ) floating “f-rote”
f-rot
( r1 r2 r3 – r3 r1 r2 ) floating “f-not-rote”
Forth システムは、 リターン・スタックにローカル変数を保持することができます。 通常、 ローカル変数を使用すると、 リターン・スタックを明示的に使用する必要がなくなるため、 これは合理的です。 したがって、 標準に準拠したプログラムを作成する場合で、 ワード内でローカル変数を使用している場合は、 そのワード内でのリターン・スタック操作のことは忘れてください(正確なルールについては標準ドキュメントを参照してください)。
>r
( w – R:w ) core “to-r”
r>
( R:w – w ) core “r-from”
r@
( – w ; R: w – w ) core “r-fetch”
rdrop
( R:w – ) gforth-0.2 “rdrop”
2>r
( w1 w2 – R:w1 R:w2 ) core-ext “two-to-r”
2r>
( R:w1 R:w2 – w1 w2 ) core-ext “two-r-from”
2r@
( R:w1 R:w2 – R:w1 R:w2 w1 w2 ) core-ext “two-r-fetch”
2rdrop
( R:w1 R:w2 – ) gforth-0.2 “two-r-drop”
n>r
( x1 .. xn n – r:xn..x1 r:n ) tools-ext “n-to-r”
nr>
( r:xn..x1 r:n – x1 .. xn n ) tools-ext “n-r-from”
sp0
( – a-addr ) gforth-0.4 “sp0”
ユーザー変数 – データ・スタック・ポインターの初期値。
sp@
( S:... – a-addr ) gforth-0.2 “sp-fetch”
sp!
( a-addr – S:... ) gforth-0.2 “sp-store”
fp0
( – a-addr ) gforth-0.4 “fp0”
ユーザー変数 – 浮動小数点スタック・ポインターの初期値。
fp@
( f:... – f-addr ) gforth-0.2 “fp-fetch”
fp!
( f-addr – f:... ) gforth-0.2 “fp-store”
rp0
( – a-addr ) gforth-0.4 “rp0”
ユーザー変数 – リターン・スタック・ポインターの初期値。
rp@
( – a-addr ) gforth-0.2 “rp-fetch”
rp!
( a-addr – ) gforth-0.2 “rp-store”
lp0
( – a-addr ) gforth-0.4 “lp0”
ユーザー変数 – ローカル・スタック・ポインターの初期値。
lp@
( – c-addr ) gforth-0.2 “lp-fetch”
C_addr は、 ローカル・スタック・ポインターの現在の値です。
lp!
( c-addr – ) gforth-internal “lp-store”
標準 Forth のメモリー割り当てワードに加えて ガベージ・コレクター(garbage collector) もあります。
標準 Forth は、 Forth システムが複数のアドレス空間で構成されているとみなします。 そのうちの「データ空間」(data space)のみを管理し、 メモリー・ワードでアクセスできます。 メモリーには、 スタックと、 コード(コード空間(code space))と呼ばれる)と、 ヘッダー(名前空間(name space)と呼ばれる)とが含まれ、 それらは必ずしもデータ空間にある必要はありません。 Gforth ではすべてがデータ空間内にありますが、 プリミティブのコードは通常読み取り専用です。
データ空間は、 いくつかの領域に分割されます。 ディクショナリー(dictionary)10と、 ヒープと、 システムによって割り当てられた多数のバッファーから成ります。
Gforth は 1 つの大きなアドレス空間を提供し、 その任意のアドレス間でアドレス演算を実行できます。 ただし、 ディクショナリーではヘッダーまたはコードがデータと代わる代わる出てくるため(interleaved)、 連続するデータ空間領域は、 標準 Forth で連続していると記述されているものだけがほとんどです。 しかし、 連続する領域間であっても、 増加するアドレス方向にディクショナリーが確実に割り当てられます。 ヒープ内でのメモリー割り当ての順序はプラットフォームに依存します(また、実行ごとに異なる可能性もあります)。
ディクショナリーの割り当てはスタック指向の割り当てスキーム(stack-oriented allocation scheme)です。 つまり、 X の割り当てを解除したい場合は、 X の後に割り当てられたすべての割り当ても解除します。
以下のワード達を使用した割り当ては連続しており、 アドレスの増加方向に向けて領域が拡張されます。
あらゆる種類のディクショナリー・メモリーを割り当てる他のワード(つまり、 :noname
を含む定義ワード)は、
連続領域(contiguous region)を終了し、 新しい領域を開始します。
標準 Forth では、 create
されたワードのみが、 後続の連続領域の開始となるアドレスを生成することが保証されています。
特に、 variable
によって割り当てられたセルが、 後続の allot
で割り当てられたメモリーと連続していることは保証されません。
allot
に負の引数を指定して使用すると、 メモリーの割り当てを解除できます(いくつかの制限があります。 allot
を参照してください)。 大規模な割り当て解除の場合は、 marker
を使用します。
here
( – addr ) core “here”
データ空間内の次の空き位置のアドレスを返します。
unused
( – u ) core-ext “unused”
here
でアドレス指定された領域(以降)に残っている空き領域の量をアドレス単位(address units)で返します。
allot
( n – ) core “allot”
初期化せずに、 データ空間に n アドレス単位を予約します。 n は符号付きの数値で、 負の n を渡すとメモリーが解放されます。 ANS Forth では、この方法で現在の連続領域からメモリーの割り当てを解除することしかできません。 Gforth では、この方法で名前付きワード以外のあらゆるものを割り当て解除できます。 システムはこの制限をチェックしません。
->here
( addr – ) gforth-1.0 “to-here”
here
の値を addr に変更します。
c,
( c – ) core “c-comma”
文字(char)用に1つのデータ空間を予約し、 その空間に c を格納します。
f,
( f – ) gforth-0.2 “f,”
浮動小数点数(floating-point number)用の1つのデータ空間を予約し、 その空間に f を格納します。
,
( w – ) core “comma”
セル(cell)用の1つのデータ空間を予約し、 その空間に w を格納します。
2,
( w1 w2 – ) gforth-0.2 “2,”
2つのセル用のデータ空間を予約し、 そこに w1 w2 格納します。 最初(低位アドレス側)に w2 を格納します。
w,
( w – ) gforth-1.0 “w-comma”
l,
( l – ) gforth-1.0 “l-comma”
x,
( x – ) gforth-1.0 “x-comma”
xd,
( xd – ) gforth-1.0 “x-d-comma”
A,
( addr – ) gforth-0.2 “A,”
1 つのセル用のデータ空間を予約し、 そこに addr を格納します。 私達のクロス・コンパイラーの場合には、
再配置可能なイメージに必要な型情報(type information)を提供します。 ただし、 通常では、 これは ,
と同等です。
mem,
( addr u – ) gforth-0.6 “mem,”
save-mem-dict
( addr1 u – addr2 u ) \
訳注:文字列をhereからのディクショナリーに書き込み、その(ディクショナリー上の)文字列を返します
メモリー・アクセスはアライメントする必要があります(see Address arithmetic)。 したがって、
メモリー割り当てもアライメントされるべきです。 つまり、 セルを割り当てる前に、 here
をセル・アライメントする必要があります。
以下のワード達は、 here
が既に指定の型のアライメントに合っている状態で無い場合は合わせます。 基本的に、
既に割り当てたのがその型のサイズの倍数であり、 かつ、 here
が以前にその型のアライメントに対して合うようにしてあった場合にのみ、
その型のアライメントに対して既にすでに合っているということが言えます。
新しくワードを create
した後、here
は 標準 Forth では align
されます(Gforth
では maxalign
されます)。
align
( – ) core “align”
データ空間ポインターがアライメントできてない場合は、 アライメントするのに十分な空間を予約します。
falign
( – ) floating “f-align”
データ空間ポインターが浮動小数点数にアライメントされていない場合は、 アライメントするのに十分な空間を予約します。
sfalign
( – ) floating-ext “s-f-align”
データ空間ポインターが単精度浮動小数点数にアライメントされていない場合は、 アライメントするのに十分な空間を予約します。
dfalign
( – ) floating-ext “d-f-align”
データ空間ポインターが倍精度浮動小数点数にアライメントされていない場合は、 アライメントするのに十分な空間を予約します。
maxalign
( – ) gforth-0.2 “maxalign”
すべてのアライメント要件に合わせてデータ空間ポインターをアライメントします。
cfalign
( – ) gforth-0.2 “cfalign”
データ空間ポインターをコード・フィールドの要件に合わせてアライメントします(つまり、 対応する本体が maxalign された状態になるようにする)。
ヒープ割り当ては、 割り当てられたメモリーの割り当て解除を任意の順序でサポートします。 ディクショナリーの割り当ては影響を受けません(つまり、 連続領域(contiguous region)は終了しません)。 Gforth では、 これらのワードは標準の C 言語ライブラリー呼び出しである malloc() や free() や realloc() を使用して実装されます。
allocate
または resize
の 1 回の呼び出しによって生成されるメモリ領域は、 内部的に連続しています。
このような領域と他の領域(ヒープから割り当てられた他の領域を含む)との間には連続性はありません。
allocate
( u – a-addr wior ) memory “allocate”
連続したデータ空間を u アドレス単位分割り当てます。 データ空間の初期内容は未定義です。 割り当てが成功した場合、 a-addr は割り当てられた領域の開始アドレスで、 wior は 0 になります。 割り当てが失敗した場合、 a-addr は未定義で、 wior ゼロ以外の I/O 結果コードです。
free
( a-addr – wior ) memory “free”
a-addr で始まるデータ空間の領域をシステムに返します。 領域は元々 allocate
または resize
を使用して取得されている必要があります。 操作が成功した場合、 wior は 0 になります。 操作が失敗した場合、 wior
はゼロ以外の I/O 結果コードになります。
resize
( a-addr1 u – a-addr2 wior ) memory “resize”
a-addr1 に割り当てられた領域のサイズを u アドレス単位に変更します。 但し内容を別の領域に移動する可能性があります。
a-addr2 は、 結果の領域のアドレスです。 操作が成功した場合、 wior は 0 になります。 操作が失敗した場合、
wior はゼロ以外の I/O 結果コードになります。 a-addr1 が 0 の場合、 Gforth の (非標準の )
resize
は u アドレス単位の割り当てを行います。
以下のワード達はメモリー・ブロックを扱うのに役立ちます:
save-mem
( addr1 u – addr2 u ) gforth-0.2 “save-mem”
指定のメモリー・ブロックをヒープ内で新しく割り当てられた領域にコピーします。
free-mem-var
( addr – ) gforth-experimental “free-mem-var”
addr は、 メモリー範囲のアドレスとサイズを含む 2variable のアドレスです。 これはメモリーを解放し、 2variable をクリアします。
extend-mem
( addr1 u1 u – addr addr2 u2 ) gforth-experimental “extend-mem”
u (アドレス単位)によってヒープから割り当てられたメモリー・ブロック addr1 u1 を拡張します。 (おそらく再割り当てされた)開始アドレスは addr2 で、 その合計長さは u2 で、 拡張部分の開始アドレスは addr です(訳注: 例えば元々長さ10のブロックを5拡張すると( addr1 10 5 – addr2+10 addr2 10+5))
$tring ワード群は、 メモリー・ブロックの処理にも使用できます。 See $tring words (訳注: $tring (ダラー tring)と String (エス string) とあることに注意)
拡張可能なメモリー・バッファーの場合は、 $trings または以下のワード群を使用できます。 adjust-buffer
で管理されるバッファーに割り当てられたメモリーは縮小できないため、
これまでに確認された最大サイズよりも小さいサイズにバッファーを調整(adjust)するときはヒープ管理のオーバーヘッドは発生しません。
buffer%
( – u1 u2 ) gforth-experimental “buffer%”
u1 は アライメント(alignment)、 u2 は バッファー・デスクリプタのサイズです。
init-buffer
( addr – ) gforth-experimental “init-buffer”
adjust-buffer
( u addr – ) gforth-experimental “adjust-buffer”
addr の buffer% を長さ u に調整します。 これにより、 割り当てられた領域が拡大する可能性がありますが、 決して縮小されることはありません。
2@
を使用すると、 このようなバッファーの現在のアドレスと長さを取得できます。
典型的な使い方:
create mybuf buffer% %size allot mybuf init-buffer s" frobnicate" mybuf adjust-buffer mybuf 2@ move mybuf 2@ type s" foo" mybuf adjust-buffer mybuf 2@ move mybuf 2@ type
@
( a-addr – w ) core “fetch”
a-addr に保存されているセルを w に取得します。
!
( w a-addr – ) core “store”
w を a-addr のセルに格納します。
+!
( n a-addr – ) core “plus-store”
a-addr のセルに n を加算します。
c@
( c-addr – c ) core “c-fetch”
c_addr に保存されている文字(char) を c に取得します。
c!
( c c-addr – ) core “c-store”
c を c-addr の char に格納します。
2@
( a-addr – w1 w2 ) core “two-fetch”
w2 は a-addr に格納されているセルの内容、 w1 はその次のセルの内容です。
2!
( w1 w2 a-addr – ) core “two-store”
w2 を c-addr のセルに格納し、 w1 をその次のセルに格納します。
f@
( f-addr – r ) floating “f-fetch”
アドレス f-addr の浮動小数点数を r に取得します。
f!
( r f-addr – ) floating “f-store”
r をアドレス f-addr の浮動小数点数として格納します。
sf@
( sf-addr – r ) floating-ext “s-f-fetch”
アドレス sf-addr から 単精度 IEEE 浮動小数点値を r に取得します。
sf!
( r sf-addr – ) floating-ext “s-f-store”
r を単精度 IEEE 浮動小数点値としてアドレス sf-addr に格納します。
df@
( df-addr – r ) floating-ext “d-f-fetch”
アドレス df-addr からの倍精度 IEEE 浮動小数点値を r に取得します。
df!
( r df-addr – ) floating-ext “d-f-store”
r を 倍精度 IEEE 浮動小数点値としてアドレス df-addr からに保存します。
このセクションでは、 他のソフトウェアや他のコンピュータと通信する際に役立つメモリ・アクセスについて説明します。 これは、 アクセスが特定のビット幅であり(Gforth のセル幅とは独立)、 自然な並びでない可能性があり、 通常、 Gforth が実行されるシステムのネイティブなバイト順序(バイト・オーダー)とは異なる可能性がある特定のバイト順序を持つことを意味します。
私達は以下のプレフィックスを使います:
c
8ビット(文字;character)
w
16ビット
l
32ビット
x
64 ビットを 1 つのセルとして表現
xd
64 ビットを 2 つのセルとして表現
x
プレフィックスのワードは 32 ビット・システムでは正しく機能しないため、 32
ビット・システムに移植することを目的としたコードの場合は、 xd
プレフィックスのワードを使用する必要があります。 注意:
xd
プレフィックスのワードは 64 ビット・システムでも動作することに注意してください。 64 ビット・システムでは、
上位のセルは単なる 0 (符号なし値の場合)、 または下位のセルの符号拡張です。
以下のメモリー・アクセス・ワード群はすべて、 任意の(非)アライメントされたアドレスで動作します(一部のハードウェアでアライメントが必要な
@
や !
や f@
や f!
とは異なります)。
w@
( c-addr – u ) gforth-0.5 “w-fetch”
u は c_addr に格納されているゼロ拡張された 16 ビット値(zero-extended 16-bit value)です。
w!
( w c-addr – ) gforth-0.7 “w-store”
w の下位 16 ビットを c_addr に格納します。
l@
( c-addr – u ) gforth-0.7 “l-fetch”
u は、 c_addr に格納されているゼロ拡張された 32 ビット値(zero-extended 32-bit value)です。
l!
( w c-addr – ) gforth-0.7 “l-store”
w の下位 32 ビットを c_addr に格納します。
x@
( c-addr – u ) gforth-1.0 “x-fetch”
u は、 c_addr に格納されているゼロ拡張された 64 ビット値です。
x!
( w c-addr – ) gforth-1.0 “x-store”
w の下位 64 ビットを c_addr に格納します。
xd@
( c-addr – ud ) gforth-1.0 “x-d-fetch”
ud は、 c_addr に格納されているゼロ拡張された 64 ビット値です。
xd!
( ud c-addr – ) gforth-1.0 “x-d-store”
ud の下位 64 ビットを c_addr に格納します。
特定のバイト順序(byte order)でアクセスする場合は、 取得直後(符号拡張の前)、 または格納の直前にバイト順序調整を行う必要があります。 これらのバイト順調整ワードの結果は常にゼロ拡張(zero-extended)されます。
wbe
( u1 – u2 ) gforth-1.0 “wbe”
u1 の 16 ビット値をネイティブ・バイト順からビッグ・エンディアンに、 またはビッグ・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
wle
( u1 – u2 ) gforth-1.0 “wle”
u1 の 16 ビット値をネイティブ・バイト順からリトル・エンディアンに、 またはリトル・エンディアンからネイティブ・バイト順に変換します(両方の操作は同一の操作です)
lbe
( u1 – u2 ) gforth-1.0 “lbe”
u1 の 32 ビット値をネイティブ・バイト順からビッグ・エンディアンに、 またはビッグ・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
lle
( u1 – u2 ) gforth-1.0 “lle”
u1 の 32 ビット値をネイティブ・バイト順からリトル・エンディアンに、 またはリトル・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
xbe
( u1 – u2 ) gforth-1.0 “xbe”
u1 の 64 ビット値をネイティブ・バイト順からビッグ・エンディアンに、 またはビッグ・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
xle
( u1 – u2 ) gforth-1.0 “xle”
u1 の 64 ビット値をネイティブ・バイト順からリトル・エンディアンに、 またはリトル・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
xdbe
( ud1 – ud2 ) gforth-1.0 “xdbe”
ud1 の 64 ビット値をネイティブ・バイト順からビッグ・エンディアンに、 またはビッグ・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
xdle
( ud1 – ud2 ) gforth-1.0 “xdle”
ud1 の 64 ビット値をネイティブ・バイト順からリトル・エンディアンに、 またはリトル・エンディアンからネイティブ・バイト順に変換します(両方は同一の操作です)
特定のバイト順序での符号付きでの取得の場合、 符号なし取得とバイト順序修正の後に符号拡張ワードを実行する必要があります:
c>s
( x – n ) gforth-1.0 “c-to-s”
x の 8 ビット値をセル n に符号拡張します。
w>s
( x – n ) gforth-1.0 “w-to-s”
x の 16 ビット値をセル n に符号拡張します。
l>s
( x – n ) gforth-1.0 “l-to-s”
x の 32 ビット値をセル n に符号拡張します。
x>s
( x – n ) gforth-1.0 “x>s”
x の 64 ビット値をセル n に符号拡張します。
xd>s
( xd – d ) gforth-1.0 “xd>s”
xd の 64 ビット値を 2倍長セル d に符号拡張します。
これら全般を、 以下のような流れで使います:
w@ wbe w>s \ 16ビット 非アライメント 符号付き ビッグ・エンディアン を取得し、 >r lle r> l! \ 32-bit 非アライメント リトル・エンディアン として 格納
アドレス演算は、 配列やレコード(see Structures)やオブジェクト(see Object-oriented Forth)のようなデータ構造を構築可能な基礎基盤です。
標準 Forth では、 データ型のサイズは規定されていません。 代わりに、 サイズを計算したりアドレス演算を行うための多数のワードが提供されます。
アドレス演算はアドレス単位(au;Address Unit, aus;Address UnitS)で実行されます。 ほとんどのシステムでは、
1アドレス単位は 1 バイトです。 注意: 1 つの文字に複数 au が含まれる可能性があるため、 chars
は何もしない(noop)訳ではないことに注意してください(noop であるプラットフォームでは、 chars
は何もコンパイルしません)。
基本的なアドレス算術ワードは +
と -
です。 たとえば、 セルのアドレスがわかっている場合、 1 cells
+
を実行すると、 次のセルのアドレスがわかります。
標準 Forth では、 特定の型のアドレスをアライメントするためのワードも定義されています。 多くのコンピュータでは、 特定のデータ型へのアクセスは特定のアドレスでのみ行われる必要があります。 たとえば、 セルは 4 で割り切れるアドレスでのみアクセスできます。 マシンが非アライメント・アクセスを許可する場合でも、 通常はアライメント・アクセスの方が高速に実行できます。
パフォーマンス重視で行く場合: 通常、 アライメント操作はデータ構造の定義中にのみ必要であり、 (より頻繁な)データ構造へのアクセス中には必要ありません。
標準 Forth では、 文字のアドレス・アライメント用ワードは定義されていません。 Forth-2012 では、すべてのアドレスが文字単位でアライメントされています(all addresses are character-aligned)
標準 Forth は、 CREATE
されたワードによって返されるアドレスがセル・アライメントされていることを保証します。
さらに、Gforth は、 これらのアドレスがあらゆる目的に合わせてアライメント済みであることを保証します(addresses are
aligned for all purposes)。
注意: 標準の Forth ワード char
はアドレス演算とは何の関係もないことに注意してください。
chars
( n1 – n2 ) core “chars”
n1 文字(char)が何アドレス単位になるかを n2 に返します。
char+
( c-addr1 – c-addr2 ) core “char-plus”
1 chars +
.
char-
( c-addr1 – c-addr2 ) gforth-0.7 “char-minus”
cells
( n1 – n2 ) core “cells”
n1 個のセルのアドレス単位の数を n2 に取得します。
cell+
( a-addr1 – a-addr2 ) core “cell-plus”
1 cells +
cell-
( a-addr1 – a-addr2 ) core “cell-minus”
1 cells -
cell/
( n1 – n2 ) gforth-1.0 “cell-divide”
n1 個のアドレス単位の幅の中に入れられるセルの数が n2 です
cell
( – u ) gforth-0.2 “cell”
定数 – 1 cells
aligned
( c-addr – a-addr ) core “aligned”
a-addr は、 c-addr 以上の最初にアライメントされたアドレスです。
floats
( n1 – n2 ) floating “floats”
n2 は n1 個の浮動小数点数(float)のアドレスユニットの数です。
float+
( f-addr1 – f-addr2 ) floating “float-plus”
1 floats +
.
float
( – u ) gforth-0.3 “float”
定数 – 浮動小数点数に対応するアドレスユニットの数。
float/
( n1 – n2 ) gforth-1.0 “float-divide”
faligned
( c-addr – f-addr ) floating “f-aligned”
f-addr は、c-addr 以上の、 浮動小数点数にアライメントされた最初のアドレスです。
sfloats
( n1 – n2 ) floating-ext “s-floats”
n2 は、 n1 個の単精度 IEEE 浮動小数点数のアドレス単位の数です。
sfloat+
( sf-addr1 – sf-addr2 ) floating-ext “s-float-plus”
1 sfloats +
.
sfloat/
( n1 – n2 ) gforth-1.0 “dfloat-divide”
sfaligned
( c-addr – sf-addr ) floating-ext “s-f-aligned”
sf-addr は、 c-addr 以上の、 最初の単精度浮動小数点数アライメント・アドレスです。
dfloats
( n1 – n2 ) floating-ext “d-floats”
n2 は、 n1 個の倍精度 IEEE 浮動小数点数のアドレス単位の数です。
dfloat+
( df-addr1 – df-addr2 ) floating-ext “d-float-plus”
1 dfloats +
.
dfloat/
( n1 – n2 ) gforth-1.0 “sfloat-divide”
dfaligned
( c-addr – df-addr ) floating-ext “d-f-aligned”
df-addr は、 c-addr 以上の、 最初の倍精度浮動小数点数アライメント・アドレスです。
maxaligned
( addr1 – addr2 ) gforth-0.2 “maxaligned”
addr2 は、 すべてのアライメント制限を満たす addr1 以上の最初のアドレスです。
cfaligned
( addr1 – addr2 ) gforth-0.2 “cfaligned”
addr2 は、 addr1 以上の最初のアドレスで、 コード・フィールド用にアライメントされています(つまり、 対応する本体(body)が maxalign されるようにします)。
*aligned
( addr1 n – addr2 ) gforth-1.0 “*aligned”
addr1 以上で、 n でアライメントされた(n で割り切れる)アドレスを addr2 に返します。
*align
( n – ) gforth-1.0 “*align”
here
を n で割り切れるアドレスにアライメントします。
waligned
( addr – addr’ ) gforth-1.0 “waligned”
Addr’ は、 addr 以上、かつ、次の偶数アドレスです。
walign
( – ) gforth-1.0 “walign”
here
を 偶数アドレスにアライメントします。
laligned
( addr – addr’ ) gforth-1.0 “laligned”
Addr’ は、 addr 以上、 かつ、 4 で割り切れるアドレスです。
lalign
( – ) gforth-1.0 “lalign”
here
を 4 で割り切れるアドレスにアライメントします。
xaligned
( addr – addr’ ) gforth-1.0 “xaligned”
Addr’ を addr 以上、 かつ、 8 で割り切れるアドレスにします。
xalign
( – ) gforth-1.0 “xalign”
here
を 8 で割り切れるアドレスにアライメントします。
環境クエリ address-unit-bits
(see Environmental Queries) と、 以下のワード群は、
バイトアドレスを持たないマシン(non-byte-addressed machines)に移植可能なソフトウェアを作成したい人に役立つかもしれません。
/w
( – u ) gforth-0.7 “slash-w”
16ビット値に必要なアドレス単位
/l
( – u ) gforth-0.7 “slash-l”
32ビット値に必要なアドレス単位
/x
( – u ) gforth-1.0 “slash-x”
64ビット値に必要なアドレス単位
メモリー・ブロックは多くの場合、 文字の連なり(character strings)を表します。 文字の連なりをメモリーに保存する方法については、 String representations を参照してください。 他の連なり処理ワード(string-processing words)については、 Displaying characters and strings を参照してください。
これらのワードのいくつかは、 アドレス単位ブロック(address unit blocks)で機能します。 その場合、
文字の連なり(character strings)を扱う際には通常、 ワードの前に CHARS
を挿入する必要があります。
ほとんどのワードは文字ブロック(character blocks)で機能し、 文字アライメント・アドレス(char-aligned
address)を期待します。
重複するメモリー領域間で文字達をコピーする場合は、 move
を使用します。 cmove
と cmove>
は、 適切に実装された move
よりも遅くなる傾向があります。
move
( c-from c-to ucount – ) core “move”
c-from アドレスにある ucount 個のcharの内容を c-to アドレスにコピーします。 move
は、 2 つの領域が重なっている場合でも正しく機能します。
cmove
( c-from c-to u – ) string “c-move”
データ空間で c-from アドレスから ucount 個のcharの内容を c-to アドレスにコピーします。コピーは、 1
char
ずつコピーしながら下位アドレスから上位アドレス方向へ進めます。 つまり、 重複領域の場合、 c-to <=
c-from であれば安全です。
cmove>
( c-from c-to u – ) string “c-move-up”
データ空間で c-from アドレスから ucount 個のcharの内容を c-to アドレスにコピーします。 コピーは、 1
char
ずつコピーし上位アドレスから下位アドレス方向に進みます。 つまり、 重複領域の場合、 c-to >=
c-from であれば安全です。
fill
( c-addr u c – ) core “fill”
c を c-addr から u 個char単位で格納します。
erase
( addr u – ) core-ext “erase”
addr から始まる u aus のすべてのビットをクリアします。
blank
( c-addr u – ) string “blank”
スペース文字を c-addr アドレスから u 個char単位で格納します。
insert
( string length buffer size – ) gforth-0.7 “insert”
バッファーの先頭に文字列を挿入します。 残りのバイトは後ろにずらされます(訳注: sizeを超えた分は捨てられます)。
delete
( buffer size u – ) gforth-0.7 “delete”
最初の u バイトをバッファーから削除し、 その分前にずらして、 残りの最後のバイトを空白(0x20)で埋めます。
compare
( c-addr1 u1 c-addr2 u2 – n ) string “compare”
連なり(string)内のバイトの値に基づいて、 2 つの連なり(strings)を(英語の)辞書順(lexicographically)に比較します(つまり、 英大文字と小文字が区別され、 ロケール固有の照合順序は無視されます)。 2つが等しい場合、 n は 0 です。 最初の連なり(string)が小さい場合、 n は -1 です。 最初の連なり(string)が大きい場合、 n は 1 です。
pad
( – c-addr ) core-ext “pad”
c-addr は、 一時的なデータ・ストレージとして使用できる一時領域のアドレスです。 少なくとも 84 文字分の空間が使用可能です。
Forth は、 c@
などのワードで使用される char (別名バイト)をサポートします。 これらは ASCII
文字を表すために使用できます。
Forth は、 複数のバイト(つまり、 複数の char)のシーケンスで表現できる拡張文字(extended characters)もサポートしています。 一般的な文字エンコーディングは、 ユニコードの UTF-8 表現です。
一般に、 ほとんどのプログラム・コードは拡張文字(extended characters)について心配する必要はありません。
連なり(string)表現では、 当該バイトが拡張文字の一部であるか、 それ自体が1つの文字であるかは問題ではありません。 拡張文字が char
のシーケンスとして転送される場合、 (emit
のような) char を消費するワードも機能します。 Forth は未だ、
拡張文字を処理するためのワード群を提供しています(see Xchars and Unicode)。
ユニコード用語では、 char コード・ユニット、 拡張文字は コード・ポイント です。 Unicode 抽象文字(abstract character) はコード・ポイントのシーケンスとしで構成されますが、 (他のプログラミング言語と同様、 ) Forth には抽象文字個々のデータ型はないことに注意してください。 もちろん、 これらは連なり(string)として表すことができます。
スタック上の char および Xchar には通常の整数ワードを使用できますが、 Gforth にはスタック上の char を処理するためのワードもいくつかあります:
toupper
( c1 – c2 ) gforth-0.2 “toupper”
c1 が小文字の ASCII 文字である場合、 c2 は同等の大文字です。 それ以外の場合、 c2 と c1 は同じです。
Forth は通常、 文字列(strings)をスタック上のセルのペア c-addr u として表します。 u はバイト単位の文字列の長さ(別名 文字数)、 c-addr は文字列の最初のバイトのアドレスです。 コード・ポイントは、 文字列内の複数の文字(char)のシーケンスによって表される場合があることに注意してください(また、 ユニコード の「抽象文字」(abstract character)は複数のコード・ポイントで構成される場合があります)。 See String words
もう一つの文字列表現が、 $
を含む文字列ライブラリー・ワード群で使用されます。 それらは、
変数などに配置できるセル・サイズの文字列ハンドル(cell-sized string handle)のアドレスを通じてスタック上の文字列を表します。
See $tring words
旧来からの文字列表現は「カウンタ付き文字列」(counted strings)で、 スタック上では c-addr と表現されます。 c-addr によって指定される char には、 文字列の文字数 n が含まれており、 その文字列はメモリー内の後続の n char アドレスを占有します。 カウントされる文字列の長さは 255 バイトに制限されます。 カウンタ付き文字列は、 必要なスタック項目が 1 つだけであるため魅力的に見えるかもしれませんが、 この制限があるため、 特にワードの入力パラメーターとしては使用しないことをお勧めします。 See Counted string words
文字列リテラル(string literal)を記述する至高の方法は、 "STRING"
と記述することです。
s\"
の場合と同じく、 バックスラッシュによるエスケープ(\-escapes)を使用できます。 ただし、
この方法は標準ではないため(non-standard)、 移植性を高めるためには以下のいずれかのワードを使用することをお勧めします:
s\"
( compilation ’ccc"’ – ; run-time – c-addr u ) core-ext,file-ext “s-backslash-quote”
これは s"
と似ていますが、
C言語のようなバックスラッシュ・エスケープ・シーケンス(\-escape-sequences)を次のように変換します: \a
BEL
(ビープ音)、 \b
BS 、 \e
ESC (not in C99)、 \f
FF 、 \n
改行(newline)、 \r
CR 、 \t
HT 、\v
VT 、\"
" 、
\\
\ 、 \
[0-7]{1,3} 8進数(非標準)、 \x
[0-9a-f]{0,2}
16進指定char値(標準は2桁のみ)、 \u
[0-9a-f]{4}
ユニコード・コードポイント(サロゲート・ペア自動マージ;auto-merges surrogate pairs)、
\U
[0-9a-f]{8} 拡張ユニコード・コード・ポイント。 \
は他の文字よりも前に予約されています。
注意:
\x
XX は生のバイトを生成することに注意してください。 一方、 \u
XXXX と \U
XXXXXXXX は、
現在のエンコーディングのコード・ポイントを生成します。 たとえば、 UTF-8 エンコーディングを使用し、 ä (コード・ポイント
U+00E4)をエンコードしたい場合は、 次のように指定できます: 文字 ä 自体を記述するか、 \xc3\xa4
(このコード・ポイントの UTF-8 バイト) または \u00e4
または \U000000e4
を記述します。
注意: C言語とは異なり、 \n
はホストOS に適した改行シーケンスを生成します。 これは複数の文字で構成される場合があります。
つまり、 "\n"
は newline
と同等です。
S"
( compilation ’ccc"’ – ; run-time – c-addr u ) core,file “s-quote”
コンパイル・モード用コードは "
(二重引用符) を区切り文字として文字列 ccc をパースします。 その実行時コードは、 長さ
u と 文字列の開始アドレス c-addr を返します。 インタープリター時: 同様に文字列をパースし、 c-addr と
u 返します。 Gforth では文字列(string)を allocate
します。
結果として生じるメモリー・リークは通常は問題ではありません。 例外は、 S"
を含む文字列を作成し、 その文字列を
evaluate
する場合です。 その場合、 リークはインタプリトされたファイルのサイズに制限されないため、 文字列を
free
することもできます。 Forth-2012 では、それぞれ 80 文字のバッファーが 2 つしか保証されないため、
標準プログラムでは、 文字列は 2 つ前の s"
までしか存続していない想定する必要があります。
同様に、 'C'
を使用すると、 文字 C のコード xc を取得できます。 この方法は Forth-2012
から標準化されています。 これを取得する古い方法は、 以下のいずれかのワードを使用することです:
char
( ’<spaces>ccc’ – c ) core,xchar-ext “char”
先頭のスペース達をスキップします。 文字列 ccc をパースし、 ccc の最初の文字の文字コード c を返します(訳注:ASCII以外の文字コードもいけるっぽい(LANG=ja_JP.UTF-8) char あ hex . decimal 3042 ok)
[char]
( compilation ’<spaces>ccc’ – ; run-time – c ) core,xchar-ext “bracket-char”
コンパイル状態: 先頭のスペース達をスキップします。 文字列 ccc をパースします。 その実行時コード: ccc の最初の文字の文字コード c を返します。 このワードのインタープリター機能(interpretation semantics)は未定義です。
通常は、 コロン定義の外側で char
使用するか、 コロン定義の内側で [char]
を使用するか、 単にその両方で
'C'
を使用するかです。
注意: 例えば、
"C" type
の方が下記よりも(わずかに)効率的です
'C' xemit
なぜなら、 後者はコード・ポイントをバイトのシーケンスに変換し、 それらを個別に emit
するからです。 同様に、
一般的な文字達を扱う場合は、 通常、 コード・ポイントではなく文字列として表す方が効率的です。
S"
または 'C'
では生成できない、 一般的に使用される文字や文字列を生成するために以下のワード群があります:
newline
( – c-addr u ) gforth-0.5 “newline”
ホストOSの改行シーケンスを含む文字列(訳注: 文字コードを返す訳では無いことに注意。 文字列の内容は LF だったり CRLF だったりする)
bl
( – c-char ) core “b-l”
c-char は空白(space)の文字コード値です。
#tab
( – c ) gforth-0.2 “number-tab”
#lf
( – c ) gforth-0.2 “number-l-f”
#cr
( – c ) gforth-0.2 “number-c-r”
#ff
( – c ) gforth-0.2 “number-f-f”
#bs
( – c ) gforth-0.2 “number-b-s”
#del
( – c ) gforth-0.2 “number-del”
#bell
( – c ) gforth-0.2 “number-bell”
#esc
( – c ) gforth-0.5 “number-esc”
#eof
( – c ) gforth-0.7 “number-e-o-f”
実際には EOT (ASCII コード 4 別名 ^D
)
メモリー・ブロックに使用されるワードは文字列(string)にも役立つため、 文字列(string)の移動やコピーや比較や検索を行うワードについては、 Memory Blocks を参照してください。 文字や文字列を表示するワードについては、 see Displaying characters and strings を参照してください。
以下のワード群は、 既に存在している文字列(strings)に対して機能します:
str=
( c-addr1 u1 c-addr2 u2 – f ) gforth-0.6 “str-equals”
str<
( c-addr1 u1 c-addr2 u2 – f ) gforth-0.6 “str-less-than”
string-prefix?
( c-addr1 u1 c-addr2 u2 – f ) gforth-0.6 “string-prefix-question”
c-addr2 u2 が c-addr1 u1 の接頭辞部分と合致しますか?
string-suffix?
( c-addr1 u1 c-addr2 u2 – f ) gforth-1.0 “string-suffix-question”
c-addr2 u2 が c-addr1 u1 の接尾辞部分と合致しますか?
search
( c-addr1 u1 c-addr2 u2 – c-addr3 u3 flag ) string “search”
文字列 c-addr1, u1 の中で、 文字列 c-addr2, u2 を検索します。 flag が true の場合: 見つかったアドレスを c-addr3 に、 文字列 c-addr2, u2 を含むそれ以降の文字数を u3 に返します(訳注: s" GNU gforth マニュアル" s" ニュア" search drop ." [" type ." ]" [ニュアル] ok )。 flag が false の場合: 一致するものが見つかりませんでした。 c-addr3, u3 は c-addr1, u1 と同じです。
scan
( c-addr1 u1 c – c-addr2 u2 ) gforth-0.2 “scan”
c に等しくないすべての文字をスキップします。 結果は c で始まる(c-addr2 u2)か空(empty; c-addr2+u2, 0)です。
Scan
はシングルバイト文字(ASCII)に限定されます。 マルチバイト文字を検索するには、 search
を使用します。
scan-back
( c-addr u1 c – c-addr u2 ) gforth-0.7 “scan-back”
skip
( c-addr1 u1 c – c-addr2 u2 ) gforth-0.2 “skip”
c に等しいすべての文字をスキップします。 結果は最初の非 C 文字で始まる(アドレス、長さ)か、 空(文字列の長さ0)になります。
skip
はシングルバイト文字(ASCII)に限定されます。
-trailing
( c_addr u1 – c_addr u2 ) string “dash-trailing”
c-addr, u1 で指定された文字列の末尾をトリムします(末尾のスペースをすべて削除します)。 u2 は変更後の文字列の長さです。
/string
( c-addr1 u1 n – c-addr2 u2 ) string “slash-string”
c-addr1, u1 で指定された文字列の先頭から n 文字削除します(訳注: c-addr1+n, u1-n するだけっぽい…)。
safe/string
( c-addr1 u1 n – c-addr2 u2 ) gforth-1.0 “safe-slash-string”
c-addr1, u1 で指定された文字列の先頭から n 文字を削除します。 /string
とは異なり、safe/string
は少なくとも 0 文字、 最大で u1 文字を削除します。
cstring>sstring
( c-addr – c-addr u ) gforth-0.2 “cstring-to-sstring”
C-addr はゼロで終わる文字列の開始アドレス、 u はその長さです。
以下のワード群は、 ASCII 文字については大文字と小文字を区別せずに比較しますが、 (ワードリストの検索のような)非 ASCII 文字については大文字と小文字を区別します 。
capscompare
( c-addr1 u1 c-addr2 u2 – n ) gforth-0.7 “capscompare”
文字列内のバイトの値に基づいて、 2 つの文字列(string)を辞書順(lexicographically)に比較します。ただし、 ASCII 文字は大文字と小文字を区別せずに比較し、 非 ASCII 文字は大文字と小文字を区別して、 かつ、 ロケール固有の照合順序を使用せずに比較します。 それらが等しい場合 n は 0 です。 最初の文字列が小さい場合 n は -1 です。 最初の文字列が大きい場合n は 1 です。
capsstring-prefix?
( c-addr1 u1 c-addr2 u2 – f ) gforth-1.0 “capsstring-prefix?”
string-prefix?
と似ていますが、 ASCII 文字の大文字と小文字は区別されません: c-addr2 u2 は
c-addr1 u1 の接頭辞か?
capssearch
( c-addr1 u1 c-addr2 u2 – c-addr3 u3 flag ) gforth-1.0 “capssearch”
search
と似ていますが、 ASCII 文字の大文字と小文字は区別されません: c-addr1 u1 内で
c-addr2 u2 を検索します。 見つかった場合 flag は true になります。
以下のワード群は、 ヒープに文字列(string)を作成、 またはヒープの文字列(string)を拡張します。
s+
( c-addr1 u1 c-addr2 u2 – c-addr u ) gforth-0.7 “s-plus”
c-addr u は、c-addr1 u1 (最初) と c-addr2 u2 (2 番目) を連結したのを含む、 新しく
allocate
された文字列です。
append
( c-addr1 u1 c-addr2 u2 – c-addr u ) gforth-0.7 “append”
C-addr u は、c-addr1 u1 (最初) と c-addr2 u2 (2 番目) を連結したものです。
c-addr1 u1 は allocate
された文字列であり、 u
文字の領域に対応するために(可能なら)サイズ変更(resize)して c-addr2 u2
の文字列を追加(append)します(サイズ変更できなくて、新しいアドレスに移動する可能性があります)。
>string-execute
( ... xt – ... addr u ) gforth-1.0 “>string-execute”
xt を実行(execute)すると、 標準出力 (type
や emit
やそれらを使用するすべてのもの)
を文字列にリダイレクトします。 結果の文字列は addr u で、これは割り当てられた(allocate
)メモリー内にあります。
この文字列を開放(free
)するのは、 >string-execute
の呼び出し元の責任です。
以下のようにして、>string-execute
を使用して s+
を定義できます:
: s+ ( c-addr1 u1 c-addr2 u2 – c-addr u ) [: 2swap type type ;] >string-execute ;
2 つの文字列だけを連結する場合、 >string-execute
は効率的ではありませんが、 多くの文字列を連結する場合は、
>string-execute
の方が効率的です。
以下の文字列ライブラリーは、 文字列を通常のセル・サイズの変数(文字列ハンドル;string handle)に格納します。 これらのハンドルには、 ヒープに割り当てられたセル・カウント文字列(cell-counted string;カウンターがセル・サイズであるカウンター付き文字列)へのポインターが含まれています。 この文字列ライブラリーは bigFORTH 由来です。
内容への恒久的な参照は 1 つだけ(ハンドル内の参照)しかないため、 未解決の参照を恐れることなく文字列を再配置または削除できます。 これには、
プログラマが、 例えば $@
等によって生成された参照を一時的な目的でのみ使用する必要があります。 つまり、 これらの一時的な参照は、
戻り値として渡されたり、 グローバル・メモリーに格納されたりすることはなく、 ハンドルを変更する可能性のあるワードは、
これらの一時的な参照が存在する間は呼び出されません。
このライブラリーを補完するものとしてセル・ペア表現(cell-pair representation)があります。 文字列変数に対して $tring ワードを使用しますが、 これは c-addr u 表現では面倒です。 セル・ペア表現は、 文字列を変更しないで処理(検査など)するために使用します。
$!
( addr1 u $addr – ) gforth-0.7 “string-store”
文字列 addr1 u を、 新しく割り当てられた文字列バッファーに格納し、 その文字列バッファーのアドレスを $addr に格納します。 必要に応じて以前のバッファーを解放します。
$@
( $addr – addr2 u ) gforth-0.7 “string-fetch”
格納された文字列のアドレスと長さを返します。
$@len
( $addr – u ) gforth-0.7 “string-fetch-len”
格納されている文字列の長さを返します。
$!len
( u $addr – ) gforth-0.7 “string-store-len”
格納されている文字列(string)領域の長さを変更します(足し算ではない。 長さを直にいじる) それゆえ、メモリー領域を変更し、 アドレスとカウンタ・セルも調整(adjust)する必要があります(訳注: 元の長さより長くした場合、 領域を拡張するだけで初期化はしない。 そこにはゴミが入っているので自分で初期化する必要がある)
$+!len
( u $addr – addr ) gforth-1.0 “string-plus-store-len”
$addr によって参照されるメモリー領域の最後に u バイト用の空間を確保します(追加。確保するだけで初期化はしない。 $addr の「長さ」に追加もする)。 addr は、 確保した空間の最初のアドレスです。
$del
( addr off u – ) gforth-0.7 “string-del”
文字列のオフセット位置 off から u バイト削除します。
$ins
( addr1 u $addr off – ) gforth-0.7 “string-ins”
オフセット位置 off に文字列を挿入します。
$+!
( addr1 u $addr – ) gforth-0.7 “string-plus-store”
文字列(addr1 u)を別の文字列($addr)に追加します。
c$+!
( char $addr – ) gforth-1.0 “c-string-plus-store”
文字列に文字を追加します。
$free
( $addr – ) gforth-1.0 “string-free”
$addr が指す文字列を解放し、 $addr のポインターを 0 にします( $@ すると 0 0 を返す)
$init
( $addr – ) gforth-1.0 “string-init”
以前に何があったかに関係なく、そこに空の文字列を保存します(訳注: ポインターを 0 にする訳ではなくて、 ポインターの指す先に長さゼロの文字列がある)
$split
( addr u char – addr1 u1 addr2 u2 ) gforth-0.7 “string-split”
文字列中で最初に現れる char を区切り文字として文字列を 2 つに分割します(例: HTML クエリの引数の ’?’)(訳注: 区切り文字 char 自体は分割後の文字列に含まれない)
$iter
( .. $addr char xt – .. ) gforth-0.7 “string-iter”
区切り文字 char で切り出した部分文字列(addr u)ごとに xt を呼び出します。 xt は (addr u – ) でなければなりません。 これにより、 例えば ’&’ で区切られた引数を簡単に分解できます(訳注: : my-type ( addr u – ) ." [" type ." ]" ; variable title s" GNU gforth manual" title $! title $ my-type [GNU gforth manual] title bl ’ my-type $iter [GNU][gforth][manual] ok)。
$over
( addr u $addr off – ) gforth-1.0 “string-over”
$addr の文字列のオフセット位置 off から文字列(addr u)で上書きします。
$exec
( xt addr – ) gforth-1.0 “string-exec”
実行トークン xt を実行し、 その標準出力(TYPE や EMIT やそれらを使用するすべてのもの)を addr が指す文字列の末尾に「追加」します。
$tmp
( xt – addr u ) gforth-1.0 “string-t-m-p”
ワードの出力から一時的な文字列を生成します
$.
( addr – ) gforth-1.0 “string-dot”
文字列を出力する、 ショートカット( $@ TYPE → $. )
$slurp
( fid addr – ) gforth-1.0 “string-slurp”
(slurp;音を立ててすする)ファイル fid を最後まで読み取り(ファイルのクローズは行いません)、 読み取ったデータを addr の指す文字列に入れます。
$slurp-file
( c-addr u addr – ) gforth-1.0 “string-slurp-file”
c-addr u という名前のファイル内のすべてのデータを addr の文字列に入力します。
$+slurp
( fid addr – ) gforth-1.0 “string-plus-slurp”
ファイル fid を最後まで読み取り(但しクローズはしません)、 読み取ったデータを addr の文字列に「追加」します。
$+slurp-file
( c-addr u addr – ) gforth-1.0 “string-plus+slurp-file”
c-addr u という名前のファイル内のすべてのデータを addr の文字列に「追加」します。
$[]
( u $[]addr – addr’ ) gforth-1.0 “string-array”
addr’ は、 文字列配列 $[]addr の u 番目の要素のアドレスです。 配列のサイズは必要に応じて変更されます。
$[]!
( c-addr u n $[]addr – ) gforth-1.0 “string-array-store”
文字列 c-addr u を文字列配列 $[]addr のインデックス n に格納します。 必要に応じて配列のサイズが変更されます。
$[]+!
( c-addr u n $[]addr – ) gforth-1.0 “string-array-plus-store”
文字列 c-addr u をインデックス n の文字列に「追加」します。 必要に応じて配列のサイズが変更されます。 これを
$+[]!
と混同しないでください。
$+[]!
( c-addr u $[]addr – ) gforth-1.0 “string-append-array”
文字列 c-addr u を文字列配列 $[]addr の新しい最後の要素として保存します(つまり要素が1つ増える)。 必要に応じて配列のサイズが変更されます。
$[]@
( n $[]addr – addr u ) gforth-1.0 “string-array-fetch”
配列インデックス n から文字列を取得します — 空の場合はゼロ文字列( 0 0 )を返し、 誤って配列が成長しないようにします(訳注:要素はあるけど空文字列(長さ0)の場合と、範囲外の要素を指した場合の区別は付かないので注意)
$[]#
( addr – len ) gforth-1.0 “string-array-num”
配列内の要素の数を返します
$[]map
( addr xt – ) gforth-1.0 “string-array-map”
文字列配列 addr のすべての要素に対して 実行トークン xt を実行します。 xt は ( addr u – ) で、一度に 1 つの文字列を取得します
$[]slurp
( fid addr – ) gforth-1.0 “string-array-slurp”
ファイル fid の内容を 1 行ずつ文字列配列 addr に入れます
$[]slurp-file
( addr u $addr – ) gforth-1.0 “string-array-slurp-file”
名前付きファイル addr u を 1 行ずつ文字列配列 $addr に入れます。
$[].
( addr – ) gforth-1.0 “string-array-dot”
すべての配列エントリを出力します
$[]free
( addr – ) gforth-1.0 “string-array-free”
addr は沢山のセルカウント文字列(string)へのアドレス達を含む、 セル・カウント文字列(string)へのアドレスで、 $[]free はこれらの文字列を解放、 つまり、 この配列を開放し、 addr の値を 0 にセットします。
$save
( $addr – ) gforth-1.0 “string-save”
savesys のディクショナリーに文字列(string)をプッシュします
$[]save
( addr – ) gforth-1.0 “string-array-save”
文字列配列を savesys のディクショナリーにプッシュ
$boot
( $addr – ) gforth-1.0 “string-boot”
ディクショナリーから文字列を新しく割り当てたメモリーに取り込みます。 その後、 ディクショナリーのその文字列の領域を(0で)クリアします(長さはそのまま)。
$[]boot
( addr – ) gforth-1.0 “string-array-boot”
ディクショナリーから、 文字列配列を割り当てられたメモリーに取得します
$saved
( addr – ) gforth-1.0 “string-saved”
アドレスを ブート済み(booted)/保存済み(saved) としてマークする
$[]saved
( addr – ) gforth-1.0 “string-array-saved”
アドレスを ブート済み(booted)/保存済み(saved) としてマークする
$Variable
( – ) gforth-1.0 “string-variable”
savesystem 全体にわたって保存される文字列変数。
$[]Variable
( – ) gforth-1.0 “string-array-variable”
savesystem 全体にわたって保存される文字列変数。
カウンタ付文字列(counted string)は、 指定のアドレスのバイトとして長さを格納し、 その後に文字列のバイトが続きます。 可能な長さは厳しく制限されており、 入力文字列を破壊せずにその場で部分文字列を作成することはできません。 したがって、 カウンタ付文字列を使用しないことをお勧めします。 それでも、 カウンタ付文字列を処理する必要がある場合、 以下のようなワード群があります:
count
( c-addr1 – c-addr2 u ) core “count”
c-addr2 は最初の文字で、 u は c-addr1 のカウンタ付文字列の、 長さです。
以下のワードは、 (s"
とは異なり) 有用なインタープリター機能(interpretation semantics)がなく、
([char]
とは異なり) 対応するインタプリタ用コードがないため、 コロン定義内でのみ使用する必要があります(コロン定義がある場合):
C"
( compilation "ccc<quote>" – ; run-time – c-addr ) core-ext “c-quote”
コンパイル状態: "
(二重引用符) を区切り文字として文字列 ccc をパースします。 その実行時コードは、
指定のカウンタ付文字列 ccc のアドレスを c-addr として返します。 インタープリター機能は未定義です。
( gforth-obsolete ) place
( c-addr1 u c-addr2 –) \ c-addr2
に長さ u のカウンタ付き文字列を作成し、 文字列 c-addr1 u をその場所にコピーします。
string,
( c-addr u – ) gforth-0.2 “string,”
文字列をカウンタ付き文字列として(here以降の)データ空間に書き込みます。
Forth の制御構造は、 コロン定義内のみで、 対話的(interpretively)に使用することはできません11。 私たちもこのような制限を好まないので、 多くのスキームが提案されてはいますが、 これを回避する満足のいく方法はまだ見つかっていません。
flag IF code ENDIF
flag がゼロ以外の場合(IF
などに関する限り、 任意のビットが設定されたセルは true 扱いです)、 code
が実行されます。
flag IF code1 ELSE code2 ENDIF
flag が true の場合は code1 が実行され、 それ以外の場合は code2 が実行されます。
ENDIF
の代わりに THEN
を使用できます。 実際 THEN
は標準です、 がしかし、
ENDIF
は非常に人気があるものの、 標準ではありません。 私達は ENDIF
の使用をお勧めします。 なぜなら、
他の言語を知っている人にとっても混乱が少ないためです(また、 ENDIF
に対しては、 これらの人々の Forth
に対する否定的な偏見が強化される傾向が見られません)。 なお、 THEN
のみを提供するシステムに ENDIF
を追加するのは以下のように簡単です:
: ENDIF POSTPONE then ; immediate
[Webster’s New Encyclopedic Dictionary によると、then (副詞) には以下の意味があります:
... 2b: 順番に次の後に続く ... 3d: 必然的な結果として (if you were there, then you saw them)
Forth の THEN
は 2b の意味を持ちますが、 Pascal や他の多くのプログラミング言語の THEN
は 3d
の意味を持ちます。]
Gforth には ?DUP-IF
および ?DUP-0=-IF
というワードも用意されているため、 ?dup
の使用を避けることができます。これらの代替手段を使用することは、 ?dup
を使用するよりも効率的です。 ENDIF
や
?DUP-IF
や ?DUP-0=-IF
の標準 Forth での定義は、compat/control.fs
で提供されます。
x CASE x1 OF code1 ENDOF x2 OF code2 ENDOF ... ( x ) default-code ( x ) ENDCASE ( )
xi が x に等しいなら、 最初の codei を実行します。 x1 〜 xn のいずれも一致しない場合は、
オプションの default-code が実行されます。 オプションのデフォルト・ケースは、 最後の ENDOF
の後にコードを記述するだけで追加できます。 スタック頂上にある x を使用することはできますが、 それを消費してはなりません。 値 x
は、 この構造によって(一致する OF
によって、 または一致する OF がない場合は ENDCASE
によって)
消費されます。 例:
: num-name ( n -- c-addr u ) case 0 of s" zero " endof 1 of s" one " endof 2 of s" two " endof \ default case: s" other number" rot \ get n on top so ENDCASE can drop it endcase ;
(非標準の) ?of
を使用して、 case
を 3 つ以上の選択肢の一般的な選択構造として使用することもできます。
?Of
はフラグを受け取ります。 例:
: sgn ( n1 -- n2 ) \ sign function case dup 0< ?of drop -1 endof dup 0> ?of drop 1 endof dup \ n1=0 -> n2=0; dup an item, to be consumed by ENDCASE endcase ;
プログラミング・スタイル・メモ: コードを理解しやすくするには、 選択構造を介したすべての経路で同一の方法でスタックを変更(消費およびプッシュされるスタック項目の数と型)するようにする必要があります。
BEGIN code1 flag WHILE code2 REPEAT
code1 が実行され、 flag が計算されます。 flag が true の場合、 code2 が実行され、
BEGIN からループが再開されます。 flag が false の場合、 REPEAT
の後へ実行が続行されます。
BEGIN code flag UNTIL
code が実行されます。 flag
が false の場合、 BEGIN からループが再開されます。
プログラミング・スタイル・メモ: コードを理解しやすくするために、 ループの完全な反復(complete iteration)によってスタック上の項目の数と型が変更されるべきではありません。
BEGIN code AGAIN
これは無限ループです。
基本のカウント・ループ:
limit start ?DO body LOOP
これは、 start から始まり limit まで(limit 自身は除く)、 各整数値に対して 1 回の反復を実行します。
カウンタ、 つまりインデックスには、 i
を使用してアクセスできます。 たとえば、 以下のループをご覧ください:
10 0 ?DO i . LOOP
出力: 0 1 2 3 4 5 6 7 8 9
最も内側のループのインデックスには i
を使用してアクセスでき、 その一つ外側のループのインデックスには j
を使用してアクセスでき、 さらにもう一つ外側のループのインデックスには k
を使用してアクセスできます。
i'
を使用すると最も内側のループの limit にアクセスでき、 delta-i
を使用すると i'
-
i
にアクセスできます。
: foo 7 5 ?do cr i . i' . delta-i . loop ;
出力:
5 7 2 6 7 1
ループ制御データはリターン・スタックに保持されるため、 リターン・スタックへのアクセスとカウント・ループ・ワードの混在にはいくつかの制限があります。 特に、ループの外側のリターン・スタックに値を置いた場合、 ループ内で値を読み取る事はできません12。 ループ内のリターン・スタックに値を置く場合は、 ループの終了前、 およびループのインデックスにアクセスする前に値を削除する必要があります。
カウント・ループにはいくつかのバリエーションがあります:
LEAVE
は、 最も内側のカウント・ループを直ちに抜け出します。 それが関係する LOOP
または NEXT
の後へ実行は移ります。 例:
10 0 ?DO i DUP . 3 = IF LEAVE THEN LOOP
出力: 0 1 2 3
UNLOOP
は、 例えば EXIT
などを介しての異常なループ終了の準備を行います。 UNLOOP
は、
EXIT
がリターン・アドレスに到達できるように、 リターン・スタックからループ制御パラメーターを削除します(訳注: 1重ループなので
unloop 1つ。2重ループなら unloop 2つ)。 例:
: demo 10 0 ?DO i DUP . 3 = IF UNLOOP EXIT THEN LOOP ." Done" ;
出力: 0 1 2 3
?DO
はループを開始します(ラップアラウンド演算(処理可能な範囲をの最後に達した後に最初に戻る事)によって両者が等しくなるまで LOOP
を反復します)。 通常、 この振る舞いは望ましくないものです。 したがって、 Gforth は (?DO
の代替として、)
+DO
と U+DO
を提供します。 これらは、 start が limit
より大きい場合にはループを開始しません。 +DO
は符号付きループ・パラメータ用で、 U+DO
は符号なしループ・パラメーター用です。
?DO
は DO
に置き換えることはできます。 DO
は、 ループ・パラメーター に関係なく、
常にループに入ります。 あなたが、 どの場合にもループに入ることを知っている場合でも DO
は使用しないでください。
このような知恵はプログラムを保守していく中で無効になる傾向があり、 それゆえ DO
が問題を引き起こすことになります。
LOOP
は n +LOOP
に置き換えることができます。 これにより、インデックスが 1 ではなく n
によって更新されます。 limit-1 と limit の間の境界を越えると、 ループは終了します。 例:
4 0 +DO i . 2 +LOOP
出力: 0 2
4 1 +DO i . 2 +LOOP
出力: 1 3
n +LOOP
の動作は奇妙です:
-1 0 ?DO i . -1 +LOOP
出力: 0 -1
0 0 ?DO i . -1 +LOOP
出力: なし。
私たちは ?DO
と +LOOP
を組み合わせないことをお勧めします。 Gfors はいくつかの代替手段を提供します:
I
=limit の反復を含める -1 +LOOP
の振る舞いが必要な場合、 -[DO
または
U-[DO
] でループを開始します(ここで、 [
は、 包含範囲の数学的表記法(例: [1,n]
からインスピレーションを得ています):
-1 0 -[DO i . -1 +LOOP
出力: 0 -1
.
0 0 -[DO i . -1 +LOOP
出力: 0
0 -1 -[DO i . -1 +LOOP
出力: なし。
limit を除外したい場合、 代わりに 1 -LOOP
(または一般的には u -LOOP
)を使用し、
?DO
または -DO
または U-DO
でループを開始します。 -LOOP
は、limit+1 と limit の間の境界を越えたときにループを終了します。 例:
-2 0 -DO i . 1 -LOOP
出力: 0 -1
-1 0 -DO i . 1 -LOOP
出力: 0
0 0 -DO i . 1 -LOOP
出力: なし。
残念ながら、 +DO
, U+DO
, -DO
, U-DO
, -LOOP
は 標準
Forth では定義されていません。 ただし、 標準のワードのみを使用するこれらのワードの実装が compat/loops.fs
にて提供されています。
bounds
があるため、セル配列 v
を介した前方ループを以下のように記述できます:
create v 1 , 3 , 7 , : foo v 3 cells bounds U+DO i . cell +LOOP ; foo
これは 1 3 7
を出力します。 逆方向にたどるための入力の前処理はより複雑であるため、 Gforth はそれを行う
MEM-DO
… LOOP
形式のループ構造を提供します。 これは addr uバイト
表現の配列と要素サイズを受け取り、 要素のアドレスを逆順に反復処理します。
create v 1 , 3 , 7 , : foo1 v 3 cell array>mem MEM-DO i . LOOP ; foo1
これは 7 3 1
を出力します。ARRAY>MEM
は addr uelems uelemsize を
MEM-DO
が期待する addr ubytes uelemsize に変換します(ubytes は uelems *
uelmsize です)。 このループは MEM-DO
通過後にuelemsize ずつ減算される、 LOOP
と対になるループとなります。
Gforth は、 完全を期すために MEM+DO
も追加します。 MEM-DO
と同一のパラメーターを受け取りますが、
配列を順(forwards)に処理します:
create v 1 , 3 , 7 , : foo2 v 3 cell array>mem MEM+DO i . LOOP ; foo2
出力: 1 3 7
n FOR body NEXT
これは、 ?DO
ループを適切に最適化するのが面倒な、 ネイティブ・コード・コンパイラー作成者達が好むループです。 このループ構造は標準
Forth では定義されていません。 Gforth では、 このループは n+1 回繰り返します。 i
は、 n
で始まり 0 で終わる値を生成します。 他の Forth システムは、FOR
ループをサポートしている場合でも、
振る舞いが異なる場合があります。 この問題を回避するには、 FOR
ループを使用しないようにしてください。
カウント・ループ・ワード群:
?DO
( compilation – do-sys ; run-time w1 w2 – | loop-sys ) core-ext “question-do”
See Counted Loops.
+DO
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-0.2 “plus-do”
See Counted Loops.
U+DO
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-0.2 “u-plus-do”
See Counted Loops.
bounds
( addr u – addr+u addr ) gforth-0.2 “bounds”
開始アドレス addr と長さ u で表されるメモリー・ブロックを指定すると、 u+do
または ?do
の終了アドレス addr+u と開始アドレス addr を正しい順序で生成します。
-[do
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-experimental “minus-bracket-do”
負の方向へカウントされるループを開始します。 n2<n1 の場合、 ループをスキップします。 このようなカウント・ループは、
増分が負である +loop
と対になります。 I
>=n1 である限り実行されます。
u-[do
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-experimental “u-minus-bracket-do”
負の方向へカウントするループを開始します。 u2<u1 の場合、ループをスキップします。 このようなカウント・ループは、 増分が負の
+loop
と対になります。 I
>=u1 である限り実行されます。
-DO
( compilation – do-sys ; run-time n1 n2 – | loop-sys ) gforth-0.2 “minus-do”
See Counted Loops.
U-DO
( compilation – do-sys ; run-time u1 u2 – | loop-sys ) gforth-0.2 “u-minus-do”
See Counted Loops.
array>mem
( uelements uelemsize – ubytes uelemsize) \ ubytes は
uelements * uelementsize です
mem+do
( compilation – w xt do-sys; run-time addr ubytes +nstride – ) gforth-experimental “mem-plus-do”
I
を addr から開始し、 I
<addr+ubytes である限り、 nstride
幅のステップでメモリー内をアドレスが増える方向にカウント・アップするカウント・ループを開始します。 loop と対にする必要があります。
mem-do
( compilation – w xt do-sys; run-time addr ubytes +nstride – ) gforth-experimental “mem-minus-do”
I
を addr+ubytes-ustride として開始し、 I
>=addr である間
-nstride 幅のステップでメモリーをアドレス下位方向(backward)にステップするカウント・ループを開始します。 loop
と対にしなければなりません。
DO
( compilation – do-sys ; run-time w1 w2 – loop-sys ) core “DO”
See Counted Loops.
FOR
( compilation – do-sys ; run-time u – loop-sys ) gforth-0.2 “FOR”
See Counted Loops.
LOOP
( compilation do-sys – ; run-time loop-sys1 – | loop-sys2 ) core “LOOP”
See Counted Loops.
+LOOP
( compilation do-sys – ; run-time loop-sys1 n – | loop-sys2 ) core “plus-loop”
See Counted Loops.
-LOOP
( compilation do-sys – ; run-time loop-sys1 u – | loop-sys2 ) gforth-0.2 “minus-loop”
See Counted Loops.
NEXT
( compilation do-sys – ; run-time loop-sys1 – | loop-sys2 ) gforth-0.2 “NEXT”
See Counted Loops.
i
( R:n – R:n n ) core “i”
n は、最も内側のカウント・ループのインデックスです。
j
( R:n R:w1 R:w2 – n R:n R:w1 R:w2 ) core “j”
n は、 最も内側から数えて 2 番目のカウント・ループのインデックスです。
k
( R:n R:w1 R:w2 R:w3 R:w4 – n R:n R:w1 R:w2 R:w3 R:w4 ) gforth-0.3 “k”
n は、 最も内側から数えて3番目のカウント・ループのインデックスです。
i'
( R:w R:w2 – R:w R:w2 w ) gforth-0.2 “i-tick”
最も内側のカウント・ループの limit
delta-i
( r:ulimit r:u – r:ulimit r:u u2 ) gforth-1.0 “delta-i”
u2=I'
-I
(limit とインデックスの差)
LEAVE
( compilation – ; run-time loop-sys – ) core “LEAVE” \ 訳注:LEAVE
は、 最も内側のカウンタ付きループを抜け出します。 \ それが関係するLOOP
またはNEXT
の直後から実行を続行します。
See Counted Loops.
?LEAVE
( compilation – ; run-time f | f loop-sys – ) gforth-0.2 “question-leave”
\訳注: f が true なら leave します。
See Counted Loops.
unloop
( R:w1 R:w2 – ) core “unloop”
DONE
( compilation do-sys – ; run-time – ) gforth-0.2 “DONE”
do-sys までのすべての LEAVE を解決します(訳注: loop, +loop , next 等の中で内部的に呼び出されます)。
標準では、 do-sys で CS-PICK
や CS-ROLL
を使用することは許可されていません。
MEM+DO
と MEM-DO
によって生成される do-sys を除いて、 Gforth では
CS-PICK
や CS-ROLL
の使用を許可しますが、 すべての ?DO
などに対して、
定義を介した任意の経路上に UNLOOP
が正確に 1 つだけ存在すること(LOOP
などの失敗経路上で
UNLOOP
コンパイルするなど)を確認するのはあなたの仕事です。 また、 すべての LEAVE
が(ループ終了ワードの 1
つまたは DONE
を使用して)解決されていることを確認する必要があります。
Begin
loops with multiple exits ¶カウント・ループの場合、 複数箇所で leave
を使用できます。 begin
ループの場合は、 以下の選択肢があります:
ループ内で exit
を使用(複数記述できます)すると、 ループだけでなくコロン定義全体からも去ります。 例:
: foo begin condition1 while condition2 if exit-code2 exit then condition3 if exit-code3 exit then ... repeat exit-code1 ;
このアプローチの欠点は、 ループ後に共通コードが必要な場合、 共通コードを含む別のワードで foo
を包むか、 または、 それぞれの
exit-code から共通コードを呼び出す必要があることです。
もう 1 つのアプローチは、 begin
ループ内で複数の while
を使用することです。 追加の
while
ごとにループの後ろに then
を追加する必要があります。 例:
begin condition1 while condition2 while condition3 while again then then then
ここでは、 ループの最後に again
を使用して、 各 while
に then
を用意しました。
repeat
は then
を 1 つ減らしますが、 それ以外の場合は同じ動作になります。
これが機能する理由の説明については、 See Arbitrary control structures をご覧下さい。
後で共通のコードを使用することはできますが、 上で示したように、 異なる出口(exit)に対して異なる exit-code を使用することはできません。 以下のようにすると、 これらの異なる exit-code を使用できます:
begin condition1 while condition2 while condition3 while again then exit-code3 else exit-code2 then else exit-code1 then
exit-code は終了条件から比較的離れているため、 これを理解するのは比較的困難です(このような制御構造に慣れていないことも理由にはなりません)。
case
¶Gforth は、 拡張 case
を提供することで、 上で説明した複数出口ループの問題を解決する追加のオプションを提供します。 この拡張
case
の移植可能な実装は compat/caseext.fs にあります。
この拡張には 3 つの追加ワードがあります。 1 つ目は ?of
で、 case
内で(単なる同等性のテストではなく、)一般的なテストが可能です。 例:
: sgn ( n -- -1|0|1 ) ( n ) case dup 0 < ?of drop -1 endof dup 0 > ?of drop 1 endof \ otherwise leave the 0 on the stack 0 endcase ;
注意: endcase
は値(a value)を drop することに注意してください。 これは of
ではほとんどうまいこと機能しますが、 ?of
ではたいていうまいこといかないので、 今回も endcase
で drop
するための値として 0 をスタック置きます。 ここでは、 sgn
に渡される n は、 いずれの ?of
もトリガーしない場合返り値の 0 そのものになります。
2 番目の追加ワードは next-case
で、 これにより case
をループに変えることができます。
出口が3つのループは以下のようになります:
case condition1 ?of exit-code1 endof condition2 ?of exit-code2 endof condition3 ?of exit-code3 endof ... next-case common code afterwards
ご覧のとおり、 これにより、 先程議論したバリエーションの両方の問題が解決されます(see Begin
loops with multiple exits)。 注意: endcase
とは異なり、 next-case
は値をドロップしないことに注意してください。
13
最後の追加ワードは contof
です。 これは endof
の代わりに使用され、
ループを終了する代わりに次の反復を開始します。 これは、 ダイクストラのガード付きコマンド 繰り返し: do と同様の方法で使用できます。 例:
: gcd ( n1 n2 -- n ) case 2dup > ?of tuck - contof 2dup < ?of over - contof endcase ;
ここで、 2 つの ?of
はループを継続する異なる方法を持っています。 どちらの ?of
もトリガーされない場合、 2
つの数値は等しく、gcd(最大公約数) になります。 Endcase
はそれらの 1 つを削除し、もう 1 つは n として残します。
これらのワードを組み合わせることもできます。 以下は、 endcase
を除く、 各 case
ワードをそれぞれ 1
回使用する例です:
: collatz ( u -- ) \ print the 3n+1 sequence starting at u until we reach 1 case dup . 1 of endof dup 1 and ?of 3 * 1+ contof 2/ next-case ;
この例では、 シーケンスの現在の値をスタックに保持します。 1 の場合、 of
がトリガーされ、 値が削除され、 case
構造から去ります。 奇数の場合、 ?of
がトリガーされ、 3n+1 が計算され、 contof
で次の反復が開始されます。
それ以外の場合、 数値が偶数の場合は 2 で除算され、 next-case
でループが再開されます。
標準 Forth は、 ネストされない方法での制御構造の使用を許可・サポートします。 まだ完成されてない制御構造に関する情報は、 制御フロー・スタック(control-flow stack)に保存されます。 このスタックは Forth のデータ・スタック上に実装でき、 Gforth はそうしました。
orig エントリは未解決の前方分岐を表し、 dest エントリは後方分岐ターゲットを表します。 いくつかのワードは、 可能なあらゆる制御構造を構築するための基礎となります(呼び出しやコルーチンやバックトラッキングのような、 ストレージを必要とする制御構造を除く)。
IF
( compilation – orig ; run-time f – ) core “IF”
実行時(run-time)、 f=0 の場合、 (コンパイル時に) orig を消費する THEN
(または
ELSE
) の後から実行が続行されます。 それ以外の場合は、 IF
の直後に続きます(see Selection)。
AHEAD
( compilation – orig ; run-time – ) tools-ext “AHEAD”
実行時、 (コンパイル時に) orig を消費する THEN
の後から実行が続行されます(訳注: つまり、 単純に
THEN
へジャンプする)。
THEN
( compilation orig – ; run-time – ) core “THEN”
(コンパイル時に) orig をプッシュした IF
または AHEAD
または ELSE
または
WHILE
は、 THEN
の直後にジャンプします(see Selection)。
BEGIN
( compilation – dest ; run-time – ) core “BEGIN”
(コンパイル時に) dest を消費する UNTIL
または AGAIN
または REPEAT
は、
BEGIN
の直後へジャンプします(see Simple Loops)。
UNTIL
( compilation dest – ; run-time f – ) core “UNTIL”
実行時、 f=0 の場合、 (コンパイル時に) dest をプッシュした BEGIN
の直後から実行が続行されます。
それ以外の場合は、 UNTIL
の直後から実行が続行されます(see Simple Loops)。
AGAIN
( compilation dest – ; run-time – ) core-ext “AGAIN”
実行時、 (コンパイル時に) dest をプッシュした BEGIN
の直後から実行が続行されます(see Simple Loops)。
CS-PICK
( orig0/dest0 orig1/dest1 ... origu/destu u – ... orig0/dest0 ) tools-ext “c-s-pick”
CS-ROLL
( destu/origu .. dest0/orig0 u – .. dest0/orig0 destu/origu ) tools-ext “c-s-roll”
CS-DROP
( dest – ) gforth-1.0 “CS-DROP”
標準ワードの CS-PICK
や CS-ROLL
を使用すると、 移植可能な方法で制御フロー・スタックを操作できます。
これら無しだと制御フロー・エントリが占めるスタック項目の数を知る必要があります(多くのシステムは 1 つのセルを使用します。 Gforth では現在 4
つを使用しますが、 これは将来変更される可能性があります)。
orig は 1 回だけ解決する必要があるため、 CS-PICK
は dest を pick することしかできず、 かつ、
CS-DROP
は dest を drop することしかできません。
一部の標準の制御構造ワードは、 以下のワード群から構築されます:
ELSE
( compilation orig1 – orig2 ; run-time – ) core “ELSE”
実行時、 (コンパイル時に) orig を消費する THEN
の直後から実行が続行されます。 orig1 をプッシュした
IF
または AHEAD
または ELSE
または WHILE
は、 ELSE
の直後にジャンプします(see Selection)。
WHILE
( compilation dest – orig dest ; run-time f – ) core “WHILE”
実行時、 f=0 の場合、 (コンパイル時の) orig を消費する REPEAT
(または THEN
または ELSE
) の直後から実行が継続されます。 それ以外の場合は、 WHILE
の直後から実行されます(see Simple Loops)。
REPEAT
( compilation orig dest – ; run-time – ) core “REPEAT”
実行時、 (コンパイル時に) dest をプッシュした BEGIN
の直後から実行が続行されます。 orig
をプッシュした WHILE
または IF
または AHEAD
または ELSE
は、
REPEAT
の直後にジャンプします(see Simple Loops)。
Gforth は、さらにいくつかの制御構造ワードを追加します:
ENDIF
( compilation orig – ; run-time – ) gforth-0.2 “ENDIF”
THEN
と同一です。
?dup-IF
( compilation – orig ; run-time n – n| ) gforth-0.2 “question-dupe-if”
これは、スタック・チェッカー(stack checker)などのツールでより適切に処理できるため、 イディオム「?DUP
IF
」の代替として推奨されます。 しかも、 ?DUP IF
より速いです。
?DUP-0=-IF
( compilation – orig ; run-time n – n| ) gforth-0.2 “question-dupe-zero-equals-if”
制御構造ワードのもう一つのグループ:
case
( compilation – case-sys ; run-time – ) core-ext “case”
case
構造の開始。
endcase
( compilation case-sys – ; run-time x – ) core-ext “end-case”
case
構造を終わらせます。 x を drop して、endcase
の後ろへ進みます。 x の drop は、
元の(of
のみの)case
構造では便利ですが、 他の場合(特に ?of
を使用する場合)では(drop
する為の) x を明示的に指定する必要がある場合があります。
next-case
( compilation case-sys – ; run-time – ) gforth-1.0 “next-case”
一致する case
にジャンプして、 case
ループを再開します。 endcase
とは異なり、
next-case
はセルを drop しないことに注意してください。
of
( compilation – of-sys ; run-time x1 x2 – |x1 ) core-ext “of”
x1=x2 の場合は続行します(両方を drop します)。 それ以外の場合は、 x1 をスタック上に残し、 endof
または
contof
の後ろにジャンプします。
?of
( compilation – of-sys ; run-time f – ) gforth-1.0 “question-of”
f が true の場合は続行します。 それ以外の場合は、 endof
または contof
の後ろにジャンプします。
endof
( compilation case-sys1 of-sys – case-sys2 ; run-time – ) core-ext “end-of”
endcase
/next-case
の後ろにジャンプして、 囲んでいる case
構造を終了(exit)します。
contof
( compilation case-sys1 of-sys – case-sys2 ; run-time – ) gforth-1.0 “cont-of”
囲んでいる case
にジャンプして、 case
ループを再開します。
内部的には、 of-sys は orig
で、 case-sys はセルとスタック深さ情報と、0 個以上の
orig
と、 dest
です。
読みやすさを確保するために、 任意の制御構造を直接作成せず、 必要な制御構造に対して新しい制御構造ワードを定義し、 プログラム内でこれらのワードを使用することをお勧めします。たとえば、 以下のように書く代わりに:
BEGIN ... IF [ 1 CS-ROLL ] ... AGAIN THEN
以下のように制御構造のワードを定義することをお勧めします。 例:
: WHILE ( DEST -- ORIG DEST ) POSTPONE IF 1 CS-ROLL ; immediate : REPEAT ( orig dest -- ) POSTPONE AGAIN POSTPONE THEN ; immediate
そして、 次に、 これらを使用して制御構造を作成します:
BEGIN ... WHILE ... REPEAT
このほうがずっと読みやすいですよね。 もちろん、 REPEAT
と WHILE
は定義済みなので、
この例を見て改めて定義する必要はありません。
呼び出す定義の名前を記述するだけで定義を呼び出すことができます。 通常、 定義はそれ自身の定義中は表示されません。 直接再帰的な定義を記述したい場合は、
recursive
を使用して現在の定義を見えるようにする(使えるようにする)か、 recurse
を使用して現在の定義を直接呼び出すことができます。
recursive
( compilation – ; run-time – ) gforth-0.2 “recursive”
現在の定義中の定義をその定義内で呼び出せるように(表示できるように)し、 それ自体を再帰的に呼び出せるようにします。
recurse
( ... – ... ) core “recurse”
現在の定義の別名(alias)。
これらのワードの使用例については See Recursion を参照してください。
プログラミング・スタイル・メモ:
著者は、recurse
よりも recursive
を使用することを好みます。
名前で定義を呼び出す方が、 やや難解な recurse
よりも説明的であるためです(名前が適切に選択されていれば)。
たとえば、クイックソートの実装では、「今、再帰呼び出しを行う」(now do a recursive
call)と読むよりも、「今、パーティションをソートする」(now sort the
partitions)と読む(そして考える)方がはるかに優れています。
相互再帰(mutual recursion)の場合は、 以下のように defer
ワードを使用します:
Defer foo : bar ( ... -- ... ) ... foo ... ; :noname ( ... -- ... ) ... bar ... ; IS foo
defer された ワードについては、 Deferred Words で詳しく説明します。
定義の終わりに達するか、 EXIT
に遭遇すると、 現在の定義は呼び出し元の定義に制御を返します。
EXIT
( compilation – ; run-time nest-sys – ) core “EXIT”
呼び出し元の定義に戻る(return): 通常、 定義から速やかに戻るのを強制する方法として使用されます。 EXIT
する前に、
リターン・スタックをクリーンアップし、 未処理の ?DO
...LOOP
を UNLOOP
する必要があります。
ローカル変数(local)が無い場合に exit
のように動作するティック可能なワード(tickable word)には
;s
を使用します。
?EXIT
( – ) gforth-0.2 “?EXIT”
f が true の場合、 呼び出し元の定義に戻ります(return)。
;s
( R:w – ) gforth-0.2 “semis”
EXIT
によってコンパイルされたプリミティブ。
ワードが処理できないエラー状態を検出した場合、 例外を投げる(throw
)ことができます。 最も単純なケースでは、
これによりプログラムが終了し、適切なエラーが報告されます。
throw
( y1 .. ym nerror – y1 .. ym / z1 .. zn error ) exception “throw”
nerror が 0 の場合は、 それを drop して続行します。 それ以外の場合は、 動的に囲んでいる次の例外ハンドラー(next dynamically enclosing exception handler)に制御を移し、 それに応じてスタックをリセットし、 nerror をプッシュします。
fast-throw
( ... wball – ... wball ) gforth-experimental “fast-throw”
軽量の throw
バリエーション: ゼロ以外のみに使用され、 バックトレースを保存したり、 欠落している catch
を扱ったりしません。
throw
は、スタック上のセル・サイズのエラー番号数値を消費します。 標準 Forth
には事前定義されたエラー番号がいくつかあります(errors.fs 参照)。 Gforth (および他のほとんどのシステム)では、
さまざまなワードによって生成された ior をエラー番号として使用できます(たとえば、 allocate
の一般的な使用法は
allocate throw
です)。 Gforth は、 (適切なエラー報告付きで)独自のエラー番号を定義するための
Exception
というワードも提供します。 このワードの標準 Forth バージョン(ただしエラー・メッセージなし) は
compat/excel.fs
で入手できます。 最後に、 あなた独自のエラー番号(-4095 〜 0
の範囲以外の任意の番号)を使用できますが、 表示されるのは適切なエラー・メッセージではなく、数字のみです。 たとえば、
以下のことを試してみてください:
-10 throw \ 標準で定義済 -267 throw \ システムで定義済 s" my error" exception throw \ ユーザー定義 7 throw \ 思いつくままの任意の番号
exception
( addr u – n ) gforth-0.2 “exception”
n は、 -4095 〜 -256 の範囲内で以前に使用されていなかった throw
値です。
Exception
を連続して呼び出すと、 連続して減少する数値が返されます。 Gforth は文字列 addr u
をエラー・メッセージとして使用します(訳注: 同じエラーメッセージを使いまわすには、 s" hoge err msg" exception
constant hoge-error hoge-error ! などとして得られた hoge-error を使いまわす。 hoge-error
throw 等する)
エラー番号を文字列に変換するためのワード(通常は POSIX の strerror
をモデルにしたもの)がある場合もあります。
以下のワードを使用すると、 これらの文字列を Gforth のエラー処理に取り込むことができます:
exceptions
( xt n1 – n2 ) gforth-1.0 “exceptions”
throw時: xt ( +n -- c-addr u )
は、 0<=n<n1
の範囲のローカルのエラー・コードをエラー・メッセージに変換します。 Exceptions
は、 n2-n1<n3<=n2 の範囲で
n1 個(0 〜 n1)のエラー・コードを予約します。 n2 に、 対応する Gforth エラー・コード(ローカルのエラー番号 0 に対応。
つまり、 0<=n<n1 の範囲のローカルのエラー・コードに 対応する Gforth エラー・コードは n2 <= n3 <
n2+n1)を返します。 (後の時点で)その範囲に対応した Gforth エラー・コード n3 が throw されると、 n2-n3
がプッシュされ(つまりローカルのエラー番号に変換した値をプッシュし)、 xt が実行されて、 xt
がエラー・メッセージを生成します(訳注: minos2/pulse-audio.fs 等で確認。 xt にC-interface ワードをセットし、
外部C言語ライブラリーのエラー・メッセージを gforth 内から表示するのに使っているようだ)
たとえば、 C言語ライブラリーの errno
エラー (および strerror
を使用した変換) がまだ Gforth
で直接サポートされていないとした場合、 以下のようにして strerror
を gforth と結び付けることができます:
' strerror 1536 exceptions constant errno-base : errno-ior ( -- n ) \ n は errno の値に対応する Gforth ior を求めなければならないので、 \ ここでerrno 範囲と Gforth ior の範囲の間で変換する必要があります。 \ ERRNO は Gforth ワードではないため、 \ それにアクセスするには C インターフェイスを使用する必要があります。 errno errno-base over - swap 0<> and ;
C言語の関数(C言語インターフェイス(C interface)を使用)を呼び出し、 その戻り値がエラーが発生したことを示している場合、
errno-ior throw
を実行して、適切なエラー・メッセージ (“Permission denied”
など)を含む例外を生成できます。
フラグが true の場合、 特定の err# でエラーを投げる(THROW
)一般的な慣用句は以下のとおりです:
( flag ) 0<> err# and throw
あなたのプログラムで、 例外をキャッチする例外ハンドラーを提供できます。 例外ハンドラーを使用すると、 問題を修正したり、
一部のデータ構造をクリーンアップして例外を次の例外ハンドラーに投げたり(throw)することができます。 throw
は動的に最も内側の例外ハンドラー(the dynamically innermost exception
handler)にジャンプすることに注意してください。 システムの例外ハンドラーは最も外側にあり、
エラーを出力してコマンド・ラインの通訳(interpretation)を再開するだけです(または、 バッチ・モード(つまり、
シェル・コマンド・ラインの処理中)では Gforth を終了します)。
例外をキャッチする標準 Forth での方法は catch
です:
catch
( x1 .. xn xt – y1 .. ym 0 / z1 .. zn error ) exception “catch”
xt を実行します。 実行から正常に戻った場合、 catch
はスタックに 0 をプッシュします。 throw
を介して実行が戻った場合、 すべてのスタックは catch
へ入る時点の深さにリセットされ、 TOS (xt の位置) は
throw コードに置き換えられます。
nothrow
( – ) gforth-0.7 “nothrow”
再 throw しない catch
または endtry
の後ろでこれ (または標準のシーケンス [']
false catch 2drop
) を使用します。 これにより、 次の throw
でバックトレースが確実に記録されます。
例外ハンドラーの最も一般的な使用法は、 エラーが発生したときに状態をクリーンアップすることです。 例:
base @ >r hex \ actually the HEX should be inside foo to protect \ against exceptions between HEX and CATCH ['] foo catch ( nerror|0 ) r> base ! ( nerror|0 ) throw \ pass it on
myerror
というエラー番号を処理するための catch
の使用は以下のようになります:
['] foo catch CASE myerror OF ... ( do something about it ) nothrow ENDOF dup throw \ default: pass other errors on, do nothing on non-errors ENDCASE
コードを別のワードでくるむ要があるのは面倒な場合が多いため、 Gforth では代替構文を提供しています:
TRY code1 IFERROR code2 THEN code3 ENDTRY
これは、 code1 を実行しします。 code1 が正常に完了すると、 code3 の実行へ続きます。 code1
または、 endtry
より前で例外があった場合、 スタックは try
時の深さにリセットされ、 throw
された値をデータ・スタックにプッシュし、 code2 の実行に続き、 そして、 最終的に code3 に到達します。
try
( compilation – orig ; run-time – R:sys1 ) gforth-0.5 “try”
例外キャッチ領域の開始
endtry
( compilation – ; run-time R:sys1 – ) gforth-0.5 “endtry”
例外キャッチ領域の終わり
iferror
( compilation orig1 – orig2 ; run-time – ) gforth-0.7 “iferror”
例外処理コードを開始します(try
と endtry
の間に例外がある場合に実行されます)。 この部分は
then
で終了する必要があります。
code2 が必要ない場合は、 iferror then
の代わりに restore
を記述できます:
TRY code1 RESTORE code3 ENDTRY
先程の例をこの構文で身綺麗にしてみます:
base @ { oldbase } TRY hex foo \ now the hex is placed correctly 0 \ value for throw RESTORE oldbase base ! ENDTRY throw
このバリエーションの追加の利点は、 restore
と endtry
の間の例外(たとえば、 ユーザーが
Ctrl-C を押すことによる例外)でも、 restore
の直後へコードの実行が移ることです。 ゆえに、
いかなる状況であっても base は復元されます。
ただし、 このコード自体が例外を引き起こさないようにする必要があります。 そうしないと、 iferror
/restore
コードがループします。 さらに、 iferror
/restore
コードで必要なスタックの内容が try
と
endtry
の間のあらゆる場所に存在することも確認する必要があります。 この例では、 これは try
の前にデータをローカル変数(local)に置くことによって実現されます(リターン・スタック上の例外フレーム(sys1)が邪魔なのでリターン・スタックは使用できません)。
この種の使用法は、 Lisp の unwind-protect
と同様のものです。
もし、あなたが、 この例外再開始(exception-restarting)の振る舞いを望まない場合は、 以下のようにしてください:
TRY code1 ENDTRY-IFERROR code2 THEN
code1 に例外がある場合は code2 が実行され、 それ以外の場合は then
の後ろ (または
else
分岐の可能性あり) から実行が続行されます。 これはバージョン 0.7 より前の Gforth では以下の構成要素に該当します
TRY code1 RECOVER code2 ENDTRY
つまり、 この recover
を使用しているコードを直に try ... entry-iferror ... then
へと置き換えることができます。 ただし、 その置き換え作業中に他の try
バリエーションのいずれかを使用した方が良いかどうかも検討することをお勧めします。
移行を容易にするために、 Gforth は 2 つの互換性ファイルを提供します: 1つ目は endtry-iferror.fs で、
古いシステム用に try ... endtry-iferror ... then
構文を提供します(ただし、 iferror
または restore
は提供しません)。 2つ目の recover-endtry.fs は、 新しいシステム上で古い構文の
try ... recover ... endtry
構文を提供するので、 古いプログラムを実行するための一時しのぎとして使用できます。
どちらのファイルもどのシステムでも動作します(システムが、 実装する構文を既に定義済みの場合は何も行わないだけです)。 そのため、
古いシステムと新しいシステムを混在させて使用している場合でも、 これらのファイルのいずれかを無条件に require
することができます。
restore
( compilation orig1 – ; run-time – ) gforth-0.7 “restore”
コードの復元(restore)を開始します。 これは、 例外がある場合と無い場合に行われます。
endtry-iferror
( compilation orig1 – orig2 ; run-time R:sys1 – ) gforth-0.7 “endtry-iferror”
例外キャッチ領域を終了し、 その領域外で例外処理コードを開始します(try
と endtry-iferror
の間に例外がある場合に実行されます)。 この部分は then
(または else
...then
)
で終了する必要があります。
ここで、 エラー処理の例を以下に示します:
TRY foo ENDTRY-IFERROR CASE myerror OF ... ( do something about it ) nothrow ENDOF throw \ pass other errors on ENDCASE THEN
プログラミング・スタイル・メモ:
いつものように、 エラーを渡すための throw
の後、 または ENDTRY
の後のいずれか(または、catch
を使用する場合は、エラーを処理するための選択構造の終了後)で、
スタックの深さが静的に明白であることを保証する必要があります。
throw
の代替は 2 つあります: Abort"
は条件付きでエラー・メッセージを提供できます。
Abort
は「中止」(Abort)エラーを生成するだけです。
これらのワードの問題は、 例外ハンドラーが、 異なる abort"
を区別できないことです。 例外ハンドラーにとってはそれらは
-2 throw
のように見えるだけです(標準のプログラムではエラー・メッセージにアクセスできません)。 同様に、
abort
は例外ハンドラーに対して -1 throw
のように見えます。
ABORT"
( compilation ’ccc"’ – ; run-time f – ) core,exception-ext “abort-quote”
f のいずれかのビットがゼロ以外の場合、 -2 throw
の機能を実行し、 例外スタック上例外フレームがない場合は文字列
ccc を表示します。
abort
( ?? – ?? ) core,exception-ext “abort”
-1 throw
.
実行を中止する必要があるほど深刻でない問題の場合は、 警告を表示するだけで済みます。 変数 warnings
を使用すると、
表示される警告の数を調整できます。
WARNING"
( compilation ’ccc"’ – ; run-time f – ) gforth-1.0 “WARNING"”
f がゼロ以外の場合、 警告メッセージとして文字列 ccc を表示します。
warnings
( – addr ) gforth-0.2 “warnings”
以下の警告レベルをセットしてください
0
警告オフ
-1
通常警告オン
-2
初心者警告オン
-3
偏執狂的警告オン
-4
全ての警告をエラーとして扱います(初心者警告を含む)
定義ワード(defining word)は、 ディクショナリーに新しいエントリを作成することによって Forth を拡張するために使用されます。
CREATE
¶定義ワードは、 ディクショナリーに新しいエントリを作成するために使用されます。 最も単純な定義ワードは CREATE
です。
CREATE
は以下のように使用します:
CREATE new-word1
CREATE
はパース・ワード(parsing word)です。
つまり、入力ストリームから引数(argument)を受け取ります(この例では new-word1
です)。 CREATE
は
new-word1
のディクショナリー・エントリを作成します。 new-word1
が実行される時は、
アドレスがスタックに残されるだけです。 そのアドレスは、 new-word1
が定義された時点のデータ空間ポインター(HERE
)の値を表します。したがって、 CREATE
は名前をメモリー領域のアドレスに関連付ける方法です。
Create
( "name" – ) core “Create”
注意: 標準 Forth は、 create
に対してのみ、 そのボディ部分がディクショナリのデータ空間(つまり、 here
や allot
などが機能する空間。 see Dictionary allocation)にあることを保証することに注意してください。 また、 標準 Forth では、 does>
で変更できるのは
create
で作成されたワードのみで(see User-defined Defining Words)、 標準 Forth の
>body
は create
されたワードにのみ適用できます。
この new-word1 の例を拡張して、 データ空間にメモリーを少々確保すると、 最終的には variable のような代物になります。 これを行う 2 つの異なる方法を以下に示します:
CREATE new-word2 1 cells allot \ 1 セル予約 - 初期値未定義 CREATE new-word3 4 , \ 1 セル予約 かつ (4で)初期化
これらの変数は、 以下のように @
(「フェッチ」(fetch;取り出す)) と !
(「ストア」(store;格納する)) を使用して検査(examine)および変更(modify)できます:
new-word2 @ . \ get address, fetch from it and display 1234 new-word2 ! \ new value, get address, store to it
同様のメカニズムを使用して配列を作成できます。 たとえば、 80 文字のテキスト入力バッファーです:
CREATE text-buf 80 chars allot text-buf 0 chars + c@ \ the 1st character (offset 0) text-buf 3 chars + c@ \ the 4th character (offset 3)
メモリーに適切な領域を割り当てることで、 思いつく限りの複雑なデータ構造を構築できます。 これについてさらに詳しく説明し、 それを容易にする Gforth ツールについて知りたい場合は、 See Structures を参照ください。
前のセクションでは、 一連のコマンドを使用して変数を生成する方法を説明しました。 最終的な改良として、 (次のセクションの主題を先取りして)そのコード・シーケンス全体を定義ワードでまとめることができ、 新しい変数の作成が容易になります:
: myvariableX ( "name" -- a-addr ) CREATE 1 cells allot ; : myvariable0 ( "name" -- a-addr ) CREATE 0 , ; myvariableX foo \ variable foo starts off with an unknown value myvariable0 joe \ whilst joe is initialised to 0 45 3 * foo ! \ set foo to 135 1234 joe ! \ set joe to 1234 3 joe +! \ increment joe by 3.. to 1237
当然のことながら、 Forth にはすでに Variable
の定義があるため、 myvariable
を定義する必要はありません。 標準 Forth は、 Variable
が作成時に初期化されることを保証しません(つまり、
myvariableX
のように振る舞う可能性があります)。 対照的に、Gforth の Variable
は変数を 0
に初期化します(つまり、 myvariable0
とまったく同じように振る舞います)。 Forth は、
2倍長変数と浮動小数点変数に対して、 それぞれ 2Variable
と fvariable
も提供します。
これらは、Gforth ではそれぞれ 0. と 0e に初期化されます。 Variable
を使用してブール値を保存する場合、
on
と off
を使用してその状態を切り替えることができます。
Variable
( "name" – ) core “Variable”
name を定義し、 addr で始まるセルを予約します。 name 実行時: ( -- addr )
AVariable
( "name" – ) gforth-0.2 “AVariable”
variable
と同様に機能しますが、
(クロス・コンパイルされたコードで使用される場合)その変数に格納されているセルがアドレスであることをクロス・コンパイラーに伝えます。
2Variable
( "name" – ) double “two-variable”
fvariable
( "name" – ) floating “f-variable”
最後に、 任意の長さのバッファーは以下のようになります
buffer:
( u "name" – ) core-ext “buffer-colon”
name を定義し、addr から始まる u バイトを予約します。 name 実行時: ( -- addr
)
なお、 Gforth は予約したバイトを 0 に初期化しますが、 標準では保証されません。
constant
を使用すると、 固定値を宣言し、 名前でそれを参照できます。 例:
12 Constant INCHES-PER-FOOT 3E+08 fconstant SPEED-O-LIGHT
Variable
は読み取りと書き込みの両方ができるため、 その実行時の振る舞いは、 現在の値を操作できるアドレスを提供することです。
それとは対照的に、 Constant
の値は一度宣言すると変更できないため、 アドレスを指定する必要はありません14
– 定数の値を直接返す方が効率的です。 そして正にそのとおりになります。 つまり、
定数の実行時の効果は、その値をスタックの頂上に置くことです((Constant
を実装する方法の1つは User-defined Defining Words で見つけることができます)。
Forth は、それぞれ2倍長定数と浮動小数点定数を定義するための 2Constant
と fconstant
も提供します。
Constant
( w "name" – ) core “Constant”
定数 name を値 w で定義します。
name 実行時: – w
AConstant
( addr "name" – ) gforth-0.2 “AConstant”
constant
と似ていますが、 アドレスのための定数を定義します(これはクロス・コンパイラーでのみ違いが生じます)。
2Constant
( w1 w2 "name" – ) double “two-constant”
fconstant
( r "name" – ) floating “f-constant”
Forth の定数は、他のプログラミング言語の定数とは異なる振る舞いをします。 他の言語では、 定数(アセンブラーの EQU や C の #define など)はコンパイル時にのみ存在します。 実行プログラム(executable program)では、 定数は即値(absolute number)に変換されているため、 シンボリック・デバッガを使用しない限り、 その数値がどのような抽象的なものを表しているかを知ることは不可能です。 Forth では、 定数はヘッダー空間にエントリを持ち、 それを使用するコードが定義された後もそこに残ります。 実際、 実行時に機能する義務があるため、 それをディクショナリに残しておく必要があります。 例:
12 Constant INCHES-PER-FOOT : FEET-TO-INCHES ( n1 -- n2 ) INCHES-PER-FOOT * ;
ここで、 FEET-TO-INCHES
が実行されると、 定数 INCHES-PER-FOOT
に関連付けられた xt
が実行されます。 see
を使用して FEET-TO-INCHES
の定義を逆コンパイルすると、
INCHES-PER-FOOT
を呼び出していることがわかります。 一部の Forth
コンパイラーは、定数を使用する場所にインライン展開(in-lining)することによって定数を最適化しようとします。 以下のようにして Gforth
に定数をインライン化するように強制できます:
: FEET-TO-INCHES ( n1 -- n2 ) [ INCHES-PER-FOOT ] LITERAL * ;
ここで、 see
を使用して FEET-TO-INCHES
の このバージョンを逆コンパイルすると、
INCHES-PER-FOOT
が存在しないことがわかります。 これがどのように機能するかを理解するには、
Interpret/Compile states と Literals を読んでください。
この方法で定数をインライン化すると、 実行時間がわずかに改善される可能性があり、 定数がコンパイル時にのみ参照されるようにすることができます。 ただし、 定数の定義はまだディクショナリーに残っています。 一部の Forth コンパイラーは、 一時的なワード(transient words)を保持する 2 番目のディクショナリーを制御するメカニズムを提供し、 メモリー領域を回復するために後でこの 2 番目のディクショナリーを削除できるようしています。 ただし、 これを行う標準の方法はありません。
Value
は Constant
のように動作しますが、 変更することができます。 TO
は、Values
を変更するパース・ワード(parsing word)です。 (標準 Forth ではなく) Gforth では、
>body
を使用しても value
にアクセス(および変更)できます。
ここで幾つか例を示します:
12 Value APPLES \ APPLES を初期値 12 で定義 34 TO APPLES \ APPLES の値を変更。 TO はパース・ワード 1 ' APPLES >body +! \ APPLES をインクリメント。 非標準の使い方 APPLES \ スタック頂上に 35 を置く(はず)
Value
( w "name" – ) core-ext “Value”
name を初期値 w で定義します。 この値は to name
または ->name
で変更できます(訳注: -> と name の間に空白を開けない。 ->name とする。 ワードではなく、
テキスト・インタープリターの認識器(recognizer)機能によるもの)。
name 実行時: – w2
AValue
( w "name" – ) gforth-0.6 “AValue”
value
と似ていますが、 アドレスの為の値を定義します(これはクロス・コンパイラーでのみ違いが生じます)。
2Value
( d "name" – ) double-ext “two-value”
fvalue
( r "name" – ) floating-ext “f-value”
実行時: ( -- r1 )
な name を定義します。 ここで r は初期値です。 値は to
name
または ->name
で変更できます。
TO
( value "name" – ) core-ext “TO”
name の値を value に変更します
+TO
( value "name" – ) gforth-1.0 “+TO”
name の値に value を足し込みます
(訳注: vaLue ではなくて vaRue) value のようなワードでアドレスを取得したい場合があります。 これにはいくつかの欠点があるため、
Gforth では、 これについて明示的に指定し、 varue
を使用して名前を宣言するように求めます(variable と value
の特性を組み合わせたものであるため、 そのように名付けられました)。
Varue
( w "name" – ) gforth-1.0 “Varue”
value
と似ていますが、 addr name
で得たアドレスで値にアクセスすることもできます。 将来的には、
varues の効率が values よりも低くなる可能性があります。
2varue
( x1 x2 "name" – ) gforth-1.0 “2varue”
2value
と似ていますが、 addr name
で得たアドレスで値にアクセスすることもできます。
将来的には、2varues は 2values よりも効率が低くなる可能性があります。
fvarue
( r "name" – ) gforth-1.0 “fvarue”
fvalue
と似ていますが、 addr name
で得たアドレスで値にアクセスすることもできます。
将来的には、fvarues は fvalues よりも効率が低くなる可能性があります。
addr
( "name" – addr ) gforth-1.0 “addr”
varue name または 2varue name または fvarue name のアドレスを提供します。
または wa: ca: da: fa: xta:
のいずれかで定義されたローカル変数 name のアドレスを提供します。
: name ( ... -- ... ) word1 word2 word3 ;
name
というワードを作成し、 実行時に word1 word2 word3
を実行します。 name
は
定義(コロン定義)((colon) definition) です。
上記の説明はやや表面的です。 コロン定義の簡単な例については、 Your first Forth definition を参照してください。 関連する問題の一部についての詳細な説明については、 See Interpretation and Compilation Semantics を参照してください。
:
( "name" – colon-sys ) core “colon”
;
( compilation colon-sys – ; run-time nest-sys – ) core “semicolon”
最終的には自動インライン化を実行する予定ですが、 今のところは以下のようにしてインライン化を実行できます
inline:
( "name" – inline:-sys ) gforth-experimental “inline-colon”
インライン・コロン定義を開始します。 inline:
と ;inline
の間のコードは、
インライン化するコードを(実行するのではなく)コンパイルする必要がありますが、 結果の定義 name は、
インライン化されたコードを実行するコロン定義です。 コンパイルするコードはスタック効果が( -- )
(スタックの深さが変わらない)である必要があることに注意してください。 さもないと、 Gforth が name
のコロン定義を作成しようとしたときにエラーが発生します。
;inline
( inline:-sys – ) gforth-experimental “semi-inline”
inline:
で始まるインライン定義を終了します
例として、 インライン化されたワードを定義し、 以下のようにして使います
inline: my2dup ( a b -- a b a b ) ]] over over [[ ;inline #1. my2dup d. d. : foo my2dup ; #1. foo d. d. see foo
インライン・ワードはマクロ(see Macros)に関連しています。 マクロとの違いは、マクロには即時コンパイル機能(immediate
compilation semantics)があるのに対し、 inline:
で定義されたワードにはデフォルトのコンパイル機能(compilation semantics)があることです。 つまり、
通常はコロン定義内でのみマクロを使用しますが、 inline:
ワードは対話的(interpretively)にも使用できることを意味します。 しかしそれは、 inline:
ワードとしては実行できないいくつかのことをマクロでは実行できることも意味します。 例:
\ Doesn't work: \ inline: endif ]] then [[ ;inline \ Instead, write a macro: : endif ]] then [[ ; immediate
逆に、 非即時コロン定義(non-immediate colon definitions)として問題ないワードについては、
非即時コロン定義として定義するか、 (最大限のパフォーマンスが必要な場合) inline:
ワードとして定義します。
それらをマクロとして定義しないでください。 対話的(interpretively)に適切に使用できなくなります:
: another2dup ]] over over [[ ; immediate \ Doesn't work: \ #1. another2dup d. d.
なぜ inline:
と ;inline
の間にコンパイル・コードを書かなければならないのか疑問に思われるかもしれません。
これは、 上記の my2dup
のようなインライン・ワードの実装が以下のように動作するためです:
: compile-my2dup ( xt -- ) drop ]] over over [[ ; : my2dup [ 0 compile-my2dup ] ; ' compile-my2dup set-optimizer
DROP
や 0
があるのは、 compile-my2dup
が my2dup
ための
compile,
の実装であり、 compile,
は xt を期待する為です(see User-defined compile,
)。
しばしば匿名のワード(anonymous word)を定義したい場合があります。 つまり、 名前無しのワードです。 これは以下のようにします:
:noname
( – xt colon-sys ) core-ext “colon-no-name”
これにより、 終わりの ;
の後にワードの実行トークンがスタックに残ります。 以下は、 defer された ワード(deferred
word)が匿名コロン定義(anonymous colon definition)の xt
で初期化される例です:
Defer deferred :noname ( ... -- ... ) ... ; IS deferred
Gforth は、 2 つの別々のワードを使用して、 これを行う別の方法を提供します:
noname
( – ) gforth-0.2 “noname”
次に定義するワードは匿名になります。
その定義するワードは(noname
がいじる訳ではなくて)入力ストリームからのをそのままを使います(The defining word
will leave the input stream alone)。 その定義したワードの xt は latestxt
で取得します。
latestxt
( – xt ) gforth-0.6 “latestxt”
xt は、 最後に定義されたワードの実行トークンです。
先の例を、 noname
と latestxt
を使用して書き直すことができます:
Defer deferred noname : ( ... -- ... ) ... ; latestxt IS deferred
noname
は、 :
だけでなく、 あらゆる定義ワードで機能します。
latestxt
は、 最後のワードが noname
として定義されていない場合にも機能します。 ただし、
複合ワード(combined words)には機能しません。 また、 これは、
定義のヘッダーが構築されるやいなや有効になる便利なプロパティでもあります。 したがって、 以下のようにすると:
latestxt . : foo [ latestxt . ] ; ' foo .
これは3つの数値を出力: 後ろの2つは同一の数値です。
引用(quotation)は、 別のコロン定義内の匿名コロン定義です。 引用(quotation)は、 catch
や
outfile-execute
など、 実行トークンを消費するワードを扱うときに便利です。 例えば、 以下の
outfile-execute
(see Redirection) の使用例を考えてみましょう:
: some-warning ( n -- ) cr ." warning# " . ; : print-some-warning ( n -- ) ['] some-warning stderr outfile-execute ;
上記のように、 some-warning
をヘルパー・ワードとして定義し、 その xt を outfile-execute
に渡すことができます。 その代わりに、 引用(quotation)を使用して print-some-warning
内でそのようなワードを匿名で定義できます:
: print-some-warning ( n -- ) [: cr ." warning# " . ;] stderr outfile-execute ;
引用(quotation)は [:
と ;]
で囲まれています。 それは実行時に実行トークンを生成します。
[:
( compile-time: – quotation-sys flag colon-sys ) gforth-1.0 “bracket-colon”
引用(quotation)を開始します
;]
( compile-time: quotation-sys – ; run-time: – xt ) gforth-1.0 “semi-bracket”
引用(quotation)を終了します
デフォルトでは、 定義ワードは入力ストリームから定義されるワードの名前を取得します。 しばしば文字列から名前を指定したい場合があります。 これは以下のようにして行うことができます:
nextname
( c-addr u – ) gforth-0.2 “nextname”
次に定義されるワードの名前は c-addr u になります。 定義中のワードは入力ストリームからそのままで nextname
がいじることはありません。
例:
s" foo" nextname create
これは以下と同等です:
create foo
nextname
は、 あらゆる定義ワードで機能します。
既存の定義ワードに基づいて新しい定義ワードを定義できますが、 :
と
create
...does>
/set-does>
は特に柔軟です。 一方、 たとえば、
constant
の子供達は全て単なる定数です。
既存の定義ワードを定義時(defining-time)コードで取り囲み、 その取り囲んだ一連のコードをコロン定義に入れることで、 新しい定義ワードを作成できます。
たとえば、 定義の xt を指定してコロン定義に関する統計を収集するワード stats
があり、
アプリケーション内のすべてのコロン定義で stats
を呼び出す必要があるとします。 これには、 以下のように :
の新しいバージョンを定義して使用できます:
: stats ( xt -- ) DUP ." (Gathering statistics for " . ." )" ... ; \ other code : my: : latestxt postpone literal ['] stats compile, ; my: foo + - ;
my:
を使用して foo
を定義する場合、 以下のステップが実行されます:
my:
が実行されます。
:
(my:
と latestxt
の間にあるもの) が実行され、 :
として、
いつもと同じことを行います。 名前を得るために入力ストリームをパースし、 名前 foo
のディクショナリー・ヘッダーを構築し、
state
をインタープリター状態からコンパイル状態に切り替えます。
latestxt
というワードが実行されます。 定義中のワード(foo
)の xt をスタックに置きます。
postpone literal
によって生成されたコードが実行されます。 これにより、 スタック上の値(foo
の
xt)が foo
のコード領域にリテラルとしてコンパイルされます。
['] stats
は、 my:
の定義時に、 リテラルを my:
の定義内にコンパイルします(訳注:
そして、 my:
の実行時は stats
の xt をスタックに積みます)。 そして、
compile,
が実行されると、 foo
のコード領域に、 上記に続けて stats
の xt
をコンパイルします15。
my:
の実行が完了し、 制御がテキスト・インタープリターに戻ります。
テキスト・インタープリターはコンパイル状態にあるため、 後続のテキスト + -
は foo
の定義にコンパイルされ、
いつものように ;
によって定義が終了します。
see
を使用すると、 my:
を使用して定義されたワードを逆コンパイルし、 それが通常の :
定義とどのように異なるかを確認できます。 例:
: bar + - ; \ like foo but using : rather than my: see bar : bar + - ; see foo : foo `foo stats + - ;
`foo
は ['] foo
を記述する別の方法です。
定義ワードで定義されたワードを、 標準の定義ワードで定義されたワードとは異なる振る舞いにしたい場合は、 以下のように定義ワードを記述できます:
: def-word ( "name" -- ) CREATE code1 DOES> ( ... -- ... ) code2 ; def-word name
このコード断片は 定義ワード(defining word) def-word
を定義し、 そして、 それを実行します。
def-word
が実行されると、 新しいワード name
が CREATE
され、コード code1
が実行されます。 コード code2 は現時点では実行されません。 name
というワードは、 def-word
の子供(child)と呼ばれることもあります。
name
を実行すると、 name
の本体のアドレスがデータ・スタックに置かれ、 code2
が実行されます(name
の本体のアドレスは、 CREATE
の直後に HERE
が返すアドレス、 つまり、
create
されたワードがデフォルトで返すアドレスです)。
def-word
を使用して、 同様に動作する一連の子ワード達を定義できます。 つまり、 これらはすべて code2
によって決定される共通の実行時の振る舞いを持っています。 通常、 code1 シーケンスは、 子ワードの本体にデータ領域を構築します。
データの構造は def-word
のすべての子に共通ですが、 データ値は各子ワードに固有で、 そして、 プライベートです。
子ワードが実行されると、 そのプライベート・データ領域のアドレスが TOS 上のパラメーターとして渡され、 code2
によって使用および操作されます16。
定義ワードを構成する 2 つのコード断片は、 完全に別々の 2 つの時点で動作(実行)されます:
def-word
と name
の振る舞いを理解するもう 1 つの方法は、 以下のように定義することです:
: def-word1 ( "name" -- ) CREATE code1 ; : action1 ( ... -- ... ) code2 ; def-word1 name1
この場合、 name1 action1
を使用することは、 name
を使用することと同じです。
def-word
を記述するもう 1 つの方法が引用(see Quotations)です:
: def-word ( "name" -- ; name execution: ... -- ... ) create code1 [: code2 ;] set-does> ;
Gforth は実際は does>
を使用してコードを後者のコードと同等のコードにコンパイルします。 set-does>
アプローチの利点は、 その背後に他のコードを配置でき、 回避策を必要とせずに制御構造内でそれを使用できることです。 欠点は、 Gforth
固有であることです。
典型的な例は、 以下のようにして CONSTANT
を定義できることです:
: CONSTANT ( w "name" -- ) CREATE , DOES> ( -- w ) @ ;
または同等の
: CONSTANT ( w "name" -- ; name execution: -- w ) create , ['] @ set-does> ;
5 CONSTANT five
を使用して定数を作成すると、 一連の定義時アクションが実行されます。 最初に新しいワード
five
が作成され、 次に ,
を使用して five
の本体に値 5 が配置されます。
five
が実行されると、 その本体のアドレスがスタックに置かれ、 @
は値 5 を取得します。 ワード
five
にはそれ自体のコードはありません。 ワード five
には、 データ・フィールドと、 引用(quotation)
の xt または @
の xt が含まれるだけです。
このセクションの最後の例は、 CREATE
されたワードで予約されている空間はデータ空間であるため、
標準プログラムによる読み取りと書き込みの両方が可能であることを思い出していただくことを目的としています17:
: foo ( "name" -- ) CREATE -1 , DOES> ( -- ) @ . ; foo first-word foo second-word 123 ' first-word >BODY !
first-word
が CREATE
されたワードであった場合、
単純にそれを実行してデータ・フィールドのアドレスを取得できます。 ただし、 DOES>
アクションを持つように定義されているため、
その実行機能(execution semantics)は、 それらの DOES>
アクションを実行することになります。
データ・フィールドのアドレスを取得するには、 '
を使用して xt を取得し、 次に >BODY
を使用して xt
をデータ・フィールドのアドレスに変換する必要があります。 first-word
を実行すると、 123
が表示されます。
second-word
を実行すると、-1
が表示されます。
上記の例では、 DOES>
の後のスタック・コメントは、 DOES>
に続くコードのスタック効果ではなく、
定義されたワードのスタック効果を指定しています(DOES>
に続くコードは、 本体のアドレスがスタックの先頭にある事を期待しています。
これはスタック・コメントには反映されません)。 これは著者が使用し、 推奨している規則です(ただし、
スタック効果の指定にローカル変数宣言を使うのと少々衝突する)。
CREATE..DOES>
¶あなたは、 この機能をどのように使用するのか不思議に思うかもしれません。 いくつかの使用パターンを以下に示します:
とあるコードのフレーズが複数回出現し、 その意味を同一視できる場合は、 それをコロン定義としてくくり出します。 類似のコロン定義が見つかった場合は、
CREATE..DOES>
を使用してそれらをファクタリングできます。 たとえば、 アセンブラは通常、
非常によく似たいくつかのワードを定義します:
: ori, ( reg-target reg-source n -- ) 0 asm-reg-reg-imm ; : andi, ( reg-target reg-source n -- ) 1 asm-reg-reg-imm ;
これは以下のようにファクタリングできます:
: reg-reg-imm ( op-code -- ) CREATE , DOES> ( reg-target reg-source n -- ) @ asm-reg-reg-imm ; 0 reg-reg-imm ori, 1 reg-reg-imm andi,
CREATE..DOES>
の別の観点は、
これをワードのパラメーターの一部を提供する素朴な方法(関数型言語コミュニティではカリー化(currying)として知られています)とみなすことです。
たとえば、 +
には 2 つのパラメーターが必要ですが、そのうち 1 つのパラメーターを固定した +
のバージョンを作成するには、 以下のようにします:
: curry+ ( n1 "name" -- ) CREATE , DOES> ( n2 -- n1+n2 ) @ + ; 3 curry+ 3+ -2 curry+ 2-
CREATE..DOES>
¶DOES>
( compilation colon-sys1 – colon-sys2 ) core “does”
以下は、 一つの定義内で CREATE
と DOES>
の両方を使用する必要がないことを意味します。
DOES>
部分を別の定義に置くことができます。 これにより、 たとえば、 さまざまな DOES>
部分から選択するようにすることができます:
: does1 DOES> ( ... -- ... ) code1 ; : does2 DOES> ( ... -- ... ) code2 ; : def-word ( ... -- ... ) create ... IF does1 ELSE does2 ENDIF ;
この例では、 does1
と does2
のどちらを使用するかの選択は、 子ワードが CREATE
される定義時に行われます。
注意: 定義を終了する does>
の性質により、 追加の定義 does1
と does2
を導入する必要があることに注意してください。 set-does>
を使用するとこれを回避できます:
: def-word ( ... -- ... ) create ... IF [: code1 ;] set-does> ELSE [: code2 ;] set-does> ENDIF ;
標準のプログラムでは、 最後のワードが CREATE
で定義されている場合にのみ DOES>
部分を適用できます。
Gforth では、 DOES>
部分は、 いかなる場合でも定義された最後のワードの振る舞いをオーバーライドします。
標準のプログラムでは、 DOES>
はコロン定義でのみ使用できます。 Gforth では、 一種のワンショット・モードとして、
インタープリター状態で使用することもできます。 例:
CREATE name ( ... -- ... ) initialization DOES> code ;
これは、 以下の標準のと同等です:
:noname DOES> code ; CREATE name EXECUTE ( ... -- ... ) initialization
Gforth は対話時に打ち込んだコード内での引用(quotations)もサポートしており、 引用は現在の定義を保存および復元するため、 上記の例を以下のように記述することもできます:
CREATE name ( ... -- ... ) initialization [: code ;] set-does>
set-does>
( xt – ) gforth-1.0 “set-does>”
現在のワードを変更して、 本体アドレスをプッシュしてから xt を実行します。 それに応じて compile,
の実装も変更します。 より効率的な実装が必要な場合、 この後で set-optimizer
を呼び出します。
>body
( xt – a-addr ) core “to-body”
xt で表されるワードの本体(body)のアドレス(ワードのデータ・フィールドのアドレス)を取得します。
MIPS 逆アセンブラー(arch/mips/disasm.fs)には、 非常に反復的なスキームに従って逆アセンブルするための多くのワードが含まれています(訳注: very repetetive scheme; repetitiveのスペルミスっぽい):
:noname disasm-operands s" inst-name" type ; entry-num cells table + !
もちろん、 これは共通点をくくり出して以下のような定義を可能にするというアイデアを刺激します:
disasm-operands entry-num table define-inst inst-name
通常、 パラメーター disasm-operands と table は相関しています。 さらに、 著者が逆アセンブラーを作成する前に、 以下のような命令を定義するコードがすでに存在していました:
entry-num inst-format inst-name
このコードは assembler 由来で、 arch/mips/insts.fs にあります。
したがって、 実行時に上記のスキームを実行する inst-format ワードを定義する必要がありました。 著者は、 まず最初に、ランタイム・コード生成を使用することを選択しました:
: inst-format ( entry-num "name" -- ; compiled code: addr w -- ) :noname Postpone disasm-operands name Postpone sliteral Postpone type Postpone ; swap cells table + ! ;
注意: これにより、 上記のスキームの他に 2 つのパラメーターが提供されることに注意してください。
別の方法として、 create
/does>
を使用してこれを記述することもできます:
: inst-format ( entry-num "name" -- ) here name string, ( entry-num c-addr ) \ parse and save "name" noname create , ( entry-num ) latestxt swap cells table + ! does> ( addr w -- ) \ disassemble instruction w at addr @ >r disasm-operands r> count type ;
どういうわけか、 最初の解決策の方が簡単です。 その主な理由は、 string,
やその友達を使用するよりも、
sliteral
を使用した方が、 文字列を定義時から使用時へシフトするのが簡単だからです。
私はこのスキームに従ってたくさんのワードを書き、 すぐにそれらの共通点を取り出すことを考えました。 これは 2 レベルの定義ワード、 つまり通常の定義ワードを定義するワードを使用していることに注意してください。
今回は、 postpone
とそのファミリーが関与する解決策はより困難に思えたので(研究課題として試してみましょう)、
create
/does>
というワードを使用することにしました。 私はすでにそれを行っていたので、 下位レベルにも
create
/does>
を使用しました(研究課題として postpone
などを使用してみましょう)。
その結果、 以下の定義が得られました:
: define-format ( disasm-xt table-xt -- ) \ 逆アセンブルに disasm-xt を使用する命令フォーマットを定義し、 \ 定義された命令を \ 表(table) table-xt に入れます create 2, does> ( u "inst" -- ) \ 命令 inst を逆アセンブルする匿名ワードを定義し、 \ それを u 番目のエントリとして table-xt に入れます 2@ swap here name string, ( u table-xt disasm-xt c-addr ) \ remember string noname create 2, \ define anonymous word execute latestxt swap ! \ enter xt of defined word into table-xt does> ( addr w -- ) \ disassemble instruction w at addr 2@ >r ( addr w disasm-xt R: c-addr ) execute ( R: c-addr ) \ disassemble operands r> count type ; \ print name
注意: ここでのテーブルは(上記とは対照的に) cells +
を単独で実行することに注意してください (そのため、xt
を渡す必要があります)。 このワードは以下のように使用されます:
' disasm-operands ' table define-format inst-format
上に示したように、 定義された命令フォーマットは以下のように使用されます:
entry-num inst-format inst-name
カリー化に関しては、 この種の 2 レベルの定義ワードは 3 段階でパラメーターを提供します。 最初に disasm-operands と
table、 次に entry-num と inst-name 、最後に addr w
つまり、
逆アセンブル対象の命令です。
もちろん、 これは insts.fs で使用されるすべての命令フォーマット名に完全に適合するわけではないため、 パラメーターを正しい形式に条件付けるいくつかのラッパーを定義する必要がありました。
あなたが、 このセクションを理解するのが難しい場合でも、 心配する必要はありません。 まず、 これは複雑であり、
理解するのに時間がかかります(おそらく多少いじくりまわすことも必要です)。 2 番目に、 これは私が Forth の 17 年間で書いた最初の 2
レベルの create
/does>
ワードです。 また、 最初に insts.fs がなかった場合は、 1
レベルの定義ワードのみを使用することを選択した可能性があります(定義ワードを使用するときにパラメーターをいくつか繰り返します)。 したがって、
これを理解する必要はありませんが、 あなたの Forth についての理解が深まるかもしれません。
to
and defer@
¶Gforth の value にはいくつかの操作子(operators)があります。 to
(is
はエイリアスであり、
defer!
は入力ストリーム内の名前の代わりに xt を受け取ります)や +to
や addr
や
action-of
(defer@
は入力ストリーム内の名前の代わりに xt を受け取ります)です。
Gforth を使用すると、 ワードの (to)
アクションを変更できます。
(to)
( val operation xt – ) gforth-1.0 “paren-to”
name という名前の、 ワードに似ている value の xt です。 name に val を保存します。
operation は、 to
と +to
と addr
と action-of
から選択します。
to-table:
( "name" "xt1" .. "xtn" – ) gforth-experimental “to-table-colon”
TO
や +TO
や ADDR
や ACTION-OF
のエントリを含むテーブルを作成します。
n/a
を使用して、 サポートされていない操作をマークします。
to-method:
( xt table "name" – ) gforth-experimental “to-method-colon”
to-method を作成します。 ここで、xt はフィールドにアクセスするためのアドレスを計算し、 table にはそれに格納する操作子(operators)が含まれます。
set-to
( to-xt – ) gforth-1.0 “set-to”
現在のワードの (to) ( val xt -- )
メソッドの実装を to-xt に設定します。
n/a
( – ) gforth-experimental “not-available”
このワードをチック(tick)することはできますが、 インタープリター時およびコンパイル時に “Operation not supported” (操作はサポートされていません)という例外が投げられます(throw)。 サポートされていないメソッドなどにこれを使用します。
(to)
は to
内で使用されるワードです。 実行時(run-time)に値を保存します。 (to)
メソッドの一般的なスタック効果は ( val Operation xt -- )
です。 ここで、 xt
は格納されているワードを示し、 operation は to
風の操作の実際のバリエーションを示します。 val
はそこに格納されている (適切な型の) 値です。
to-table:
を使用して to
メソッドを実装し、
タイプ固有の操作テーブルを作成し(テーブルの最後にある指定されていないスロットは n/a
で埋められます)、
to-method:
を、 value の xt からそのデータ・フィールドを取得する操作と組み合わせて使用します(通常、
ディクショナリー内の値の場合は >body
ですが、 value-style データは構造体またはユーザー領域に存在することもできます)。
たとえば、 以下のように fvalue
を実装できます:
to-table: f!-table f! f+! ' >body f!-table to-method: fvalue-to : fvalue ( r "name" -- ; name: -- r ) create f, ['] f@ set-does> ['] fvalue-to set-to ; 5e fvalue foo : bar foo 1e f+ to foo ; see bar
compile,
¶以下を使用して、 とあるワードのための compile,
の実装を変更することもできます
set-optimizer
( xt – ) gforth-1.0 “set-optimizer”
compile,
で xt execute するように現在のワードを変更します(compile,
に渡されたものと同じスタック内容を使用)。 注意: compile,
は execute
とスタック内容が一致している必要があるため、 set-optimizer
は同じ振る舞いの、
より効率的な実装をインストールする場合にのみ使用しなければならないことに注意してください。
opt:
( compilation – colon-sys2 ; run-time – nest-sys ) gforth-1.0 “opt:”
名前無しのコロン定義を開始します。 完了すると、 このコロン定義は(opt:
の前の)最新のワードの compile,
の実装になります。
注意: 結果として得られる compile,
は、 依然として postpone literal postpone
execute
と同等である必要があることに注意してください。 そのため、 set-optimizer
は、
振る舞いを変更するためではなく、 効率化のために役立てるものです。 しかし、 あなたが自分の足を撃つことを妨げるものは何もありません。 あなたが
set-optimizer
を使用したときの結果と、 最初に以下を定義して使用を無効にしたときに得られる結果を比較することで、
set-optimizer
の使用が正しいかどうかを確認できます。
: set-optimizer drop ;
set-optimizer
の使用例として、 上記の CONSTANT
の定義の 1 つを以下のように拡張できます。
: CONSTANT ( n "name" -- ; name: -- n ) create , ['] @ set-does> [: >body @ postpone literal ;] set-optimizer ;
唯一の変更は、 set-optimizer
行の追加です。 あなたが定数を定義してコンパイルすると、 以下のようになります:
5 constant five : foo five ;
foo
内のコンパイル済み five
は、 five
の一般的な呼び出しではなく、 literal 5
とコンパイルされるようになりました。 引用(quotation)には、 compile,
と同じスタック効果があり、 それは
( xt -- )
です。 渡された xt は compile,
されたワード、 つまりこの例では five
に属します。 この例では、 まず xt が本体アドレスに変換され、 次にその場所の値 5 が取り出され、 その値が postpone
literal
でコンパイルされます(see Literals)。
この set-optimizer
の使用は、 ユーザーが、 例えば 6 ' five >body !
などして定数の値を変更しないことを前提としています。 five
は create
で定義されていますが、 これは
CONSTANT
の実装の詳細であり、 それを文書化しない場合、 ユーザーはそれに依存してはなりません。 また、
本体(body)が変更されないことを前提とした方法で set-optimizer
を使用する場合、 (ここで行われているように)
create
が使用されていることを文書化してはなりません。 逆に、 それを文書化する場合は、 本体の変更を処理できるように
compile,
実装を記述する必要があります。
もう一つの例は、 前述の fvalue
の例をさらに最適化したものです:
: compile-fvalue-to ( xt-value-to -- ) drop ]] >body f! [[ ; : fvalue-to ( r xt -- ) >body f! ; ' compile-fvalue-to set-optimizer : fvalue ( r "name" -- ; name: -- r ) create f, ['] f@ set-does> [: >body ]] literal f@ [[ ;] set-optimizer ['] fvalue-to set-to ; 5e fvalue foo : bar foo 1e f+ to foo ; see bar
bar
のコードを以前の定義のコードと比較します。 ここでは、 fvalue を読み取るコード(fvalue
の
set-optimizer
より)と、 fvalue を書き込むコード(fvalue-to
に適用された
set-optimizer
より)の両方の最適化が見られます。 fvalue は(定数とは異なり)変化する可能性があるため、
(fvalue
内部の)読み取り部分が、 アドレスと、 実行時に実行される f@
をコンパイルします。
fvalue-to
の場合、 compile,
の実装は基本的に、 fvalue
によってインラインで実行されるコードをコンパイルするだけです。 to
のコンパイル機能(compilation
semantics)は、アドレスをリテラルとしてコンパイルしてから、 (to)
の実装(つまり、
fvalue-to
)をコンパイルします。 この処理では、 >body
が最適化されて削除されます。
実際には、 Gforth の fvalue
は、 たとえば +TO
をサポートするなど、
いくつかの追加の工夫が含まれています。
注意: set-optimizer
の呼び出しは、 set-does>
(または does>
の呼び出しの後に実行する必要があることに注意してください。 なぜなら、 set-does>
は compile,
の実装それ自体を上書きするからです。
fvalue-to
の例でわかるように、 set-optimizer
を constant
や
fvalue
のような定義ワード内ではなく、 個々のワードに適用することもできます。 この場合、 オプティマイザに渡されるワードの xt
は通常は不要であり、 compile-fvalue-to
のように drop
されます。
エンジン gforth-itc
は compile,
に ,
を使用するので、 そこでは
set-optimizer
は効果ありません。
上記では、 最初に create
を使用してワードを定義し、 それから set-does>
や set-to
や set-optimizer
などで変更する方法を示しました。
別の方法として、 以下のワード群を使用してプロトタイプを作成し、そのプロトタイプから新しいワードを作成します。
この種のコピーでは本体(body)部分は網羅されないため、 明示的に割り当てて初期化する必要があります。 上記の fvalue
を例に取ると、 代わりに以下のように定義できます:
create fvalue-prototype ( -- r ) ['] f@ set-does> [: >body ]] literal f@ [[ ;] set-optimizer ['] fvalue-to set-to : fvalue ( r "name" -- ; name: -- r ) ``fvalue-prototype create-from f, reveal ;
このアプローチの利点は、まず create
ヘッダー・メソッド達を複製し、 それらを変更し、 最終的に重複排除する必要がないため、
fvalue
ワード群の作成が高速化されることだ。 ただし、 この利点は、
この定義ワードで作成されるワードの数が膨大な場合にのみ意味があります。
create-from
( nt "name" – ) gforth-1.0 “create-from”
指定の nt のように振る舞うが、 ボディが空のワード name を作成します。 nt は名前付きワードの nt
でなければなりません。 結果として得られるヘッダーはまだ非公開状態です(訳注: not yet
reveal;ワードリスト未登録なので普通は呼び出せない)。 set-
ワード群を使用せずに create-from
でワードを作成すると、 set-
ワード群または immediate
または does>
を使用してワードを作成するよりも高速になります。 noname
で create-from
を使用できます。
reveal
( – ) gforth-0.2 “reveal”
(reveal;明らかにする、暴露する)ヘッダー定義時の現在のワード・リスト(wordlist current)に現在のワードを入れます。
noname
を定義ワードとともに使用しても、 パフォーマンス上の利点は得られません。 したがって、 以下もいっしょに使います
noname-from
( xt – ) gforth-1.0 “noname-from”
指定の xt のように動作するが、 本体が空の名前のないワードを作成します。 xt は、 名前のないワードの xt でなければなりません。
以下に使用例を示します:
``fvalue-prototype noname create-from latestnt constant noname-fvalue-prototype : noname-fvalue ( r -- xt ; xt execution: -- r ) noname-fvalue-prototype noname-from f, latestxt ;
immediate
や set-optimizer
など、
上記の多くのワードは、「現在の」(current)ワードまたは「一番最近定義された」(most recently defined)ワードを変更します。
しばしば以前のワードを変更したい場合があると思います。 その場合は以下のようにして行うことができます
make-latest
( nt – ) gforth-1.0 “make-latest”
nt を最新の定義にし、 immediate
および set-*
操作で操作できるようにします。 nt
によって参照されるワードを既に使用している(特にコンパイル済みの)場合、 そのワードの振る舞いを変更しないでください(その実装だけを変更してください)。
さもないと、 Gforth エンジンやバージョン間で一貫性のない驚くべき振る舞いの組み合わせが発生する可能性があります。
Const-does>
¶create
...does>
は、 定義時から実行時に幾つかの値を転送(transfer)するためによく使用されます。
Gforth は、 このために以下を用意しています
const-does>
( run-time: w*uw r*ur uw ur "name" – ) gforth-obsolete “const-does”
定義時: name を定義して返ります。
name 実行時: w*uw r*ur をプッシュし、 const-does>
に続くコードを実行します。
このワードの一般的な使用例は以下のとおりです:
: curry+ ( n1 "name" -- ) 1 0 CONST-DOES> ( n2 -- n1+n2 ) + ; 3 curry+ 3+
ここで 1 0
は、 1 つのセルと 0 の浮動小数点が定義時から実行時に転送されることを意味します。
const-does>
を使用する利点は以下のとおりです:
does>
を使用する場合は、 最適化できない @
を導入する必要があります(なぜなら
>body
...!
を使用してデータを変更できるため)。 const-does>
はこの問題を回避します。
const-does>
の標準 Forth 実装は compat/const-does.fs で利用できます。
定義ワード defer
(訳注: (期限を定めない)延期)を使用すると、 その振る舞いを定義せずに名前でワードを定義できます。
その振る舞いの定義は延期(defer)されます。 これが役立つ 2 つの状況を以下に示します:
以下の例では、 foo
は常に greet
の「Good breakfast
」を出力するバージョンを呼び出し、
bar
は常にgreet
の「Hello
」を出力するバージョンを呼び出します。
ソースコードを並べ替えて再コンパイルすることなく、 foo
で新しいバージョンを使用できるようにする方法はありません。
: greet ." Good morning" ; : foo ... greet ... ; : greet ." Hello" ; : bar ... greet ... ;
この問題は、 greet
を defer
された ワードとして定義することで解決できます。 defer
された
ワードの振る舞いは、 IS
を使用して以前に定義されたワードの xt と関連付けることによって、 いつでも定義および再定義できます。
上記の例は以下のようになります:
Defer greet ( -- ) : foo ... greet ... ; : bar ... greet ... ; : greet1 ( -- ) ." Good morning" ; : greet2 ( -- ) ." Hello" ; ' greet2 IS greet \ make greet behave like greet2
プログラミング・スタイル・メモ: すべての defer された ワードに対してスタック・コメントを記述し、 かつ、 そのスタック効果に一致する XT のみを defer されたワードに入れる必要があります。 そうしないと、 defer されたワードを使用するのは非常に困難です。
defer された ワードを使用すると、 User-defined Defining Words
の統計収集の例(statistics-gathering example)を改善できます。 アプリケーションのソース・コードを編集してすべての
:
を my:
に変更するのではなく、 以下のようにします:
: real: : ; \ retain access to the original defer : \ redefine as a deferred word ' my: IS : \ use special version of : \ \ load application here \ ' real: IS : \ go back to the original
注意すべき点の 1 つは、 IS
には特別なコンパイル機能(compilation semantics)があり、 (TO
のように)コンパイル時に名前をパースするということです:
: set-greet ( xt -- ) IS greet ; ' greet1 set-greet
IS
が適合しない状況では、 代わりに defer!
を使用してください。
defer された ワードは、 xt から実行機能(execution semantics)のみを継承できます(xt が表現できるのはそれだけであるためです – これについての詳しい説明は see Tokens for Words 参照)。 デフォルトでは、この実行機能(execution semantics)から派生したデフォルトのインタープリター機能(interpretation semantics)とコンパイル機能(compilation semantics)を持ちます。 ただし、 defer された ワードのインタープリター機能とコンパイル機能は、 通常の方法で変更できます。
: bar .... ; immediate Defer fred immediate Defer jim ' bar IS jim \ jim has default semantics ' bar IS fred \ fred is immediate
Defer
( "name" – ) core-ext “Defer”
defer された ワード name を定義します。 その実行機能(execution semantics)は defer!
または is
で設定できます(最初に name を実行する前に設定する必要があります)。
defer!
( xt xt-deferred – ) core-ext “defer-store”
xt (xt-deferred) で表される defer された ワードに実行のための xt を設定します。
IS
( xt "name" – ) core-ext “IS”
defer
された ワード name に実行のための xt を設定します。
defer@
( xt-deferred – xt ) core-ext “new-defer-fetch”
xt は、 defer された ワード xt-deferred に現在関連付けられているワードを表します。
action-of
( interpretation "name" – xt; compilation "name" – ; run-time – xt ) core-ext “action-of”
Xt は、 現在 name に割り当てられている XT です。
Forth-94 では、 これら Forth-2012 のワードの定義は、compat/defer.fs で提供されます。 さらに、 Gforth は以下を提供します:
defers
( compilation "name" – ; run-time ... – ... ) gforth-0.2 “defers”
defer された ワード name の現在の内容を現在の定義にコンパイルします。 つまり、 これにより、 name が defer されなかったかのように静的結び付け(static binding)が生成されます。
wrap-xt
( xt1 xt2 xt: xt3 – ... ) gforth-1.0 “wrap-xt”
defer された ワード xt2 の現在の xt-current をどこかに退避して xt1 に設定した上で、 xt3 を実行し、 xt3 実行後、 退避した xt-current を defer されたワード xt2 に戻します(訳注: xt3 の内部のどこかで defer されたワード xt2 を使っているとして、 xt2 の実行機能を一時的に xt1 に設定してから xt3 を実行し、 実行後に xt2 の実行機能を元に戻しておく。 元に戻すのは try ... restore ... endtry で囲まれた部分なので xt3 実行中にエラーがあっても確実に復旧される。 詳しくはソースコード見て下さい。 なお、 xt: は xt3 を deferフレーバーに設定する)
preserve
( "name" – ) gforth-1.0 “preserve”
指定の defer された ワード name で、 現在設定されている実行機能 xt を、 その場で is
や
defer!
したかのようなコードに変換します(訳注: 上記例より、 ’ greet2 is greet :
preserve-greet2 preserve greet ; → greet1 is greet ok → greet Good morning
ok → preserve-greet2 ok → greet Hello ok ; see preserve-greet2 → :
preserve-greet2 ‘greet2 ‘greet ! ; ok)
forward.fs
内の定義ワード forward
を使用すると、 前方参照(forward
references)を作成できます。 これは自動的に解決され、 Defer
の間接化(indirection)のような追加コストは発生しません。 ただし、 これらの forward 定義はコロン定義に対してのみ機能します。
forward
( "name" – ) gforth-1.0 “forward”
コロン定義への前方参照(forward reference)を定義します。
同一のワードリスト内(wordlist)で同一の名前のコロン定義を定義すると、 前方参照が解決されます。 .unresolved
を使用して、 未解決の forward があるかどうかを確認します。
.unresolved
( – ) gforth-1.0 “.unresolved”
未解決の前方参照(forward references)をすべて出力します
定義ワード synonym
を使用すると、 他のワードと同一の振る舞いをするワードを名前で定義できます。 これが役立つ 2
つの状況を以下に示します:
root
ワードリストの定義を参照してください)。
THEN
と ENDIF
は同義語(synonyms)です)。
Synonym
( "name" "oldname" – ) tools-ext “Synonym”
oldname と同一に振る舞うように name を定義します。 つまり、 同一のインタプリタ機能(interpretation
semantics)と、 同一のコンパイル機能(compilation semantics)と、 同一の to
/defer!
や defer@
機能を持ちます。
Gforth は、 コンパイル機能、 または to
/defer!
や defer@
機能を親から継承しない、
非標準の alias
も提供します。 あなたは後で、 例えば immediate
などを使用してコンパイル機能などを変更できます。
Alias
( xt "name" – ) gforth-0.2 “Alias”
name を xt を実行するワードとして定義します。 defer された ワードとは異なり、 エイリアス(aliase)にはコンパイル時に間接的なオーバーヘッドがありません。
Example:
: foo ... ; immediate ' foo Alias bar1 \ bar1 is not an immediate word ' foo Alias bar2 immediate \ bar2 is an immediate word synonym bar3 foo \ bar3 is an immediate word
synonyms(同義語)とaliases(別名)はどちらも元の nt とは異なる nt を持ちますが、 それをチック(tick)すると(または
name>interpret
を使用すると)、 元の xt と同じ xt が生成されます(see Tokens for Words)。
(名前付き)ワード(word)のインタープリター機能(interpretation semantics)は、
テキスト・インタープリターがインタープリター状態でワードに遭遇したときに行うことです。 これは他の文脈でも表れます。 たとえば、 '
word
によって返される実行トークンは、 word のインタープリター機能(interpretation
semantics)を識別します(つまり、 ' word execute
は、 word
のインタープリター状態でのテキスト通訳(interpretation)と同等です)。
(名前付き)ワード(word)のコンパイル機能(compilation semantics)は、
テキスト・インタープリターがコンパイル状態でワードに遭遇したときに行うことです。 これは他の文脈でも表れます。 たとえば、 POSTPONE
word
は、 word のコンパイル機能(compilation
semantics)コンパイルします18。
ほとんどのワードにはデフォルトのコンパイル機能(compilation semantics)があります。 つまり実行機能(execution
semantics)(スタック効果 ( -- )
)をコンパイルします。 ただし、多くのワードが他のコンパイル機能(compilation
semantics)を持っていて、 その個々のワードについては文書化されています(スタック効果を含む)。
標準では、 実行機能(execution semantics)についても述べています。 標準では、 両方が定義されている場合はインタープリター機能(interpretation semantics)と異なることはありませんが、 一方が定義されていない、 または、 両方とも定義されていない場合もあります。 Gforth ではインタープリター機能(interpretation semantics)と実行機能(execution semantics)には違いがないため、 これらの用語は同じ意味で使用されます。
Gforth (1.0 以降)では、 すべてのワードに インタープリター機能/実行機能 が定義されています。
標準でインタープリター機能も実行機能も定義されていない多くのワード(if
など)については、 Gforth の
インタープリター機能/実行機能 がコンパイル機能を実行します。
標準では、 実行機能はデフォルトでインタープリター機能とコンパイル機能を定義するために使用されます。
デフォルトでは、ワードのインタープリター機能はその実行機能を execute
し、 ワードのコンパイル機能はその実行機能を
compile,
します19。
名前無しワード(see Anonymous Definitions)は、 テキスト・インタープリターまたはチック(tick)または
postpone
では検出できません。 このようなワードはその xt (see Tokens for Words)
によって表され、 この xt が execute
されたときは、 その実行機能が呼び出されます。
あなたは、 最後に定義されたワードの機能(semantics)を変更できます:
immediate
( – ) core “immediate”
ワードのコンパイル機能を、 その実行機能を実行するように設定します。
compile-only
( – ) gforth-0.2 “compile-only”
最後の定義をコンパイル専用としてマークします。 その結果、 テキスト・インタプリタと '
は、
そのようなワードに遭遇すると警告を発します。
restrict
( – ) gforth-0.2 “restrict”
compile-only
の同義語(synonym)
慣習により、 デフォルト以外のコンパイル機能を持つワード(即実行ワードなど)は、多くの場合、
名前が括弧(brackets;角括弧)で囲まれています(例: [']
see Execution token)。
注意: compile-only のワードにチック(tick)('
)を付けると、警告(“<word> is
compile-only”)が表示されることに注意してください。
Gforth を使用すると、 複合ワード(combined words)、 つまりインタープリター機能(interpretation semantics)とコンパイル機能(compilation semantics)を任意に組み合わせたワードを定義できます。
interpret/compile:
( interp-xt comp-xt "name" – ) gforth-0.2 “interpret/compile:”
この機能は、 TO
と S"
を実装するために導入されました。 このようなワードは、
たとえ小さなかわいいものであっても定義しないことをお勧めします。 なぜなら、 ワードによっては、
いくつかの文脈でワードの両方の部分を理解するのが困難になるからです。 たとえば、 あなたがコンパイル部分の実行トークンを取得したいとすると、 代わりに、
2つのワードを定義します。 1つはインタープリター機能部分を具体化するもで、 もう一つはコンパイル機能部分を具体化するものです。 それら完成したら、
あなたのユーザーの便宜のために interpret/compile:
を使用して、 それらを組み合わせた複合ワード(combined
word)を定義できます。
この機能を使用して、 ワードのデフォルトのコンパイル機能(compilation semantics)の最適化実装を提供してみるとしましょう。 たとえば、 以下のように定義します:
:noname foo bar ; :noname POSTPONE foo POSTPONE bar ; interpret/compile: opti-foobar
上記は以下の最適化バージョンです:
: foobar foo bar ;
残念ながら、 これは [compile]
では正しく動きません。 なぜなら、 [compile]
では、 すべての
interpret/compile:
ワードのコンパイル機能がデフォルトでは無いと想定しているためです。 つまり、
[compile] opti-foobar
はコンパイル機能をコンパイルしますが、 [compile] foobar
はインタープリター機能をコンパイルします。
state-smart ワードを使用して、interpret/compile:
によって提供される機能をエミュレートしようとする人もいます(実行途中に STATE
をチェックする場合、 ワードは state-smart
になります)。 たとえば、foobar
を以下のようにコーディングしようとします:
: foobar STATE @ IF ( compilation state ) POSTPONE foo POSTPONE bar ELSE foo bar ENDIF ; immediate
これは foobar
がテキスト・インタープリターによってのみ処理される場合には機能しますが、 他のコンテキスト('
や
POSTPONE
など)では機能しません。 たとえば、 ' foobar
は、 元の foobar
のインタープリター機能ではなく、 state-smart ワードの実行トークンを生成します。 この実行トークンを(EXECUTE
で直接、
または COMPILE,
で間接的に)コンパイル状態において実行(execute)すると、 結果は期待したものになりません(つまり、
foo bar
は実行されません)。 state-smart ワードは良くないアイディアです。 対策は、
こんなの書かない事!です20。
このセクションでは、 ワードを表すトークンの作成とその使用について説明します。
実行トークン(execution token)(xt)はワードの振る舞いを表します。 execute
を使用して
xt で表される振る舞いを呼び出すことができ、 そして、 compile,
(see Macros)
を使用してそれを現在の定義にコンパイルできます。 その他の使用法としては、 defer された ワード(see Deferred Words)
があります。
特に、 ワードのインタープリター機能(interpretation semantics)(別名 実行機能(execution semantics))を表す「実行トークン」(execution token)があります21。
名前付きワード x の場合、
`x
(訳注:アポストロフィではなくてバックスラッシュ)を使用してその実行トークンを取得できます:
5 `. ( n xt ) execute ( ) \ execute the xt (i.e., ".") : foo `. execute ; 5 foo
しかしながら、 `
プレフィックスは Gforth 拡張であるため、 以下の標準 Forth のワードを使用する方が好ましいです:
'
( "name" – xt ) core “tick”
xt は name のインタープリター機能(interpretation semantics)を表します。
ワードにインタープリター機能が無い場合、 -14 throw
を実行します。
[']
( compilation. "name" – ; run-time. – xt ) core “bracket-tick”
xt は name のインタープリター機能(interpretation semantics)を表します。
ワードにインタープリター機能が無い場合、 -14 throw
を実行します。
これらはパース・ワード(parsing words)であるため(なお、 `x
は認識器(recognizer)によってリテラルとして扱われます)、
通訳(interpret)されたりコンパイルされたコードの振る舞いが直感的ではない場合があります:
5 ' . ( n xt ) execute ( -- ) \ execute the xt of . \ 以下は意図したとおりに動きません: \ : foo ' . ; \ 5 foo execute \ その代わりに以下のようにします: : foo ['] . ; 5 foo execute \ execute the xt of . \ コロン定義内で ' を使った場合: : bar ' execute ; 5 bar . \ execute the xt of . \ 訳注: 一見普段と変わらないように見えるが、 \ テキスト・インタープリターが . を実行するのではなく、 \ bar が その後ろのワードの xt を execute している。 \ よって bar の後ろを省略すると下記のようにエラーとなる 5 bar *the terminal*:10:6: error: Attempt to use zero-length string as a name 5 bar>>><<<
'
は実行時にパースするため、 bar
のようにコロン定義に置くと、
コロン定義内の次のワードを消費せずに実行時に次のワードが消費されます。 `x
と書かずにリテラル xt
をコロン定義に入れたい場合は、 ['] x
と書きます。
Gforth の `x
や '
や [']
は、 compile-only
ワードで使用すると警告(warn)します。 そのような使用法が、 異なる Forth システム間で移植できない可能性があるためです。
ワードの immediate のバリエーションを定義することで、 この警告(warning)と移植性の問題を回避できます。 例:
: if postpone if ; immediate : test [ ' if execute ] ." test" then ;
結果として得られる実行トークンが execute
されたときは、 if
のコンパイル機能(compilation
semantics)を実行します。
xt を取得するもう一つ方法は、 :noname
または latestxt
です(see Anonymous Definitions)。 匿名のワードの場合、 :noname
または latestxt
により、
そのワードが持つ唯一の振る舞い(実行機能(execution semantics)) の xt が得られますが、 名前付きワードの定義の後ろで
latestxt
を使用して、 その xt を取得することもできます。
:noname ." hello" ; execute
xt は 1 つのセルを占有し、 他のセルと同様に操作できます。
標準 Forth では、 xt は単なる抽象データ型です(つまり、 xt を生成または消費する操作によって定義されます)。 (Gforth 1.0 以降の)具体的な実装は、 ワードの本体アドレス(昔は PFA だった)です。 Gforth 0.7 以前では、 xt はコード・フィールド・アドレス(CFA は PFA の 2 セル前)として実装されていました。
execute
( xt – ) core “execute”
実行トークン xt によって表される機能(semantics)を実行します。
execute-exit
( compilation – ; run-time xt nest-sys – ) gforth-1.0 “execute-exit”
xt
を実行し、 末尾呼び出し最適化法(tail-call-optimized way)で現在の定義から戻ります。 戻りアドレス
nest-sys
とローカル変数(local)は、 xt
を execute
する前に割り当て解除(deallocate)されます。
perform
( a-addr – ) gforth-0.2 “perform”
@ execute
.
Noop
は、 しばしば実行トークンのプレースホルダーとして使用されます:
noop
( – ) gforth-0.2 “noop”
Gforth は、名前付きワードを「名前トークン」name token(nt)で表します。 名前トークンは、 引数または、 以下のワードの結果として発生するセル・サイズの抽象データ型です。
Gforth 1.0 以降、 ほとんどのワードの nt の具体的な実装は、 xt と同一アドレスです(これは xt のための基本 nt (primary
nt)です)。 ただし、 synonym と、 aliase と、 interpret/compile:
で定義されたワードは、
別のワードから xt を取得しますが、 (xt とは異なり)依然として自分独自の nt を持ちます。 したがって、 Gforth 1.0
固有のコードを作成する準備ができている場合でも、 xt と nt を同一の意味で使用することはできません。 >name
を使用してこれら自分独自の nt を持つワードの xt から代替 nt を取得することはできません。
``x
(Gforth 1.0 以降) または、 以下を使用して、 ワード x の nt を取得します
find-name
( c-addr u – nt | 0 ) gforth-0.2 “find-name”
現在の検索順序スタック(the search order)で名前 c-addr u を見つけます。 nt が見つかった場合はそれを返し、 それ以外の場合は 0 を返します。
find-name-in
( c-addr u wid – nt | 0 ) gforth-1.0 “find-name-in”
wid で識別されるワードリストで、 c-addr u の文字列で指定された定義を検索します。 nt が見つかった場合はそれを返し、 それ以外の場合は 0 を返します。
latest
( – nt ) gforth-0.6 “latest”
nt は、 最後に定義されたワードの名前トークンです。 最後のワードに名前がない場合は 0 です。
latestnt
( – nt ) gforth-1.0 “latestnt”
nt は、 最後に定義されたワードの名前トークンです(訳注: 名前が無い場合でも nt を返す。 その nt を name>string すると、 長さ 0 の文字列(addr u) を返す)
>name
( xt – nt|0 ) gforth-0.2 “to-name”
xt で表されるワードの基本名前トークン(primary name token) nt を取得します。 xt が ぢつは xt でない場合(非 xt を xt と誤認する可能性が低いヒューリスティック・チェックを使用)、 または基本 nt (primary nt)が名前無しワードである場合は 0 を返します。 Gforth 1.0 の時点では、 すべての xt に基本 nt がありますが、 他の名前付きワードも同一のインタープリター機能(interpretation sematics) xt を持つ可能性があります。
xt>name
( xt – nt ) gforth-1.0 “xt-to-name”
xt の基本 nt (primary nt)を生成します。 xt が、 ぢつは xt でない場合、 nt は nt であるとは保証されません。
以下を使用すると、 ワードリスト内のすべての nt を取得できます
traverse-wordlist
( ... xt wid – ... ) tools-ext “traverse-wordlist”
f が false になるかワードリストがなくなるまで、 ワードリスト wid 内のワード nt ごとに xt ( ... nt – f ... ) を 1 回実行します。 xt はスタックその下側を自由に使用できます(訳注: つまり TOS に f さえ返せばスタックに何が積まれても(積まれなくても)気にしない)
nt を使用すると、 ワードのインタープリター機能(interpretation semantics)とコンパイル機能(compilation semantics)や、 その名前や、 ワードリスト内の次のワードのワード、 にアクセスできます:
name>interpret
( nt – xt ) tools-ext “name-to-interpret”
xt はワード nt のインタープリター機能(interpretation semantics)を表します。
name>compile
( nt – w xt ) tools-ext “name-to-compile”
w xt はワード nt のコンパイル・トークン(compilation token)です(訳注: コンパイル・トークン CT は 2セル構成)。
name>string
( nt – addr u ) tools-ext “name-to-string”
addr count は、 nt で表されるワードの名前です。
id.
( nt – ) gforth-0.6 “i-d-dot”
nt で表されるワードの名前を出力します。
.id
( nt – ) gforth-0.6 “dot-i-d”
id.
の F83 での名前です。
name>link
( nt1 – nt2 / 0 ) gforth-1.0 “name-to-link”
ワード nt1 の場合、 同じワードリスト内の1つ前のワード nt2 を返します。 前のワードがない場合は 0 を返します。
名前なしワードは通常、 インタープリター機能(interpretation compilation)も無く、 コンパイル機能(compilation
semantics)も無く、 名前も無く、 単語リストにもありません。 しかし、Gforth (1.0 以降) ではすべてのワードは等しいため、
名前無しのワードにも nt があります(ただし、 ワードリストには含まれません)。 その nt は latestnt
で取得でき、 nt
を消費する上記のワード群はこれらの nt に対して適切な処理を行います。
使用例として、 以下のコードは、デフォルト以外のコンパイル機能(compilation semantics)を持つ
forth-wordlist
内のすべてのワードをリストします:
: ndcs-words ( wid -- ) [: dup name>compile ['] compile, <> if over id. then 2drop true ;] swap traverse-wordlist ; forth-wordlist ndcs-words
このコードは、 コンパイル・トークンの xt 部分が compile,
の xt である場合、
ワードがデフォルトのコンパイル機能(compilation semantics)を持っていると仮定しています。
いにしえの Forth システムの nt に最も近いのはネーム・フィールド・アドレス(NFA)ですが、 Gforth とは顕著な違いがあります。
いにしえの Forth システムでは、 各ワードに一意の NFAとLFAとCFAとPFA(または、LFAとNFAとCFAとPFAの順)があり、
あるワードから次のワードへと進むためのワードがありました。 これとは対照的に、 Gforth では、 いくつかの nt が
name>interpret
xt からは同一の xt を取得できます。
名前トークンによって識別される構造体にリンク・フィールドがありますが、 通常、 検索にはこれらの構造体の外部にあるハッシュ・テーブルが使用されます。
Gforth の名前はセル幅のカウントおよびフラグ・フィールド(cell-wide count-and-flags field)がありますが、 nt
はそのカウント・フィールドへのアドレスとしては実装されていません。
名前付きワードのコンパイル機能(compilation semantics)は、 2 つのセル w xt
で構成されるコンパイル・トークン(compilation token)によって表されます。 一番上のセル xt は実行トークンです。
コンパイル・トークンによって表されるコンパイル機能は、 execute
で実行できます。 これは、 コンパイル・トークン全体を消費し、
コンパイル・トークンが表すコンパイル機能(compilation semantics)によって決定される追加のスタック効果を伴います。
現時点では、 コンパイル・トークンの w 部分は実行トークンで、 xt 部分は execute
または
compile,
のいずれかを表します22。 ただし、 必要な場合を除き、 この知識に頼らないでください。 Gforth の将来のバージョンでは、
普通でない(unusual)なコンパイル・トークン(リテラルのコンパイル機能を表すコンパイル・トークンなど)が導入される可能性があります。
たとえば、 if
のコンパイル・ークンは、 name>compile
を使用した標準的な方法(例: `if
name>compile
)で取得できますが、 コンパイル・トークンを取得するためのパース・ワードもあります:
[COMP']
( compilation "name" – ; run-time – w xt ) gforth-0.2 “bracket-comp-tick”
コンパイル・トークン w xt は、 name のコンパイル機能(compilation semantics)を表します。
COMP'
( "name" – w xt ) gforth-0.2 “comp-tick”
コンパイル・トークン w xt は、 name のコンパイル機能(compilation semantics)を表します。
execute
を使用して、 コンパイル・トークンによって表されるコンパイル機能(compilation
semantics)を実行できます。 postpone,
を使用してコンパイル機能(compilation
semantics)をコンパイルできます。 つまり、 ``x name>compile postpone,
は
postpone x
と同等です。
postpone,
( w xt – ) gforth-0.2 “postpone-comma”
コンパイル・トークン w xt で表されるコンパイル機能(compilation semantics)をコンパイルします。
他のほとんどの言語とは異なり、 Forth にはコンパイルと実行時(run-time)の間に厳密な境界がありません。 たとえば、
ワードの定義中(または constant
のようなワードの定義によって使用されるデータのための計算)に任意のコードを実行できます。
さらに、 immediate
(see Interpretation and Compilation Semantics) や
[
...]
(下記参照)を使用すると、 コロン定義のコンパイル中に任意のコードを実行できます(例外:
ディクショナリー空間で割り当てしてはいけません)。
最も単純で最も頻繁に使用される例は、 コンパイル中にリテラルを計算することです。 たとえば、 以下の定義は、 文字列の配列を 1 行に 1 つの文字列で出力します:
: .strings ( addr u -- ) \ gforth 2* cells bounds U+DO cr i 2@ type 2 cells +LOOP ;
Gforth のような愚かで低能なコンパイラーを使用すると、 ループ反復ごとに 2 cells
が計算されます。 あなたは、
この値をコンパイル時に一度だけ計算し、 定義にコンパイルできます。 以下のようにします:
: .strings ( addr u -- ) \ gforth 2* cells bounds U+DO cr i 2@ type [ 2 cells ] literal +LOOP ;
[
は、 テキスト・インタープリターをインタープリター状態に切り替えます(この例を対話的に入力し、 [
と ]
の間に改行を挿入すると、 ok
プロンプトが表示されます)。 2 cells
のインタープリター機能(interpretation semantics)を実行します。 ここでは数値を計算します。 ]
は、
テキスト・インタープリターをコンパイル状態に戻します。 次に、 この数値を現在のワードにコンパイルする Literal
のコンパイル機能(compilation semantics)を実行します。 see .strings
を使用してワードを逆コンパイルすると、 コンパイルされたコードへの影響を確認できます。
この方法で 2* cells
を [ 2 cells ] literal *
に最適化することもできます。
[
( – ) core “left-bracket”
インタープリター状態にします。 即実行ワードです。
]
( – ) core “right-bracket”
コンパイル状態にします。
Literal
( compilation n – ; run-time – n ) core “Literal”
コンパイル機能(compilation semantics): 実行機能(run-time semantics)をコンパイルします。 実行機能(run-time semantics): n をスタックにプッシュします。 インタープリター機能(interpretation semantics): 未定義です。
ALiteral
( compilation addr – ; run-time – addr ) gforth-0.2 “ALiteral”
literal
と同様に機能しますが、
(クロス・コンパイルされたコードで使用される場合、)リテラルがアドレスであることをクロス・コンパイラーに伝えます。
]L
( compilation: n – ; run-time: – n ) gforth-0.5 “]L”
] literal
と同等。
単一セル以外のデータ型をリテラルとしてコンパイルするためのワードもあります:
2Literal
( compilation w1 w2 – ; run-time – w1 w2 ) double “two-literal”
実行時に w1 w2 がスタックに配置されるように、 適切なコードをコンパイルします。 インタープリター機能(interpretation semantics)は未定義です。
FLiteral
( compilation r – ; run-time – r ) floating “f-literal”
実行時に r が(浮動小数点)スタックに配置されるように、 適切なコードをコンパイルします。 インタープリター機能(interpretation semantics)は未定義です。
SLiteral
( Compilation c-addr1 u ; run-time – c-addr2 u ) string “SLiteral”
コンパイル時: c-addr1, u で指定された文字列を現在の定義にコンパイルします。 実行時: 文字列のアドレスと長さを記述する c-addr2 u を返します。
データ・スタック上のコロン定義の外側から内側にデータを渡したいと思うかもしれません。 :
がコロン colon-sys をプッシュし、
colon-sys より下のモノのにアクセスできなくなるため、 これは機能しません。 たとえば、 以下は機能しません:
5 : foo literal ; \ error: "unstructured"
代わりに、 変数など、 他の方法で値を渡す必要があります:
variable temp 5 temp ! : foo [ temp @ ] literal ;
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]
は冗長です)。
テキスト・インタープリター(text interpreter)は、 現在の入力デバイスからの入力を処理する無限ループです。 インタープリターの実装上でコンパイルされた Forth コードを実行する内部インタープリター(inner interpreter)(see Engine)とは対照的に、 これは外部インタープリター(outer interpreter)とも呼ばれます24。
テキスト・インタープリターは、 インタープリター状態(interpret state) と コンパイル状態(compile
state)の 2 つの状態のいずれかで動作します。 現在の状態は、 それにふさわしい名前の変数である state
によって定義されます。
このセクションでは、 テキスト・インタープリターがインタープリター状態にあるときに、 ユーザー入力デバイス(キーボード)からの入力処理についてどのように振る舞うかについて説明することから始めます。 これは Forth システムが動き始めた時のモードです。
テキスト・インタープリターは、 入力バッファー(input buffer)25と呼ばれるメモリー領域で動作します。 この領域には、 RET キーを押したときにキーボードからの入力が保存され、 入力バッファーの先頭から開始して、 先頭の空白文字達(デリミタ(delimiters)と呼ばれる)をスキップし、 空白文字、 またはバッファーの終わりに達するまで文字列(空白以外の文字のシーケンス)をパースします。 文字列をパースした後、 以下の2つの試行を行います:
上記両方の試行が失敗した場合、 テキスト・インタープリターは入力バッファーの残りを破棄し、 そして、 エラー・メッセージを発行して、そして、
さらなる入力を待ちます。 いずれかの試行が成功すると、 テキスト・インタープリターは入力バッファー全体が処理されるまでパース処理を繰り返し、
全体が処理された時点でステータス・メッセージ “ ok
” を出力し、 そして、 さらなる入力を待ちます。
テキスト・インタープリターは、 >IN
(“to-in”(トゥーイン)と発音します)と呼ばれる変数を更新することにより、
入力バッファー内の位置を追跡します。 >IN
の値は 0 で始まり、 入力バッファーの先頭からのオフセットが 0 であることを示します。
オフセット >IN @
から入力バッファーの末尾までの領域は、 パース領域(parse
area)と呼ばれます26。 以下の例は、
テキスト・インタープリターが入力バッファーをパースする際に >IN
がどのように変化するかを示しています:
: remaining source >in @ /string cr ." ->" type ." <-" ; immediate 1 2 3 remaining + remaining . : foo 1 2 3 remaining swap remaining ;
これらの結果はそれぞれ以下のようになります:
->+ remaining .<- ->.<-5 ok ->swap remaining ;-< ->;<- ok
>IN
の値は、 テキスト・インタープリターによって実行される入力バッファー内のワードによって変更することもできます。 これは、
ワードがテキスト・インタプリタをだまして、 入力バッファーの一節をスキップしたり27、 一節を 2
回パースしたりすることができることを意味します。 例:
: lat ." <<foo>>" ; : flat ." <<bar>>" >IN DUP @ 3 - SWAP ! ;
flat
が実行されると、 以下の出力が生成されます28(訳注:ユーザーが flat と打ち込と、 入力バッファーには flat があり、 ワード flat
の実行が初まったときは >IN は flatの t の次を指している。 そこから 3 戻ると >IN が 指すのは flat の l で、
そこからテキスト・インタープリターがパースを再開すると、 テキスト・インタープリターに見えるのは lat である。 よって lat が実行されて
<<foo>> が出力される):
<<bar>><<foo>>
この手法を使用すると、 ワードのパースにおける相互運用性の問題(interoperability problems)の一部を回避できます。 もちろん、 ワードのパースは可能な限り避けた方が良いです。
テキスト・インタープリターの振る舞いに関する 2 つの重要な注意事項:
テキスト・インタープリターがコンパイル状態にある場合、 その振る舞いは以下のように変化します:
state
が変更され、 テキスト・インタープリターはインタプリタ状態に戻ります。
ok
” ではなく “ compiled
” を出力します。
テキスト・インタープリターがキーボード以外の入力デバイスを使用している場合、 その振る舞いは以下のように変化します:
ok
” や “ compiled
” メッセージは出力されません。
これについて詳しくは、 Input Sources をご覧ください。
>in
( – addr ) core “to-in”
uvar
変数 – a-addr は、 入力バッファーの先頭からパース領域の先頭までの char
オフセットを格納するセルへのアドレスです。
source
( – addr u ) core “source”
現在の入力バッファーのアドレス addr と長さ u を返します
tib
( – addr ) core-ext-obsolescent “t-i-b”
#tib
( – addr ) core-ext-obsolescent “number-t-i-b”
uvar
変数 – a-addr は、 端末の入力バッファー内の文字数を含むセルのアドレスです。
時代遅れ(OBSOLESCENT): source
はこのワードの機能を置き換えます。
interpret
( ... – ... ) gforth-0.2 “interpret”
デフォルトでは、 テキスト・インタープリターは、 Forth の起動時にユーザー入力デバイス(キーボード)からの入力を処理します。 テキスト・インタープリターは、 以下のいずれかの入力ソースからの入力を処理できます:
evaluate
使用。
プログラムは、 source-id
と blk
の値から現在の入力デバイスを識別できます。
source-id
( – 0 | -1 | fileid ) core-ext,file “source-i-d”
戻り値: 0 (入力ソースはユーザー入力デバイス)または、 -1 (入力ソースは evaluate
によって処理されている文字列)または、
fileid (入力ソースは fileid で指定されたファイル)です。
blk
( – addr ) block “b-l-k”
uvar
変数 – このセルには現在のブロック番号(現在の入力ソースがブロックでない場合は 0)が格納されています。
save-input
( – x1 .. xn n ) core-ext “save-input”
n 個のエントリ xn 〜 x1 は、 restore-input
で使用できるプラットフォーム依存の方法で、
入力ソース仕様(input source specification)の現在の状態を記述します。
restore-input
( x1 .. xn n – flag ) core-ext “restore-input”
入力ソース仕様(input source specification)を n 個のエントリ xn 〜 x1 で記述された状態に復元しようとします。 復元が失敗した場合は flag が true になります。 新しい入力プログラムを使用した Gforth では、 再度 throw するために使用できるフラグがある場合にのみ失敗します。 異なるアクティブな入力ストリーム間で保存および復元することも可能です。 注意: 入力ストリームを閉じる場合は、 開いたときとは逆の順序で行う必要がありますが、 その間はすべて許可されます。
evaluate
( ... addr u – ... ) core,block “evaluate”
現在の入力ソース仕様(input source specification)を保存します。 そして、 -1
を
source-id
に保存し、 かつ、 0
を blk
に保存します。 それから、 >IN
を
0
に設定し、 文字列 c-addr u を入力ソースおよび入力バッファーにし、 通訳(interpret)します。
そしてパース領域が空になったら、 入力ソース仕様を復元します。
query
( – ) core-ext-obsolescent “query”
ユーザー入力デバイスを入力ソースにします。 入力を端末入力バッファー(Terminal Input Buffer)で受け取ります。 >IN
をゼロに設定します。 時代遅れ(OBSOLESCENT): accept
で置き換えられました。
テキスト・インタープリターが数値入力をどのように変換するかの概要は Literals in source code にあります。 このセクションでは、 関連するいくつかのワードについて説明します。
デフォルトでは、 整数変換に使用される基数は変数 base
の内容によって与えられます。 注意: 予期しない base
の値によって多くの混乱が生じる可能性があることに注意してください。 base
を変更する場合、 必ず古い値を保存し、
後で復元してください。 さらに良いのは、これを自動的に実行する base-execute
を使用することです。 一般に、
base
を 10 進数のままにし、 一般的な 10 進数以外の基数については Literals in source code
で説明されている接頭辞を使用することをお勧めします。
base-execute
( i*x xt u – j*x ) gforth-0.7 “base-execute”
BASE
の内容を u にして xt を実行し、 その後元の BASE
を復元します。
base
( – a-addr ) core “base”
User
変数 – a-addr は、 入出力時の数値変換にデフォルトで使用される基数を格納するセルへのアドレスです。
base
に保存せず、 代わりに base-execute
を使用してください。
hex
( – ) core-ext “hex”
base
を &16 (16 進数) に設定します。 hex
を使用せず、 代わりに
base-execute
を使用してください。
decimal
( – ) core “decimal”
base
を &10 (10 進数) に設定します。 10 進数
を使用せず、 代わりに
base-execute
を使用してください。
dpl
( – a-addr ) gforth-0.2 “Decimal-PLace”
User
変数 – a-addr は、 最新の数値変換における小数点の位置を格納するセルのアドレスです。 -1
に初期化されます。 小数点を含まない数値を変換すると、 dpl
は -1 になります。 2.
の変換後は 0
が保持されます。 234123.9 の変換後は 1 が保持されます。
数値変換は、 不注意な人にとっては多くの罠があります:
base @ .
を使用して現在の基数を測定することはできません。 これは現在の基数では常に 10 と表示されます。
代わりに、 base @ dec.
なととしてください。
bin
というワードがありますが、 これは基数を設定するものではありません(see General files)。
.
が文字列の最後の文字である必要があります。 Gforth では .
をどこにでも置くことができます。
Line input and conversion で説明されているワードを使用して、 プログラムに数値を読み込むことができます。
標準のプログラムでは state
を明示的に変更することは許可されていません。 ただし、[
と ]
というワードを使用して、 state
を暗黙的に変更できます。 [
が実行されると、 state
がインタープリター状態に切り替わり、 テキスト・インタープリターが通訳(interpret)を開始します。 ]
が実行されると、
state
がコンパイル状態に切り替わり、 テキスト・インタープリターはコンパイルを開始します。
これらのワードの最も一般的な使用法は、コロン定義内でインタープリター状態に切り替えたり、 元の状態に戻したりすることです。 この手法は、
リテラルのコンパイル(see Literals の例参照)または条件付きコンパイル(例: see Interpreter Directives の例を参照)に使用できます。
以下のワード群は通常、 インタープリター状態で使用されます。 通常は、 ソース・ファイルのどの部分がテキスト・インタープリターによって処理されるかを制御します。 これらは、 標準 Forth のワードには少数しかありませんが、 Gforth では、 非即実行バージョンがコンパイル状態でのみ使用できるという事実を補うために、 豊富な即実行制御構造ワードのセットでこれらを補っています(see Control Structures)。 一般的な使用法は以下のとおりです:
[undefined] \G [if] : \G ... ; immediate [endif]
これは、 システムが \G
を定義していない場合は、 幾つかの置き換えプログラムをコンパイルします(機能が制限される可能性があります)。
[IF]
( flag – ) tools-ext “bracket-if”
flag が TRUE
の場合は何もしません(したがって、 後続のワードは通常どおり実行されます)。 flag が
FALSE
の場合、 対になる [ELSE]
または [THEN]
がパースされて破棄されるまで、
[IF]
.. [ELSE]
.. [THEN]
や [IF]
.. [THEN]
の入れ子になったモノを含むパース領域(入力バッファー/パース領域 は、 必要に応じて REFILL
を使用して再充填します)からワードをパースして破棄します。 即実行ワードです。
[ELSE]
( – ) tools-ext “bracket-else”
対になる [THEN]
がパースされて破棄されるまで、 [IF]
..
[ELSE]
.. [THEN]
や [IF]
.. [THEN]
の入れ子になったモノを含むパース領域(入力バッファー/パース領域 は、 必要に応じて REFILL
を使用して再充填します)からワードをパースして破棄します。 FALSE
の場合、[IF]
は [ELSE]
をパースして破棄し、後続のワードは通常どおり実行されたままになります。 即実行ワードです。
[THEN]
( – ) tools-ext “bracket-then”
何もしない。 パースして破棄する他のワードのマーカーとして使用されます。 即実行ワードです。
[ENDIF]
( – ) gforth-0.2 “bracket-end-if”
何もしません。 [THEN]
の同義語(synonym)です。
[defined]
( "<spaces>name" – flag ) tools-ext “bracket-defined”
現在の検索順序スタック(the search order)で名前が見つかった場合は true を返します
[undefined]
( "<spaces>name" – flag ) tools-ext “bracket-undefined”
現在の検索順序スタック(the search order)で名前が見つかった場合は false を返します
[IFDEF]
( "<spaces>name" – ) gforth-0.2 “bracket-if-def”
現在の検索順序スタック(the search order)で name が見つかった場合は、 TRUE
フラグを伴った
[IF]
のように振る舞いし、 そうでない場合は、 FALSE
フラグを伴った [IF]
のように動作します。
即実行ワードです。
[IFUNDEF]
( "<spaces>name" – ) gforth-0.2 “bracket-if-un-def”
現在の検索順序スタック(the search order)で name が見つからない場合は、 TRUE
フラグを伴った
[IF]
のように動作し、 それ以外の場合は、FALSE
フラグを伴った [IF]
のように動作します。
即実行ワード。
[?DO]
( n-limit n-index – ) gforth-0.2 “bracket-question-do”
[DO]
( n-limit n-index – ) gforth-0.2 “bracket-do”
[LOOP]
( – ) gforth-0.2 “bracket-loop”
[+LOOP]
( n – ) gforth-0.2 “bracket-question-plus-loop”
[FOR]
( n – ) gforth-0.2 “bracket-for”
[NEXT]
( n – ) gforth-0.2 “bracket-next”
[I]
( run-time – n ) gforth-0.2 “bracket-i”
実行時、 [I]
はテキスト・インタープリター時の [do]
の反復(iteration)のループ・インデックスをプッシュします。 インタープリター時にインデックスを処理したい場合は、 [I]
を対話的に通訳(interpret)するか、 または INT-[I]
を使用します。
INT-[I]
( – n ) gforth-1.0 “int-bracket-i”
テキスト・インタープリター時に [do]
反復のループ・インデックスをプッシュします。
[BEGIN]
( – ) gforth-0.2 “bracket-begin”
[UNTIL]
( flag – ) gforth-0.2 “bracket-until”
[AGAIN]
( – ) gforth-0.2 “bracket-again”
[WHILE]
( flag – ) gforth-0.2 “bracket-while”
[REPEAT]
( – ) gforth-0.2 “bracket-repeat”
#line
を使用すると、 現在のソース行番号とソース・ファイルに関する Gforth の認識を変更できます。 これは、 Forth
ファイルが他のソース・コード・ファイルから生成されており、 たとえば、
オリジナルのソース・コードを参照するエラー・メッセージなどを取得したい場合に便利です。 その場合、 Forth コード・ジェネレーターは、 必要に応じて
Forth コードに #line
行を挿入する必要があります。
#line
( "u" "["file"]" – ) gforth-1.0 “#line”
行番号を u に設定し、 (存在する場合)ファイル名を file に設定します。 行の残りの部分を消費します。
テキスト・インタープリターはソース・コードを処理するときに、 コードを空白で区切られた文字列に分割し、
認識器群(recognizers)の中からどれか1つの認識器が文字列を識別(identify)(認識(recognize))するまで認識器群を呼び出して、
それらをワードや数値などとして識別します。 その文字列が認識されない場合、 テキスト・インタプリタはエラー(undefined
word
)を報告します。
認識器を取り扱う通常の方法は、 認識器の 1 つを識別(identify)するコードを記述するだけです(see Default Recognizers)。 ただし、 それらを操作したり(see Dealing with existing Recognizers)、 新しい認識器を定義したりすることもできます(see Defining Recognizers)。
標準の Forth テキスト・インタープリターは、 検索順序スタック(the search order)内のワード(rec-nt
) と、
整数(rec-num
)と、 浮動小数点数(rec-float
)を認識します。 デフォルトでは、 Gforth は
以下の構文も認識します
"mystring"
(rec-string
)
0e+1ei
(rec-complex
)
->myvalue
(rec-to
)
`dup
(rec-tick
)
``mysynonym
(rec-dtick
)
<myarray+8>
(rec-body
)
${HOME}
(rec-env
)
myvoc1:myvoc2:myword
(rec-scope
)
?
というプレフィックスが付いたワードは、 rec-
recognizer によって処理され、 認識器の曖昧さを排除します。 例:
hex num?cafe num?add
rec-num としてのみパースさせる、 float?1.
rec-floatとしてパースさせる)(rec-meta
)
locate
(see Locating source code definitions) を使用して、 ソース・コードの一片を、
どの認識器(recognizer)が認識するか調べられます。 例:
defer mydefer locate ->mydefer
これは rec-to
が ->mydefer
を認識したことを示すハズです。 ただし、
認識器がディクショナリーのワードを認識する場合(スコープ認識機能など)、 locate はそのワードを表示します。
以下を使用すると、 使用されている認識器と認識器の順序を確認できます
.recognizers
( – ) gforth-experimental “dot-recognizers”
(.order とは異なり、)最初に検索された認識器を左端にして、 現在の認識器の順序を出力します。 すべての認識器に共通のプレフィックスである
rec-
の代わりに、 反転表示の ~
が表示されます。
通常、 認識器は、 他の認識器と同一の文字列との一致を避けるように設計されています。 たとえば、rec-env
(環境変数認識器)には、
$ADD
のような入力文字列の数値認識器との競合を避けるために波括弧(braces;{})が必要です。 ただし、
このポリシーにはいくつかの例外があります:
ただし、 名前は 0
(ゼロ)で始まらない傾向がある(そして 0
(ゼロ)
で始まる場合は特殊文字が含まれる傾向がある)ため、 base が hex
の場合は、 数値を 0
(ゼロ)で始めることをお勧めします。
これまで私たちが見てきた幾多のコードでは、 ワードを '
(クォート。 別名ティック)で始める方が、 `
(バック・クォート。別名バック・ティック) で始めるよりもはるかに一般的であるため、 xt と nt の認識器は `
(バック・クォート)を使用して競合を減らしてください。
rec-num
と、 浮動小数点認識器 rec-float
は、 どちらも、 たとえば 1.
を認識します。 しかし、 rec-num
が(デフォルトでは)先にあるため、 1.
は2倍長セルの整数として認識されます。
rec-float
を最初に使用するように認識順序を変更すると、 1.
は浮動小数点数として認識されますが、 標準
Forth で書かれたコードを読み込むと、 非標準的な振る舞いになる可能性があります。
いずれの場合も、 以下のようにして、 あなた独自のコード内では、 この競合を回避することをお勧めします。 つまり、
常に数値プレフィックスを付けて2倍長セル整数を記述します(例: #1.
)。 また、 浮動小数点数は常に e
を使用して記述します(例: 1e
)。
->
で始まるワードをいくつか見てきました。 to myvalue
または to?->myvalue
(訳注: rec-meta 構文で rec-to を指定の上で ->myvalue を評価)を使用すると競合を回避できます(後者は
postpone
で機能します)。
認識器(recognizer)は、 あなたが文字列を渡すワードです。 認識器が文字列を認識すると、 通常はデータとそのデータを処理するためのワードの
xt を返します。 このワードはトランスレーター(translator)と呼ばれます。 認識器が文字列を認識しない場合は、
notfound
の xt を返します。
すべての認識器(recognizers)のスタック効果は ( c-addr u – ... xt ) です。
認識器は文字列を受け取り、 いくつかのデータと、 そのデータを通訳(interpret)するためのトランスレーター(翻訳器)を返します。 Gforth
はそのトランスレーターを xt として実装します(これを実行すると、 現在の state でその文字列を処理する適切なアクションが実行されます)。
しかし、 他の Forth システムでは、 内部に 3 つの xt を含む実際のテーブルとして実装する可能性があります。 最初の xt は
通訳時(interpretation)/実行時(run-time) xt で、 データのインタープリター機能(interpretation
semantics)を実行します(通常はデータをスタックに置くだけです)。 2 番目の xt はコンパイル機能(compilation
semantics)を実行し、 データと実行時機能(run-time semantics)の xt を取得します。 3 番目の xt は
postpone 機能(postpone semantics)を実行し、 データと実行時機能の xt も取得します。 >postpone
を使用すると、 実行時機能の xt を postpone できます。
認識器群はスタックとして編成されるため、 ボキャブラリー・スタックと同じ方法で認識器群のシーケンスを配置できます。 認識器スタックはそれ自体が認識器群です。 つまり、 認識器スタックは実行可能であり、 文字列を受け取り、 トランスレーターを返します。
notfound
( state – ) gforth-experimental “notfound”
認識器の認識が失敗すると、 notfound
の認識器トークン(訳注: 現状では notfound
の xt )を返します
rec-nt
( addr u – nt translate-nt | notfound ) gforth-experimental “rec-nt”
名前トークンを認識します
rec-num
( addr u – n/d table | notfound ) gforth-experimental “rec-num”
数値を 1倍長/2倍長 整数に変換します
rec-float
( addr u – r translate-float | notfound ) gforth-experimental “rec-float”
浮動小数点数を認識します
rec-string
( addr u – addr u’ r:string | rectype-null ) gforth-experimental “rec-string”
二重引用符(double quotes)で囲まれた文字列(strings)を文字列リテラルに変換します。 エスケープは S\"
と同様に扱われます。
rec-to
( addr u – xt n r:to | rectype-null ) gforth-experimental “rec-to”
->
で始まるワードは、 TO
が前にあるものとして扱い、 +>
で始まるワードは +TO
が前にあるものとして扱い、 '>
で始まるワードは ADDR
が前にあるものとして扱い、 @>
で始まるワードは ACTION-OF
が前にあるものとして扱い、 =>
で始まるワードは IS
が前にあるものとして扱います。
rec-tick
( addr u – xt rectype-num | rectype-null ) gforth-experimental “rec-tick”
`
(バッククォート)の接頭辞が付いたワードはその xt を返します。 例: `dup
は dup の xt を返します
rec-dtick
( addr u – nt rectype-num | rectype-null ) gforth-experimental “rec-dtick”
``
(バッククォート2つ)で始まるワードは、 その nt を返します。 例: ``S"
は S"
の nt
を返します。
rec-body
( addr u – xt translate-tick | translate-null ) gforth-experimental “rec-body”
'<'
'>'
で囲まれたワードはその本体(body)を返します。 例: <dup>
は dup
の本体(body)を返します
get-recognizers
( – xt1 .. xtn n ) gforth-experimental “get-recognizers”
認識器スタックの内容(content)をスタックにプッシュします。 n は xt の個数です。
set-recognizers
( xt1 .. xtn n – ) gforth-experimental “set-recognizers”
スタック上の内容(content)を認識器スタックに設定します。 n は xt の個数です。
recognize
( addr u rec-addr – ... rectype ) gforth-experimental “recognize”
訳注: 認識器スタック rec-addr の認識器を順に文字列 addr u に適用し、 認識したら、 そのトランスレーター
rectype (の xt ) を得ます。 何も適用できなかった場合はトランスレーター notfound
(の xt )を返します
recognizer-sequence:
( xt1 .. xtn n "name" – ) gforth-experimental “recognizer-sequence:”
訳注: xt1 .. xtn n という内容で "name" という名前の認識器スタックを作成します。
forth-recognize
( c-addr u – ... translate-xt ) recognizer “forth-recognize”
システム認識器スタックです。
forth-recognizer
( – xt ) gforth-experimental “forth-recognizer”
Matthias Trute recognizer API との下位互換性があります。 この構造は、 defer されたワードを value のようなワードに変換します。
set-forth-recognize
( xt – ) recognizer “set-forth-recognize”
システム認識器スタックを変更する
translate:
( int-xt comp-xt post-xt "name" – ) gforth-experimental “translate:”
name という名前のトランスレーター(トランスレーター・テーブル)を作成し、 トランスレーターのインタプリンタ機能アクション int-xt と、 トランスレーターのコンパイル機能アクション comp-xt と、 トランスレーターの postpone 機能アクション post-xt を格納します。 加えて拡張機能(extensions)用に 7 つのスロットを用意します(拡張機能スロット初期値は no.extensions の xt で埋めます)。 実行時: name の 実行時は state の値に応じてアクションが実行されます。 state が 0 (インタプリンタ状態)ならばインタプリンタ機能アクションを実行し、 stete が -1 (コンパイル状態) ならばコンパイル機能アクションを実行し、 state が -2 (通常無い値。このために特別にセットする。アクション実行後は以前の値復元の事) ならばpostpone 機能アクションを実行します。
translate-nt
( i*x nt – j*x ) gforth-experimental “translate-nt”
名前トークンのトランスレーター
translate-num
( x – | x ) gforth-experimental “translate-num”
数値のトランスレーター
translate-dnum
( dx – | dx ) gforth-experimental “translate-dnum”
2倍長整数のトランスレーター
doc-translate-float(訳注: まだ説明書いて無いっぽい)
try-recognize
( addr u xt – results | false ) gforth-experimental “try-recognize”
入れ子(nested)になった認識器(recognizers)用です: addr u の認識を試み、 xt を実行して結果が望ましいかどうかを確認します。 xt が false を返した場合は、 認識器の副作用をすべてクリーンアップして false を返します。 それ以外の場合は、 xt 呼び出しの結果をスタックに返し、 そのTOSはゼロ以外でなければなりません。
>interpret
( translator – ) gforth-experimental “>interpret”
指定のトランスレーターのインタプリンタ機能アクションを実行します。
>compile
( translator – ) gforth-experimental “>compile”
指定のトランスレーターのコンパイル機能アクションを実行します。
>postpone
( translator – ) gforth-experimental “>postpone”
指定のトランスレーターの postpone 機能アクションを実行します
translate-method:
( "name" – ) gforth-experimental “translate-method:”
新しいトランスレーター・メソッドを作成し、 トランスレーター・テーブルを拡張します。 xt rectype to
translator を使用して、 xt を既存のrectypeに割り当てることができます(訳注: >interpret
や
>compile
や >postpone
定義用。 あなたが更に トランスレーター・メソッドを定義した場合はこれ以降の新規
state 値に対するアクションとなる。 現時点で 7 つまで追加可能)
translate-state
( xt – ) gforth-experimental “translate-state”
xt として渡された translate-method に一致するトランスレーターのアクションを実行するように、 システムの現在の state を変更します。
before-line
( – ) gforth-1.0 “before-line”
テキスト・インタープリターが次の行をパースする前に呼び出される defer されたワード(訳注: 初期値 noop
。
interpret
実行開始時にも呼び出される)
before-word
( – ) gforth-0.7 “before-word”
テキスト・インタープリターが次のワードをパースする前に呼び出される deferd されたワード(訳注: 初期値 noop
)
line-end-hook
( – ) gforth-0.7 “line-end-hook”
ファイルからテキスト通訳(text-interpreting)するときに行末ごとに呼び出される defer されたワード(訳注: 初期値
noop
)
テキスト・インタープリターは、 複数のソース(see Input Sources)が利用可能な入力ストリームから読み取ります。
いくつかのワード、特に定義ワードや '
のようなワードは、 スタックからではなく入力ストリームからパラメーターを読み込みます。
このようなワードは入力ストリームをパース(parse)するため、 パース・ワードと呼ばれます。 パース・ワードの使用は困難です。 なぜならプログラムで生成されたパラメーターを入力ストリームを通じて渡すのが難しいからです。 また、 素朴な実装をすると、 インタープリター機能(interpretation semantics)とコンパイル機能(compilation semantics)の組み合わせが直感的でないことが多く、 より直感的な振る舞いを実現しようとする様々なアプローチが生まれています(see Combined Words)。
ワードをパースするのは悪い考えであることは、 最早明らかです。 あなたが、 利便性のためにパース・ワードを実装したいなら、 非パースのバリエーション、 つまり、 パースを行わずスタック上のパラメーターを受け取るワードも提供してください。 もし、 その上でパース・ワードを実装するなら、 以下のワード群を使用できます:
parse
( xchar "ccc<xchar>" – c-addr u ) core-ext,xchar-ext “parse”
パース領域で xchar で区切られた ccc をパースします。 c-addr u は、
パース領域内でパースされた文字列を指します。 パース領域が空の場合、 u は 0 です(訳注: bl parse ccc type
ccc ok
。 直後でENTERキーを押して行を終了すると(parse
ENTER)、 長さ0の文字列をスタックに積む)
string-parse
( c-addr1 u1 "ccc<string>" – c-addr2 u2 ) gforth-1.0 “string-parse”
文字列 c-addr1 u1 で区切られた ccc をパース領域でパースします。 c-addr2 u2 は、
パース領域内のパースされた文字列を指します。 パース領域が空の場合、 u2 は 0 です(訳注: " " string-parse
ccc type ccc ok
)
parse-name
( "name" – c-addr u ) core-ext “parse-name”
入力バッファーから次の単語(word)を取得します(訳注: parse-name ccc type ccc ok
)
parse-word
( – c-addr u ) gforth-obsolete “parse-word”
parse-name
の古い名前。 このワード語は、他の一部のシステムでは矛盾する振る舞いをします。
name
( – c-addr u ) gforth-obsolete “name”
parse-name
の古い名前
word
( char "<chars>ccc<char>– c-addr ) core “word”
先頭の区切り文字をスキップします。 パース領域で char で区切られた ccc をパースします。 c-addr は、 カウンタ付き文字列形式で、 パースされた文字列を格納する一時領域のアドレスです。 パース領域が空であるか、 区切り文字以外の文字が含まれてい無い場合、 結果の文字列の長さはゼロになります。 プログラムは、 カウンタ付き文字列内の文字を置き換える場合があります。 時代遅れ(OBSOLESCENT): カウンタ付き文字列の末尾に、 その長さに含まれないスペースがあります。
refill
( – flag ) core-ext,block-ext,file-ext “refill”
入力ソースにより入力バッファーを満たすことを試みます。 入力ソースがユーザー入力デバイスの場合、 端末入力デバイスからの入力の受け取りを試みます。
成功した場合は、 結果を入力バッファーにし、 >IN
を 0 に設定して true を返します。 それ以外の場合は false
を返します。 入力ソースがブロックの場合、 BLK
の値に 1 を加算して、 次のブロックを入力ソースかつ現在の入力バッファーにし、
>IN
を 0 に設定します。 BLK
の新しい値が有効なブロック番号の場合は true を返し、 それ以外の場合は
false を返します。 入力ソースがテキスト・ファイルの場合、 ファイルから次の行を読み取ろうと試みます。 成功した場合は、
結果を現在の入力バッファーにし、 >IN
を 0 に設定して true を返します。 それ以外の場合は false を返します。
いずれの場合も、 成功した結果には、0 文字を含む行の受け取りが含まれます。
非パースなバリエーションが無いパース・ワードを取り扱う必要がある場合、 execute-parsing
を使用して(スタック経由で)文字列を渡すことができます:
execute-parsing
( ... addr u xt – ... ) gforth-0.6 “execute-parsing”
addr u を現在の入力ソースにして xt ( ... -- ... )
を実行してから、
以前の入力ソースを復元します。
Example:
5 s" foo" ' constant execute-parsing \ これは、以下と同等です 5 constant foo
標準 Forth でのこのワードの定義は compat/execute-parsing.fs で提供されます。
ファイルに対してワードのパースを実行したい場合は、 以下のワードが役に立ちます:
execute-parsing-file
( i*x fileid xt – j*x ) gforth-0.6 “execute-parsing-file”
fileid を現在の入力ソースにして、 xt ( i*x -- j*x )
を実行してから、
以前の入力ソースを復元します。
ワードリスト(wordlist)は名前付きワードのリストです。 新しいワードを追加したり、
ワードを名前で探したりできます(マーカー(markers)を使用して制限された方法でワードを削除することもできます)。 全ての名前付き(および
reveal
された)ワードは 1 つのワードリスト内にあります。
テキスト・インタープリターは、 検索順序スタック(the search order;ワードリストのスタック)に存在するワードリストをTOSから下へ検索します。 各ワードリスト内では、 概念的には最新のワードから検索が開始されます。 つまり、 ワードリスト内に同一の名前の 2 つのワードがある場合、 新しいワードが検索にヒットします。
新しいワードは「コンパイル・ワードリスト」(compilation wordlist)(現在のワードリスト(current wordlist)とも呼ばれます)に追加されます。
ファイルがファイル・ハンドルによって識別されるのとほぼ同じ方法で、 ワードリストはセル・サイズのワードリスト識別子(word list identifier; wid)によって識別されます。 wid の数値には(移植可能な)意味はなく、 セッションごとに変わる可能性があります。
標準 Forth の “Search order” ワード・セットは、
さまざまな異なるスキームの実装を可能にする低レベル・ツールのセットを提供することを目的としています。 Gforth は、 伝統的な Forth
ワードである vocabulary
も提供します。 compat/vocabulary.fs は、 標準 Forth
での実装を提供します。
forth-wordlist
( – wid ) search “forth-wordlist”
定数(Constant
) – wid は、Gforth が提供するすべての標準のワードを含むワードリストを識別します。
Gforth が呼び出されると、 このワードリストがコンパイル・ワードリストとなり、 検索順序スタック(the search
order)のTOSになります。
definitions
( – ) search “definitions”
現在検索順序スタック(the search order)のTOSにあるワードリストをコンパイル・ワードリストにします(訳注: :
definitions context current ! ;
)
get-current
( – wid ) search “get-current”
wid は、 現在のコンパイル・ワードリストのワードリスト識別子です。
set-current
( wid – ) search “set-current”
wid で識別されるワードリストをコンパイル・ワードリストに設定します。
get-order
( – widn .. wid1 n ) search “get-order”
検索順序スタック(the search order)の内容をデータ・スタックにコピーします。 現在の検索順序スタックには n 個のエントリがあり、 そのうち wid1 は最初に検索されるワードリスト(検索順序スタックのTOSにあるワードリスト) を表し、 widn は最後に検索されるワードリストを表します。
set-order
( widn .. wid1 n – ) search “set-order”
n=0 の場合、 検索順序スタック(the search order)を空(empty)にします。 n=-1 の場合、
検索順序スタックを実装定義で最小化します (Gforth の場合、 これはワードリスト Root
です)。 それ以外の場合は、
n 個の wid エントリを持ち、 wid1 が最初に検索されるワードリストを表し、 widn
が最後に検索されるワードリストを表すように、 既存の検索順序スタックを置き換えます。
wordlist
( – wid ) search “wordlist”
wid で表される新しい空(empty)のワードリストを作成します。
table
( – wid ) gforth-0.2 “table”
検索テーブル(lookup table)を作成します(英大文字と小文字を区別し、 警告なし)。
cs-wordlist
( – wid ) gforth-1.0 “cs-wordlist”
英大文字小文字を区別するワードリストを作成する。
cs-vocabulary
( "name" – ) gforth-1.0 “cs-vocabulary”
英大文字小文字を区別するボキャブラリーを作成する
>order
( wid – ) gforth-0.5 “to-order”
検索順序スタック(the search order)というワードリストのスタックにワードリスト wid をプッシュする(訳注: wid が 検索順序スタックの TOS になる)。
previous
( – ) search-ext “previous”
検索順序スタック(the search order)というワードリスト・スタックのTOSを捨てる
also
( – ) search-ext “also”
検索順序スタック(the search order)でTOSを DUP
するかのように振る舞います(訳注: : also
context >order ;
)。 通常はボキャブラリーの前で使います(例えば also Forth
)。 これにより、
ボキャブラリーによって表されるワードリストが検索順序スタックへプッシュされるという複合的な効果が得られます(訳注:検索順序スタックのTOSを指定のボキャブラリーで置き換えるのではなくて、
検索順序スタックのTOSにさらにプッシュする形にして、 ボキャブラリーの次が以前の検索順序スタックのTOSになるようにする)
Forth
( – ) search-ext “Forth”
検索順序スタック(the search order)の先頭にある wid を、 ワードリスト forth-wordlist
に関連付けられた wid に置き換えます。
Only
( – ) search-ext “Only”
検索順序スタック(the search order)を実装定義で最小化します(Gforth の場合、 これはワードリスト Root
です)。
order
( – ) search-ext “order”
検索順序スタック(the search order)とコンパイル・ワードリストを出力します。 検索順序スタックのワードリストは検索される順序で出力されます(従来のスタック表示方法とは逆になります)。 続けて、 コンパイル・ワードリストが最後に表示されます。
.voc
( wid – ) gforth-0.2 “dot-voc”
wid で表されるワードリストの名前を出力します。 vocabulary
または wordlist
constant
で定義された名前のみ出力できます。 それ以外の場合はアドレスを出力します。
find
( c-addr – xt +-1 | c-addr 0 ) core,search “find”
カウンタ付き文字列 c-addr によって指定された定義の名前を現在の検索順序スタック(the search
order)内の全てのワードリストで検索します。 定義が見つからない場合、 0 を返します。 定義が見つかった場合は、 1
(定義にデフォルト以外のコンパイル機能(compilation semantics)ある場合)または、 -1
(定義にデフォルトのコンパイル機能(compilation semantics)がある場合)を返します。 インタープリター状態で返される xt
はインタープリター機能(interpretation semantics)を表します。 コンパイル状態で返される xt は、
コンパイル機能(デフォルト以外のコンパイル機能の場合)を表すか、または、 コンパイル機能が compile,
(デフォルトのコンパイル機能の場合)となる実行時機能(run-time semantics)を表します。 ANS Forth 標準では、 返される
xt が何を表すかを明確に規定していない(または、 デフォルト以外のコンパイル機能ではなく即実行性についても言及している)ため、
このワードは移植可能なプログラムでは問題があります。 移植性がなくても問題ない場合には、 find-name
とその友達の方が優れています(see Name token)。
search-wordlist
( c-addr count wid – 0 | xt +-1 ) search “search-wordlist”
wid で識別されるワードリスト内で、 c-addr count の文字列で指定された定義を検索します。 定義が見つからない場合は 0 を返します。 定義が見つかった場合は、 xt とともに 1 (定義が即実行である場合)または、 -1 (定義が即実行でない場合)を返します。 Gforth では、返される xt はインタープリター機能(interpretation semantics)を表します。 ANS Forth では、 xt が何を表すか明確に規定していません。
words
( – ) tools “words”
検索順序スタック(the search order)の先頭にあるワードリスト内のすべての定義のリストを表示します。
vlist
( – ) gforth-0.2 “vlist”
WORDS
の古い(Forth-83 より前の)名前。
wordlist-words
( wid – ) gforth-0.6 “wordlist-words”
ワードリスト wid の内容を表示します(訳注: ワードリストに含まれているワードをリストする)。
mwords
( ["pattern"] – ) gforth-1.0 “mwords”
オプションのパラメーター pattern にマッチするすべてのワードをリストします。 指定しない場合、 すべてのワードが一致します。
ワードは古い方から新しい方へとリストされます。 search
のようなパターン・マッチ(デフォルト)ですが、 '
mword-filename-match is mword-match
を使用してワイルドカード(globbing)に切り替えることができます(訳注:
mwords
ENTER で全部リスト、 例えば mwords value
で value を含む名前をリスト)。
Root
( – ) gforth-0.2 “Root”
root ワードリストを検索順序スタック(the search order)に追加します。 このボキャブラリは最小の検索順序を構成し、 search-order のワードのみが含まれます。
Vocabulary
( "name" – ) gforth-0.2 “Vocabulary”
"name" の定義を作成し、 それに新しいワードリストを関連付けます。 "name" の実行時の効果は、 検索順序スタック(the search order)のTOSにある wid を、 "name" に関連付けられた wid に置き換えることです。
seal
( – ) gforth-0.2 “seal”
現在検索順序スタック(the search order)のTOSにあるワードリスト以外のすべてのワードリストを検索順序スタックから削除します。
vocs
( – ) gforth-0.2 “vocs”
システムで定義されているボキャブラリーとワードリストをリストします。
current
( – addr ) gforth-0.2 “current”
変数(Variable
) – コンパイル・ワードリストの wid を保持。
context
( – addr ) gforth-0.2 “context”
context
@
すると、 検索順序スタック(the search order)のTOSにあるワードリストの
wid が得られます。
map-vocs
( ... xt – ... ) gforth-1.0 “map-vocs”
システム内のすべてのワードリスト(テーブルと cs-wordlist を含む)に対して xt ( ... wid – ...) を実行します。
以下は、 標準の Forth ワード群を使用して新しいワードリストを作成および使用する例です:
wordlist constant my-new-words-wordlist : my-new-words get-order nip my-new-words-wordlist swap set-order ; \ add it to the search order also my-new-words \ alternatively, add it to the search order and make it \ the compilation word list also my-new-words definitions \ type "order" to see the problem
この例での問題は、 order
には my-new-words
という名前をワードリスト の wid
に関連付ける方法がないことです(Gforth では、 order
と vocs
において、 名前が関連付けられていない
wid では wid そのものが表示されます)。 名前を wid に関連付ける標準の方法はありません。
Gforth では、 この例は、 以下のように、 名前を wid に関連付ける vocabulary
を使用して再コーディングできます:
vocabulary my-new-words \ add it to the search order also my-new-words \ alternatively, add it to the search order and make it \ the compilation word list my-new-words definitions \ type "order" to see that the problem is solved
人々がワードリストを使用する理由は以下のとおりです:
CODE
ワードが定義されるときに使用される別のワードリストで定義されます)。
forth-wordlist
または、
他の一般的なワードリスト内)と、 実装のためだけに使用されるヘルパー・ワードの組(別のワードリストに隠されている)に編成します。 これにより、
words
の出力が少なくなり、 実装とインターフェイスが分離され、 共通のワードリスト内で名前が競合する可能性が減ります。
IF
が存在する場合があります。 この定義を別のワードリストに配置すると、
検索順序スタック上のワードリストの順序を制御することで、 ホスト・システムの IF
またはターゲット・システムの IF
を特定の文脈で使用するかどうかを制御できます。
ワードリストを使用する場合の欠点は以下のとおりです:
see
は、 そのような場合に、
名前がいくつかの考えられるワードのうちのどれに解決(resolve)されるかを確認するのに役立ちます)。 see
はワードの名前だけを表示し、 ワードがどのワードリストに属しているかを表示しないため、 誤解を招く可能性があります。 一意の名前を使用することは、
名前の競合を避けるためのより良いアプローチです。
以下の例は garbage collector からのもので、 ワードリストを使用してパブリック・ワードとヘルパー・ワードを分離しています:
get-current ( wid ) vocabulary garbage-collector also garbage-collector definitions ... \ define helper words ( wid ) set-current \ restore original (i.e., public) compilation wordlist ... \ define the public (i.e., API) words \ they can refer to the helper words previous \ restore original search order (helper words become invisible)
Forth-94 は、システム上で実行されているプログラムがシステムの特定の特性を判断する方法として「環境問い合わせ」(“environmental query”)という概念を導入しました。 標準では、 システムが認識できるたくさんの文字列と、 それらを問い合わせする方法が指定されています。
environment?
( c-addr u – false / ... true ) core “environment-query”
文字列 c-addr, u を指定します。 文字列が認識されない場合は、 false
フラグを返します。
それ以外の場合は、true
フラグと、 問い合わせした文字列に関する(それ固有の)情報を返します。
注意: 例えば ADDRESS-UNIT-BITS
のドキュメントではスタック上に 1 つのセルを返すことが示されていますが、
environment?
を使用して問い合わせすると、 文字列が認識されたことを示す true
フラグという追加の項目が返されることに注意してください。 ADDRESS-UNIT-BITS
を問い合わせた場合、
environment?
のスタック効果は ( c-addr u -- n true )
です。
いくつかの環境問い合わせ(environmental query)はシステムの制限を取り扱います:
ADDRESS-UNIT-BITS
( – n ) environment “ADDRESS-UNIT-BITS”
1 つのアドレス単位のサイズ(ビット単位)。
MAX-CHAR
( – u ) environment “MAX-CHAR”
文字セット内の任意の文字の最大値
/COUNTED-STRING
( – n ) environment “slash-counted-string”
カウンタ付き文字列の最大サイズ(文字単位)。
/HOLD
( – n ) environment “slash-hold”
表示数値文字列の出力バッファーのサイズ(文字単位)。
/PAD
( – n ) environment “slash-pad”
PAD
が指すスクラッチ領域のサイズ(文字数)。
CORE
( – f ) environment “CORE”
完全なコア・ワード・セットが存在する場合は true。 Gforth では常に当てはまります。
CORE-EXT
( – f ) environment “CORE-EXT”
完全なコア拡張ワード・セットが存在する場合は true。 Gforth では常に当てはまります。
FLOORED
( – f ) environment “FLOORED”
true ならば、 /
などで、 フロア除算(floored division)を実行します。
MAX-N
( – n ) environment “MAX-N”
使用可能な符号付き整数の最大値。
MAX-U
( – u ) environment “MAX-U”
使用可能な符号なし整数の最大値。
MAX-D
( – d ) environment “MAX-D”
使用可能な符号付き2倍長整数の最大値。
MAX-UD
( – ud ) environment “MAX-UD”
使用可能な符号なし2倍長整数の最大値。
return-stack-cells
( – n ) environment “return-stack-cells”
リターン・スタックの最大サイズ(セル単位)。
stack-cells
( – n ) environment “stack-cells”
データ・スタックの最大サイズ(セル単位)。
floating-stack
( – n ) environment “floating-stack”
n はゼロ以外で、 Gforth が深さ n の別個の浮動小数点スタックを維持していることを示しています。
#locals
( – n ) environment “number-locals”
定義内のローカル変数の最大数
wordlists
( – n ) environment “wordlists”
検索順序スタック(the search order)で使用できるワード・リストの最大数
max-float
( – r ) environment “max-float”
使用可能な浮動小数点数の最大値(Gforth では最大の有限数(finite number)として実装)
XCHAR-ENCODING
( – addr u ) environment “XCHAR-ENCODING”
エンコーディングを表す印刷可能なASCII文字列を返し、 (あれば)、 優先されるMIME名、 または “ISO-LATIN-1” や “UTF-8” のような http://www.iana.org/assignments/character-sets の名前を使用します。 ただし、“ASCII” の場合は、 エイリアスの “ASCII” を優先します。
MAX-XCHAR
( – xchar ) environment “MAX-XCHAR”
xchar の最大値。 これはエンコーディングによって異なります。
XCHAR-MAXMEM
( – u ) environment “XCHAR-MAXMEM”
1 つの xchar によって消費される最大メモリーをアドレス単位で返します。
Forth-94 バージョンのワードセットの存在を確認するための環境問い合わせ(environemtal query)がいくつかあります。
当該文字列が存在する場合、 それらはすべて ( -- f )
のスタック効果を持ちます(つまり、 これらの問い合わせの
environment?
のスタック効果は ( c-addr u -- false / f true )
です)。
block block-ext double double-ext exception exception-ext facility
facility-ext file file-ext floating floating-ext locals locals-ext
memory-alloc memory-alloc-ext tools tools-ext search-order search-order-ext
string string-ext
上記ワードセットの問い合わせは、 ほとんど使用および実装されなかったため、 Forth-2012 ではこれらのワードセットの Forth-2012
バリエーションを問い合わせする方法が導入されませんでした。 代わりに、 [defined]
(see Interpreter Directives) を使用するというアイディアです。
Forth-200x (次の標準に取り組むグループ。 このグループが作成するドキュメントも Forth-200x と呼ばれます)は、
提案された拡張機能(extension proposals)の変更が完了すると(CfV 段階)、
提案された拡張機能に対する拡張機能問い合わせ(extension query)を定義します。 そのため、 これらの提案を採用するプログラムは、
提案された拡張機能が適切かどうかを確認できます。 システムにはそれらがあり、 おそらくリファレンス実装(存在する場合)をロードします。
environment?
がそのような問い合わせを見つけた場合、 www.forth200x.org
上の対応する提案がシステムに実装されます(ただし、 environment?
の場合と同様、 存在しないモノは何も分かりません)。
これらの問い合わせにはスタック効果 ( -- )
があります。 つまり、 問い合わせに対して environment?
ではスタック効果 ( c-addr u -- false / true )
があり、 これはワードセット問い合わせよりも便利です。。
これらの提案の多くは Forth-2012 に組み込まれています。 拡張機能問い合わせも Forth
システム実装者の間で特に人気があるわけではないため、 [define]
を使用する方が良いアプローチである可能性があります。
とにもかくにも、 Gforth は以下の拡張機能問い合わせ(extension query)を実装します:
X:2value X:buffer X:deferred X:defined X:ekeys X:escaped-strings
X:extension-query X:fp-stack X:ftrunc X:fvalue X:locals X:n-to-r
X:number-prefixes X:parse-name X:required X:s-escape-quote X:s-to-f
X:structures X:synonym X:text-substitution X:throw-iors X:traverse-wordlist
X:xchar
さらに、 Gforth は以下の Gforth 固有の問い合わせを実装します:
gforth
( – c-addr u ) gforth-environment “gforth”
Gforth のこのバージョン(バージョン > 0.3.0)のバージョン文字列を表す文字列。 さまざまなバージョンのバージョン文字列は、 辞書順(lexicographically)に並べることができることが保証されています。
os-class
( – c-addr u ) gforth-environment “os-class”
ホスト・オペレーティング・システムを説明する文字列。
os-type
( – c-addr u ) gforth-environment “os-type”
$host_os に等しい文字列
標準の問い合わせでは、 環境問い合わせ(environmental query)に使用されるヘッダー・スペース(header space)が、 定義に使用されるヘッダー・スペースとは異なることが要求されます。
通常、 Forth システムは、 環境問い合わせ(environmental query)「だけ」に使用されるワードリスト内に定義を作成することにより、 環境問い合わせをサポートします。 それが Gforth のやっていることです。 認知されている環境問い合わせのセットに定義を追加する標準的な方法はありませんが、 Gforth や、 ワードリスト・メカニズムを使用するその他のシステムでは、 環境問い合わせを受け入れるために使用されるワードリストは、 他のワードリストと同様に操作できます。
environment-wordlist
( – wid ) gforth-0.2 “environment-wordlist”
wid は、 環境問い合わせ(environmental query)によって検索されるワードリストを識別します(SwiftForth および VFX に存在)。
environment
( – ) gforth-0.6 “environment”
environment-wordlist
のボキャブラリー (Win32Forth および VFX に存在)。
以下に、 環境問い合わせ(environmental query)の使用例をいくつか示します:
s" address-unit-bits" environment? 0=
[IF]
cr .( environmental attribute address-units-bits unknown... ) cr
[ELSE]
drop \ ensure balanced stack effect
[THEN]
\ 標準で throw を使うときにプログラムの冒頭でありそうなコード断片
\ 訳注: throw をサポートしていなければ abort"
に置き換え
s" exception" environment? [IF]
0= [IF]
: throw abort" exception thrown" ;
[THEN]
[ELSE] \ we don't know, so make sure
: throw abort" exception thrown" ;
[THEN]
s" gforth" environment? [IF] .( Gforth version ) TYPE
[ELSE] .( Not Gforth..) [THEN]
\ a program using v*
s" gforth" environment? [IF]
s" 0.5.0" compare 0< [IF] \ v* is a primitive since 0.5.0
: v* ( f_addr1 nstride1 f_addr2 nstride2 ucount -- r )
>r swap 2swap swap 0e r> 0 ?DO
dup f@ over + 2swap dup f@ f* f+ over + 2swap
LOOP
2drop 2drop ;
[THEN]
[ELSE] \
: v* ( f_addr1 nstride1 f_addr2 nstride2 ucount -- r )
...
[THEN]
以下は、 environment ワードリストに定義を追加する例です:
get-current environment-wordlist set-current true constant block true constant block-ext set-current
以下のようにして、 environment ワードリストどのような定義があるかを確認できます:
environment-wordlist wordlist-words
Gforth は、 ホスト・オペレーティング・システムのファイル・ システムに保存されているファイルにアクセスするための機能を提供します。 Gforth によって処理されるファイルは、 以下の 2 つのカテゴリに分類できます:
ファイルの内容を通訳(interpret)する最も簡単な方法は、 以下の 2 つの形式のいずれかを使用することです:
include mysource.fs s" mysource.fs" included
通常、 ファイルをインクルードする必要があるのは、 そのファイルがまだインクルードされていない場合(たとえば、 別のソース・ファイルなど)です。 その場合、 以下の 3 つの形式のいずれかを使用できます:
require mysource.fs needs mysource.fs s" mysource.fs" required
ソース・ファイルを通訳(interpret)してもスタックが変更されないようにソース・ファイルを作成することをお勧めします。
この方法で設計されたソース・ファイルは、 required
やそのファミリーと一緒に問題なく使用できます。 例:
1024 require foo.fs drop
ここでは、 引数 1024 (バッファ・サイズなど) を foo.fs に渡しています。 foo.fs
の通訳(interpret)にはスタック効果 ( n – n ) があり、 require
での使用が可能になります。 もちろん、
require されるファイルにこのようなパラメーターを指定する場合は、 最初の require
がすべての用途に適合することを確認する必要があります(つまり、 マスター・ロード・ファイルの早い段階で require
することになります)。
include-file
( i*x wfileid – j*x ) file “include-file”
ファイル wfileid の内容を通訳(interpret)します(テキスト・インタープリターを使用して処理します)。
included
( i*x c-addr u – j*x ) file “included”
文字列 c-addr u で指定される名前のファイルを include-file
します。
included?
( c-addr u – f ) gforth-0.2 “included?”
ファイル c-addr u が以前にインクルードされたファイルのリストにある場合にのみ true。 ファイルがロードされている場合、
たとえば foo.fs として指定されたのが Forth 検索パスのどこかで見つかった可能性があります。 include?
から true
を返すには、 (たとえ ./foo.fs であっても、 )ファイルへの正確なパスを指定する必要があります。
include
( ... "file" – ... ) file-ext “include”
file を include-file
する。
required
( i*x addr u – i*x ) file-ext “required”
既に include
(または required
) されてないなら、 addr u で指定した名前のファイルを
include-file
します。 現状では、
これは(パス(path)付きの)ファイル名を以前にインクルードしたファイルの名前と比較することによって機能します。
require
( ... "file" – ... ) file-ext “require”
file がまだインクルードされていない場合のみ、 include-file
します。
needs
( ... "name" – ... ) gforth-0.2 “needs”
require
のエイリアス。 他のシステム(Win32Forth など)に存在します。
\\\
( – ) gforth-1.0 “\\\”
ソース・ファイルの残りをEOFまでスキップする
.included
( – ) gforth-0.5 “.included”
インクルードされたファイルの名前をリストします。
sourcefilename
( – c-addr u ) gforth-0.2 “sourcefilename”
現在入力ソースとなっているソース・ファイルの名前。 結果は、 ファイルがロードされている間のみ有効です。
現在の入力ソースが(ストリーム)ファイルでない場合、 結果は未定義です。 Gforth では、 結果はセッション全体で有効です(ただし、
savesystem
などを跨いで有効ではありません)。
sourceline#
( – u ) gforth-0.2 “sourceline-number”
(ストリーム)ファイルから現在通訳(interpret)されている行の行番号。 最初の行は番号 1 です。 現在の入力ソースが(ストリーム)ファイルでない場合、 結果は未定義です。
required
の標準 Forth の定義は compat/required.fs で提供されます。
ファイルは名前と種類によって開かれたり作成されたりします。 以下のファイル・アクセス・メソッド(FAM)が認識されます:
r/o
( – fam ) file “r-o”
r/w
( – fam ) file “r-w”
w/o
( – fam ) file “w-o”
bin
( fam1 – fam2 ) file “bin”
+fmode
( fam1 rwxrwxrwx – fam2 ) gforth-1.0 “plus-f-mode”
ファイル・アクセス・モードを fam に追加 - create-file のみ(訳注: rwxrwxrwx は chmod と同様 777 とか 666 。 ただし gforth には直接 8進数記述する方法が無い)
ファイルを開いたり作成したりすると、 他のすべてのファイル・コマンドに使用されるファイル識別子 wfileid が返されます。 すべてのファイル・コマンドは、 ステータス値 wior も返します。 これは、 操作が成功した場合は 0 を返し、 エラーの場合は実装で定義されたゼロ以外の値を返します。
open-file
( c-addr u wfam – wfileid wior ) file “open-file”
create-file
( c-addr u wfam – wfileid wior ) file “create-file”
close-file
( wfileid – wior ) file “close-file”
delete-file
( c-addr u – wior ) file “delete-file”
rename-file
( c-addr1 u1 c-addr2 u2 – wior ) file-ext “rename-file”
ファイル c_addr1 u1 のファイル名を新しい名前 c_addr2 u2 に変更します(rename)
read-file
( c-addr u1 wfileid – u2 wior ) file “read-file”
ファイル wfileid から u1 文字を c_addr からのバッファーに読み取ります。 ゼロ以外の wior はエラーを示します。 U2は読み出したデータの長さを示します。 ファイルの終わりはエラーではなく、 u2$<$u1 かつ wior=0 によって示されます。
read-line
( c_addr u1 wfileid – u2 flag wior ) file “read-line”
wfileid から c_addr u1 のバッファーに行を読み取ります。 Gforth は、 LF と CR と CRLFの 3
つの一般的な行終端文字をすべてサポートします。 ゼロ以外の wior はエラーを示します。 false の flag
は、ファイルの最後(end of the file)で read-line
が呼び出されたことを示します。 u2
は行の長さ(ターミネータなし)を示します。 u2<u1 は行の長さが u2 文字であることを示します。
u2=u1 は、 行が少なくとも u1 文字長であり、 バッファーの u1 文字がその行の文字で埋められており、
そして、 その行の次のスライスは次の read-line
で読み取られます。 行の長さが u1 文字の場合、最初の
read-line
は u2=u1 を返し、 次の read-line は u2=0 を返します。
key-file
( wfileid – n ) gforth-0.4 “key-file”
wfileid から 1 文字 n を読み取ります。 このワードは wfileid のバッファリングを無効にします。
あなたが端末から非標準モード(non-canonical
mode)(RAWモード)で文字を読み取りたい場合は、(C言語インターフェイスを使用して)自分で端末を非標準モード(non-canonical
mode)にする必要があります。 例外は stdin
で、 この場合 Gforth は自動的に非正規モード(non-canonical
mode)に設定します。
key?-file
( wfileid – f ) gforth-0.4 “key-q-file”
f は、 ブロッキング無しに wfileid から少なくとも 1 文字を読み取ることができる場合に true になります。
ファイルに対して read-file
または read-line
も使用したい場合は、 最初に
key?-file
または key-file
を呼び出す必要があります(これら 2
つのワードはバッファリングを無効にします)。
file-eof?
( wfileid – flag ) gforth-0.6 “file-eof-query”
wfileid のファイル終了インジケーター(end-of-file indicator)がセットされている場合、 Flag は true です。
write-file
( c-addr u1 wfileid – wior ) file “write-file”
write-line
( c-addr u wfileid – ior ) file “write-line”
emit-file
( c wfileid – wior ) gforth-0.2 “emit-file”
flush-file
( wfileid – wior ) file-ext “flush-file”
file-status
( c-addr u – wfam wior ) file-ext “file-status”
\ 訳注: wior <> 0 ;ファイルが存在しない(wfamは未定義の値)、
\ wior=0 かつ ( wfam=2 r/w 、 wfam=0 r/o 、 wfam=4 w/o 、
\ wfam=1 r/o bin いずれのacessモードチェックもエラーになった時。
\ 便宜的に読み込み専用かつバイナリとして返す)
file-position
( wfileid – ud wior ) file “file-position”
reposition-file
( ud wfileid – wior ) file “reposition-file”
file-size
( wfileid – ud wior ) file “file-size”
resize-file
( ud wfileid – wior ) file “resize-file”
slurp-file
( c-addr1 u1 – c-addr2 u2 ) gforth-0.6 “slurp-file”
c-addr1 u1 はファイル名、 c-addr2 u2 はファイルの内容です(訳注: slurp;音を立ててすする の意味)
slurp-fid
( fid – addr u ) gforth-0.6 “slurp-fid”
addr u はファイル fid の内容です(訳注: slurp;音を立ててすする の意味)
stdin
( – wfileid ) gforth-0.4 “stdin”
Gforth プロセスの標準入力ファイル。
stdout
( – wfileid ) gforth-0.2 “stdout”
Gforth プロセスの標準出力ファイル。
stderr
( – wfileid ) gforth-0.2 “stderr”
Gforth プロセスの標準エラー出力ファイル。
type
や emit
の出力と、
それらを使用するすべてのワード(明示的なターゲット・ファイルを持たないすべての出力用ワード)を、 outfile-execute
を使用して任意のファイルにリダイレクトできます。 以下のように使用します:
: some-warning ( n -- ) cr ." warning# " . ; : print-some-warning ( n -- ) ['] some-warning stderr outfile-execute ;
これは、 some-warning
を execute した後、 元の出力先を復元します。 この構造は例外に対して安全です。 同様に、
key
からの入力と、 それを利用した入力(明示的にファイルを指定しない入力用ワード)をリダイレクトするための
infile-execute
があります。
outfile-execute
( ... xt file-id – ... ) gforth-0.7 “outfile-execute”
type
などの出力を file-id にリダイレクトして xt を execute します。
outfile-id
( – file-id ) gforth-0.2 “outfile-id”
File-id は、 emit
や、 type
や、 入力として file-id
を受け取らない出力用ワードによって使用されます。 outfile-execute
で変更しない限り、 デフォルトでは
outfile-id
は実行中のプロセスの stdout
を生成します。
infile-execute
( ... xt file-id – ... ) gforth-0.7 “infile-execute”
key
などの入力を file-id にリダイレクトして xt を実行します。
infile-id
( – file-id ) gforth-0.4 “infile-id”
File-id は、 key
や、 ?key
や、 「ユーザー入力デバイス」(user input
device)を参照するものすべてによって使用されます。 デフォルトでは、 infile-execute
で変更しない限り、infile-id
は実行中のプロセスの stdin
を生成します。
あなたが、 入力または出力をファイルにリダイレクトしたくない場合は、 key
や emit
や type
が
defer されたワードであるという事実を利用することもできます(see Deferred Words)。 ただし、 その場合は、 復元と、
例外からの保護について、 自分で心配する必要があります。 また、 この方法で出力をリダイレクトするには、 emit
と
type
の両方をリダイレクトする必要があることに注意してください。
あなたは、 ファイル名をディレクトリとベース・コンポーネントに分割できます:
basename
( c-addr1 u1 – c-addr2 u2 ) gforth-0.7 “basename”
ファイル名が c-addr1 u1 の場合、 c-addr2 u2 は、 先頭のディレクトリ・コンポーネントが削除された部分です(訳注: "os-class" environment? type unix ok な環境では動いたが、 Windows系では不明(’/’ を区切り文字としてハードコーディングしてあるっぽい)。
dirname
( c-addr1 u1 – c-addr1 u2 ) gforth-0.7 “dirname”
C-addr1 u2 は、 ファイル名 c-addr1 u1 のディレクトリ名部分で、 末尾の /
も含まれます。
caddr1 u1 に /
が含まれていない場合、 u2=0 です(訳注: pathの区切り文字として /
をハードコーディングしているので、 Windows系で動くかどうか不明)。
ファイルと同様に、 ディレクトリを開いて読み取ることができます。 読むと、 一度に 1 つのディレクトリ・エントリが得られます。 これを、 (ワイルドカードを使用して)ファイル名とマッチングさせることができます。
open-dir
( c-addr u – wdirid wior ) gforth-0.5 “open-dir”
c-addr, u で指定されたディレクトリを開き、 さらにそこにアクセスするために dir-id を返します。
read-dir
( c-addr u1 wdirid – u2 flag wior ) gforth-0.5 “read-dir”
dir-id で指定されたディレクトリから、 アドレス c-addr にある長さ u1 のバッファーへ、 次のエントリの読み取りを試みます。 これ以上エントリがないために試行が失敗した場合は、 ior=0 かつ flag=0 かつ u2=0 となり、 バッファーは変更されません。 他の理由で次のエントリの読み取りに失敗した場合は、 ior<>0 を返します。 試行が成功した場合、 ファイル名を c-addr からのバッファーに保存し、 ior=0 かつ flag=true かつ ファイル名のサイズに等しい u2 を返します。 ファイル名の長さが u1 より長い場合は、 ファイル名の最初の u1 文字をバッファーに格納し、 ior はエラー "name too long" を示し(訳注: ior= -548 日本語環境では "ファイル名が長すぎます")、 かつ flag=true かつ u2=u1 です。
close-dir
( wdirid – wior ) gforth-0.5 “close-dir”
dir-id で指定されたディレクトリを閉じます。
filename-match
( c-addr1 u1 c-addr2 u2 – flag ) gforth-0.5 “match-file”
ファイル名 c_addr1 u1 とパターン c_addr2 u2 のマッチングを行います。 パターンは、複数の文字 (’*’) または 1 つの文字 (’?’) のワイルドカードである特殊文字 ’*’ と ’?’ を除き、 文字ごとにマッチします。 マッチングできた場合は true
get-dir
( c-addr1 u1 – c-addr2 u2 ) gforth-0.7 “get-dir”
c-addr1, u1で指定されたバッファーにカレント・ディレクトリのパス(path)を格納します。 バッファーサイズが十分でない場合は、 0 0 を返します。
set-dir
( c-addr u – wior ) gforth-0.7 “set-dir”
現在のディレクトリを c-addr, u に変更します。 これが不可能な場合はエラーを返します(訳注: 例えば ior = -514 ; "そのようなファイルやディレクトリはありません")
=mkdir
( c-addr u wmode – wior ) gforth-0.7 “equals-mkdir”
モード wmode でディレクトリ c-addr u を作成します。
mkdir-parents
( c-addr u mode – ior ) gforth-0.7 “mkdir-parents”
ディレクトリ c-addr u とそのすべての親をモード mode (umask が掛けられます)で作成します(訳注:
フルパスで指定したディレクトリを作成する。 aaa/bbb/ccc
というディレクトリを指定すればそれを一気に作る。 ここで、
ccc というファイルが既にある場合は何もせず ior=-529 「ファイルが存在します」エラーとなる。 注意: aaa または
bbb がファイルの場合は「削除」してからディレクトリを作成するので注意。 ior=-525 なら「許可がありません」(aaa ,
aaa/bbb , aaa/bbb/ccc のいずれで許可が無かったのかは不明)。 注意: gforth には8進数リテラルが無いので注意。
2進数(%0111111101 ; -rwxrwxr-x )がおすすめ。 ディレクトリの作成であるので実行権限(x)の付与を行うこと)
あなたが、 included
や、 そのファミリーのために、 直接ファイル名を指定する場合(つまり、 / や
~ で始まるファイル名、 または 2 番目の位置に : が付くファイル名 (‘C:...’ など))、
そのファイルは、 あなたの期待どおりにインクルードされます。
ファイル名が ./ で始まる場合、 これは「現在の」ファイルがインクルードされたディレクトリを指します。 これにより、
(現在の作業ディレクトリや絶対位置に関係なく)ファイル自体の位置を基準にして他のファイルを含めることができます。 この機能は、
ファイルにライブラリーの他のファイルが含まれる可能性がある、 複数のファイルで構成されるライブラリーにとって不可欠です。 C言語の
#include "..."
に相当します。 現在の入力ソースがファイルでない場合、 .
はインクルードされた最も内側のファイルのディレクトリを参照します。 インクルードされたファイルがない場合は、
現在の作業ディレクトリからインクルードされます。
(./ で始まらない)相対ファイル名の場合、 Gforth は検索パス(a search path)を使用します。 検索パスに書かれた各ディレクトリ内で、 指定のファイル名を検索し、 最初に見つかったファイル名をインクルードします。 Forth のソース・ファイルと一般ファイルには別個の検索パスがあります。 検索パスにディレクトリ . が含まれている場合、 ファイルが ./ で指定されているかのように、 「現在のファイルのディレクトリ」または作業ディレクトリを参照します。
~+ を使用して、 (bash
の Tilde Expansion ~+/foo
→
$PWD/foo
のように)現在の作業ディレクトリを参照します(訳注: "~+/my-mkdir-parents.fs"
file-status .s <2> 2 0 ok 2 )
absolute-file?
( addr u – flag ) gforth-1.0 “absolute-file?”
ファイル名が /
または ~
(チルダ展開) で始まる場合、 または ./*
拡張正規表現:
^[/~]|./
の形式である場合、 または 2 番目の文字としてコロンが含まれる場合(C:...
)、
絶対(absolute)ファイル名です( true を返す)。 パスが /
を含んでいるだけでは絶対ファイル名ではありません(訳注:
厳密にファイル名として成立するかどうかチェックしている訳では無い事に注意、 例えば "~ccc.txt" absolute-file?
. -1 ok
、また ./
形式を絶対と見なすことも注意。 "./aaa/bbb/ccc.txt"
absolute-file? . -1 ok
)
検索パス(the search path)は、 Gforth の起動時に初期化されます(see Invoking Gforth)。
fpath
を一般的なパス処理ワードと組み合わせて使用すると、これを表示したり変更したりできます。
fpath
( – path-addr ) gforth-0.4 “fpath”
.fpath
( – ) gforth-0.4 “.fpath”
Forth 検索パスの内容を表示します。
file>fpath
( addr1 u1 – addr2 u2 ) gforth-1.0 “file>fpath”
fpath
で c-addr1 u1 という名前のファイルを検索します。 成功した場合、 c-addr u2
は絶対ファイル名または現在の作業ディレクトリからの相対ファイル名になります。 ファイルを開けない場合(ファイルが見つからない場合)は例外を throw
します。
fpath
と require
の使用例を以下に示します:
fpath path= /usr/lib/forth/|./ require timer.fs
あなたのアプリケーションでは、 included
のように、 複数のディレクトリ内のファイルを探さなければならない場合があります。
これを容易にするために、 Gforth では、 Forth 検索パスを一般化した等価なモノを提供することで、
あなた独自の検索パスを定義して使用できます。
open-path-file
( addr1 u1 path-addr – wfileid addr2 u2 0 | ior ) gforth-0.2 “open-path-file”
パス path-addr で、 指定のファイル addr1 u1 を探します。 見つかった場合(ior=0)は、 結果のパス(addr2 u2)と、 (読み取り専用の)ファイル・デスクリプタ(open file descriptor)(wfileid)を返します。 ファイルが見つからない場合、 ior は(現在の実装では)ファイルを開こうとした最後の試行時点で返されたものです(訳注:ファイルが見つからない場合、 つまり ior<>0 の場合、返されるのは ior のみです ( addr1 u1 path-addr – ior ) )。
file>path
( c-addr1 u1 path-addr – c-addr2 u2 ) gforth-1.0 “file>path”
path-addr に保存されたパス内で c-addr1 u1 という名前のファイルを検索します。 成功した場合、c-addr u2 は絶対ファイル名または現在の作業ディレクトリからの相対ファイル名になります。 ファイルを開けない場合は例外をスローします。
clear-path
( path-addr – ) gforth-0.5 “clear-path”
パス path-addr を空(empty)にします。
also-path
( c-addr len path-addr – ) gforth-0.4 “also-path”
ディレクトリ c-addr len を path-addr に追加します。
.path
( path-addr – ) gforth-0.4 “.path”
検索パス path-addr の内容を表示します。
path+
( path-addr "dir" – ) gforth-0.4 “path+”
ディレクトリ dir を検索パス path-addr に追加します(訳注: 検索パスの後ろに追加する)。
path=
( path-addr "dir1|dir2|dir3" – ) gforth-0.4 “path-equals”
パス path-addr に、 完全に新しい検索パスを作成します。 パス区切り文字は |
です。
カスタム検索パスの作成例を以下に示します:
variable mypath \ no special allocation required, just a variable mypath path= /lib|/usr/lib \ assign initial directories mypath path+ /usr/local/lib \ append directory mypath .path \ output:"/lib /usr/lib /usr/local/lib"
ファイルを検索し、 結果のパスを表示します(訳注: file>path
だと内部で throw しちゃうので、代わりに
opne-path-file
を使った例…だと思う):
s" libm.so" mypath open-path-file throw type close-file \ output:"/lib/libm.so"
今どきのデスクトップ・コンピューターで Gforth を実行すると、 特定のサービスを提供するオペレーティング・システムの制御下で Gforth が実行されます。 これらのサービスの 1 つはファイル・サービスです。 これにより、Forth のソース・コードとデータをファイルに保存し、 Gforth に読み込むことができます(see Files)。
伝統的に、 Forth は、 オペレーティング・システムを介さずに基盤となるハードウェアと直接インターフェイスするシステム上で有用なプログラミング言語でした。 Forth は、 そのようなシステム上で大容量ストレージにアクセスするためのブロック(blocks)と呼ばれるメカニズムを提供します。
ブロックは 1024 バイトのデータ領域であり、 データまたは Forth ソース・コードを保持するために使用できます。 ブロックの内容に構造は課されません。 ブロックはその番号によって識別されます。 ブロックには、1 から実装定義の最大値まで連続した番号が付けられます。
ブロックを使用するが、 オペレーティング・ステムを使用しない一般的なシステムでは、 大容量ストレージとして 1 台のフロッピー・ディスク・ドライブを使用し、 ディスクは 256 バイトのセクターを提供するようにフォーマットされます。 ブロックは、 ディスクの容量の制限まで、 ディスクの最初の 4 セクタをブロック 1 に割り当て、 次の 4 セクタをブロック 2 に割り当てることによって実装されます。 ディスクにはファイル・システム情報は含まれず、 ブロック達のみが含まれます。
ファイル・サービスを提供するシステムでは、 ブロックは通常、単一のブロック・ファイル内に一連のブロックを格納することによって実装されます。 ブロック・ファイルのサイズは、 含まれるブロックの数に対応する 1024 バイトの正確な倍数になります。 これが Gforth が使用するメカニズムです。
一度に開くことができるブロック・ファイルは 1 つだけです。 ブロック・ファイルを指定せずにブロック・ワードを使用すると、 Gforth はデフォルトでブロック・ファイル blocks.fb を使用します。 Gforth は、 ブロック・ファイルを見つけようとするときに Forth 検索パスを使用します(see Source Search Paths)。
プログラム制御下でブロックの読み取りと書き込みを行う場合、 Gforth は中間ストレージとして多数のブロック・バッファー(block
buffers)を使用します。 load
を使用してブロックの内容を解釈する場合は、 これらのバッファーは使用されません。
ブロック・バッファーの動作はキャッシュ(cache)の動作に似ています。 各ブロック・バッファーには 3 つの状態(state)があります:
最初は、すべてのブロック・バッファーは「未割り当て」(unassigned)です。 ブロックにアクセスするには、 ブロック(ブロック番号で指定)をブロック・バッファーに割り当てる必要があります。
ブロック・バッファーへのブロックの割り当ては、 block
または buffer
によって行われます。
ブロックの既存の内容を変更する場合は、 block
を使用します。 ブロックの既存の内容を気にしない場合は、 buffer
を使用します29。
block
または buffer
を使用してブロックをブロック・バッファーに割り当てると、
そのブロック・バッファーが「現在のブロック・バッファー」(current block buffer)になります。
データは「現在のブロック・バッファー」内でのみ操作(読み取りまたは書き込み)できます。
「現在のブロック・バッファー」の内容が変更されている場合は、 「block
または buffer
を再度呼び出す前に、」(何もせずに)変更を破棄するか、 update
を使用して、
ブロックを変更済みとしてマークする必要があります(割り当て済・変更中;assigned-dirty)。 update
を使用してもブロック・ファイルは変更されません。
ブロック・バッファーの状態を「割り当て済・変更中」(assigned-dirty)に変更するだけです。 そのブロックは、
そのブロック・バッファーが別のブロックで必要なときに暗黙的に書き込まれるか、 または、 flush
や
save-buffers
によって明示的に書き込まれます。
ワード flush
は、 すべての「割り当て済・変更中」(assigned-dirty)の
ブロックをディスク上のブロック・ファイルに書き込みます。 bye
を指定して Gforth を終了するときは、 flush
も実行されます。
Gforth では、block
と buffer
は direct-mapped
アルゴリズムを使用してブロック・バッファーをブロックに割り当てます。 つまり、 特定のブロックは、
(特定の操作に対して)「いけにえ・バッファー」(victim buffer)と呼ばれる 1
つの特定のブロック・バッファーにのみ割り当てることができます。
いけにえ・バッファーが「未割り当て」(unassigned)状態または「割り当て済・未編集」(assigned-clean)状態の場合、
直ちに新しいブロックが割り当てられます。 「割り当て済・編集中」(assigned-dirty)の場合、 その現在の内容は、
新しいブロックが割り当てられる前に、 ディスク上のブロック・ファイルに書き戻されます。
ブロックの内容に構造は課されていませんが、 伝統的に内容は 1 行当たり 64 文字の 16 行として表示します。 一つのブロックは、 単一の連続した入力ストリームを提供します(たとえば、単一のパース領域として機能します) – ブロック内に行末文字はなく、 ブロックの末尾にファイル終端文字もありません。 これは以下の 2 つの結果をもたらします:
\
というワード – 行末までのコメント – は特別な処理を行います。 ブロックでは、 現在の 64
文字の「1行分」の終わりまでのすべての文字が無視されます。
Gforth では、 存在しないブロック番号を指定して block
を使用すると、 現在のブロック・ファイルが適切なサイズに拡張され、
ブロック・バッファーがスペースで初期化されます。
Gforth にはシンプルなブロック・エディターが含まれています(詳細については use blocked.fb 0 list
と入力してください)。 ただし、ブロックの使用は推奨されません。 このメカニズムは下位互換性のためにのみ提供されています。
ブロックを取り扱うときに使用される一般的な手法は以下のとおりです:
load
する多数の thru
コマンドが含まれています。
ブロックが Forth プログラミング環境にどの程度うまく統合できるかを確認するには、 Frank Sergeant の Pygmy Forth を参照してください。
open-blocks
( c-addr u – ) gforth-0.2 “open-blocks”
c-addr u で指定された名前のファイルをブロック・ファイルとして使用します(訳注: エラーの場合はその時点で throw されます)。
use
( "file" – ) gforth-0.2 “use”
file をブロック・ファイルとして使用します。
block-offset
( – addr ) gforth-0.5 “block-offset”
最初のブロックの番号を含むユーザー変数(0.5.0 以降のデフォルト: 0)。 0.5.0 より前のバージョンの Gforth
で作成されたブロックファイルのオフセットは 1 からです。 これらのファイルを使用する場合は、次のことができます。 1 offset !
または、 使用されるすべてのブロック番号に 1 を加算します。 または、 ファイルの先頭に 1024 文字追加します。
get-block-fid
( – wfileid ) gforth-0.2 “get-block-fid”
現在のブロック・ファイルのファイル ID を返します。 まだブロック・ファイルが開かれていない場合は、 blocks.fb
をデフォルトのブロック・ファイルとして open-blocks
し、
そのファイルID(現在のブロック・ファイルのファイルID)を返します。
block-position
( u – ) block “block-position”
ブロック・ファイル内のファイル位置をブロック番号 u のブロックの先頭に合わせます。
list
( u – ) block-ext “list”
ブロック番号 u のブロックの内容を表示します。 Gforth では、ブロックは 1行 64 文字からなる 16 行の行番号付きで表示されます。
scr
( – a-addr ) block-ext “s-c-r”
このユーザー変数には、 list
によって最後に処理されたブロックのブロック番号が含まれます。
block
( u – a-addr ) block “block”
ブロック番号 u のブロックにブロック・バッファーが既に割り当てられている場合、
そのブロック・バッファーが更新(「割り当て済・変更中」(assigned-dirty)とマークされているか)されているなら、
そのブロック・バッファー内容を当該ブロック(大容量ストレージ)に上書きしてから、 当該ブロックの内容をそのブロック・バッファーに読み込み、
そのブロック・バッファーを「割り当て済・未変更」(assigned-clean)とマークして、 そのブロック・バッファーの開始アドレス
a-addr を返します。 それ以外の場合は、 ブロック u にブロック・バッファーを新たに割り当て、
その新たに割り当てたブロック・バッファーの開始アドレス a-addr を返します。 Gforth では、 buffer
は単に
block
を呼び出します。
buffer
( u – a-addr ) block “buffer”
Gforth では、 buffer
は単に block
を呼び出します。
empty-buffers
( – ) block-ext “empty-buffers”
すべてのブロック・バッファーを「未割り当て」(unassigned)としてマークします。 (update
によって)「割り当て済・変更中」(assigned-dirty)としてマークされているブロックがある場合、 それらのブロックへの変更は失われます。
empty-buffer
( buffer – ) gforth-0.2 “empty-buffer”
update
( – ) block “update”
現在のブロック・バッファー(current block buffer)の状態を「割り当て・ダーティ」(assigned-dirty)としてマークします。
updated?
( n – f ) gforth-0.2 “updated?”
ブロック番号 n のブロックが更新されている(割り当て済・編集中」(assigned-dirty)としてマークされている)なら true を返す。
save-buffers
( – ) block “save-buffers”
更新されている(「割り当て済・変更中」(assigned-dirty)とマークされている)各ブロック・バッファーの内容を大容量ストレージに転送(上書き)し、 すべてのブロック・バッファーを「割当済み・未編集」(assigned-clean)としてマークします。
save-buffer
( buffer – ) gforth-0.2 “save-buffer”
flush
( – ) block “flush”
save-buffers
の機能を実行してから empty-buffers
を実行します。
load
( i*x u – j*x ) block “load”
ブロック番号 u のブロックをテキスト通訳(Text-interpret)します。 ブロック番号 0 のブロックは load
できません。
thru
( i*x n1 n2 – j*x ) block-ext “thru”
ブロック n1 〜 n2 を順番に load
します。
+load
( i*x n – j*x ) gforth-0.2 “+load”
現在のブロック番号に n を足した番号のブロックをロードします。 ブロック内で使います。
+thru
( i*x n1 n2 – j*x ) gforth-0.2 “+thru”
現在のブロック番号 + n1 の番号 〜 現在のブロック + n2 の番号の、 ブロックの範囲をロードします。 ブロック内で使います。
-->
( – ) gforth-0.2 “chain”
ブロック番号 n のブロックのロード中にこのシンボルが見つかった場合、 そのブロックの残りを破棄してブロック n+1 をロードします。
複数のブロックを単一のロード可能なユニットとして連鎖させるために使用されます。 これは ロードの独立性が損なわれるため、 お勧めできません。
代わりに(標準の) thru
または +thru
を使用してください。
block-included
( a-addr u – ) gforth-0.2 “block-included”
load
によって処理されるブロック内で使用します。 現在のブロック・ファイル仕様を保存し、 a-addr u
で指定されたブロック・ファイルを開き、 そのファイルからブロック番号 1 のブロックを load
します(これにより、
他のブロックがチェーンまたはロードされる可能性があります)。 そして最後に、 ブロック・ファイルを閉じて、 元のブロック・ファイルを復元します。
最も単純な出力機能は、 データ・スタックからの数値を表示する機能です。 数値は、 base
に保存されている基数(別名
radix)で表示されます。
.
( n – ) core “dot”
(符号付きの1倍長整数である) n を自由形式(free-format)で表示し、 その後に空白1つを続けます。
dec.
( n – ) gforth-0.2 “dec.”
n を符号付き 10 進数として表示し、 その後に空白1つを続けます。
h.
( u – ) gforth-1.0 “h.”
u は、 先頭に ‘$‘ を付けた符号なし 16 進数として表示し、 その後に空白1つ続けます。
hex.
( u – ) gforth-0.2 “hex.”
u を符号なし 16 進数として表示し、 先頭に $
を付け、 その後に空白1つを付けます。 この単語の別名は
h.
で、 他のいくつかのシステムには存在しますが、 1.0 より前の Gforth には存在しません。
u.
( u – ) core “u-dot”
(符号なしの1倍長整数の) u を自由形式で表示し、 その後に空白1つを続けます。
.r
( n1 n2 – ) core-ext “dot-r”
n1 を n2 文字幅のフィールドに右揃えで表示します。 数値を表示するために n2 を超える文字が必要な場合は、 すべての桁が表示されます。 必要に応じて、 n2 には先頭に ‘-‘ の文字分の幅を含める必要があります。
u.r
( u n – ) core-ext “u-dot-r”
符号なし整数 u を n 文字幅のフィールドに右揃えで表示します。 数値を表示するために n 文字を超える文字が必要な場合は、すべての桁が表示されます。
dec.r
( u n – ) gforth-0.5 “dec.r”
符号なし整数 u を n 文字幅のフィールドに符号なし 10 進数として表示します。
d.
( d – ) double “d-dot”
(符号付き2倍長整数の) d を自由形式で表示します。 その後に空白1つが続きます。
ud.
( ud – ) gforth-0.2 “u-d-dot”
(符号付無し2倍長整数の) ud を自由形式で表示し、 その後に空白1つを続けます。
d.r
( d n – ) double “d-dot-r”
2倍長整数 d を n 文字幅のフィールドに右揃えで表示します。 数値を表示するために n を超える文字が必要な場合は、 すべての桁が表示されます。 必要に応じて、n には先頭に ‘-‘ 文字分の幅を含める必要があります。
ud.r
( ud n – ) gforth-0.2 “u-d-dot-r”
符号無し2倍長整数 ud を n 文字幅のフィールドに右揃えで表示します。 数値を表示するために n 文字を超える文字が必要な場合は、すべての桁が表示されます。
Forth は伝統的に、 整数の書式設定された出力に「表示数値出力」(pictured numeric Output)
と呼ばれる手法を使用しています。 この手法では、 数値から数字桁(digits)が抽出され(base
で定義された現在の出力基数を使用
see Number Conversion)、 ASCII コードに変換され、
メモリーのスクラッチパッド領域(see Implementation-defined options)に構築される文字列の先頭に付加されます。 抽出プロセス中に、
文字列の先頭に任意の文字を追加できます。 完成した文字列はアドレスと長さによって指定され、 プログラム制御の下で操作(TYPE
や、
コピーや、 変更)できます。
前のセクションで説明したすべての整数出力ワード (see Simple numeric output) は、 Gforth では表示数値出力(pictured numeric output)を使用して実装されています。
表示数値出力(pictured numeric output)について覚えておくべき 3 つの重要な点:
標準 Forth は、 <#
で空にして初期化し、 #>
で結果文字列を取得する単一の出力バッファー(別名ホールド領域;hold area)をサポートします。
Gforth はさらに、 このバッファーの入れ子になった使用をサポートしており、 たとえば、 ホールド領域を処理するコード内でデバッグ・トレーサー
~~
からの出力を入れ子にすることができます。 <<#
は新しい入れ子を開始し、 #>
は結果文字列を生成し、 #>>
は入れ子を解除します。 入れ子のホールド領域が再利用され、#>
は次に外側の入れ子の文字列を生成します。 Gforth の高レベルの数値出力ワードはすべて <<#
... #>
... #>>
を使用し、 ホールド領域の他のユーザー内に入れ子にできます。
<#
( – ) core “less-number-sign”
表示数値出力文字列を 初期化/クリア します(訳注: 表示数値出力用のポインターをホールド領域の末尾(初期値)に戻すだけ。 中身は消さない。)
<<#
( – ) gforth-0.5 “less-less-number-sign”
#>>
で終わるホールド領域を開始します。 相互に入れ子にすることも、 <#
で入れ子にすることもできます。 注:
<<#
と #>>
を一致させないと、 最終的にホールド領域が不足します。 <#
を使用してホールド領域を空にリセットできます。
#
( ud1 – ud2 ) core “number-sign”
<<#
と #>
の間で使用されます。 ud1 の最下位桁(base
による)を、
表示数値出力文字列の先頭に追加します。 ud2 は ud1/base、 つまり残りの桁を表す数値です。
#s
( ud – 0 0 ) core “number-sign-s”
<<#
と #>
の間で使用されます。 ud のすべての数字を表示数値出力文字列の先頭に追加します。
#s
は少なくとも 1 つの数字を変換します。 したがって、 ud が 0 の場合、 #s
は表示数値出力文字列の先頭に ‘0‘ を追加します。
hold
( char – ) core “hold”
<<#
と #>
の間で使用されます。 表示数値出力文字列の前に文字 char を追加します。
holds
( addr u – ) core-ext “holds”
<<#
と #>
の間で使用されます。 表示数値出力文字列の前に文字列 addr u
を追加します。
sign
( n – ) core “sign”
<<#
と #>
の間で使用されます。 n (1倍長整数) が負の場合、 表示数値出力文字列の先頭に
-
を追加します。
#>
( xd – addr u ) core “number-sign-greater”
変換対象(変換残りの)数値 xd を破棄し、 フォーマットされた文字列のアドレスと長さを示す addr u を返すことで、
表示数値出力文字列を完成させます。 標準のプログラムでは、 文字列内の文字を変更する場合があります。 ホールド領域は解放されません。
#>>
を使用して <<#
で始まるホールド領域を解放するか、 <#
を使用してすべてのホールド領域を解放します。
#>>
( – ) gforth-0.5 “number-sign-greater-greater”
<<#
で始まるホールド領域を解放します。
以下に、 表示数値出力の使用例をいくつか示します:
: my-u. ( u -- ) \ PNS(Pictured Number String)の最も単純な使用法。標準の u. のように振る舞います。 0 \ 上記 u を 2倍長にする <<# \ 変換開始 #s \ 全桁を変換 #> \ 変換終了 TYPE SPACE \ 表示、続けて空白1つ #>> ; \ ホールド領域を開放 : cents-only ( u -- ) 0 \ 上記 u を 2倍長整数に変換 <<# \ 変換開始 # # \ 最下位と最下位からの次の 2 桁のみ変換 #> \ 変換完了。他の桁は破棄 TYPE SPACE \ 表示、続けて空白1つ #>> ; \ ホールド領域を開放 : dollars-and-cents ( u -- ) 0 \ 上記 u を符号無し2倍長整数に変換 <<# \ 変換開始 # # \ 下位2桁を変換 '.' hold \ 小数点を打つ #s \ 残りの桁を変換 '$' hold \ 通貨記号を打つ #> \ 変換完了 TYPE SPACE \ 表示、続けて空白1つ #>> ; \ ホールド領域を開放 : my-. ( n -- ) \ 負数も処理する標準の . のように振る舞う s>d \ 符号付き2倍長整数に変換 swap over dabs \ 符号バイトを別途保存して数値は符号無し2倍長に <<# \ 変換開始 #s \ 全桁を変換 rot sign \ 符号チェック。必要なら "-" 付加 #> \ 変換完了 TYPE SPACE \ 表示、続けて空白1つ #>> ; \ ホールド領域を開放 : account. ( n -- ) \ (会計風出力)会計士はマイナス記号が嫌いで、 \ 負の数には括弧を使用します s>d \ 符号付き2倍長整数に変換 swap over dabs \ 符号バイトを別途保存して数値は符号無し2倍長に <<# \ 変換開始 2 pick \ 符号バイトのコピーを得る 0< IF ')' hold THEN \ これが(あれば)出力の右端の文字 #s \ 全桁を変換 rot \ 符号バイトを得る 0< IF '(' hold THEN #> \ 変換完了 TYPE SPACE \ 表示、続けて空白1つ #>> ; \ ホールド領域を開放
これらのワードの利用例をいくつか示します:
1 my-u. 1 ok hex -1 my-u. decimal FFFFFFFFFFFFFFFF ok 1 cents-only 01 ok 1234 cents-only 34 ok 2 dollars-and-cents $0.02 ok 1234 dollars-and-cents $12.34 ok 123 my-. 123 ok -123 my-. -123 ok 123 account. 123 ok -456 account. (456) ok
浮動小数点数出力は常に基数 10 を使用して表示されます。
f.
( r – ) floating-ext “f-dot”
(浮動小数点数) r を指数なしで表示し、 その後に空白1つ続けます。
fe.
( r – ) floating-ext “f-e-dot”
r を工学表記(3 で割り切れる指数) で表示し、 その後に空白1つ続けます。
fs.
( r – ) floating-ext “f-s-dot”
r を科学表記(指数付き)で表示し、 その後に空白1つ続けます。
fp.
( r – ) floating-ext “f-p-dot”
r を SI 接頭辞表記(3 で割れる指数を使用し、 可能な場合は SI 接頭辞に変換)で表示し、 その後に空白1つ続けます。
数値 1234.5678E23 をさまざまな浮動小数点数出力形式で出力する例を以下に示します。
1234.5678E23 f. 123456780000000000000000000. ok 1234.5678E23 fe. 123.456780000000E24 ok 1234.5678E23 fs. 1.23456780000000E26 ok 1234.5678E23 fp. 123.456780000000Y ok
出力桁幅は以下の影響を受けます:
precision
( – u ) floating-ext “precision”
u は、 f.
や fe.
や fs.
や fp.
で現在使用されている有効桁数です。
set-precision
( u – ) floating-ext “set-precision”
f.
や fe.
や fs.
や fp.
で現在使用されている有効桁数を u
に設定します。
以下のコマンドを使用して、 出力をより詳細に制御できます:
f.rdp
( rf +nr +nd +np – ) gforth-0.6 “f.rdp”
浮動小数点数 rf を書式化して表示します。 出力の合計幅は nr です。 固定小数点表記の場合、 小数点以下の桁数は
+nd、 有効桁数の最小値は np です。 Set-precision
は f.rdp
には影響しません。
固定小数点表記は、 有効桁数が少なくとも np の場合で、 かつ、 小数点以下の桁数が収まる場合に、 使用されます。
固定小数点表記が使用されない場合は指数表記が使用され、 それでも適合しない場合はアスタリスクが出力されます。
数値がまったく適合しないリスクを避けるために、 nr>=7 を使用することをお勧めします。 f.rdp
が指数表記に切り替わるケースを避けるために、 nr>=np+5 をお勧めします。 どうしてかというと、
固定小数点表記でも有効桁数が少なすぎるのに、 指数表記では有効桁数が更に少なくなるためです。 一部の数値を固定小数点表記しなければならない場合は、
nr>=nd+2 をお勧めします。 np の値が小さいほど、 より多くの場合で固定小数点表記で表示されます
(固定小数点表記に有効数字がほとんどまたはまったく残っていない場合)。 すべての数値を指数表記したい場合は、np>nr
をお勧めします。
出力にどのような影響を与えるかをより直感的に理解できるように、 パラメーターの組み合わせの例をいくつか示します。 各行内は同じ数値が出力されていて、 各列には同じパラメーターの組み合わせが出力に使用されています:
12 13 0 7 3 4 7 3 0 7 3 1 7 5 1 7 7 1 7 0 2 4 2 1 |-1.234568E-6|-1.2E-6| -0.000|-1.2E-6|-1.2E-6|-1.2E-6|-1.2E-6|****| |-1.234568E-5|-1.2E-5| -0.000|-1.2E-5|-.00001|-1.2E-5|-1.2E-5|****| |-1.234568E-4|-1.2E-4| -0.000|-1.2E-4|-.00012|-1.2E-4|-1.2E-4|****| |-1.234568E-3|-1.2E-3| -0.001| -0.001|-.00123|-1.2E-3|-1.2E-3|****| |-1.234568E-2|-1.2E-2| -0.012| -0.012|-.01235|-1.2E-2|-1.2E-2|-.01| |-1.234568E-1|-1.2E-1| -0.123| -0.123|-.12346|-1.2E-1|-1.2E-1|-.12| |-1.2345679E0| -1.235| -1.235| -1.235|-1.23E0|-1.23E0|-1.23E0|-1E0| |-1.2345679E1|-12.346|-12.346|-12.346|-1.23E1|-1.23E1| -12.|-1E1| |-1.2345679E2|-1.23E2|-1.23E2|-1.23E2|-1.23E2|-1.23E2| -123.|-1E2| |-1.2345679E3|-1.23E3|-1.23E3|-1.23E3|-1.23E3|-1.23E3| -1235.|-1E3| |-1.2345679E4|-1.23E4|-1.23E4|-1.23E4|-1.23E4|-1.23E4|-12346.|-1E4| |-1.2345679E5|-1.23E5|-1.23E5|-1.23E5|-1.23E5|-1.23E5|-1.23E5|-1E5|
以下を使用して、 数値を表示する代わりに文字列を生成できます:
f>str-rdp
( rf +nr +nd +np – c-addr nr ) gforth-0.6 “f>str-rdp”
rf を c-addr nr の文字列に変換します。 nr +nd np の変換規則と意味は f.rdp
と同じです。 結果は表示数値出力(pictured numeric output)バッファーに格納され、
そのバッファを破壊するものによって破壊されます(訳注: このバッファーは、 他の 表示数値出力(pictured numeric output)
と共用であるということ)。
f>buf-rdp
( rf c-addr +nr +nd +np – ) gforth-0.6 “f>buf-rdp”
rf を c-addr nr の文字列に変換します。 nr nd np の変換規則と意味は f.rdp
と同じです。
以下のような、 高レベルの FP-to-string ワードを実装するために使用されるプリミティブもあります:
represent
( r c-addr u – n f1 f2 ) floating “represent”
r の 10 進仮数部(別名 mantissa)をバッファ c-addr u 内の文字列に変換します。 n は指数で、r が負の場合は f1 が true、 r が有効(Gforth の有限数)の場合は f2 が true です(訳注: 仮数部が u の桁数になるようそれ以下を四捨五入するっぽい)。
cr
( – ) core “c-r”
(ホスト OS の好みの種類の)改行(newline)を出力します。 注意: Forth
コマンド・ライン・インタープリターの改行(newline)挿入のクセのため、 cr
をテキストの先頭で使用することをお勧めします。 例:
cr ." hello, world"
.
space
( – ) core “space”
空白を1つ表示する。
spaces
( u – ) core “spaces”
u 個の空白を表示します。
out
( – addr ) gforth-1.0 “out”
addr
には、 ユーザー出力デバイス上の現在行内のカーソルの位置を指定しようと試みる数値が含まれています。 cr
で 0
にリセットされ、 type
によって文字数ずつ増加、 emit
で増加、 backspaces
で減少します。
残念ながら、 タブ文字や、マルチバイト文字や、 幅 0 と 幅2 のユニコード文字の存在は考慮されていないため、 単純な場合にのみ機能します。
.\"
( compilation ’ccc"’ – ; run-time – ) gforth-0.6 “dot-backslash-quote”
."
と似ていますが、
C言語のようなバックスラッシュによるエスケープ・シーケンス(\-escape-sequences)を認識します(詳しくは S\"
参照)。
."
( compilation ’ccc"’ – ; run-time – ) core “dot-quote”
コンパイル時: ‘"‘ (二重引用符)で区切られた文字列 ccc をパースします。 実行時、 その文字列を表示します。 このワードのインタープリター機能(interpretation semantics)は、 標準 Forth では定義されていません。 Gforth でのインタープリター機能(interpretation semantics)は、 その文字列を表示することです。
.(
( compilation&interpretation "ccc<paren>" – ) core-ext “dot-paren”
コンパイル時(compilation semantics)とインタープリター時(interpretation semantics):
)
(右括弧)で区切られた文字列 ccc をパースします。 その文字列を表示します。 これは、
コンパイル中に進行状況情報を表示するためによく使用されます。 下記の例を参照してください。
あなたが .( hello)
を使用するべきか ." hello"
を使用するべきかを気に掛けたくない場合は、
"hello" type
と書くことができます。 これにより、 通常必要なものが得られます(ただし、他の Forth
システムへの移植性は低くなります)。
例として、 ファイル test.fs に保存されている以下のテキストについて考えてみましょう:
.( text-1) : my-word ." text-2" cr .( text-3) "text-4" type ; ." text-5" "text-6" type
このコードを Gforth にロードすると、 以下の出力が生成されます:
include test.fs RET text-1text-3text-5text-6 ok
.(
は即実行ワードであるため、 メッセージ text-1
と text-3
が表示されます。
コロン定義の内側でも外側でも、 全く同じに振る舞います。
."
に対する Gforth の追加インタープリター機能(interpretation semantics)により、 メッセージ
text-5
が表示されます。
"text-6" type
が通訳(interpret)され、 メッセージ text-6
が表示されます。
my-word
の定義内で ."
のコンパイル機能(compilation
semantics)を実行するため、 メッセージ text-2
は「表示されません」。
"text-4" type
は my-word
内にコンパイルされるため、 メッセージ text-4
は「表示されません」。
type
( c-addr u – ) core “type”
u>0 なら、 c-addr からに格納されている文字列から u 文字を表示します(訳注: 日本語(UTF-8)の出力も対応)。
xemit
( xc – ) xchar “x-emit”
端末に xchar を出力します。
emit
( c – ) core “emit”
バイト値 c を現在の出力に送信します。 ASCII 文字の場合、 emit
は xemit
と同等です。
typewhite
( addr n – ) gforth-0.2 “typewhite”
type と似ていますが、 文字の代わりに空白が表示されます(訳注: 指定の文字数 n だけ空白出すだけなので日本語(UTF-8)だと表示幅がズレる)。
端末に出力している場合、 カーソル位置を制御することができます:
at-xy
( x y – ) facility “at-x-y”
カーソルを位置 x y に置きます。 ディスプレイの左上角は 0 0 です。
at-deltaxy
( dx dy – ) gforth-0.7 “at-deltaxy”
現在の位置を x y として、 カーソルを x+dx y+dy に置きます。
カーソルをどこに置くかを知るには、 以下のように、 画面(screen)のサイズを知っておくと役立つことがよくあります:
form
( – nlines ncols ) gforth-0.2 “form”
\ 訳注:画面のサイズを得る
page
( – ) facility “page”
画面(screen)をクリアする
注意: ターミナル以外では、フォーム・フィード(form feed;FF)を取得するには page
ではなく 12 Emit
を使用する必要があることに注意してください。
以下のワード群は、 意味・理由ごとに色を変えるために使用されます。 更に細かい設定は、 ワードによって指定された色とスタイルで生成されます。 実際の色とスタイルはテーマによって異なります(下記を参照)。
default-color
( – ) gforth-1.0 “default-color”
システムのデフォルト用の色
error-color
( – ) gforth-1.0 “error-color”
エラー用の色: (通常は)赤
error-hl-inv
( – ) gforth-1.0 “error-hl-inv”
エラー用にハイライトとして反転表示するカラーモード
error-hl-ul
( – ) gforth-1.0 “error-hl-ul”
エラー用にアンダーラインでハイライトする色変更モード
warning-color
( – ) gforth-1.0 “warning-color”
警告(warning)の色: 背景が黒の端末では 青/黄
info-color
( – ) gforth-1.0 “info-color”
情報(info)用の色: 黒色の背景の端末では 緑/シアン
success-color
( – ) gforth-1.0 “success-color”
成功(success)の色: 緑
input-color
( – ) gforth-1.0 “input-color”
ユーザー入力の色: 黒/白 (両方ともボールド)
status-color
( – ) gforth-1.0 “status-color”
エラー用にハイライトとして反転表示するカラーモード
あなたが、 明るい背景を好むか暗い背景を好むかに応じて、 前景の色テーマ(foreground colors-theme)を以下のように変更できます:
light-mode
( – ) gforth-1.0 “light-mode”
白背景用の色テーマ
dark-mode
( – ) gforth-1.0 “dark-mode”
黒背景用の色テーマ
uncolored-mode
( – ) gforth-1.0 “uncolored-mode”
このモードでは色は設定されませんが、 デフォルトの色が使用されます。
magenta-input
( – ) gforth-1.0 “magenta-input”
入力色(input color)を認識しやすくします(プレゼンテーションに役立ちます)
単一の印刷可能な文字を取得したい場合は、 key
を使用できます。 文字が key
に使用できるかどうかを確認するには、
key?
を使用できます。
key
( – char ) core “key”
1 文字 char を受け取ります(ただし表示はされません)。
key-ior
( – char|ior ) gforth-1.0 “key-ior”
1 文字 char を受け取ります (ただし表示はされません)。 エラーまたは割り込みの場合は、 代わりに、 負数の ior を返します。
key?
( – flag ) facility “key-question”
文字が key
で使用可能かどうかを判断します。 文字が使用可能な場合、flag は true です。 次に
key
を呼び出すと、 文字が生成されます。 一度 key?
が true を返すと、 その後に key
または ekey
を呼び出す前に key?
を呼び出した場合も true が返されます。
xkey?
( – flag ) xchar “x-key-query”
\ 訳注: UTF-8 な環境では key? は xkey? のエイリアスです。
\ xchar 文字が xkey で使用可能かどうかを判断します(たぶん)
印刷可能な文字と印刷不可能な文字を組み合わせて処理したい場合は、ekey
とそのファミリーを使用して実行できます。
ekey
は、 ekey>char
で文字に変換するか、 ekey>fkey
でキー識別子に変換する必要があるキーボード・イベントを生成します。
ekey を使用するための一般的なコードは以下のようになります:
ekey ekey>xchar if ( xc ) ... \ do something with the character else ekey>fkey if ( key-id ) case k-up of ... endof k-f1 of ... endof k-left k-shift-mask or k-ctrl-mask or of ... endof ... endcase else ( keyboard-event ) drop \ just ignore an unknown keyboard event type then then
ekey
( – u ) facility-ext “e-key”
キーボード・イベント u を受け取りす(実装定義のエンコーディングです) 。
ekey>xchar
( u – u false | xc true ) xchar-ext “e-key-to-x-char”
可能であれば、 キーボード・イベント u を xchar xc
に変換します。
ekey>char
( u – u false | c true ) facility-ext “e-key-to-char”
可能であれば、 キーボード・イベント u を文字 c
に変換します。 注意: 非 ASCII 文字は、
ekey>char
と ekey>fkey
の両方から false
が返ることに注意してください。
利用可能な場合は、 ekey>char
の代わりに ekey>xchar
を使用します。
ekey>fkey
( u1 – u2 f ) facility-ext “e-key-to-f-key”
u1 が特殊キー(special key)セット内のキーボード・イベントの場合、 キーボード・イベント u1 をキー ID u2 に変換し、 true を返します。それ以外の場合は、 u1 と false を返します。
ekey?
( – flag ) facility-ext “e-key-question”
キーボード・イベントが利用可能な場合は True。
カーソル・キーのキー識別子は以下のとおりです:
k-left
( – u ) facility-ext “k-left”
k-right
( – u ) facility-ext “k-right”
k-up
( – u ) facility-ext “k-up”
k-down
( – u ) facility-ext “k-down”
k-home
( – u ) facility-ext “k-home”
別名 Pos1
k-end
( – u ) facility-ext “k-end”
k-prior
( – u ) facility-ext “k-prior”
別名 PgUp
k-next
( – u ) facility-ext “k-next”
別名 PgDn
k-insert
( – u ) facility-ext “k-insert”
k-delete
( – u ) facility-ext “k-delete”
著者の xterm 上ではDEL キーで、 Backspace ではありません
ファンクション・キー(別名キーパッド・キー)のキー識別子は以下のとおりです:
k-f1
( – u ) facility-ext “k-f-1”
k-f2
( – u ) facility-ext “k-f-2”
k-f3
( – u ) facility-ext “k-f-3”
k-f4
( – u ) facility-ext “k-f-4”
k-f5
( – u ) facility-ext “k-f-5”
k-f6
( – u ) facility-ext “k-f-6”
k-f7
( – u ) facility-ext “k-f-7”
k-f8
( – u ) facility-ext “k-f-8”
k-f9
( – u ) facility-ext “k-f-9”
k-f10
( – u ) facility-ext “k-f-10”
k-f11
( – u ) facility-ext “k-f-11”
k-f12
( – u ) facility-ext “k-f-12”
k-f11
と k-f12
はそれほど広くには利用可能ではないことに注意してください。
これらのキー識別子をさまざまなシフト・キーのマスクと組み合わせることができます:
k-shift-mask
( – u ) facility-ext “k-shift-mask”
k-ctrl-mask
( – u ) facility-ext “k-ctrl-mask”
k-alt-mask
( – u ) facility-ext “k-alt-mask”
ASCII 値を持つキーが多数あるため、 特殊キーとして報告される可能性は低いですが、 以下のキーとシフト・キーの組み合わせは特殊キーとして報告される可能性があります:
k-enter
( – u ) gforth-1.0 “k-enter”
k-backspace
( – u ) gforth-1.0 “k-backspace”
k-tab
( – u ) gforth-1.0 “k-tab”
さらに、 キーおよびその他のイベントには以下のキー・コードがあります:
k-winch
( – u ) gforth-1.0 “k-winch”
ユーザーがウィンドウ・サイズを変更したときに生成される可能性のあるキー・コード。
k-pause
( – u ) gforth-1.0 “k-pause”
k-mute
( – u ) gforth-1.0 “k-mute”
k-volup
( – u ) gforth-1.0 “k-volup”
k-voldown
( – u ) gforth-1.0 “k-voldown”
k-sel
( – u ) gforth-1.0 “k-sel”
Androidでの選択(selections)のキー・コード
k-eof
( – u ) gforth-1.0 “k-eof”
注意: Forth システムに ekey>fkey
とキー識別子のワードがある場合でも、 そのキーが必ずしも利用可能であるとは限らず、
すべてのキーとシフト・マスクとの可能なすべての組み合わせを報告できるとは限らないことに注意してください。
したがって、キーやキーの組み合わせが押せない場合や認識されない場合でも、プログラムが使えるようにプログラムを作成してください。
例: 古いキーボードには F11 キーと F12 キーがないことがよくあります。 xterm で Gforth を実行すると、xterm は多数の組み合わせ(例: Shift-Up)を捕捉しますが、それを Gforth に渡すことはありません。 最後に、Gforth は現在、 複数のシフト・キーの組み合わせを認識して報告しません(そのため、上記の例の shift-ctrl-left のケースは決して入力されません)。
Gforth は、ANSI 端末で利用可能なさまざまなキーを認識します(MS-DOS では、 その動作を実現するには ANSI.SYS ドライバーが必要です)。 これは、 そのようなキーが押されたときに ANSI 端末が送信するエスケープ・シーケンスを認識することによって機能します。 他のエスケープ・シーケンスを送信する端末を使用している場合、 Gforth では有益な結果は得られません。 他の Forth システムは異なる方法で動作する可能性があります。
Gforth には、 ファンクション・キーの名前を出力するためのいくつかのワードも用意されています:
fkey.
( u – ) gforth-1.0 “fkey-dot”
ファンクション・キー u の文字列表現を出力します。 U はファンクション・キー(おそらく修飾子マスク付き)でなければなりません。 そうでない場合は例外が発生する可能性があります。
simple-fkey-string
( u1 – c-addr u ) gforth-1.0 “simple-fkey-string”
c-addr u は、ファンクション・キー u1 の文字列名です。 修飾子マスクのない単純なファンクション・キーに対してのみ機能します。 現在、 単純なファンクション・キーでない u1 では例外を生成します。
文字列をメモリーに保存する方法については、 String representations を参照してください。
キーボードから一行入力するためのワード群:
accept
( c-addr +n1 – +n2 ) core “accept”
ユーザー入力デバイスから最大 n1 文字の文字列を取得し、 c-addr に保存します。 n2
は受け取った文字列の長さです。 ユーザーは RET を押して終了を指示します。 Gforth は、 accept
で、
Forth コマンド・ラインで利用できるすべての編集機能(履歴やワード補完を含む)をサポートしています。
edit-line
( c-addr n1 n2 – n3 ) gforth-0.6 “edit-line”
accept
のように振る舞いますが、バッファ c-addr n1 の内容を先頭から長さ n2 まで引用します(
n2=0 の場合、 全く引用せず、 空の状態から入力編集開始になります)。 受け取った文字列の長さを n3 に返します。
変換ワード群:
s>number?
( addr u – d f ) gforth-0.5 “s>number?”
文字列 addr u を 符号付き2倍長整数 d に変換し、 フラグ f が true ならば成功、 false
ならば変換失敗です( d の値は保証されません) (訳注: フラグが true の場合でも -15.
基数プレフィックスを付けずに記述した場合、 警告が出力されます。 警告が出ないようにするには基数プレフィックスを付けて下さい #-15.
)
s>unumber?
( c-addr u – ud flag ) gforth-0.5 “s>unumber?”
文字列 c-addr u を 符号なし2倍長整数 ud に変換します。 フラグ f が true ならば成功、 false
ならば変換失敗です( d の値は保証されません)。 (訳注: フラグが true の場合でも 15.
基数プレフィックスを付けずに記述した場合、 警告が出力されます。 警告が出ないようにするには基数プレフィックスを付けて下さい #15.
、
注意: 負数も受け付けます; "#-15." s>unumber? .s <3> -15 -1 -1 ok drop -15 -1
ud. 340282366920938463463374607431768211441 ok
"#340282366920938463463374607431768211441." s>unumber? .s <3> -15 -1 -1 ok 3
)
>number
( ud1 c-addr1 u1 – ud2 c-addr2 u2 ) core “to-number”
文字列 c-addr1 u1 を現在の基数で符号なし2倍長整数に変換しようと試みます。 符号なし2倍長整数 ud1
に変換結果を積算して ud2 にします。 変換は、 文字列全体が変換されるか、 現在の基数で変換できない文字( ‘+‘ または ‘-‘
を含む)が検出されるまで、 左から右に続行されます。 変換可能な各文字ごとに、 ud1 * base
してから、
次にその文字によって表される値を足しこみます( new ud1 = ud1 * base + digit )。 c-addr2 は、
最初の未変換文字の位置です(文字列全体が変換された場合は文字列の末尾以降)。 u2 は、 文字列内の未変換の文字の数です。
最後まで変換出来たときは 0 です。 オーバーフローは検出されません。
>float
( c-addr u – f:... flag ) floating “to-float”
実際のスタック効果: ( c_addr u – r t | f )。 文字列 c-addr u を内部浮動小数点表現に変換しようとします。 文字列が有効な浮動小数点数を表す場合、 r が浮動小数点スタックに配置され、 flag が true になります。 それ以外の場合、flag は false になります(訳注: この場合、 浮動小数点数スタックには何も積まれない)。 空白の文字列は特殊なケースであり、 浮動小数点数 0 を表します。
>float1
( c-addr u c – f:... flag ) gforth-1.0 “to-float1”
実際のスタック効果: ( c_addr u c – r t | f ) c を小数点として使用して(訳注: ’.’ が小数点とは限らない。ロケールにより異なるため、指定できるようになっている)、 文字列 c-addr u を内部浮動小数点表現に変換しようと試みます。 文字列が有効な浮動小数点数を表す場合、 r が浮動小数点スタックに配置され、 flag が true になります。 それ以外の場合、flag は false になります(この場合、浮動小数点数スタックには何も積まれません)。 空白の文字列は特殊なケースであり、浮動小数点数 0 を表します。
時代遅れ(OBSOLESCENT)の入力ワード群と変換ワード群:
convert
( ud1 c-addr1 – ud2 c-addr2 ) core-ext-obsolescent “convert”
時代遅れ(OBSOLESCENT): >number
に置き換えられました。
expect
( c-addr +n – ) core-ext-obsolescent “expect”
最大 +n 文字の文字列を受け取り、c-addr から始まるメモリーに保存します。 文字列は表示されます。
<return>キーを押すか、 +n 文字を受け取ると入力が終了します。 通常の Gforth 行編集機能が利用可能です。 文字列の長さは
span
に保存されますが、 <return> 文字は含まれません。 時代遅れ(OBSOLESCENT): accept
に置き換えられました。
span
( – c-addr ) core-ext-obsolescent “span”
変数(variable) – c-addr は、 expect
によって最後に受け取った文字列の長さを格納するセルのアドレスです。 時代遅れ(OBSOLESCENT)。
他のプロセスによって作成されたパイプライン(see Gforth in pipes)を Gforth で使用することに加えて、 あなたは
open-pipe
を使用してあなた独自のパイプラインを作成し、 読み書きすることができます。
open-pipe
( c-addr u wfam – wfileid wior ) gforth-0.2 “open-pipe”
close-pipe
( wfileid – wretval wior ) gforth-0.2 “close-pipe”
パイプラインに書き込む場合、 Gforth は broken-pipe-error
を throw する可能性があります。 あなたが、
この例外をキャッチしない場合、 Gforth は例外をキャッチして通常は黙って(silently)に終了(exit)します(see Gforth in pipes)。 しかし、 おそらくこれはあなたの望む事ではないので、 open-pipe
から close-pipe
までのコードを catch
または try
ブロックで囲むべきです。 そして、 あなた自身で問題を解決し、
通常の処理に戻します。
broken-pipe-error
( – n ) gforth-0.6 “broken-pipe-error”
壊れたパイプラインのエラー番号
ASCII は英語(English language)にのみ適しています。 ただし、 ほとんどの西洋言語(western languages)は、それぞれの少数の特殊文字をエンコードするには 1 バイトで十分であるため、 Forth の枠組みにある程度適合します(ただし、常に同じエンコードを使用できるとは限りません。 ただし、 latin-1 が最も広く使用されています)。 他の言語の場合は、 異なる文字セットを使用する必要があり、その一部は可変幅です。 この問題に対処するために、 文字はスタック上ではユニコード・コードポイント(Unicode codepoints)として表され、 メモリー内では UTF-8 バイト文字列として表されることがよくあります。 ユニコード・コードポイントは、 多くの場合、 1 つのアプリケーション・ レベル(one application-level character)の文字を表しますが、 ユニコードは、基本文字(base letter)と結合発音記号(combining diacritical mark)など、 複数のコード・ポイントで構成される分解文字(combining diacritical mark)もサポートします。
ユニコード・コードポイントはメモリー内の複数バイトを消費する可能性があるため、 ここで我々の用語をすり合わせしておきましょう: char はメモリー内の生のバイト、 またはスタック上の 0 ~ 255 の範囲の値です。 xchar (extended char) は 1 つのコードポイントを表します。 これはメモリー内の 1 バイト以上で表され、 スタック上にはより大きな値が存在する可能性があります。 ASCII 文字は char や xchar と同一です。 つまり、0 ~ 127 の範囲の値、 かつ、 メモリー内のその値を含む 1 バイトです。
UTF-8 エンコードを使用する場合、 他のすべてのコードポイントは 1 文字あたり 1 バイト以上必要になります。 ほとんどの場合、 このような文字はメモリー内の文字列として扱うだけでよく、 以下のワード群を使用する必要はありませんが、 個々のコードポイントを処理したい場合は、 以下のワード群が役に立ちます。 現時点では、 分解文字(decomposed characters)を扱うためのワードはありません。
xchar ワード群はいくつかのデータ型を追加します:
xc-size
( xc – u ) xchar “x-c-size”
xchar xc のメモリー・サイズを char で計算します。
x-size
( xc-addr u1 – u2 ) xchar “x-size”
xc-addr に格納されている最初の xchar のメモリー・サイズを char で計算します。
xc@
( xc-addr – xc ) xchar-ext “xc-fetch”
xc-addr1 から xchar xc を取得します。
xc@+
( xc-addr1 – xc-addr2 xc ) xchar “x-c-fetch-plus”
xc-addr1 から xchar xc を取得します。 xc-addr2 は、 xc の後ろの最初のメモリー位置を指します。
xc@+?
( xc-addr1 u1 – xc-addr2 u2 xc ) gforth-experimental “x-c-fetch-plus-query”
文字列 xc-addr1 u1 の最初の xchar xc を取得します。 xc-addr2 u2 は xc の後ろの残りの文字列です。
xc!+?
( xc xc-addr1 u1 – xc-addr2 u2 f ) xchar “x-c-store-plus-query”
xchar xc を、 アドレス xc-addr1 で始まり u1 文字分の大きさであるバッファーに格納します。
xc-addr2 は xc の後の最初のメモリー位置を指し、 u2 はバッファの残りのサイズです。 xchar
xc がバッファーに収まった場合、 f は true、 それ以外の場合は f は false で
xc-addr2 u2 は xc-addr1 u1 と等しくなります。 XC!+?
はバッファー・オーバーフローに対して安全であるため、 XC!+
よりも推奨されます。
xc!+
( xc xc-addr1 – xc-addr2 ) xchar “x-c-store”
xchar xc を xc-addr1 に保存します。 xc-addr2 は、 バッファ内の次の未使用アドレスです。 これは最大 4 バイトを書き込むため、 アドレスをバッファの末尾と照合するだけの場合は、 有用なデータの上書きを避けるために、 バッファの末尾の後に少なくとも 3 バイトの余裕(パディング)が必要であることに注意してください。
xchar+
( xc-addr1 – xc-addr2 ) xchar “x-char-plus”
xc-addr1 に格納されている xchar のサイズをこのアドレスに加算し、 xc-addr2 を与えます(訳注: つまり、 次の xchar 文字の位置を返す)
xchar-
( xc-addr1 – xc-addr2 ) xchar-ext “x-char-minus”
xc-addr1 から xchar が見つかるまで逆方向に進み、 この xchar のサイズを xc-addr2 に加算すると xc-addr1 になります。
+x/string
( xc-addr1 u1 – xc-addr2 u2 ) xchar-ext “plus-x-slash-string”
アドレス xc-addr1 サイズ u1 文字で定義されたバッファーで xchar 1つ分だけ進めたアドレスを xc-addr2 に返します。 u2 は xchar 分だけ u1 より減ります(残り文字列長さです)。 u2 が 0 になったら末尾まで到達しています。 注意: 0 になってもそこで止まりません更に進んでしまうので注意(長さが負数になる)
x\string-
( xc-addr u1 – xc-addr u2 ) xchar-ext “x-backslash-string-minus”
バッファの最後から開始して、 アドレス xc-addr とサイズ u1 (char単位) で定義されたバッファー内で 1 xchar ずつ後方に進みます。 xc-addr は変わらず、 u2 は xchar 分ずつ短くなります。 注意: 0 になっても止まりません。そのまま長さが負数になっていきます。
-trailing-garbage
( xc-addr u1 – xc-addr u2 ) xchar-ext “minus-trailing-garbage”
xc-addr u1 というバッファー内の最後の XCHAR を調べます — エンコードが正しく、 完全な文字を表す場合 u2 は u1 と等しいです。 それ以外の場合は u2 は、 最後の(文字化けした) xchar を除いた文字列を表します。
x-width
( xc-addr u – n ) xchar-ext “x-width”
アドレス xc-addr 長さ u (char単位)の文字列に対して、同じ幅の等幅 ASCII 文字での文字数を n に得ます。 等幅フォントを想定しています。 つまり文字の幅はいずれも ASCII 文字の幅の整数倍であると仮定します(訳注: 含まれる xchar ごとにその表示幅を調べたのを積算して返します buf $@ type abcあdef ok buf $@len . 9 ok buf $@ x-width . 8 ok)
xkey
( – xc ) xchar “x-key”
端末から xchar を 1 つ読み取ります。 これにより、xchar の読み取りが完了するまでのすべての入力イベントが破棄されます(訳注: ヒストリ操作は効いたので…全ての入力イベント…?)。
xc-width
( xc – n ) xchar-ext “x-c-width”
xc の幅は、通常の固定幅グリフ(fixed-width glyph)の幅の n 倍です。
xhold
( xc – ) xchar-ext “x-hold”
<<#
と #>
の間で使用されます。 表示数値出力文字列(pictured numeric output
string)の前に xc を追加します。 代替手段としては holds
を使用することができます。
xc,
( xchar – ) xchar “x-c-comma”
エンド・ユーザー向けのプログラムは、 エンド・ユーザーの母国語に対応する必要があります。 このような機能については古くから提案があり、 Xchars
(see Xchars and Unicode)や Substitute (see Substitute)
などの国際文字セットに関する他の提案から分割されました。 画面に表示されるメッセージは、 開発者の母国語(native
language)からユーザーの現地言語(local languages)に翻訳する必要があります(訳注: gforth
に最初から組み込まれていないので都度 require i18n.fs
等する必要ある。 本マニュアルに掲載が無いが i18n な
日付用として require i18n-date.fs
するのもいいかもしれない)。
翻訳対象の文字列は L"
string"
で宣言します。 これにより、
ロケール文字列識別子(LSID)が返されます。 LSID は不明瞭なタイプ(opaque types)であり、 スタック上のセルとして扱われます。
LSID はロケールに変換できます。 ロケールは、 言語およびその言語の国固有のバリエーションです。
L"
( "lsid<">" – lsid ) gforth-experimental “l-quote”
文字列が一意に新しい場合は、 文字列をパースし、 新しい lsid を定義します。 同一文字列は同一の lsid となり、 同一文字列を使用して複数の場所から同じ lsid を参照できます。
LU"
( "lsid<">" – lsid ) gforth-experimental “l-unique-quote”
文字列がユニークでない場合でも(重複してても)、 文字列をパースし、 常に新しい lsid を定義します。
native@
( lsid – addr u ) gforth-experimental “native-fetch”
lsid からネイティブ文字列(native string)を取得します
locale@
( lsid – addr u ) gforth-experimental “locale-fetch”
現在の言語 かつ 現在の国(country)でローカライズされた文字列を取得します
locale!
( addr u lsid – ) gforth-experimental “locale-store”
現在のロケール かつ 現在の国の、 ローカライズされた文字列 addr u を lsid に保存します。
Language
( "name" – ) gforth-experimental “Language”
ロケールを定義します。 そのロケールを実行すると、 それが現在のロケールになります。
Country
( <lang> "name" – ) gforth-experimental “Country”
現在のロケールのバリエーション(通常: 国;country)を定義します。 そのロケールを実行すると、それが現在のロケールになります。 バリエーションのバリエーションを作成できます(国によってはバリエーションが存在する場合があります。たとえば、 多くの言語で rolls/buns を表す単語が何語あるかを考えてください)。
locale-file
( fid – ) gforth-experimental “locale-file”
fid から現在のロケールに行(lines)を読み取ります。
included-locale
( addr u – ) gforth-experimental “included-locale”
ファイル addr u から現在のロケールに行(lines)を読み取ります。
include-locale
( "name" – ) gforth-experimental “include-locale”
指定のファイル "name" から現在のロケールに行(lines)を読み取ります。
locale-csv
( "name" – ) gforth-experimental “locale-csv”
カンマ区切り値テーブルをロケールにインポートします。 最初の行にはロケール名が含まれます。 “program” と “default” は特別なエントリです。 generic languages は特定の国(specific countries)向けの翻訳よりも優先しなければなりません。 “program” の下のエントリ(一番左にある必要があります) は、 lsid の検索に使用されます。 空の場合、 行番号 1 は lsid インデックスです。
.locale-csv
( – ) gforth-experimental “dot-locale-csv”
ロケール・データベースを CSV 形式で端末に出力します。
locale-csv-out
( "name" – ) gforth-experimental “locale-csv”
ファイル "name" を作成し、 ロケール・データベースを CSV 形式でファイル "name" に書き込みます。
これは単純なテキスト・マクロ置換機能です。 "text %macro% text"
の形式のテキストが処理され、'%'
で囲まれたマクロ変数が関連する文字列に置き換えられます。 2 つの連続する %
は 1 つの %
に置き換えられます。
マクロは特定のワードリストで定義され、実行時に文字列を返します。 標準では、 マクロを宣言する方法が 1 つだけ、 replaces
のみ定義されています。 これは文字列を返すだけのマクロを作成します。
macros-wordlist
( – wid ) gforth-experimental “macros-wordlist”
文字列置換マクロのワードリスト
replaces
( addr1 len1 addr2 len2 – ) string-ext “replaces”
名前が addr2 len2、 内容が addr1 len1 のマクロを作成します。 マクロが既に存在する場合は、 内容を変更するだけです。
.substitute
( addr1 len1 – n / ior ) gforth-experimental “dot-substitute”
テキスト addr1 len1 内のすべてのマクロを置き換えて、 結果を出力します。 n は置換した数。 または負数の場合は throw 可能な ior です。
$substitute
( addr1 len1 – addr2 len2 n/ior ) gforth-experimental “string-substitute”
テキスト addr1 len1 内のすべてのマクロを置き換えます。 addr2 len2 が置換結果で、 n は置換の数で、 負数場合は throw 可能な ior です。
substitute
( addr1 len1 addr2 len2 – addr2 len3 n/ior ) string-ext “substitute”
テキスト addr1 len1 内のすべてのマクロを置き換え、 結果を addr2 len2 にコピーします。 addr2 len3 は置換結果で、 n は置換した数、または負数場合は throw 可能な ior です。
unescape
( addr1 u1 dest – dest u2 ) string-ext “unescape”
addr1 u1 内のすべての区切り文字を2重にすることで、 置換後の結果が元のテキストのままになります。 結果を格納するバッファー dest には長さ指定の必要が無いことに注意してください。 最悪の場合、 必要な文字数は u1 の 2倍です。 dest u2 は結果の文字列の長さです。
$unescape
( addr1 u1 – addr2 u2 ) gforth-experimental “string-unescape”
unescape
と同じですが、 $tmp
を使用して一時的な結果文字列を作成します。
CSV(Comma-separated values)は、 データをやり取りするための一般的なテキスト形式です。 Gforth は
csv.fs でCSVリーダーを提供します(注意: gforth に最初から組み込まれてはいないので、 都度 require
csv.fs
等する必要がある)。
read-csv
( addr u xt – ) gforth-experimental “read-csv”
CVS ファイル addr u を読み取り、 見つかった項目ごとに xt を実行します。 xt は (
addr ucol line -- )
、 つまり文字列と、 現在の列番号(0 で始まる)と、 現在の行番号(1 で始まる)を受け取ります。
コマンドラインで Gforth プログラムに引数を渡す通常の方法は、 -e オプションを使用することです。 たとえば、
gforth -e "123 456" foo.fs -e bye
ただし、 コマンドライン引数を直接パースしたい場合もあります。 その場合、 next-arg
を通じて(イメージ固有の)コマンドライン引数にアクセスできます:
next-arg
( – addr u ) gforth-0.7 “next-arg”
OS コマンドラインから次の引数を取得し、 それを消費し addr u に返します。 引数が残っていない場合は、 0 0
を返します。
next-arg
のプログラム例 echo.fs を以下に示します:
: echo ( -- ) begin next-arg 2dup 0 0 d<> while type space repeat 2drop ; echo cr bye
これは以下のようにして起動します
gforth echo.fs hello world
そうすると以下のように出力されます
hello world
以下は、 OS コマンドラインを処理する下位レベルのワード群です:
arg
( u – addr count ) gforth-0.2 “arg”
u 番目のコマンドライン引数の文字列を返します。 u が最後の引数を超えている場合は 0 0
を返します。
0 arg
は、 Gforth を起動したプログラム名です。 次の未処理の引数は常に 1 arg
で、 その後の引数は
2 arg
などです。 システムによってすでに処理された引数はすべて削除されます。 引数を処理した後、
shift-args
を使用して引数を削除できます。
shift-args
( – ) gforth-0.7 “shift-args”
1 arg
が削除され、 後続のすべての OS コマンドライン・パラメーターが 1 だけ左にシフトされ、argc @
が減ります。 このワードは argv @
を変更できます。
最後に、 Gforth は最下位レベルで以下のワード群を提供します:
argc
( – addr ) gforth-0.2 “argc”
変数(Variable
) – コマンドライン引数の数(コマンド名を含む)。 next-arg
と
shift-args
によって変更されます。
argv
( – addr ) gforth-0.2 “argv”
変数(Variable
) – コマンドライン引数 (コマンド名を含む) へのポインターのベクトル(vector)へのポインター。
各引数は、 C言語スタイルのゼロで終わる文字列として表されます。 next-arg
と shift-args
によって変更されます。
ローカル変数を使用すると、 Forth プログラミングがより楽しくなり、 Forth プログラムが読みやすくなります。 残念ながら、 標準 Forth の ローカル変数には制限がたくさんあります。 したがって、 標準 Forth のローカル変数ワードセットだけでなく、 Gforth 独自のより強力なローカル変数ワードセットも提供します(標準Forthのローカル変数ワードセットは、 Gforth のローカル変数ワードセットで実装しました)。
このセクションのアイデアは、 M. Anton Ertl, Automatic Scoping of Local Variables, EuroForth ’94 でも公開されています。
ローカル変数は以下のように定義できます
{: local1 local2 ... -- comment :}
or
{: local1 local2 ... :}
or
{: local1 local2 ... | ulocal0 ulocal1 -- comment :}
例えば、 以下のように使います
: max {: n1 n2 -- n3 :} n1 n2 > if n1 else n2 endif ;
ローカル変数定義とスタック・コメントは、 類似するよう意図しています。 ローカル変数定義は、 多くの場合、 ワードのスタック・コメントを置き換えます。
ローカル変数の順序はスタック・コメント内の順序に対応し、 --
以降はすべて実際はコメントです。
この類似性には 1 つ欠点があります。 ローカル変数宣言とスタック・コメントを混同しやすく、 バグが発生し、 見つけにくくなります。 ただし、 この問題は適切なコーディング規約によって回避できます。 同じプログラム内で両方の表記法を使用しないでください。 そうする場合は、 追加の手段を使用して区別する必要があります。 例えば、 場所によって区別します。
ローカルの名前の前に型指定子を付けることができます。 たとえば、浮動小数点値の場合は F:
です:
: CX* {: F: Ar F: Ai F: Br F: Bi -- Cr Ci :} \ 複素数の掛け算 Ar Br f* Ai Bi f* f- Ar Bi f* Ai Br f* f+ ;
Gforth は現在、 セル型指定子(W:
, WA:
, W^
)や、 2倍長整数型指定子(D:
,
DA:
, D^
)や、 浮動小数点数型指定子(F:
, FA:
, F^
)と、
さまざまなフレーバーの xt 型指定子(xt:
, xta:
)、 をサポートしています:
(see Values) valueフレーバーのローカル変数(W:
、 D:
などで定義)はその値を生成し、
TO
で変更できます。
(see Varues) varueフレーバーのローカル変数 foo (WA:
などで定義) は、 addr
foo
を使用してアドレスを取得できること(変数のスコープを離れると無効になります)を除いて、
valueフレーバーのローカル変数とまったく同じように振る舞います。 現時点ではパフォーマンスに違いはありませんが、 長期的には、
valueフレーバーのローカル変数はレジスターに常駐できるため、 大幅に高速になるでしょう。
(see Variables) variableフレーバーのローカル変数(W^
などで定義)はそのアドレスを生成します(変数のスコープを離れると無効になります)。 たとえば、 標準のワード emit
は、
以下のように variableフレーバーのローカル変数(C^ char*
) と type
で定義できます:
: emit {: C^ char* -- :} char* 1 type ;
(see Deferred Words) (XT:
または XTA:
と指定した) defer
フレーバーのローカル変数 xt を execute
します。 action-of
を使用すると、 defer
フレーバーのローカル変数から xt を取得できます。 ローカル変数が xta:
で定義されている場合、 addr
を使用して、 xt が保存されているアドレス(ローカル変数のスコープの終わりまで有効)を取得できます。 たとえば、 標準のワード
execute
は、 以下のように defer フレーバーのローカル変数で定義できます:
: execute {: xt: x -- :} x ;
型指定子のないローカル変数は W:
ローカル変数として扱われます。 以下を使用して addr
の使用を許可または禁止できます:
default-wa:
( – ) gforth-experimental “default-wa:”
型指定子なしで定義されたローカル変数で addr
を許可します。 言い換えれば、 型指定子無しでローカル変数定義したときは
wa:
型指定子を指定したのと同じです
default-w:
( – ) gforth-experimental “default-w:”
型指定子なしで定義されたローカル変数では addr
を禁止します。 言い換えれば、 型指定子なしで定義されたローカル変数は
w:
型指定子を指定してローカル変数を定義したのと同じです。
|
の後ろで定義されたローカル変数を除いて、 ローカル変数の全てのフレーバーは、 データ・スタックの値または、 (FP
ローカル変数の場合) FP スタックの値で初期化されます。 Gforth は |
の後ろで定義されたローカル変数を 0 に初期化します。
一部の Forth システムでは初期化されないままになっています。
Gforth は、 ローカル・バッファーとデータ構造体のための角括弧(square bracket)表記をサポートしています。 これらのローカル変数は
variableフレーバーのローカル変数に似ており、 サイズは定数式として指定します。 宣言は name[ size ]
のようになります。 Forth の式 size
は宣言中に評価され、 サイズをバイト単位で指定するスタック効果 (
-- +n )
が必要です。 なお、 角括弧 [
は定義された名前の一部です。
ローカルのデータ構造体は、 データ・スタックに渡されたアドレスから size バイトをコピーすることによって初期化されます。 (宣言内の
|
の後の、)初期化されていないローカルのデータ構造体は消去されず、 以前にローカル・スタックにあったデータをすべて含むだけです。
Example:
begin-structure test-struct field: a1 field: a2 end-structure : test-local {: foo[ test-struct ] :} foo[ a1 ! foo[ a2 ! foo[ test-struct dump ;
Gforth では、コロン定義内のあらゆる場所でローカル変数を定義できます。 これにより、 以下のような疑問が生じます:
このセクションでは、 ローカル変数を定義するために使用されるワード群を説明します。 注意: ローカル変数を定義するワード(W:
など)の実行時は、 右端のローカル変数定義から左端のローカル変数定義の方向で実行され、
右端のローカル変数がスタックのTOSから得る事になることに注意してください。
{:
( – hmaddr u latest latestnt wid 0 ) local-ext “open-brace-colon”
ローカル変数定義の開始。
--
( hmaddr u latest latestnt wid 0 ... – ) gforth-0.2 “dash-dash”
ローカル変数定義中の --
から :}
までのすべてが無視されます。 これは通常、
ローカル変数定義をスタック効果の説明としても使えるよう、 一人二役の役割を持たせる場合に使います。
|
( – ) gforth-1.0 “bar”
|
の後ろで定義されたローカル変数はスタックから初期化されません。 したがって、 W:
のようなワードの実行時のスタック効果は ( -- )
に変わります。
:}
( hmaddr u latest latestnt wid 0 xt1 ... xtn – ) gforth-1.0 “colon-close-brace”
ローカル変数定義の終了。
{
( – hmaddr u latest latestnt wid 0 ) gforth-0.2 “open-brace”
ローカル変数定義を開始します。 このワードの Forth-2012 標準名は {:
です。
}
( hmaddr u latest latestnt wid 0 xt1 ... xtn – ) gforth-0.2 “close-brace”
ローカル変数定義を終了します。 このワードの Forth-2012 標準名は :}
です。
W:
( compilation "name" – a-addr xt; run-time x – ) gforth-0.2 “w-colon”
valueフレーバーのセル・ローカル変数 name を定義します ( -- x1 )
WA:
( compilation "name" – a-addr xt; run-time x – ) gforth-1.0 “w-a-colon”
varueフレーバーのセル・ローカル変数 name を定義します ( -- x1 )
W^
( compilation "name" – a-addr xt; run-time x – ) gforth-0.2 “w-caret”
variableフレーバーのセル・ローカル変数 name を定義します ( -- a-addr )
D:
( compilation "name" – a-addr xt; run-time x1 x2 – ) gforth-0.2 “d-colon”
valueフレーバーの2倍長整数ローカル変数 name を定義します ( -- x3 x4 )
DA:
( compilation "name" – a-addr xt; run-time x1 x2 – ) gforth-1.0 “w-a-colon”
varueフレーバーの2倍長整数ローカル変数 name を定義します ( -- x3 x4 )
D^
( compilation "name" – a-addr xt; run-time x1 x2 – ) gforth-0.2 “d-caret”
variableフレーバーの2倍長整数ローカル変数 name を定義します ( -- a-addr )
C:
( compilation "name" – a-addr xt; run-time c – ) gforth-0.2 “c-colon”
valueフレーバーの char ローカル変数 name を定義します ( -- c1 )
CA:
( compilation "name" – a-addr xt; run-time c – ) gforth-1.0 “c-a-colon”
varueフレーバーの char ローカル変数 name を定義します ( -- c1 )
C^
( compilation "name" – a-addr xt; run-time c – ) gforth-0.2 “c-caret”
variableフレーバーの char ローカル変数 name を定義します ( -- c-addr )
F:
( compilation "name" – a-addr xt; run-time r – ) gforth-0.2 “f-colon”
valueフレーバーの浮動小数点数ローカル変数 name を定義します ( -- r1 )
FA:
( compilation "name" – a-addr xt; run-time f – ) gforth-1.0 “f-a-colon”
varueフレーバーの浮動小数点数ローカル変数 name を定義します ( -- r1 )
F^
( compilation "name" – a-addr xt; run-time r – ) gforth-0.2 “f-caret”
variableフレーバーの浮動小数点数ローカル変数 name を定義します ( -- f-addr )
XT:
( compilation "name" – a-addr xt; run-time xt1 – ) gforth-1.0 “x-t-colon”
deferフレーバーのセル・ローカル変数 name を定義します ( ... -- ... )
XTA:
( compilation "name" – a-addr xt; run-time ... – ... ) gforth-1.0 “x-t-a-colon”
addr
を使用できる defer フレーバーのローカル変数 name を定義します。
|
や --
や :}
や }
は通常、 検索順序スタック(the search
order)に含まれないことに注意してください(これらは locals-types
ボキャブラリーに含まれます)。
そしてこれらは必ずしも全ての Forth システムでワードとして存在しているわけではありません。 したがって、 これらは Gforth
のワードとして文書化されています。
ローカル変数をその名前によって可視できるのはどこまででしょうか? – 基本的に、 その答えは、 ローカル変数がブロック構造言語で期待される場所で、
場合によってはもうちょっとだけ長くできます。 ローカル変数のスコープを制限したい場合は、 その定義を
SCOPE
...ENDSCOPE
で囲んで下さい。
scope
( compilation – scope ; run-time – ) gforth-0.2 “scope”
endscope
( compilation scope – ; run-time – ) gforth-0.2 “endscope”
これらのワードは制御構造のワードのように動作するため、 CS-PICK
および CS-ROLL
とともに使用して、
任意の方法で範囲を制限できます。
可視性の質問に対するより正確な答えが必要な場合のために、 ここで基本原則を示します: ローカル変数は、
ローカル変数の定義を通じてのみ到達できるすべての場所で可視です30。
言い換えると、 ローカル変数の定義を経由せずに到達できる場所では不可視です。 たとえば、 IF
...ENDIF
の中で定義されたローカル変数は ENDIF
まで可視で、 BEGIN
...UNTIL
内で定義されたローカル変数は UNTIL
の後(たとえば、後続の ENDSCOPE
まで)で可視です。
このソリューションの背景にある理由は次のとおりです: 私達は、 意味がある限り、 ローカル変数を可視させたいと考えています。 ユーザーは、 明示的なスコープを使用することで、 いつでも可視性を短くすることができます。 ローカル変数の定義によってのみ到達できる場所では、 ローカル変数名の意味は明らかです。 他の場所ではそうではありません。 ローカル変数定義が含まれていない制御フロー・パスでローカル変数はどのように初期化されるのでしょうか? 2 つの独立した制御フロー パスで同一ローカル変数名が 2 回定義されている場合、 それはどちらのローカル変数を意味するのでしょうか?
上記で、 ほぼすべてのユーザーにとって十分詳細であるため、 このセクションの残りの部分はスキップしてかまいません。 本当にすべての血みどろの詳細とオプションを知る必要がある場合は、 以下を読み続けてください。
このルールを実装するには、 コンパイラーはどの場所が到達不能(unreachable)であるかを認識する必要があります。 AHEAD
や
AGAIN
や EXIT
や LEAVE
の後で、 これが自動的に認識されます。 他の場合(例: ほとんどの
THROW
の後)、 UNREACHABLE
というワードを使用して、
制御フローがその場所に到達しないことをコンパイラーに伝えることができます。 UNREACHABLE
が使用できる場所で使用されなかった場合、 唯一の結果は、 一部のローカル変数の可視性が上記のルールに記載されているよりも制限されることです。
UNREACHABLE
を使用すべきではない場所で使用すると(つまり、 コンパイラに嘘をついた場合)、 バグのあるコードが生成されます。
UNREACHABLE
( – ) gforth-0.2 “UNREACHABLE”
このルールのもう 1 つの問題は、 BEGIN
で、 どのローカル変数が incoming back-edge
で可視されるかをコンパイラーが認識できないことです。 以下で説明するすべての問題は、 コンパイラーのこの無知が原因です(BEGIN
ループを例として使用してこの問題について説明します。 この説明は ?DO
および他のループにも当てはまります)。
おそらく最も陰険な例は以下のとおりです:
AHEAD BEGIN x [ 1 CS-ROLL ] THEN {: x :} ... UNTIL
これは、 可視性ルールに従って合法である必要があります。 x
の使用には、 定義を介してのみ到達できます。 ただし、
下記に明示した使用法でなければなりません。
この例から、 可視性ルールを完全に実装するには大きな問題が伴うことが明らかです。 私たちの実装は、 一般的なケースを宣伝どおりに扱い、
例外は安全な方法で処理されます。 コンパイラーは、 BEGIN
の後に可視できるローカル変数について合理的な推測を行います。
悲観的すぎると、 ローカル変数が定義されていないという偽のエラーがユーザーに表示されます。 コンパイラーが楽観的すぎる場合、 後でこれに気づき、
警告を発行します。 上記の場合、 コンパイラーは x
が使用時に未定義であることについて文句を言います。
このセクションのあいまいな例から、 コンパイラーをトラブルに陥らせるには非常に特殊な制御構造が必要であることがわかりますが、
それでもコンパイラーは多くの場合問題なく動作します。
BEGIN
がそれより上から到達可能な場合、 最も楽観的な推測は、 BEGIN
の前に可視であるすべてのローカル変数も
BEGIN
の後にも可視であることです。 この推測は、 BEGIN
経由でのみ入るすべてのループ、 特に通常の
BEGIN
...WHILE
...REPEAT
および BEGIN
...UNTIL
ループに対して有効であり、 コンパイラーに実装されています。 BEGIN
への分岐が AGAIN
または
UNTIL
によって最終的に生成されると、 コンパイラーは推測をチェックし、 それが楽観的すぎる場合はユーザーに警告します:
IF {: x :} BEGIN \ x ? [ 1 cs-roll ] THEN ... UNTIL
ここで、 x
は BEGIN
までのみ存続しますが、 コンパイラーは THEN
まで存続すると楽観的に想定します。 UNTIL
をコンパイルするときにこの違いに気づき、 警告を発行します。 ユーザーは警告を回避し、
明示的なスコープを使用して x
が間違った領域で使用されていないことを確認できます:
IF SCOPE {: x :} ENDSCOPE BEGIN [ 1 cs-roll ] THEN ... UNTIL
推測は楽観的であるため、 未定義のローカル変数に関する偽のエラー・メッセージは表示されません。
BEGIN
がそれより上から到達可能でない場合(たとえば、 AHEAD
または EXIT
の後)、
BEGIN
の後で定義されたローカル変数の可視については、 コンパイラーは楽観的な推測を行うことさえできません。
悲観的に、 制御構造の外側の最新の場所(つまり、 制御フロー・スタック上に何もない場所)で可視であったすべてのローカル変数が可視であると仮定します。 これは以下のことを意味します:
: foo IF {: z :} THEN {: x :} AHEAD BEGIN ( * ) [ 1 CS-ROLL ] THEN {: y :} ... UNTIL ;
ここで、 ( * )
でマークされた場所では、 x
は可視ですが、 y
は不可視です(ただし、
到達可能性ルールによれば、 可視であるべきです)。 z
はそこでは不可視で、 可視であるべきではありません。
ただし、 ASSUME-LIVE
を使用すると、 最上位の制御フロー・スタック項目が作成された時点と同じローカル変数が BEGIN
で可視になるのだと、 コンパイラーに想定させることができます。
ASSUME-LIVE
( orig – orig ) gforth-0.2 “ASSUME-LIVE”
例えば、 以下のように使います
IF {: x :} AHEAD ASSUME-LIVE BEGIN x [ 1 CS-ROLL ] THEN ... UNTIL THEN
ここで、 x
のローカル変数定義は制御構造内にあるため、 x
を使用した時点では x
は可視ではありませんが、
ASSUME-LIVE
を使用することで、 プログラマはコンパイラーに AHEAD
の時点で可視である、
そのローカル変数が、 BEGIN
の時点でも可視であるべきであると伝えます。
BEGIN
の前にローカル変数が定義されている他のケースは、 ASSUME-LIVE
の前に適切な
CS-ROLL
を挿入する(そして ASSUME-LIVE
の背後にある制御フロー・スタック操作を変更する)ことで処理できます。
ローカル変数が BEGIN
の後で定義されている場合(ただし、 BEGIN
の直後で可視である必要があります)は、
ループを再配置することによってのみ処理できます。 たとえば、上記の「最も陰険な」例は以下のように整理できます:
BEGIN {: x :} ... 0= WHILE x REPEAT
ローカル変数の生存期間はどのくらいなのか? – 生存期間に関する質問に対する正しい答えは、次のとおりです: ローカル変数は、 少なくともアクセスできる限りは生存します。 valueフレーバーのローカル変数の場合、 これは、 その可視性が終了するまでを意味します。 ただし、 variableフレーバーのローカル変数は、可視性の範囲をはるかに超えてアドレスを通じてアクセスできる可能性があります。 結局のところ、 これはそのようなローカル変数がガベージ・コレクションを受けなければならないことを意味します。 これには、Forth らしくない実装の複雑さが伴うため、 他の言語(C言語など)と同じ引っ込み思案な解決策を採用しました。 つまり、 ローカル変数は、 それが可視である間のみ生存します。その後、そのアドレスは無効になります(そして、 その後そのアドレスにアクセスするプログラムはエラーになります)。
どこでもローカル変数を定義できる自由は、 プログラミング・スタイルを劇的に変える可能性を秘めています。 特に、
中間ストレージにリターン・スタックを使用する必要がなくなります。 さらに、 すべてのスタック操作(実行時に決定される引数を持つ PICK
や ROLL
を除く)を排除できます。 スタック項目の順序が間違っている場合は、 すべてのスタック項目のローカル変数定義を記述し、
その次に、 あなたが必要とする順序で項目を書き込むだけです。
これは少し突飛なように思えますし、 スタック操作を排除することが意識的なプログラミング目標になる可能性は低いです。 それでも、
ローカル変数を積極的に使用すれば、 スタック操作の数は大幅に減少します(例: max
(see Gforth locals) を
max
の従来の実装と比較してみましょう)。
これは、 ローカル変数の潜在的な利点の 1 つ、 つまり Forth プログラムを読みやすくすることを示しています。 もちろん、 この利点は、 プログラマがワードを長ったらしく書くための自由度を追加する訳ではなく、 ファクタリング(因数分解)の原則を尊重し続けた場合にのみ実現されます。
TO
の使用は可能な限り避けるべきです。 TO
がない場合、 すべてのvalueフレーバーのローカル変数には 1
つの代入しかなく、 関数型言語の多くの利点が Forth に当てはまります。 つまり、 プログラムの分析・最適化・読み取りが容易になります。
ローカル変数が何を表すかは定義から明らかであり、 後で別のものに変わることはありません。
たとえば、 TO
を使用したローカル変数定義は以下のようになります:
: strcmp {: addr1 u1 addr2 u2 -- n :} u1 u2 min 0 ?do addr1 c@ addr2 c@ - ?dup-if unloop exit then addr1 char+ TO addr1 addr2 char+ TO addr2 loop u1 u2 - ;
ここで、 TO
は、 ループの反復ごとに addr1
と addr2
を更新するために使用されます。
strcmp
は、TO
の使用による可読性の問題の典型的な例です。 strcmp
を読み始めると、
addr1
が文字列の先頭を指していると考えるでしょう。 ループの終わり近くになって初めて、 それが何か別のものであることがわかります。
これは、 現在の反復に適切な値で初期化される 2 つのローカル変数をループの開始時に定義することで回避できます。
: strcmp {: addr1 u1 addr2 u2 -- n :} addr1 addr2 u1 u2 min 0 ?do {: s1 s2 :} s1 c@ s2 c@ - ?dup-if unloop exit then s1 char+ s2 char+ loop 2drop u1 u2 - ;
ここで、 s1
がループの反復ごとに異なる値を持つことは最初から明らかです。
Gforth は追加のローカル変数用スタック(ローカル・スタック)を使用します。 この最も説得力のある理由は、
リターン・スタックが浮動小数点数に整列されていないことです。 この追加のスタックを使用すると、
リターン・スタックをローカル変数用スタックとして使用する場合の問題や制限も解消されます。 他のスタックと同様に、
ローカル・スタックは下位アドレスに向かって成長します。 いくつかのプリミティブにより効率的な実装になっています。 あなたは、
これらを直接使用するべきではありませんが、 see
の出力には表示されるため、 以下に文書化しておきます:
@localn
( noffset – w ) gforth-internal “fetch-local-n”
\ 訳注: ローカル・スタックのTOSを0として noffset 番目のコピーをデータ・スタックに積む
f@localn
( noffset – r ) gforth-1.0 “f-fetch-local-n”
lp@
( – c-addr ) gforth-0.2 “lp-fetch”
C_addr は、 ローカル・スタック・ポインターの現在の値です。
lp!
( c-addr – ) gforth-internal “lp-store”
>l
( w – ) gforth-0.2 “to-l”
\ 訳注: w をローカル・スタックにプッシュ
f>l
( r – ) gforth-0.2 “f-to-l”
これらのプリミティブに加えて、 一般的に発生するインライン引数に対するこれらのプリミティブのいくつかの特殊化が、 効率上の理由から提供されています(例:
0 @localn
の特殊化として @local0
)。 以下のコンパイル・ワード(compiling words)は、
適切な特殊バージョン、 または一般バージョンを適切にコンパイルします(訳注: @local0
シリーズは、
@local0
ローカル・スタックのTOS(のコピー)をスタックに積む、 @local1
ローカル・スタックの2nd(のコピー)をスタックに積む、 〜 @local4
まである):
compile-lp+!
( n – ) gforth-0.2 “compile-l-p-plus-store”
?branch-lp+!#
のような、 条件分岐と lp+!#
の組み合わせ(ローカル・スタック・ポインターは分岐が選択された場合にのみ変更されます)は、 ループの効率と正確性のために提供されています。
ディクショナリー空間内の特別な領域が、 ローカル変数名を保持するために予約されています。 {:
はディクショナリー・ポインターをこの領域に切り替え、 :}
はそれを元に戻し、 ローカル変数の初期化コードを生成します。
W:
などは通常の定義ワードです。 この特別な領域は、 すべてのコロン定義の先頭でクリアされます。
Gforth のディクショナリーの特別な機能は、 型指定子なしでローカルの定義を実装するために使用されます。
すべてのワードリスト(別名ボキャブラリー)には、 検索などのための独自のメソッド(methods)があります (see Word Lists)。
型指定子なしでローカルの定義を実装するという目的のために、 私達は特別な検索メソッドを使用してワードリストを定義しました。 ワードが検索されると、
実際には W:
を使用してそのワードが作成されます。 {:
は、 最初に :}
や W:
などを含むワードリストで検索し、 次に型指定子のないローカル変数を定義するためのワードリストで検索するよう検索順序スタック(the search
order)を変更します。
生存期間ルールは、 コロン定義内のスタック規律(stack discipline)をサポートします。 ローカル変数の生存期間は、 他のローカル変数の生存期間と入れ子になっているか、 あるいは、 他のローカル変数の生存期間と重ならないか、 です。
BEGIN
や IF
や AHEAD
では、 ローカル・スタック・ポインター操作のコードは生成されません。
制御構造のワード間で、 ローカル変数定義はローカル変数をローカル・スタックにプッシュできます。 AGAIN
は、 他の 3
つの制御フローワードとの中で最も単純です。 分岐する前に、 対応する BEGIN
のローカル・スタックの深さを復元する必要があります。
そのコードは以下のようになります:
lp+!#
current-locals-size − dest-locals-sizebranch
<begin>
UNTIL
はもう少し複雑です。 分岐して戻る場合は、 AGAIN
と同じようにローカル・スタックを調整する必要があります。
ただし、 戻らずにその後ろへ流れる場合は、 ローカル・スタックを変更してはなりません。 コンパイラーは以下のコードを生成します:
?branch-lp+!#
<begin> current-locals-size − dest-locals-size
ローカル・スタック・ポインターは、 分岐が行われた場合にのみ調整されます。
THEN
は、 やや非効率なコードを生成する可能性があります:
lp+!#
current-locals-size − orig-locals-size <orig target>:lp+!#
orig-locals-size − new-locals-size
2 番目の lp+!#
は、 ローカル・スタック・ポインターを orig 時点のレベルから THEN
の後のレベルに調整します。 最初の lp+!#
は、 ローカル・スタック・ポインターを現在のレベルから orig
時点のレベルに調整するため、 完全な効果は、THEN
の後の現在のレベルから正しいレベルに調整されることになります。
従来の Forth の実装では、 dest 制御フロー・スタック・エントリはターゲット・アドレスにすぎず、 orig エントリはパッチ当てされるアドレスにすぎません。 ローカル変数の実装は、 すべての orig または dest 項目にワードリストを追加します。 これは、 エントリによって記述された時点で可視である(または可視である想定される)ローカル変数のリストです。 私たちの実装では、 エントリの種類を識別するためのタグも追加します。 特に、 生きているのと死んでいるエントリ(到達可能なエントリと到達不可能なエントリ)を区別するためです。
ローカル変数のワードリストに対して、 いくつかの珍しい操作を実行する必要があります:
common-list
( list1 list2 – list3 ) gforth-internal “common-list”
sub-list?
( list1 list2 – f ) gforth-internal “sub-list?”
list-size
( list – u ) gforth-internal “list-size”
ローカル変数のワードリスト実装のいくつかの機能により、 これらの操作の実装が簡単になります。 ローカル変数のワードリストはリンクされたリストとして編成されます。 リストに同一のローカル変数が含まれている場合、 これらのリストの末尾は共有されます。 名前のアドレスは、 リスト内でその後ろにある名前のアドレスよりも大きくなります。
もう 1 つの重要な実装の詳細は、 変数 dead-code
です。これは、 BEGIN
と THEN
によって直接到達できるか、 解決するブランチ経由でのみ到達できるかを判断するために使用されます。 dead-code
は
UNREACHABLE
や AHEAD
や EXIT
などによって設定され、 コロン定義の先頭 や
BEGIN
や 通常は THEN
によってクリアされます。.
カウンタ付きループはほとんどの点で他のループと似ていますが、 LEAVE
には特別な注意が必要です。 基本的に AHEAD
と同じサービスを実行しますが、 制御フロー・スタック・エントリは作成されません。 したがって、 情報は別の場所に保存する必要があります。 従来、 情報は
LEAVE
によって作成されたブランチのターゲット・フィールドに、
これらのフィールドをリンク・リストに編成することによって格納されていました。 残念ながら、 この巧妙なトリックでは、
拡張制御フロー情報を保存するための十分なスペースが提供されません。 したがって、 別のスタックである Leave スタックを導入します。 これには、
すべての未解決の LEAVE
の制御フロー・スタック・エントリが含まれています。
ローカル変数名は、 どの制御フロー経路にも表示されなくなった場合でも、 コロン定義の終わりまで保持されます。 場合によっては、 これによりローカル変数の名前領域に必要な領域が増加する可能性がありますが、 通常はこの領域を再利用するよりもコード量にかかるコストは少なくなります。
Gforth は基礎的なクロージャ(closure)も提供します。 クロージャは、
引用(quotation)(see Quotations)とローカル変数の組み合わせです。 Gforth のクロージャには、
クロージャの実行時に値が入力されるローカル変数があり、 トランポリン xt (trampoline xt)が生成されます。 そのトランポリン xt を
execute すると、 ローカル・スタック上のクロージャのローカル変数にアクセスして、 クロージャのコードが実行されます。
クロージャのローカル変数の変更は永続的ではありません。 つまり、 クロージャが EXIT
されると、 変更された値は失われます。
[{:
( – hmaddr u latest latestnt wid 0 ) gforth-experimental “start-closure”
クロージャを開始します。 クロージャはまず、 クロージャのために使用するローカル変数フレームを宣言し、
次にそれらのローカル変数で実行されるコードを宣言します。 クロージャは引用(quotations)のように ;]
で終わります。
ローカル宣言は、 クロージャ・ローカルが作成される場所に応じて終了します。 実行時、 クロージャは トランポリン xt として作成され、
スタックからローカル変数・フレームの値を埋めます。 xt の実行時に、 ローカル変数・フレームがローカル・スタックにコピーされ、
クロージャのコード内で使用されます。 戻った後、 これらの値はローカル・スタックから削除され、 クロージャ自体は更新されません。
:}l
( hmaddr u latest latestnt wid 0 a-addr1 u1 ... – ) gforth-1.0 “close-brace-locals”
クロージャ・ローカルの宣言を終了します。 クロージャはローカル・スタックに割り当てられます。
:}d
( hmaddr u latest latestnt wid 0 a-addr1 u1 ... – ) gforth-1.0 “colon-close-brace-d”
クロージャ・ローカル宣言を終了します。 クロージャはディクショナリーに割り当てられます。
:}h
( hmaddr u latest latestnt wid 0 a-addr1 u1 ... – ) gforth-1.0 “colon-close-brace-h”
クロージャ・ローカル宣言を終了します。 クロージャーはヒープに割り当てられます。
:}h1
( hmaddr u latest latestnt wid 0 a-addr1 u1 ... – ) gforth-1.0 “colon-close-brace-h”
クロージャ・ローカル宣言を終了します。 クロージャーはヒープに割り当てられます。
:}xt
( hmaddr u latest latestnt wid 0 a-addr1 u1 ... – ) gforth-1.0 “colon-close-brace-x-t”
クロージャ・ローカル宣言を終了します。 クロージャは xt によってスタック上に割り当てられるため、 クロージャの実行時のスタック効果は
( xt-alloc -- xt-closure )
となります。
>addr
( xt – addr ) gforth-experimental “to-addr”
(free-closure
から呼び出されます)ヒープ上のクロージャの xt を addr に変換し、 free
に渡すことでクロージャを削除できます。
free-closure
( xt – ) gforth-internal “free-closure”
ヒープに割り当てられたクロージャを解放(free)します
: foo [{: a f: b d: c xt: d :}d a . b f. c d. d ;] ; 5 3.3e #1234. ' cr foo execute
上記 foo
は、 単一セルと浮動小数点数と2倍長整数と xt を含むクロージャをディクショナリー内に作成し、呼び出し時に最初の 3
つの値を出力後に xt を実行します。
これにより、 Algol コンパイラーをテストするために 1964 年にドナルド・クヌースが提案した “Man or boy test” を実装することができます(訳注: 手元ではサッパリ動いてない(0.7.9_20240418, 2024.7))
: A {: w^ k x1 x2 x3 xt: x4 xt: x5 | w^ B :} recursive k 0<= IF x4 x5 f+ ELSE B k x1 x2 x3 action-of x4 [{: B k x1 x2 x3 x4 :}L -1 k +! k B x1 x2 x3 x4 A ;] dup B ! execute THEN ; : man-or-boy? ( n -- ) [: 1e ;] [: -1e ;] 2dup swap [: 0e ;] A f. ;
場合によっては、 クロージャを変更するには永続的なストレージが必要です。 複数のクロージャがその永続ストレージを共有する可能性さえあります。 上の例では、 外部プロシージャのローカル変数がこれに使用されていますが、 場合によっては、 クロージャが外部プロシージャよりも長く存続します。 特に、 ディクショナリーまたはヒープ上に割り当てられたクロージャは、 親プロシージャより長く存続するように設計されています。
これらについては、 クロージャのように割り当てられるホーム・ロケーション(home locations)がありますが、 そのコードは作成時に直接実行され、 ホーム・ロケーションのアドレスを提供する必要があります。
: bar ( a b c -- aaddr baddr caddr hl-addr ) <{: w^ a w^ b w^ c :}h a b c ;> ;
この例では、 ヒープ上に 3 つのセルを持つホーム・ロケーション(home location)を作成し、 3
つのロケーションのアドレスとホーム・ロケーションのアドレスを返します。 このアドレスは、 ホーム・ロケーションが不要になったときに
free
するために使用できます。
<{:
( – hmaddr u latest latestnt wid 0 ) gforth-experimental “start-homelocation”
ホーム・ロケーション(home location)の開始
;>
( – ) gforth-experimental “end-homelocation”
ホーム・ロケーションの終了
Forth-2012 標準では、 Gforth のローカル変数の制限付きバージョンであるローカル変数構文が定義されています:
{:
〜 :}
)は 1 つだけです。
標準 Forth ローカル変数ワードセット自体は {:
と 以下の 2 つのワードで構成されます:
(local)
( addr u – ) local “paren-local-paren”
ANS Forth ローカル変数拡張ワードセット(ANS Forth locals extension wordset)は locals|
を使用して構文を定義しますが、 これはとても酷い代物なので、 使用しないことを強くお勧めします。 Gforth
への移植を容易にするためにこの構文を実装しましたが、 ここでは文書化しません。 この構文の問題は、
ローカル変数が標準のスタック・コメント表記とは逆の順序で定義されているため、 プログラムが読みにくくなり、
読み間違いや書き間違いが起こりやすくなることです。 この構文の唯一の利点は、 ANS Forth
ローカル変数ワードセットを使用して実装が簡単であることですが、 {:
構文だって同じくらい実装は簡単ですからね?
このセクションでは、 Gforth に付属する構造体パッケージを紹介します。 標準 Forth で実装されたパッケージのバージョンは、 compat/struct.fs で入手できます。 このパッケージは、 1989 年の comp.lang.forth への投稿に触発されました(残念ながら、誰による投稿かは覚えていません。おそらく John Hayes によるものでしょう)。 このセクションのバージョンは、 M. Anton Ertl, Yet Another Forth Structures Package, Forth Dimensions 19(3), pages 13–16 です。 Marcel Hendrix は有益なコメントを提供してくれました。
複数のフィールドを含む構造体を使用したい場合は、 その構造体用にメモリーを予約し、 アドレス算術演算を使用してフィールドにアクセスするだけです(see Address arithmetic)。 例として、 以下の 3 つのフィールド(a, b, c)を持つ構造体を考えてみましょう
a
これは浮動小数点数(float)です
b
これはセル(cell)です
c
これは浮動小数点数(float)です
構造体の (float 整列された) ベース・アドレスが与えられると、
a
それ以上何もせずに a フィールドが得られます。
b
float+
すると b フィールドが得られます。
c
float+ cell+ faligned
すると c フィールドが得られます。
これが非常に疲れる可能性があることは容易にわかります。
さらに加えて、 cell+
を見ても、 どの構造体がアクセスされているか、 どのフィールドがアクセスされているかがわからないため、
あまり読みやすくありません。 何らかの方法で構造体の種類を推測し、
その構造体のどのフィールドがそのオフセットに対応するかをドキュメントで調べる必要があります。
最後に、 この種のアドレス計算はメンテナンスの問題も引き起こします。 構造体の途中にフィールドを追加・削除した場合、 その後フィールドのすべてのアドレス計算を探し出して変更する必要があります。
そこで、 cell+
とそのファミリーを直接使用する代わりに、 以下のようにオフセットを定数に保存してはどうでしょうか?:
0 constant a-offset 0 float+ constant b-offset 0 float+ cell+ faligned c-offset
これで、 x-offset +
を使用してフィールド x
のアドレスを取得できるようになりました。
これはあらゆる点ではるかに優れています。 もちろん、 フィールドを追加する場合は、 その後のオフセット定義をすべて変更する必要があります。 これは、
以下の方法でオフセットを宣言することで改良できます:
0 constant a-offset a-offset float+ constant b-offset b-offset cell+ faligned constant c-offset
オフセット計算にはいつも +
を使うので、 定義されたワードのアクションに +
を含む定義ワード cfield
を使用できます:
: cfield ( n "name" -- ) create , does> ( name execution: addr1 -- addr2 ) @ + ; 0 cfield a 0 a float+ cfield b 0 b cell+ faligned cfield c
今や、 x-offset +
の代わりに、 単に x
と書くことができるようになりました。
構造体フィールドのワード群が非常にうまく使用できるようになりました。 ただし、 その定義はまだ少し面倒です。 名前を繰り返す必要があり、 サイズと配置に関する情報はフィールド定義の前後に配置されます。 このセクションで紹介する構造体パッケージは、 これらの問題に対処します。
以下のコマンドを使用して、 (データのない)リンク・リストの構造体を定義できます(訳注: これは構造体テンプレートを定義するだけです。
構造体変数とするには別途 %alloc
等する必要があります):
struct cell% field list-next end-struct list%
スタック上のリスト・ノードのアドレスを使用して、 list-next
を使用して次のノードのアドレスを含むフィールドのアドレスを計算できます。 たとえば、 以下のようにしてリストの長さを決定できます:
: list-length ( list -- n ) \ "list" is a pointer to the first element of a linked list \ "n" is the length of the list 0 BEGIN ( list1 n1 ) over WHILE ( list1 n1 ) 1+ swap list-next @ swap REPEAT nip ;
list% %allot
を使用すると、 ディクショナリー内にリスト・ノード用のメモリーを確保でき、 これにより、
リスト・ノードのアドレスがスタック上に残ります。ヒープ上で同様の割り当てを行うには、 list% %alloc
を使用できます(または、allocate
のようなスタック効果(つまり、ior を使用)が欲しい場合は、 list%
%allocate
を使用します)。 リスト・ノードのサイズは list% %size
で取得でき、 そのセル・アライメントは
list% %alignment
で取得できます。
注意: 標準 Forth では、 create
されたワードの本体は aligned
されていますが、 必ずしも
faligned
されている訳ではない事に注意してください。したがって、 以下のようにすると:
create "name" foo% %allot drop
この場合、 foo%
に割り当てられたメモリーは、 foo%
に文字フィールドやセル・フィールドや2倍長整数フィールドのみが含まれている場合にのみ、 name
の本体から開始されることが保証されます。 したがって、 浮動小数点数が含まれる場合は、以下を使用することをお勧めします
foo% %allot constant "name"
以下のように、 構造体 foo%
を別の構造体のフィールドとして含めることができます:
struct ... foo% field ... ... end-struct ...
構造体をいちから構築する代わりに、 既存の構造体を拡張できます。 たとえば、 上記例で定義したような、 データのない単純なリンク・リストはほとんど役に立ちません。 これを以下のように、 整数の値を持つリンク・リストに拡張できます:31
list% cell% field intlist-int end-struct intlist%
intlist%
は、 list-next
と intlist-int
の 2
つのフィールドを持つ構造体です。
以下のように、 n 要素の foo%
型を含む配列型を指定できます:
foo% n *
この配列型は、 通常の型を使用できる場所であればどこでも使用できます(例: field
を定義する場所や %allot
を使用するとき)。
最初のフィールドは構造体のベース・アドレスにあり、 この、 最初のフィールドのワード(例:
list-next
)は実際にはスタック上のアドレスを変更しません。 あなたは実行時間と領域の効率を考慮して、
最初のフィールドのワードを取り除きたいとと思うかもしれません。 しかし、 構造体パッケージがこの場合を最適化するため、 そのは必要ありません。
最初のフィールドのワードをコンパイルする場合、 コードは生成されません。 したがって、 読みやすさと保守性を考慮して、
最初のフィールドにアクセスするときにその最初のフィールドのワードは含めるべきです。
(構造体の命名規則)(私が)思いつくフィールド名は非常に汎用的なものが多く、 使用すると頻繁に名前の衝突が発生します。 たとえば、
多くの構造体にはたいてい counter
フィールドが含まれています。 (私の)頭に浮かぶ構造体名は、 多くの場合、
そのような構造体を作成するワードの名前の論理的な選択でもあります。
したがって、 私は以下の命名規則を採用しました:
struct-field
です。 struct
は構造体の基本名、field
はフィールドの基本名です。 フィールド・ワードは、
構造体(のアドレス)をフィールド(のアドレス)に変換するものと考えることができます。
struct%
の形式で、 struct
は構造体の基本名です。
この命名規則は、 拡張構造体のフィールドではあまり機能しません。 たとえば、 上記例の整数リスト構造体にはフィールド
intlist-int
がありますが、 intlist-next
ではなく list-next
があります。
この実装の核となるアイデアは、 構築されている構造体に関するデータをグローバル変数ではなくスタックに渡すことです。 この設計上の決定が下されると、 他のすべては自然に配置されます。
スタック上の型の説明は align size の形式です。 サイズをスタックのTOSに維持すると、 配列の処理が非常に簡単になります。
field
は、 Create
と DOES>
を使用する定義ワードです。
フィールドの本体にはフィールドのオフセットが含まれており、 通常の DOES>
アクションは以下のようになります:
@ +
つまり、 アドレスにオフセットを加算して、 フィールドのスタック効果 addr1 – addr2 を与えます。
この単純な構造は、 オフセット 0 のフィールドの最適化によって少し複雑になります。 これには、 別の DOES>
部分が必要です(そのようなフィールドがコンパイル中に呼び出された場合、 スタック上に何かがあることに依存できないため)。 したがって、 異なる
DOES>
部分を別々のワードに配置し、 オフセットに基づいてどれを呼び出すかを決定します。 ゼロ・オフセットの場合、
フィールドは基本的に noop です。 これは即実行ワードであるため、 コンパイル時にコードは生成されません。
%align
( align size – ) gforth-0.4 “%align”
データ空間ポインターをアラインメント align に整列(align)します。
%alignment
( align size – align ) gforth-0.4 “%alignment”
構造体のアライメント
%alloc
( align size – addr ) gforth-0.4 “%alloc”
サイズ size のアドレス・ユニットをアラインメント align で整列(align)して割り当て、
割り当てたデータ・ブロックのアドレスを addr に返します。 成功しなかった場合は負数の ior を throw
します。
%allocate
( align size – addr ior ) gforth-0.4 “%allocate”
allocate
と同様に、 サイズ size のアドレス単位をアライメント align
で整列(align)して割り当て、 割り当てたデータ・ブロックのアドレスを addr に返します。 成功した場合、 ior=0,
成功しなかった場合は ior<0
%allot
( align size – addr ) gforth-0.4 “%allot”
データ空間にサイズ size アドレス単位をアラインメント align で割り当てます。 結果のデータ・ブロックのアドレスを addr に返します。
cell%
( – align size ) gforth-0.4 “cell%”
\訳注: セル1つ分のサイズ size と、 サイズをアライメントした align を返す
char%
( – align size ) gforth-0.4 “char%”
\訳注: char 1つ分のサイズ size と、 そのサイズをアライメントした align を返します
dfloat%
( – align size ) gforth-0.4 “dfloat%”
double%
( – align size ) gforth-0.4 “double%”
\訳注: 2倍長整数1つ分のサイズ size と、サイズをアライメントした align を返します
end-struct
( align size "name" – ) gforth-0.2 “end-struct”
アライメント align とサイズ size を使用して 構造体/型記述子 name
を定義します(size は align の倍数になるように切り上げられます – size1)。 name
実行時: ( – align size1)
field
( align1 offset1 align size "name" – align2 offset2 ) gforth-0.2 “field”
オフセット offset1 と align size で指定された型を持つフィールド name を作成します。
offset2 は次のフィールドのオフセットで、align2 は、 (そこまでの)すべてのフィールドのアライメントです。
name
の実行時: ( addr1 – addr2 )
addr2=addr1+offset1
float%
( – align size ) gforth-0.4 “float%”
sfloat%
( – align size ) gforth-0.4 “sfloat%”
%size
( align size – size ) gforth-0.4 “%size”
構造体のサイズを返す
struct
( – align size ) gforth-0.2 “struct”
空の構造体。 構造体定義を開始するために使用されます。
Forth 2012 標準では、 やや不便な形式の構造体が定義されています。 一般に field+
を使用する場合は、
自分でアライメントを行う必要がありますが、 アライメント機能を含む便利なワード(例: field:
)が多数あります。
典型的な使用例は以下のとおりです:
0 field: s-a faligned 2 floats +field s-b constant s-struct
この構造体を記述する別の方法は以下のとおりです:
begin-structure s-struct field: s-a faligned 2 floats +field s-b end-structure
以下のように、 同一のフィールドと追加のフィールドを持つ構造体を定義できます:
s-struct cfield: t-c cfield: t-d constant t-struct
あるいは、
s-struct extend-structure t-struct cfield: t-c cfield: t-d end-structure
begin-structure
( "name" – struct-sys 0 ) facility-ext “begin-structure”
extend-structure
( n "name" – struct-sys n ) gforth-1.0 “extend-structure”
サイズ n の既存の構造体の拡張として、 新しい構造体 name を開始します。
end-structure
( struct-sys +n – ) facility-ext “end-structure”
begin-struction
で開始された構造体を終了します
+field
( noffset1 nsize "name" – noffset2 ) facility-ext “plus-field”
定義ワード。 name ( addr1 -- addr2 )
を定義します。 ここで、 addr2 は
addr1+noffset1 です。 noffset2 は noffset1+nsize です。
cfield:
( u1 "name" – u2 ) facility-ext “c-field-colon”
文字サイズのフィールドを定義します
field:
( u1 "name" – u2 ) facility-ext “field-colon”
アライメントされたセル・サイズのフィールドを定義します
2field:
( u1 "name" – u2 ) gforth-0.7 “two-field-colon”
アライメントされた2倍長セル・サイズのフィールドを定義します
ffield:
( u1 "name" – u2 ) floating-ext “f-field-colon”
アライメントされた浮動小数点サイズのフィールドを定義します
sffield:
( u1 "name" – u2 ) floating-ext “s-f-field-colon”
sfaligned された sfloat サイズのフィールドを定義します
dffield:
( u1 "name" – u2 ) floating-ext “d-f-field-colon”
dfaligned された dfloat サイズのフィールドを定義します
wfield:
( u1 "name" – u2 ) gforth-1.0 “w-field-colon”
16 ビット値のアライメントされたフィールドを定義します。
lfield:
( u1 "name" – u2 ) gforth-1.0 “l-field-colon”
32ビット値にアライメントされたフィールドを定義します。
xfield:
( u1 "name" – u2 ) gforth-1.0 “x-field-colon”
64ビット値にアライメントされたフィールドを定義します。
Gforth には、オブジェクト指向プログラミング用の 3 つのパッケージ (objects.fs と oof.fs と mini-oof.fs) が付属しています。 どれも最初から組み込まれていないため、 使用する前にインクルードする必要があります。 これらのパッケージ(および、 その他のパッケージ)の最も重要な違いについては、 Comparison with other object models で説明します。 すべてのパッケージは 標準 Forth で書かれており、 他の 標準 Forth でも使用できます。
多くの場合、 いくつかのデータ構造「オブジェクト」(object)を扱わなければなりません。 それらは、 いつくかの側面では同様に扱う必要がありますが、
それ以外の側面では異なる扱いをしなければなりません。 グラフィカル・オブジェクトとは教科書的な例で言えば、 円や三角形や恐竜の絵やアイコン等ですが、
プログラム開発中にさらに追加することもできます。 あなたが、 任意のグラフィカル・オブジェクトにいくつかの操作を適用したいとしましょう。 たとえば、
画面上に表示するための draw
操作です。 しかしながら、 この draw
はオブジェクトの種類ごとに異なる処理を行う必要があります。
draw
を、 描画されるオブジェクトの種類に依存して適切なコードを実行する、 大きな CASE
制御構造として実装することはできます。 これはあまり洗練されたものではなく、 さらに、
新しい種類のグラフィック・オブジェクト(例えば宇宙船など)を追加するたびに draw
を変更する必要があります。
私たちがやりたいことは、 宇宙船を定義するときにシステムに次のように指示することです: 「宇宙船を draw
する方法は私たちがこれこれこのとおり書いたので、 それ以外の処理はシステム側でよしなしにしてください」
これは、 (当然ながら、 )オブジェクト指向と呼ばれるすべてのシステムで解決すべき問題です。 ここで紹介するオブジェクト指向パッケージは、 この問題を解決します(それ以外の問題はあんまり解決できません…)。
このセクションは主にリファレンスであるため、 すぐにすべてを理解する必要はありません。 用語は主に Smalltalk からインスピレーションを得たものです:
いくつかの追加機能を備えたデータ構造定義。
クラス定義によって記述されたデータ構造の実体(インスタンス;instance)。
データ構造のフィールド。
(または「メソッド・セレクター」)さまざまなデータ構造(クラス)に対して操作を実行するワード(例: draw
)。 セレクターは、
「何の」(what)操作を行うかを記述します。 C++ 用語では (純粋)仮想関数 と言います
特定のクラスのセレクターによって記述された操作を実行する具体的な定義。 メソッドは、 特定のクラスに対して「どのように」(how)操作が実行されるかを指定します。
セレクターの呼び出し。 呼び出しの 1 つの引数(TOS(スタックの頂上))は、 どのメソッドが使用されるかを決定するために使用されます。 Smalltalk の用語では、 (セレクターとその他の引数で構成される、)メッセージがオブジェクトに送信される と言います。
セレクターの呼び出しによって実行されるメソッドを決定するために使用されるオブジェクト。 objects.fs モデルでは、 セレクターが呼び出されたときに TOS 上にあるオブジェクトです。 (「受信」という言葉は、 Smalltalk の 「メッセージ」関連用語由来です。)
「親クラス」のすべてのプロパティ(インスタンス変数やセレクターやメソッド)を「継承」(inherit)したクラス。 Smalltalk の用語では、 サブクラスはスーパークラスを継承します、 と言います。 C++ 用語では、 派生クラスは基底クラスから継承します(The derived class inherits from the base class.)、 と言います。
このセクションでは、 objects.fs パッケージについて説明します。 この資料は、 M. Anton Ertl, Yet Another Forth Objects Package, Forth Dimensions 19(2), pages 37–43 でも公開されています。
このセクションは、 Structures を読了済であることを前提としています。
このモデルの基礎となっている技術は、 パーサ・ジェネレーター Gray の実装に使用されており、 Gforth でもさまざまな種類のワードリスト(ハッシュの有無や、大文字と小文字の区別の有無や、 ローカル変数用などの特殊用途のワードリスト)を実装するために使用されています)。
Marcel Hendrix は、 このセクションに関して役立つコメントを提供しました。
constant
のような通常の定義ワードを使用してオブジェクトのワードを作成できます。 同様に、
オブジェクトを含むインスタンス変数と他のデータを含むインスタンス変数の間に違いはありません。
:noname
を除いて)パースするため、 これを避けるのは困難です。 ただし、 このようなワードは状態スマート(state-smart)ではないため、
他の多くのパース・ワードほど悪くはありません。
以下のようにして graphical オブジェクト(図形オブジェクト)のクラスを定義できます:
object class \ クラス名 "object" は親クラスです selector draw ( x y graphical -- ) end-class graphical
このコードは、 draw
操作を持つクラス graphical
を定義します。 任意の graphical
オブジェクトに対して draw
操作を実行できます。例:
100 100 t-rex draw
ここで、 t-rex
は、 graphical オブジェクトを生成するワード(定数(constant)など)です。
graphical オブジェクトを作成するにはどうすればよいでしょうか? 現在の定義では、 有用な graphical オブジェクトを作成できません。
クラス graphical
は graphical オブジェクト一般を記述しますが、 具体的な graphical
オブジェクト・タイプを記述しません(C++ ユーザーはこれを「抽象クラス」(abstract class)と呼びます)。 たとえば、 クラス
graphical
にはセレクター draw
のメソッドがありません。
具体的な graphical オブジェクトのために、 クラス graphical
の子クラスを定義します。 例:
graphical class \ 親クラスは graphical cell% field circle-radius :noname ( x y circle -- ) circle-radius @ draw-circle ; overrides draw :noname ( n-radius circle -- ) circle-radius ! ; overrides construct end-class circle
ここでは、 フィールド circle-radius
を持つクラス circle
を graphical
の子として定義しています(フィールド circle-radius
は構造体のフィールドと同じように動作します(see Structures)。 セレクター draw
と
construct
用の新しいメソッドを定義します(construct
は graphical
の親クラスの
object
クラスで定義されています))。
以下のようにして、 ヒープ上(つまり、 allocate
されたメモリー)に circle を作成できます:
50 circle heap-new constant my-circle
heap-new
は construct
を呼び出し、 フィールド circle-radius
を 50
で初期化します。 以下のようにして、 この新しい円を (100,100) の位置に描画(draw)できます:
100 100 my-circle draw
注意: セレクターを呼び出すことができるのは、 TOS 上のオブジェクト(受信オブジェクト) が、 セレクターが定義されたクラス、 またはその子孫の 1
つに属している場合のみです。 たとえば、 draw
は、 graphical
またはその子孫(例:
circle
)に属するオブジェクトに対してのみ呼び出すことができます。 end-class
の直前の検索順序スタック(the search order)は、 class
の直後と同じである必要があります。
あなたがクラスを定義するときは、 必ず親クラスを指定する必要があります。 では、 (最初の)クラス定義はどのようにすればよいのでしょうか?
そのために最初から使用できるクラスが 1 つだけあります。 それは object
という名前のクラスです。
これはすべてのクラスの祖先であるため、 親を持たない唯一のクラスです。 そして、 construct
と print
という
2 つのセレクターを持っています。
heap-new
( ... class – object )
を使用するとヒープ上にクラスのオブジェクトを作成して初期化することができ、 dict-new
( ... class – object
) を使用するとディクショナリー内(allot
による割り当て)にクラスのオブジェクトを作成して初期化することができます。
どちらのワードも当該クラス class の construct
を呼び出し、 当該クラス class の
construct
のスタック効果(上記「...」の部分)で示されたスタック項目を消費します(訳注: 例えば 6.24.3.2 Basic
‘objects.fs’ Usage の例のように、 circle
の construct
のスタック効果 n-radius
が必要で、 50 circle heap-new constant my-circle
としなければならない)。
オブジェクトに自分でメモリーを割り当てたい場合は、 class-inst-size 2@
( class – align size )
を使用してクラスのアライメント(alignment)とサイズを取得できます。 オブジェクト用のメモリーを確保したら、
init-object
( ... class object – )でオブジェクトを初期化できます(訳注: class
のためのデータ構造を object からに構築し、 その後そのオブジェクトに対して construct
を実行します。 注意:
当該 class の construct
用のスタック項目の指定も必要な事に注意)。
このセクションはすべてを網羅したものではありません。
一般に、 同一のセレクターのすべてのメソッドが同一スタック効果を持つようにするのは良いアイデアです。 セレクターを呼び出すとき、 どのメソッドが呼び出されるのかわからないことが多いため、 すべてのメソッドが同じスタック効果を持たない限り、 セレクター呼び出しのスタック効果を知ることはできません。
このルールには例外がひとつあります。 セレクター construct
のメソッドです。 同一の場所に構築するクラスを指定しているため、
どのメソッドが呼び出されるのかがわかります。 実際、 著者はユーザーに初期化を指定する便利な方法を提供するためだけに construct
をセレクターとして定義しました。 使用方法としては、 セレクター呼び出しとは異なるメカニズムの方が自然です(ただし、
おそらく説明するにはより多くのコードとスペースが必要になります)。
通常のセレクター呼び出し(selector invocation)では、 受信オブジェクト(receiving object)のクラスに応じて実行時(run-time)にメソッドが決定されます。 この実行時の選択(selection)は「遅延結び付け」(late binding)と呼ばれます。
場合によっては、 別のメソッドを呼び出すことが望ましい場合があります。 たとえば、 出力オブジェクト(print
ing
object
s)では、 受信用クラス(receiver class)の冗長になりがちな print
メソッドの代わりに、
単純なメソッドを使用したい事があります。 これを実現するには、 print
の呼び出しを以下のように置き換えます(コンパイル時の場合):
[bind] object print
または、 インタープリター時は以下のようにします:
bind object print
あるいは、 メソッドを名前(例: print-object
)で定義し、 その名前を使用して呼び出すこともできます。
クラス結び付け(Class binding)は、 同じ効果を達成する(多くの場合、 より便利な)方法にすぎません。 これにより、
名前の乱雑さが回避され、 最初に名前を付けずにメソッドを直接呼び出すことができます。
クラス結び付け(class binding)のよくある使用法は次のとおりです: セレクターのメソッドを定義するとき、
親クラスでセレクターが行っていることと、 それ以上のことをメソッドに実行させたいことがよくあります。 この目的には、 [parent]
という特別なワードがあります。 [parent] "selector"
は [bind] "parent selector"
と同等です。 ここで、parent
は現在のクラスの親クラスです。 たとえば、メソッド定義は以下のようになります:
:noname dup [parent] foo \ 受信オブジェクトに対して親の foo を実行します ... \ (親の foo に加えて)更に何かする ; overrides foo
Object-oriented programming in ANS Forth (Forth Dimensions, March 1997) で Andrew McKewan は最適化手法としてクラス結び付け(class binding)を紹介しています。 著者は緊急の場合を除き、 最適化手法の目的で使用しないことをお勧めします。 とにかく、 このモデルでは遅延結び付け(Late binding)が非常に高速であるため、 クラス結び付け(class binding)を使用するメリットは小さいです。 適切でない場合にクラス結び付け(class binding)を使用すると、 保守性が低下します。
プログラミング・スタイルの質問については次のとおりです。 セレクターは受信オブジェクト(receiving object)の祖先クラス(ancestor
classes)にのみ結び付け(bind)すべきです。 たとえば、 受信オブジェクトがクラス foo
またはその子孫であることがわかっているとします。 その場合は、 foo
とその祖先にのみ結び付け(bind)するべきです。
【メソッドをより便利に】通常、 メソッドでは受信オブジェクト(receiving object)に頻繁にアクセスします。
メソッドをプレーンなコロン定義(:noname
など)として定義する場合、 多くのスタック体操が必要になる場合があります。
これを回避するには、 m: ... ;m
を使用してメソッドを定義します。たとえば、 以下を使用して circle
を
draw
するメソッドを定義できます
m: ( x y circle -- ) ( x y ) this circle-radius @ draw-circle ;m
このメソッドが実行されるときは、 受信オブジェクト(receiver object)(上記例の circle)がスタックから取り除かれます。
その代わりに、 m: 〜 ;m の間は this
を使用して受信オブジェクトにアクセスできます(ええ、 確かに、この例では
m: ... ;m
を使用する利点ないかもしれませんね)。 注意: しかし、 スタック・コメントとしては m:
と
;m
の間のコードだけでなく、 メソッド全体(つまり、
受信オブジェクトを含むメソッド全体)のスタック効果を指定していることに注意してください。 なお、 m:...;m
では
exit
を使用できません。 代わりに、 exitm
を使用してください32。
あなたは this "field"
という形式のシーケンスを頻繁に使用するハメになると思います(上記例では: this
circle-radius
)。 そこで、 この方法でのみフィールドを使用する場合は、 inst-var
を使用してフィールドを定義し、
フィールド名の前の this
を削れます。 たとえば、 上記例の circle
クラスは以下のように定義することもできます:
graphical class cell% inst-var radius m: ( x y circle -- ) radius @ draw-circle ;m overrides draw m: ( n-radius circle -- ) radius ! ;m overrides construct end-class circle
radius
は、 circle
や、 その子孫クラスと、 それらの m:...;m
内でのみ使用できます。
inst-value
を使用してフィールドを定義することもできます。 これは、 variable
に対して
value
があるのと同様に、 inst-var
に対して inst-value
があるのです。
このようなフィールドの値は [to-inst]
を使用して変更できます。 たとえば、 クラス circle
を以下のように定義することもできます:
graphical class inst-value radius m: ( x y circle -- ) radius draw-circle ;m overrides draw m: ( n-radius circle -- ) [to-inst] radius ;m overrides construct end-class circle
構造体の拡張とは異なり、 継承は頻繁に行われます。 これは、フィールド名命名規則の問題をさらに悪化させます(see Structure Naming Convention): フィールドが最初にどのクラスで定義されたかを常に覚えておく必要があります。 クラス構造の一部を変更すると、 影響を受けないコードの名前も変更する必要があります。
この問題を解決するために、 著者は(著者のオリジナルのモデルにはなかった)スコープ・メカニズムを追加しました。 inst-var
(または
inst-value
)で定義されたフィールドは、 それが定義されているクラスと、 そのクラスの子孫のクラスでのみ可視です。
このようなフィールドの使用は、 いずれにしても、 これらのクラスの m:
で定義されたメソッドでのみ意味があります。
このスコープ・メカニズムにより、 名前が無関係なワードと衝突する可能性が大幅に低くなるため、 着飾っていないフィールド名を使用できます。
スコープ・メカニズムがあれば、 他のワードの可視性を制御するために使用することもできます。 protected
の後に定義されたすべてのワードは、 現在のクラスとその子孫でのみ表示されます。 public
は、 以前に有効だった the
current wordlist (新しく定義されたワードを入れるワードリスト)(つまり ユーザー変数 current
)を復元します。
public
または set-current
が介在せずに複数の protected
がある場合、
public
はこれらの最初の protected
より前の有効な the current wordlist を復元します。
メソッドの定義を、 クラスや、そのセレクターや、 フィールドや、 インスタンス変数、 の定義とは別に行う、 つまり、 実装を定義から分離することもできます。 これは以下の方法で行うことができます:
graphical class inst-value radius end-class circle ... \ do some other stuff circle methods \ now we are ready m: ( x y circle -- ) radius draw-circle ;m overrides draw m: ( n-radius circle -- ) [to-inst] radius ;m overrides construct end-methods
複数の methods
...end-methods
セクションを使用できます。
これらのセクションでクラスに対して実行できることは、 メソッドの定義とクラスのセレクターのオーバーライドのみです。
新しいセレクターやフィールドを定義してはいけません。
注意: 多くの場合、 セレクターを使用する前にオーバーライドする必要があることに注意してください。 特に、 heap-new
とその他のメソッドを呼び出す前に、 通常は construct
を新しいメソッドでオーバーライドする必要があります。 たとえば、
上記例の overrides construction
シーケンスの前に circle を作成してはなりません。
このモデルでは、 受信オブジェクト(receiving objects)のクラスまたはその祖先の 1 つに定義されたセレクターのみを呼び出すことができます。 これらのクラスのいずれにも属さない受信オブジェクトを使用してセレクターを呼び出した場合、 結果は未定義になります。 あなたの運が良ければ、 プログラムは即座にクラッシュします。
ここで、 2 つのクラスで 1 つまたは複数のセレクターを使用できるようにしたい場合を考えてみましょう。
セレクターを共通の祖先クラスに追加する必要があり、 最悪の場合は object
に追加する必要があります。 たとえば、
他の誰かがこの祖先クラスに対して責任を負っているなどの理由で、 共通の祖先クラスへのセレクターの追加を実行したくない場合もあります。
この問題の解決策はインターフェイス(interface)です。 インターフェイスはセレクターのコレクション(collection)です。 クラスがインターフェイスを実装(implement)している場合、 そのインターフェイスのセレクターはそのクラスとその子孫のクラスで使用できるようになります。 クラスは無制限の数のインターフェイスを実装できます。 上で説明した問題については、 セレクター達の為にインターフェイスを定義し、 両方のクラスでそのインターフェイスを実装します。
例として、 オブジェクトをディスクに書き込んで戻すための storage
というインターフェイスと、 それを実装するクラス
foo
について考えてみましょう。 そのコードは以下のようになります:
interface selector write ( file object -- ) selector read1 ( file object -- ) end-interface storage bar class storage implementation ... overrides write ... overrides read1 ... end-class foo
(著者は、 ここから更に read1
を内部的に使用するワード read
( file – object )
を追加するのですが、 それはインターフェイスとは関係ないので、 ここでは説明を割愛します。 )
注意: インターフェイスでは protected
を使用できないことに注意してください。 もちろん、
フィールドを定義することもできません。
Neon モデルでは、 すべてのセレクターがすべてのクラスで使用できます。 したがってインターフェイスは必要ありません。 Neon モデルで支払う代償は、 遅延結び付け(late binding)が遅くなるため、 遅延結び付けを回避するために複雑さが増すことです。
オブジェクトは、 struct...end-struct
で記述されたデータ構造体の 1 つである、 メモリーの一片です。 これは、
そのオブジェクトのクラスのメソッド・マップ(the method map) を指すフィールド object-map
を持っています。
「メソッド・マップ」(method map)33は、 そのオブジェクトのクラスのメソッドの実行トークン(xt)を含む配列です。 各セレクターには、 メソッド・マップへのオフセットが含まれています。
selector
は、CREATE
と DOES>
を使用する定義ワードです。 selector
の本体の内容はメソッド・マップのオフセット値です。 クラスのセレクターの DOES>
アクションは、 基本的には以下のとおりです(訳注:
メソッド・マップの実装上のフィールド名は "object-map"):
( object addr ) @ over object-map @ + @ execute
なお、 object-map
はオブジェクトの最初のフィールドであるため、 コードは生成されません。 ご覧のとおり、
セレクターの呼び出しには小さいけれども一定のコストがかかります。
クラスは基本的に struct
とメソッド・マップを組み合わせたものです。 struct
の場合と同様に、 クラス定義中に、
クラスのアライメントとサイズがスタックに渡されるため、 field
をクラスのフィールドの定義にも使用できます。 ただし、
スタックにさらに多くの項目を渡すと不便になるため、 class
はメモリー内にデータ構造を構築し、 変数
current-interface
を通じてアクセスします。 定義が完了すると、 クラスはポインター(たとえば、
子クラス定義のパラメーターとして利用したりする)としてスタックに置かれます。
新しいクラスは、 その親のアライメントとサイズ、 およびその親のメソッド・マップのコピーから始まります。 新しいフィールドを定義すると、
サイズとアライメントが拡張されます。 同様に、 新しいセレクターを定義すると、 メソッド・マップが拡張されます。 overrides
は、
セレクターによって指定されたオフセットでメソッド・マップに新しい xt を保存するだけです。
クラス結び付け(class binding)は、 そのクラスのメソッド・マップからセレクターによって指定されたオフセットで xt を取得し、
それをコンパイル(compile,
)するだけです([bind]
の場合)。
著者は this
を value
として実装しました。 m:...;m
メソッドの開始時に、 古い
this
がリターン・スタックに保存され、 最後に復元されます。 TOS 上のオブジェクトは TO this
で保存します。
この手法には欠点が 1 つあります。 ユーザーが ;m
経由ではなく throw
または exit
経由でメソッドを終了した場合、 this
は復元されません(そして exit
がクラッシュする可能性があります)。 著者は
throw
の問題に対処するために、 this
を保存および復元するために catch
を再定義しました(訳注: "objects.fs" をインクルードした時に "redefined catch" と警告が出るが、
意図的に再定義してあるので無視してください、 ということ)。 例外をキャッチできるワードについても全く同様に行うべきです。 exit
については、 単純に使用を禁止します(代わりに exitm
を用意しました)。
inst-var
は field
とまったく同じですが、 DOES>
アクションが異なります:
@ this +
inst-value
も同様です。
各クラスは、 inst-var
と inst-value
で定義されたワードや、 それらの protected
されたワードを含む、 ワードリストを持っています。 また、 その親へのポインターも持っています。 class
は、
クラスとそのすべての祖先のワードリストを検索順序スタック(the search order)にプッシュし、 end-class
はそれらをスタックから drop します。
インターフェイスは、 フィールドと親と protected されたワードのないクラスに似ています。 つまり、 メソッド・マップがあるだけです。 クラスがインターフェイスを実装する場合、 そのメソッド・マップにはインターフェイスのメソッド・マップへのポインターが含まれます。 マップ内の正のオフセットはクラス・メソッド用に予約されているため、 インターフェイス・マップ・ポインターは負のオフセットを持ちます。 クラス・セレクターとは異なり、 インターフェイスにはシステム全体で一意のオフセットがあります。 クラス・セレクターのオフセットは、 セレクターが利用可能な(呼び出し可能な)クラスに対してのみ一意です。
この構造は、 インターフェイス・セレクターがメソッドを見つけるために、 クラス・セレクターよりも 1
つ多い間接参照を実行する必要があることを意味します。 その本体には、 クラス・メソッド・マップ内のインターフェイス・マップ・ポインター・オフセットと、
インターフェイス・メソッド・マップ内のメソッド・オフセットが含まれています。 インターフェイス・セレクターの does>
アクションは、
基本的には以下のとおりです:
( object selector-body ) 2dup selector-interface @ ( object selector-body object interface-offset ) swap object-map @ + @ ( object selector-body map ) swap selector-offset @ + @ execute
ここで、 object-map
と selector-offset
は最初のフィールドであり、 コードは生成されません。
具体的な例として、 以下のコードについて考えてみましょう:
interface selector if1sel1 selector if1sel2 end-interface if1 object class if1 implementation selector cl1sel1 cell% inst-var cl1iv1 ' m1 overrides construct ' m2 overrides if1sel1 ' m3 overrides if1sel2 ' m4 overrides cl1sel2 end-class cl1 create obj1 object dict-new drop create obj2 cl1 dict-new drop
このコードで作成されたデータ構造 (object
のデータ構造を含む) は、セル・サイズ 4 を想定して
figure に図示されています。
bind
( ... "class" "selector" – ... ) objects “bind”
指定のクラス class の 指定セレクター selector のメソッドを execute します。
<bind>
( class selector-xt – xt ) objects “<bind>”
xt は、 指定クラス class のセレクター selector-xt のメソッドです。
bind'
( "class" "selector" – xt ) objects “bind”’
xt は 指定のクラス class の 指定のセレクター selector のメソッドです。
[bind]
( compile-time: "class" "selector" – ; run-time: ... object – ... ) objects “[bind]”
指定のクラス class の 指定のセレクター selector のメソッドをコンパイルします。
class
( parent-class – align offset ) objects “class”
parent-class の子として新しいクラス定義を開始します。 スタックに積まれた align offset は field などで使用されます。
class->map
( class – map ) objects “class->map”
指定のクラス class のメソッド・マップへのポインターを map に返します。
class-inst-size
( class – addr ) objects “class-inst-size”
指定のクラス class のインスタンス(つまり、 オブジェクト)のサイズ仕様を指します。 通常 class-inst-size
2@ ( class -- align size )
として使用されます。
class-override!
( xt sel-xt class-map – ) objects “class-override!”
指定の xt は、 指定の class-map のセレクター sel-xt の新しいメソッドです。
class-previous
( class – ) objects “class-previous”
指定の class のワードリストを検索順序スタック(the search order)から drop します。 class のワードリストが実際に検索順序スタックに含まれているかどうかのチェックは行われません。
class>order
( class – ) objects “class>order”
クラス class のワードリストを検索順序スタック(the search-order)の先頭に追加します。
construct
( ... object – ) objects “construct”
指定の object のデータ・フィールドを初期化します。 基底クラス object の construct
メソッドは何も行いません: ( object -- )
.
current'
( "selector" – xt ) objects “current”’
xt は、 現在のクラスの指定の selector のメソッドです。
[current]
( compile-time: "selector" – ; run-time: ... object – ... ) objects “[current]”
現在のクラスの selector のメソッドをコンパイルします。
current-interface
( – addr ) objects “current-interface”
変数(Variable): 現在定義中のクラスまたはインターフェイスが含まれます。
dict-new
( ... class – object ) objects “dict-new”
指定のクラス class オブジェクト用の領域を allot
を使用してディクショナリ内に確保し、
初期化(init-object
)したオブジェクトへのポインターを object に返します(訳注: object
は領域の先頭ではなくてメソッド・マップ(object-map)フィールドを指している事に注意)
end-class
( align offset "name" – ) objects “end-class”
name という名前を付けてクラス定義を終了します。 name 実行時: -- class
end-class-noname
( align offset – class ) objects “end-class-noname”
クラス定義を終了します。 結果のクラスは class です。
end-interface
( "name" – ) objects “end-interface”
name
という名前を付けてインターフェイス定義を終了します。 name
の実行時: --interface
end-interface-noname
( – interface ) objects “end-interface-noname”
インターフェイス定義を終了します。 結果のインターフェイスは interface
end-methods
( – ) objects “end-methods”
クラスのメソッド定義から通常モードに切り替えます(現在、 これは古い検索順序スタックを復元するだけです)。
exitm
( – ) objects “exitm”
メソッドから exit
します。 古い this
を復元します。
heap-new
( ... class – object ) objects “heap-new”
allocate
し、 クラス class のオブジェクトを初期化します。
implementation
( interface – ) objects “implementation”
現在のクラスは指定のインターフェイス interface を実装します。 つまり、 あなたは、 現在のクラス内とその子孫のクラス内で、 指定のインターフェイス interface の全てのセレクターを使用できます。
init-object
( ... class object – ) objects “init-object”
メモリーのチャンク object (アドレス)をクラス class のオブジェクトのために初期化します。 それから
construct
を実行します(訳注: class の construct
用のスタック項目も必要な事に注意。
6.24.3.2 Basic ‘objects.fs’ Usage の例 circle で言えば n-radius が必要で(my-circle2
というメモリー領域をクラス・オブジェクトにするとして、) 50 circle my-circle-2 init-object
としなければならない)
inst-value
( align1 offset1 "name" – align2 offset2 ) objects “inst-value”
align2 offset2 は w のサイズだけ足し込んだ値です。 name の実行時:
-- w
w は、this
オブジェクトの name フィールドの値です。
inst-var
( align1 offset1 align size "name" – align2 offset2 ) objects “inst-var”
addr は、this
オブジェクトのフィールド name のアドレスです。 name の実行時:
-- addr
interface
( – ) objects “interface”
インターフェイス定義を開始します。
m:
( – xt colon-sys; run-time: object – ) objects “m:”
メソッド定義を開始します。 定義中は指定の object が新しい this
になります。
:m
( "name" – xt; run-time: object – ) objects “:m”
名前付きメソッド定義を開始します。 定義中は指定の object が新しい this
になります。 ;m
で終わらせる必要があります。
;m
( colon-sys –; run-time: – ) objects “;m”
メソッド定義を終了します。 古い this
を復元します。
method
( xt "name" – ) objects “method”
セレクター name を作成し、xt を現在のクラスのメソッドにします。 name
の実行時:
... object -- ...
methods
( class – ) objects “methods”
class を現在のクラスにします。 これは、 セレクターをオーバーライドするメソッドを定義するために使用することを目的としています。 新しいフィールドやセレクターを定義することはできません。
object
( – class ) objects “object”
すべてのクラスの祖先。
overrides
( xt "selector" – ) objects “overrides”
現在のクラスの selector のデフォルト・メソッドを xt に置き換えます。 overrides
はインターフェイス定義中に使用してはいけません。
[parent]
( compile-time: "selector" – ; run-time: ... object – ... ) objects “[parent]”
現在のクラスの親の selector のメソッドをコンパイルします。
print
( object – ) objects “print”
オブジェクトを出力します。 指定のクラス object のメソッドは、 オブジェクトのアドレスとそのクラスのアドレスを出力します。
protected
( – ) objects “protected”
現在のクラスのワードリストをコンパイル・ワードリスト(the compilation wordlist)にセットします
public
( – ) objects “public”
実際にコンパイル・ワードリスト(the compilation wordlist)を変更した最後の protected
より前に有効だったコンパイル・ワードリストを復元します。
selector
( "name" – ) objects “selector”
現在のクラスとその子孫のセレクター name を作成します。 overrides
を使用して、
現在のクラスのセレクターのメソッドを設定できます。 name の実行時: ... object -- ...
this
( – object ) objects “this”
現在のメソッドの受信オブジェクト(receiving object)(別名アクティブ・オブジェクト)。
<to-inst>
( w xt – ) objects “<to-inst>”
実行トークン xt を inst-value で作成したフィールドとみなしてフィールド・オフセットを取得し、 現在のオブジェクト(this)の当該フィールドに指定の値 w を格納します。
[to-inst]
( compile-time: "name" – ; run-time: w – ) objects “[to-inst]”
w を this
オブジェクトの name フィールドに格納します。
to-this
( object – ) objects “to-this”
this
を設定します(内部的に使用されるものですが、 デバッグ時に役立ちます)。
xt-new
( ... class xt – object ) objects “xt-new”
xt ( align size -- addr )
を使用して何らかの形で領域を確保し、 そこに指定のクラス class
の新しいオブジェクトを作成(init-object
)します。
このセクションでは、 oof.fs パッケージについて説明します。
このセクションで説明するパッケージは、 1991 年以来 bigFORTH で使用されており、 新薬作成に使用されるクロマトグラフィー・システムと、グラフィック・ユーザー・インターフェイス・ライブラリー (MINOS) という 2 つの大きなアプリケーションに使用されています。
oof.fs の説明 (ドイツ語) は、Vierte Dimension 10(2), 1994 に掲載された Bernd Paysan による Object Oriented bigFORTH にあります。
postpone
とセレクター
'
を使用して拡張性が提供されます。
このセクションでは、 objects
(see Basic objects.fs Usage) と同じ例を使用します。
以下のようにして graphical オブジェクト(図形オブジェクト)のクラスを定義できます:
object class graphical \ "object" is the parent class method draw ( x y -- ) class;
このコードは、 draw
操作を持つクラス graphical
を定義します。 任意の graphical
オブジェクトに対して draw
操作を実行できます。例:
100 100 t-rex draw
ここで、 t-rex
(訳注: 恐竜ティラノサウルス) はオブジェクトまたはオブジェクト・ポインターであり、
たとえば次のように作成されます: graphical : t-rex
graphical オブジェクトを作成するにはどうすればよいでしょうか? 現在の定義では、 有用な graphical オブジェクトを作成できません。
クラス graphical
は graphical オブジェクト一般を記述しますが、 具体的な graphical
オブジェクト・タイプを記述しません(C++ ユーザーはこれを「抽象クラス」(abstract class)と呼びます)。 たとえば、 クラス
graphical
にはセレクター draw
のメソッドがありません。
具体的な graphical オブジェクトのために、 クラス graphical
の子クラスを定義します。 例:
graphical class circle \ "graphical" is the parent class cell var circle-radius how: : draw ( x y -- ) circle-radius @ draw-circle ; : init ( n-radius -- ) circle-radius ! ; class;
ここでは、 クラス circle
を graphical
の子として、 フィールド circle-radius
とともに定義します。 セレクター draw
および init
の新しいメソッドが定義されています(init
は、 graphical
の親クラスである object
で定義されています)。
いまや、 以下のコマンドを使用してディクショナリー内に circle を作成できます:
50 circle : my-circle
:
は init
を呼び出し、 フィールド circle-radius
を 50 で初期化します。
以下のようにして、この新しい circle を (100,100) に描画できます:
100 100 my-circle draw
注意: セレクターを呼び出すことができるのは、 受信オブジェクトが、 セレクターが定義されたクラス、またはその子孫の 1 つに属している場合のみです。
たとえば、 draw
は、 graphical
またはその子孫(例:
circle
)に属するオブジェクトに対してのみ呼び出すことができます。 このクラス階層で定義されていないセレクターを呼び出そうとすると、
スコープ・メカニズムがチェックするため、 コンパイル時にエラーが発生します。
クラスを定義するときは、 親クラスを指定する必要があります。 では、 クラスの定義はどのように始めればよいのでしょうか? 最初から使用できるクラスは 1
つあります。 それは object
です。 これをすべてのクラスの祖先として使用する必要があります。
これは親が存在しない唯一のクラスです。 クラスはインスタンス変数を持たない点を除けばオブジェクトでもあります。
クラスの継承や定義の変更などのクラス操作は、 クラス object
のセレクターを通じて処理されます。
object
はいくつかのセレクターを提供します:
class
を、 後で定義を追加するには settings
を、 型情報を取得するには
class?
(現在のクラスはスタックに渡されたクラスのサブクラスかどうか? を調べる)。
object-class
( "name" – ) oof “object-class”
object-definitions
( – ) oof “object-definitions”
object-class?
( o – flag ) oof “class-query”
init
と dispose
をオブジェクトのコンストラクターとデストラクターとして使用します。 init
はオブジェクトのメモリーが割り当て後に呼び出されますが、 dispose
は割り当て解除処理も行います。 したがって、
dispose
を再定義する場合は、 super destroy
を使用して親の destroy も呼び出す必要があります。
object-init
( ... – ) oof “object-init”
object-dispose
( – ) oof “object-dispose”
new
や new[]
や :
や ptr
や asptr
や []
を使用して、 名前付きおよび名前なしで、 オブジェクトとオブジェクト配列またはオブジェクト・ポインター を作成します。
object-new
( – o ) oof “object-new”
object-new[]
( n – o ) oof “new-array”
object-:
( "name" – ) oof “define”
object-ptr
( "name" – ) oof “object-ptr”
object-asptr
( o "name" – ) oof “object-asptr”
object-[]
( n "name" – ) oof “array”
::
と super
は明示的なスコープ設定に使用します。 明示的なスコープ指定は、
スーパークラスまたは同じインスタンス変数セットを持つクラスに対してのみ使用する必要があります。
明示的にスコープ指定されたセレクターは早期結び付け(early binding)を使用します。
object-::
( "name" – ) oof “scope”
object-super
( "name" – ) oof “object-super”
self
でオブジェクト自身のアドレスを取得します
object-self
( – o ) oof “object-self”
bind
や bound
や link
や is
を使用して、 オブジェクト・ポインターと
instance defers を割り当てます。
object-bind
( o "name" – ) oof “object-bind”
object-bound
( class addr "name" – ) oof “object-bound”
object-link
( "name" – class addr ) oof “object-link”
object-is
( xt "name" – ) oof “object-is”
'
はセレクター・トークンを取得し、 send
はスタックからセレクターを呼び出し、 postpone
はセレクター呼び出しコードを生成します。
object-'
( "name" – xt ) oof “tick”
object-postpone
( "name" – ) oof “object-postpone”
with
と endwith
は、 スタックから得たオブジェクトをアクティブ・オブジェクトにし、
そのオブジェクトのスコープを有効にします。 with
と endwith
を使用すると、
ステートスマート・オブジェクトにトラップされることなく、 セレクターを postpone
してコードを作成することもできます。
object-with
( o – ) oof “object-with”
object-endwith
( – ) oof “object-endwith”
var
( size – ) oof “var”
インスタンス変数を作成する
ptr
( – ) oof “ptr”
インスタンス・ポインターを作成します
asptr
( class – ) oof “asptr”
インスタンス・ポインターへのエイリアスを作成し、 別のクラスにキャスト(cast)します。
defer
( – ) oof “defer”
instance defer を作成します
early
( – ) oof “early”
早期結び付け(early binding)用のメソッド・セレクターを作成します。
method
( – ) oof “method”
メソッド・セレクターを作成します。
static
( – ) oof “static”
クラス全体のセル・サイズの変数を作成します。
how:
( – ) oof “how-to”
宣言を終わらせ、 実装部分を開始する
class;
( – ) oof “end-class”
クラス宣言または実装の終わり
Gforth の 3 番目のオブジェクト指向 Forth パッケージは 12行野郎(12-liner)です。 objects.fs 構文と oof.fs 構文を組み合わせて使用し、 機能を最小限に抑えています。 これは、Bernd Paysan の comp.lang.forth への投稿に基づいています。
基底クラス(base class)(class
、 オブジェクト・ポインターに 1 つのセルを割り当てます) に加えて、
7つのワードがあります。 それは、 メソッドの定義、 変数の定義、 クラスの定義開始、 クラスの定義終了、 結び付けの解決(resolve
binding)、 オブジェクトの割り当て、 クラス・メソッドのコンパイル、 です。
object
( – a-addr ) mini-oof “object”
object はすべてのオブジェクトの基底クラス(base class)です。
method
( m v "name" – m’ v ) mini-oof “method”
セレクターを定義します。
var
( m v size "name" – m v’ ) mini-oof “var”
size バイトのサイズの変数を定義します。
class
( class – class selectors vars ) mini-oof “class”
クラスの定義を開始します。
end-class
( class selectors vars "name" – ) mini-oof “end-class”
クラスの定義を終了します。
defines
( xt class "name" – ) mini-oof “defines”
xt をクラス class のセレクター name に結び付け(bind)します。
new
( class – o ) mini-oof “new”
クラス class の新しい具体化(incarnation)を作成します。
::
( class "name" – ) mini-oof “colon-colon”
クラス class のセレクター name のメソッドをコンパイルします (注意: 即実行ではありません!)。
短い例で、 このパッケージの使い方を示します。 他の例として button オブジェクト の Mini-OOF バージョンが moof-exm.fs にあります。
object class method init method draw end-class graphical
このコードは、 draw
操作を持つクラス graphical
を定義します。 任意の graphical
オブジェクトに対して draw
操作を実行できます。例:
100 100 t-rex draw
ここで、 t-rex
はオブジェクトまたはオブジェクト・ポインターであり、 たとえば次のように作成されます:
graphical new Constant t-rex
具体的な graphical オブジェクトのために、 クラス graphical
の子クラスを定義します。 例:
graphical class cell var circle-radius end-class circle \ "graphical" is the parent class :noname ( x y -- ) circle-radius @ draw-circle ; circle defines draw :noname ( r -- ) circle-radius ! ; circle defines init
暗黙的な init メソッドはないため、 明示的に定義する必要があります。 オブジェクトの作成コードは明示的に init を呼び出す必要があります。
circle new Constant my-circle 50 my-circle init
すべてのオブジェクトの同じ場所に init
がある場合、 init
の自動呼び出しで名前付きオブジェクトを作成する関数を追加することもできます:
: new: ( .. o "name" -- ) new dup Constant init ; 80 circle new: large-circle
以下のようにして、 この新しい circle を (100,100) に描画できます:
100 100 my-circle draw
遅延結び付け(late binding)を備えたオブジェクト指向システムは通常、 「vtable」アプローチを使用します。 つまり、 各オブジェクトの最初の変数はテーブルへのポインタであり、 テーブルには関数ポインターとしてメソッドが含まれています。 vtable には他の情報も含まれる場合があります。
まず、 セレクターを宣言しましょう:
: method ( m v "name" -- m' v ) Create over , swap cell+ swap DOES> ( ... o -- ... ) @ over @ + @ execute ;
セレクターの宣言中、 セレクターの数とインスタンス変数は(アドレス単位で)スタック上にあります。 method
はセレクターを 1
つ作成し、 セレクター番号をインクリメントします。 セレクターを実行するには、 オブジェクトを取得し、 vtable ポインターを取得し、
オフセットを追加して、 そこに保存されているメソッド xt を実行します。 実行時、 各セレクターは、 スタック・パラメーターのTOSとして、
セレクターを呼び出すオブジェクトを受け取り、 パラメーター(オブジェクトを含む)を変更せずに、 適切なメソッドに渡します。
いまや、 インスタンス変数も宣言しなければなりません
: var ( m v size "name" -- m v' ) Create over , + DOES> ( o -- addr ) @ + ;
同様に、 ワードには現在のオフセットを書き込んで作成されます。 インスタンス変数はさまざまなサイズ(セル、 浮動小数点数、 2倍長整数、
char)にすることができるため、 そのサイズを取得してオフセットに追加します。 マシンにアライメント制限がある場合は、 変数の前に適切な
aligned
または faligned
を配置して、 変数のオフセットを調整します。 それが size
がスタックのTOSにある理由です。
開始点(基底オブジェクト)と、 いくつかの糖衣構文も必要です:
Create object 1 cells , 2 cells , : class ( class -- class selectors vars ) dup 2@ ;
継承の場合、 新しい派生クラスが宣言されるときに、 親オブジェクトの vtable をコピーする必要があります。 これにより、 親クラスのすべてのメソッドが提供されますが、 それらはオーバーライドすることもできます。
: end-class ( class selectors vars "name" -- ) Create here >r , dup , 2 cells ?DO ['] noop , 1 cells +LOOP cell+ dup cell+ r> rot @ 2 cells /string move ;
最初の行は、 noop
で初期化された vtable を作成します。 2 行目は継承メカニズムで、 親 vtable から xt
達をコピーします。
新しいメソッドを定義する方法がまだありませんね。 ここで定義しましょう:
: defines ( xt class "name" -- ) ' >body @ + ! ;
新しいオブジェクトを割り当てるためのワードも必要です:
: new ( class -- o ) here over @ allot swap over ! ;
派生クラスが親オブジェクトのメソッドにアクセスしたい場合があります。 Mini-OOF でこれを実現するには 2 つの方法があります: 1 つ目は名前付きのワードを使用する方法で、 2 つ目は親オブジェクトの vtable を検索する方法です。
: :: ( class "name" -- ) ' >body @ + @ compile, ;
混乱しないためには良い例に勝るものはないので、 以下に例を示します。 まず、
テキストとその位置を格納するテキスト・オブジェクト(button
と呼ばれます)を宣言しましょう:
object class cell var text cell var len cell var x cell var y method init method draw end-class button
次に、 draw
と init
の 2 つのメソッドを実装します:
:noname ( o -- ) >r r@ x @ r@ y @ at-xy r@ text @ r> len @ type ; button defines draw :noname ( addr u o -- ) >r 0 r@ x ! 0 r@ y ! r@ len ! r> text ! ; button defines init
継承のデモンストレーションとして、 新しいデータや新しいセレクターを持たない、 クラス bold-button
を定義します:
button class end-class bold-button : bold 27 emit ." [1m" ; : normal 27 emit ." [0m" ;
クラス bold-button
には button
とは異なる draw メソッドがありますが、 新しいメソッドは
button
の draw メソッドに関して定義されています:
:noname bold [ button :: draw ] normal ; bold-button defines draw
最後に、 2 つのオブジェクトを作成し、セレクターを適用します:
button new Constant foo s" thin foo" foo init page foo draw bold-button new Constant bar s" fat bar" bar init 1 bar y ! bar draw
Mini-OOF2 は多くの点で Mini-OOF とよく似ていますが、 いくつかの点で大きく異なります。 特に、 Mini-OOF2
には現在のオブジェクト変数(current object variable)を持ち、 プリミティブの >o
と o>
を使用してそのオブジェクト・スタックを操作します。 すべてのメソッド呼び出しとインスタンス変数へのアクセスは、 現在のオブジェクト(current
object)を参照します。
>o
( c-addr – r:c-old ) new “to-o”
現在のオブジェクト(current object)を c_addr に設定し、 以前の現在のオブジェクトをリターン・スタックにプッシュします。
o>
( r:c-addr – ) new “o-restore”
リターン・スタックから以前の現在のオブジェクト(current object)を復元します
メソッド呼び出しまたはインスタンス変数アクセスへのオブジェクト・ポインターの受け渡しを容易にするために、 追加の認識器(recognizer)
rec-moof2
がアクティブ化されます。
rec-moof2
( addr u – xt translate-moof2 | notfound ) mini-oof2 “rec-moof2”
この認識器は非常に単純なドット・パーサ(dot-parser)で、 .SELECTOR
や .IVAR
を >o
SELECTOR o>
や >o IVAR o>
に変換します。
セレクターにメソッドを割り当てるには、 xt class is
selector を使用するため、
defines
は必要ありません。 メソッドの早期結び付け(early binding)には、 [
class
] defers
selector が使用され、 ::
は必要ありません。
【他のオブジェクト・モデルとの比較】オブジェクト指向の Forth 拡張機能が数多く提案されています(A survey of object-oriented Forths (SIGPLAN Notices, April 1996) by Bradford J. Rodriguez and W. F. S. Poehlman lists 17)。 このセクションでは、 先程説明した、 よく知られた 2つのモデルと、 (メソッド・マップの使用に関して)密接に関連した 2 つのモデルとの関係について議論します。 このセクションでは Andras Zsoter が協力してくれました。
現在最も人気のあるモデルは Neon モデル(参照: Object-oriented programming in ANS Forth (Forth Dimensions, March 1997)であるようですですが、 このモデルには多くの制限があります34:
selector object
構文を使用しているため、 スタック上でオブジェクトを渡すのが不自然になります。
もう 1 つのよく知られた出版物は、 Object-Oriented Forth (Academic Press, London, 1987) by Dick Pountain ですが、 遅延結び付け(late binding)をほとんど扱っていないため、 真のオブジェクト指向プログラミングに関するモノとは言えません。 代わりに、 Ada (83) のようなモジュラー言語の特徴である情報隠蔽やオーバーロードなどの機能に焦点を当てています。
Does late binding have to be slow?
(Forth Dimensions 18(1) 1996, pages 31-35)(「遅延結び付けは遅くないといけないのか?」) で、 Andras
Zsoter は(objects.fs の this
のような)アクティブ・オブジェクトを多用するモデルについて説明しています。 アクティブ・オブジェクトは、
すべてのフィールドにアクセスするために使用されるだけでなく、 すべてのセレクター呼び出しの受信オブジェクトも指定します。 あなたは {
... }
を使用してアクティブなオブジェクトを明示的に変更する必要がありますが、 objects.fs では m:
... ;m
で多かれ少なかれ暗黙的に変更されます。 Zsoter のモデルでは、 受信オブジェクトがすでにアクティブなオブジェクトであるため、
メソッドのエントリ・ポイントでのこのような変更は不要です。 一方、 Zsoter のモデルでは明示的な変更(機能)が絶対に必要です。 そうしないと、
誰もアクティブ・オブジェクトを変更できなくなるからです。 このモデルの標準 Forth での実装は、
http://www.forth.org/oopf.html から入手できます。
oof.fs モデルは、
(さまざまなワードリストに名前を保持することにより、)情報の隠蔽とオーバーロードの解決(resolution)をオブジェクト指向プログラミングと組み合わせます。
メソッドのエントリ時にアクティブ・オブジェクトを暗黙的に設定しますが、 (>o...o>
または
with...endwith
を使用して、)明示的に変更することもできます。 オーバーロードの解決と早期結び付け(early
binding)のために、 パースと、 ステートスマートなオブジェクトとクラスを使用します: オブジェクトまたはクラスはセレクターをパースし、
そこからメソッドを決定します。 セレクターがオブジェクトまたはクラスによってパースされない場合、 Zsoter のモデルのように、
アクティブなオブジェクトのセレクターへの呼び出し(遅延結び付け;late binding)が実行されます。
フィールドには常にアクティブ・オブジェクトを通じてアクセスします。 このモデルの大きな欠点は、 パースとステート・スマートです。
これにより拡張性が低下し、微妙なバグが発生する可能性が高くなります。 基本的に、 オブジェクトやクラスをティック(tick)したり
postpone
したりしない場合にのみ安全です(Bernd は反対しているが、 著者 (Anton) は納得していない)。
mini-oof.fs モデルは、 objects.fs モデルの非常に簡素化されたバージョンに非常に似ていますが、 構文的には objects.fs と oof.fs モデルが混合されたものです。 。
正規表現(regular expression)は、 多くの現代的プログラミング言語で見られる、 文字列のパターン・マッチング・アルゴリズムです。
あなたは require regexp.fs
を使用して正規表現機能を Gforth に追加できます。
このパターン・マッチングの古典的な実装はバックトラッキング・アルゴリズムです。 これは、 後方参照などの機能を使用する場合にも必要です。 Gforth
は、 パターン・マッチングのためのバックトラッキング・プログラムを定義する言語(language)を提供することで正規表現を実装します。
基本要素は制御構造 FORK
… JOIN
です。 これはワード内での前方呼び出しであるため、
軽量の試行錯誤制御構造(try and fail control structure)をコーディングできます。
FORK
( compilation – orig ; run-time f – ) gforth-0.7 “FORK”
AHEAD のような制御構造: JOIN の後にコードを呼び出します。
JOIN
( orig – ) gforth-0.7 “JOIN”
FORK のための、 THEN のような制御構造
あなたは、 チェックが失敗した場合に、 フラグと ?LEAVE
を計算することで、 任意の種類のチェックを自分でプログラムできます。
正規表現コードは ((
と ))
で囲まれています。
((
( addr u – ) regexp-pattern “((”
正規表現ブロックの開始
))
( – flag ) regexp-pattern “))”
正規表現ブロックの終了
正規表現のパターン・マッチングには要素として文字セットがあるため、 多くの機能を使用して文字クラス(charclass
と呼ばれる)を作成・変更できます。 ここでの文字はすべてバイトであるため、 ユニコード文字用には拡張されません。
charclass
( – ) regexp-cg “charclass”
文字クラスを作成します
+char
( char – ) regexp-cg “+char”
現在の文字クラスに char を追加します
-char
( char – ) regexp-cg “-char”
現在の文字クラスから char を削除します
..char
( start end – ) regexp-cg “..char”
char の範囲(start 〜 end)を現在の文字クラスに追加します
+chars
( addr u – ) regexp-cg “+chars”
charの文字列(string)を現在の文字クラスに追加します
+class
( class – ) regexp-cg “+class”
文字クラス class と現在の文字クラスの結合(union)
-class
( class – ) regexp-cg “-class”
現在の文字クラスから、 文字クラス class を抜き取ります(subtract)。
事前定義された文字クラス(charclasses)とそれらのテストと、 一般的なチェックがあります。 チェックが失敗した場合は、 次に考えられる正規表現の代替が試行されるか、 または、 ループが終了(leave)します(訳注: 基本的には、 チェックが成功した場合は addr を 1文字(char)分進めた addr’ を返し、 失敗した場合はスタックに何も積まずに leave する)。
c?
( addr class – addr’ ) regexp-pattern “c?”
文字クラス class にマッチ(addr の位置の char が文字クラス class に含まれているなら
addr の 1 char 次のアドレスを addr‘ に返し、 含まれていないなら leave
する)
-c?
( addr class – addr’ ) regexp-pattern “-c?”
文字クラス class 以外にマッチ(addr の位置の文字が文字クラス class に含まれていなければ
addr の1文字後ろのアドレスを addr’ に返し、含まれていたら leave
する)
\d
( addr – addr’ ) regexp-pattern “\d”
数字([0-9])にマッチ(指定の addr の char が digit かどうか([0-9])しらべ、 そうなら addr
の char 分次のアドレスを addr’ に返す。 そうでなければ leave
する)
\s
( addr – addr’ ) regexp-pattern “\s”
非表示文字(\x00〜\x20)にマッチ(指定の addr の char が blanks かどうか([\x00-\x20])しらべ、
そうなら addr の char 分次のアドレスを addr’ に返す。 そうでなければ leave
する)
.?
( addr – addr’ ) regexp-pattern “.?”
任意の1文字(char)にマッチ(任意の1文字(char)であれば次の文字のアドレスを addr’ に返します。 そうでなければ leave します)
-\d
( addr – addr’ ) regexp-pattern “-\d”
addr の文字が数字で無ければ([^0-9])次の文字のアドレスを addr’ に返します。 そうでなければ leave します。
-\s
( addr – addr’ ) regexp-pattern “-\s”
指定の addr の char が blanks で無いかどうか([^\x00-\x20])しらべ、 そうなら addr の
char 分次のアドレスを addr’ に返す。 そうでなければ leave
する
`
( addr "char" – addr’ ) regexp-pattern “‘”
指定の文字をチェックします。 ` c
の形で使用し、 addr の文字が 文字 c ならば、 次の文字へのアドレスを
addr’ に返し、そうでなければ leave します。
`?
( addr "char" – addr | addr’ ) regexp-pattern “‘?” \ 訳注:`? c
addr の文字が c ならば addr をそのまま返す。 c でなければ次の文字へのアドレスを addr’ に返す。
-`
( addr "char" – addr’ ) regexp-pattern “-‘” \ 訳注:-` c
addr の文字が c でなければ次の文字へのアドレスを addr’ に返す。 そうでなければ leave する。
指定の文字をチェックします。 ` c
の形で使用し、 addr の文字が 文字 c ならば、 次の文字へのアドレスを
addr’ に返し、そうでなければ leave します。
明示的に文字列の開始と終了、および文字列定数全体をチェックすることもできます。
\^
( addr – addr ) regexp-pattern “\^”
文字列の開始にマッチ
\$
( addr – addr ) regexp-pattern “\$”
文字列の終了にマッチ
str=?
( addr1 addr u – addr2 ) regexp-pattern “str=?”
指定の文字列 addr u にマッチ(等しい)するかどうかチェックします(マッチすれば文字列の次のアドレスを addr2 に返します。 マッチしない場合は leave します)
doc-=” (原文未記述)
繰り返される文字セットをチェックするループは、 貪欲(greedy)または非貪欲(non-greedy)にすることができます。
{**
( addr – addr addr ) regexp-pattern “begin-greedy-star”
貪欲な 0 個以上のパターンの開始
**}
( sys – ) regexp-pattern “end-greedy-star”
貪欲な 0 個以上のパターンの終わり
{++
( addr – addr addr ) regexp-pattern “begin-greedy-plus”
貪欲な 1 つ以上のパターンの開始
++}
( sys – ) regexp-pattern “end-greedy-plus”
貪欲な 1 つ以上のパターンの終わり
{*
( addr – addr addr ) regexp-pattern “begin-non-greedy-star”
非貪欲な 0 個以上のパターンの開始
*}
( addr addr’ – addr’ ) regexp-pattern “end-non-greedy-star”
非貪欲な 0 個以上のパターンの終わり
{+
( addr – addr addr ) regexp-pattern “begin-non-greedy-plus”
非貪欲な 1 つ以上のパターンの開始
+}
( addr addr’ – addr’ ) regexp-pattern “end-non-greedy-plus”
非貪欲な 1 つ以上のパターンの終わり
例: 部分文字列の検索は、 実際にはその前にあるものとの非貪欲な一致です。
//
( – ) regexp-pattern “//”
文字列内を検索(後続のパターンを文字列先頭ではなく文字列中でのマッチにする)(訳注:
: ([0-9]) ( addr u – ) (( \(
\d \) )) IF ." [" \1 type ." ]" ELSE ." fail" THEN ;
"0123" ([0-9]) [0]
ok
"A123" ([0-9]) fail ok
: (//[0-9]) ( addr u – ) (( // \( \d \) ))
IF ." [" \1 type ." ]" ELSE ." fail" THEN ;.
"0123" (//[0-9]) [0]
ok
"A123" (//[0-9]) [1] ok
"ABC3" (//[0-9]) [3] ok)
選択肢(alternative)は以下のように書きます。
{{
( addr – addr addr ) regexp-pattern “begin-alternatives”
選択肢(alternatives)の開始
||
( addr addr – addr addr ) regexp-pattern “next-alternative”
選択肢の区切り文字
}}
( addr addr – addr ) regexp-pattern “end-alternatives”
選択肢の終了
\1
~ \9
という名前の変数を最大 9 つ使用して、 一致した部分文字列を参照できます
\(
( addr – addr ) regexp-pattern “\(”
マッチング変数の開始;変数は \\1〜9 として参照されます
\)
( addr – addr ) regexp-pattern “\)”
マッチング変数の終わり
\0
( – addr u ) regexp-pattern “\0”
文字列全体
もちろん、 あなたは、 見つけたパターンを置換するコードを作成することもできます。
s>>
( addr – addr ) regexp-replace “s>>”
検索/置換 の置換パターン領域の開始
>>
( addr – addr ) regexp-replace “>>”
任意の置換コードを開始し、 コードはスタック上の文字列を評価し、 それを <<
に渡します。
<<
( run-addr addr u – run-addr ) regexp-replace “<<”
置換パターン領域の先頭からを、 指定の文字列 addr u に置換します
<<"
( "string<">" – ) regexp-replace “<<"”
置換パターン領域の先頭から文字列を string に置換します(訳注: 使い方: <<" string
)
s//
( addr u – ptr ) regexp-replace “s//”
検索/置換 ループの開始
//s
( ptr – ) regexp-replace “//s”
検索の終わり
//o
( ptr addr u – addr’ u’ ) regexp-replace “//o”
検索/置換 の単一ループの終了
//g
( ptr addr u – addr’ u’ ) regexp-replace “//g”
検索/置換 の全てのループの終了
これらの使用例は test/regexp-test.fs
にあります。
多くのプログラミング・システムは、 エディターがシステムのハブとなり、 プログラムを構築して実行できる統合開発環境(IDE)として編成されています。 それが必要な場合は、Gforth にもそれがあります(see Emacs and Gforth)。
しかしながら、いくつかの Forth システムには異なる種類の IDE を持っています: Forth コマンド・ラインが開発環境のハブです。 そこからさまざまな方法でソースを表示し、 必要に応じてエディターを呼び出すことができます。
gforth もそのような IDE を実装しています。 gforth は SwiftForth の慣習をほぼ踏襲していますが、 それ以上の機能を実装しています。
このアプローチの利点は、 お気に入りのエディターを使用できることです。 環境変数 EDITOR
をお気に入りのエディターに設定すると、
編集コマンドがそのエディターを呼び出します。 Gforth はバックグラウンドでいくつかの GUI エディターを呼び出し(そのため、 Forth
セッションは続行するので、 編集を終了する必要はありません)、 ターミナル上のエディターはフォアグラウンドで呼び出します(Gforth
に認識されないエディターのデフォルトはフォアグラウンドです)。 EDITOR
を設定していない場合、 デフォルトのエディターは
vi です。
locate
( "name" – ) gforth-1.0 “locate”
ワード name のソース・コードを表示し、 現在位置(current location)をそこに設定します。
doc-xt-locate(原文 未記述)
「現在位置」(current location)は、 locate
に加えて、 他の多くのワードによって設定されます。 また、
ファイルのロード中にエラーが発生した場合、 エラーの場所が現在位置になります。
多くのワードが現在位置(current location)で機能します:
l
( – ) gforth-1.0 “l”
現在位置(current location)のソースコードの行達を表示します。
n
( – ) gforth-1.0 “n”
現在位置より後ろの行、 または最後の n
または b
出力より後ろの行を表示します。
b
( – ) gforth-1.0 “b”
現在位置より前の行、 または最後の n
または b
出力より前の行を表示します。
g
( – ) gforth-0.7 “g”
現在位置、 または最後の n
または b
出力の先頭からエディターに入ります。
以下の値を変更することで、 l
や n
や b
で表示する行数を制御できます:
before-locate
( – u ) gforth-1.0 “before-locate”
現在位置の前に表示される行数(デフォルトは 3)。
after-locate
( – u ) gforth-1.0 “after-locate”
現在位置の後に表示される行数 (デフォルトは 12)。
最後に、以下を使用すると、 エディターを開いて当該ワードのソース・コードに直接移動できます。
edit
( "name" – ) gforth-1.0 “edit”
"name" の場所でエディターを起動します。
以下を使用すると、 似た名前のワードの定義を確認できます
browse
( "subname" – ) gforth-1.0 “browse”
(mwords
のように、) subname
を名前の一部に含むワードが定義されているすべての場所を表示します(see Word Lists)。 その後、 ww
または
nw
または bw
(see Locating uses of a word) を使用して、
特定の場所をより詳細に調査できます(訳注: 表示結果各行末尾の数(インデックス)を指定して、 ww
で当該の場所の内容を閲覧する、 等)
where
( "name" – ) gforth-1.0 “where”
name が「使用されている」すべての場所を表示します(テキスト・インタープリター時)。 その後、 ww
または
nw
または bw
を使用して、 特定の場所をより詳細に調べられます。 gforth の where
では
name の定義は表示されません。 そのためには locate
を使用してください。
ww
( u – ) gforth-1.0 “ww”
その後の l
や g
で閲覧する場所をインデックス u で指定します。 ここで、 インデックスは
browse
や where
で表示した際の行末尾の数字です。
nw
( – ) gforth-1.0 “nw”
次の l
または g
では、 browse
または where
の次の行の場所を表示します。
現在位置(current location)が browse
や where
の表示の最後のモノである場合、
nw
の後には何も無いです。 現在位置(current location)がまだ設定されていない時は、 nw
の後は、
browse
や where
の表示の最初のものが現在位置(current location)になります。
bw
( – ) gforth-1.0 “bw”
次の l
または g
では、 browse
または where
の前の行の場所を表示します。
現在位置(current location)が browse
や where
の表示の最初のモノである場合、
bw
の後には何も無いです。 現在位置(current location)がまだ設定されていない時は、 nw
の後は、
browse
や where
の表示の最後のものが現在位置(current location)になります。
gg
( – ) gforth-1.0 “gg”
このワードの次の ww
や nw
や bw
や bb
や nb
や
lb
(ただし、 locate
や edit
や l
や g
は除く)は、
その結果を(g
のように)エディターに流し込みます。 これをこの場限り(one-shot)では無くく永続的にするには、 gg
gg
を使用します。
ll
( – ) gforth-1.0 “ll”
このワードの次の ww
や nw
や bw
や bb
や nb
や
lb
(ただし、 locate
や edit
や l
や g
は除く)は(l
のように) Forth システムに表示されます。 これを1回限りではなく永続的にするには、 ll ll
を使用します。
whereg
( "name" – ) gforth-1.0 “whereg”
where
と似ていますが、 出力をエディターに流し込みます。 Emacs では、コンパイル・モード・コマンド
(see Compilation Mode in GNU Emacs Manual) を使用して、
特定の箇所をより詳細に調査できます。
short-where
( – ) gforth-1.0 “short-where”
短いファイル形式を使用するように where
を設定します(デフォルト)。
expand-where
( – ) gforth-1.0 “expand-where”
完全に拡張されたファイル形式を使用するように where
を設定します(例えば、 エディターなどに渡すためです)。
prepend-where
( – ) gforth-1.0 “prepend-where”
where
を設定してファイルを別の行に表示し、 その後にファイル名のない where
行が続きます(SwiftForth
風)。
ワードの使用状況に関するデータを利用すると、 どのワードが使用されていないのかを表示することもできます:
unused-words
( – ) gforth-1.0 “unused-words”
使用されていないすべてのワードをリストします
tt
( u – ) gforth-1.0 “tt”
\ 訳注: エラー表示時の Backtrace: で表示される番号を指定して、その場所を表示します。
nt
( – ) gforth-1.0 “nt” \ 訳注:tt
の次の backtrace 行の場所を表示します。
bt
( – ) gforth-1.0 “bt” \ 訳注:tt
で表示した1行前の backtrace 行の場所を表示します。
そして最後に、 see
とそのファミリーが、 コンパイル済のコードの表示を行います。 ソース・コード内の一部の内容は、
コンパイルされたコードには存在しません(書式設定やコメント等)。 しかし、 これは、 マクロや Gforth
の最適化機能によってどのようなスレッド化コード(threaded code)やネイティブ・コード(native
code)が生成されるかを確認するのに役立ちます。
see
( "<spaces>name" – ) tools “see”
現在の検索順序(search order)を使用して name を locate
します。 locate
できたら、 その定義を表示します。 これは定義を逆コンパイルすることで実現されるため、 書式は機械化され、
一部のソース情報(コメントや定義内のインタプリトされたシーケンスなど)は失われます。
xt-see
( xt – ) gforth-0.2 “xt-see”
xt で表される定義を逆コンパイルします。
simple-see
( "name" – ) gforth-0.6 “simple-see”
コロン定義 name を逆コンパイルして各セルごとに行表示し、 セルの意味を推測してそれを表示します。
xt-simple-see
( xt – ) gforth-1.0 “xt-simple-see”
simple-see
のようにコロン定義 xt を逆コンパイルします。
simple-see-range
( addr1 addr2 – ) gforth-0.6 “simple-see-range”
[addr1,addr2) ( addr1 <= and < addr2 )の範囲のコードを
simple-see
のように逆コンパイルします
see-code
( "name" – ) gforth-0.7 “see-code”
simple-see
と似ていますが、 インライン化されたプリミティブの動的ネイティブ・コード(dynamic native
code)も表示されます。 静的命令融合(superinstructions)の場合、
最初のプリミティブではなくプリミティブ・シーケンスが表示されます(命令融合(superinstructions)の他のプリミティブも表示されます)。
ネイティブ・コードが生成されるプリミティブの場合、 最初と最後のレジスタ内のスタック項目の数が表示されます(たとえば、 1->1
は、
最初と最後のレジスターに 1 つのスタック項目があることを意味します)。
ネイティブ・コードを使用した各プリミティブまたは命令融合(superinstructions)については、
インライン引数とコンポーネント・プリミティブが最初に表示され、 次にネイティブ・コードが表示されます。
xt-see-code
( xt – ) gforth-1.0 “xt-see-code”
コロン定義 xt を see-code
のように逆コンパイルします。
see-code-range
( addr1 addr2 – ) gforth-0.7 “see-code-range”
[addr1,addr2) ( addr1 <= and < addr2 )の範囲のコードを
see-code
のように逆コンパイルします。
例として、 以下について考えてみましょう:
: foo x f@ fsin drop over ;
この例は特に役に立つわけではありませんが、 さまざまなコード生成の違いを示しています。 これを AMD64 の gforth-fast
でコンパイルし、 see-code foo
を使用すると以下の出力が得られます(訳注: OSのコマンドラインから、
gforth
ではなくて gforth-fast
で起動する):
$7FD0CEE8C510 lit f@ 1->1 $7FD0CEE8C518 x $7FD0CEE8C520 f@ 7FD0CEB51697: movsd [r12],xmm15 7FD0CEB5169D: mov rax,$00[r13] 7FD0CEB516A1: sub r12,$08 7FD0CEB516A5: add r13,$18 7FD0CEB516A9: movsd xmm15,[rax] 7FD0CEB516AE: mov rcx,-$08[r13] 7FD0CEB516B2: jmp ecx $7FD0CEE8C528 fsin $7FD0CEE8C530 drop 1->0 7FD0CEB516B4: add r13,$08 $7FD0CEE8C538 over 0->1 7FD0CEB516B8: mov r8,$10[r15] 7FD0CEB516BC: add r13,$08 $7FD0CEE8C540 ;s 1->1 7FD0CEB516C0: mov r10,[rbx] 7FD0CEB516C3: add rbx,$08 7FD0CEB516C7: lea r13,$08[r10] 7FD0CEB516CB: mov rcx,-$08[r13] 7FD0CEB516CF: jmp ecx
まず、 コンポーネント lit
と f@
を含む静的命令融合(superinstruction)のスレッド化コード・セルが表示されます。 それは、 レジスター内の 1 つのデータ・スタック項目
(1->1
) で始まり・終わります。 )。 これに、lit
の引数 x
のセルと、 命令融合の
f@
コンポーネントのセルが続きます。 後者のセルは使用されませんが、 Gforth 内部の理由により存在します。
その次に、 命令融合 lit f@
に対して動的に生成されたネイティブ・コードを表示します。 注意: アドレスを比較するとわかるように、
このネイティブ・コードはメモリー内のスレッド化コードと混合されていない(not mixed)ことに注意してください。
ここに表示されているネイティブ・コードを理解するには、以下のレジスターについて知っておくべきです:
スレッド化コードの命令ポインターは
r13
データ・スタック・ポインターは r15
最初(TOS)のデータ・スタック・レジスターは r8
(データ・スタックのTOS、 つまり、 レジスターにデータ・スタック項目が 1 つある場合、 スタックのTOSがそこに存在します)
リターン・スタック
ポインターは rbx
FP スタック・ポインターは r12
FP スタックの先頭(TOS)は
xmm15
注意: レジスターの割り当てはエンジンによって異なるため、
コード表示においては異なるレジスターの割り当てが表示される場合があることに注意してください。
lit f@
の動的ネイティブ・コードは、 ディスパッチ・ジャンプ(dispatch jump;別名 NEXT)(訳注:
古典的には内部インタープリターへ制御を戻すためのジャンプ)で終了します。 これは、 定義内の次のワード fsin
のネイティブ・コードは動的に生成しないためです。
次に、 fsin
のスレッド化コード・セルが表示されます。 このワードには動的に生成されるネイティブ・コードはなく、
see-code
では静的ネイティブ・コードは表示されません(fsin
の静的ネイティブ・コードを確認したければ
see fsin
)。 gforth-fast
内の静的ネイティブ・コードを含むすべてのワードと同様、
データ・スタック表現への影響は 1->1
(gforth
エンジンでは 0->0
) です。 しかし、
これは表示されません。
次に、 drop
のスレッド化コード・セルが表示されます。 ここで使用されるネイティブ・コード版のバリエーションは、 レジスター内の 1
つのデータ・スタック項目で始まり、 レジスター内の 0 個のデータ スタック項目で終わります(1->0
)。 この後に
drop
のこのバリエーションのネイティブ・コードが続きます。 ここのネイティブ・コードは次のワードのコードに続くため、 ここには
NEXT はありません。
次に、 over
のスレッド化コード・セルと、 その後に 0->1
バリエーションで動的に生成されたネイティブ・コードが表示されます。
最後に、 ;s
のスレッド化されたネイティブ・コード(foo
内の ;
用にコンパイルされたプリミティブ)が表示されます。 ;s
は制御フローを実行する(制御フローからリターンする)ため、 NEXT
で終了する必要があります。
以下のワード群は、 スタックを非破壊的に調査します:
...
( x1 .. xn – x1 .. xn ) gforth-1.0 “...”
.s
のスマート・バージョン
.s
( – ) tools “dot-s”
データ・スタック上の項目の数を表示し、 その後に項目のリストを表示します(ただし、 表示項目数は maxdepth-.s
で指定された値を超えないようにします)。 右端が TOS になります。
f.s
( – ) gforth-0.2 “f-dot-s”
浮動小数点スタック上の項目の数を表示し、 その後に項目のリストを表示します(ただし、 表示項目数は maxdepth-.s
で指定された値を超えないようにします)。 右端が TOS になります。
f.s-precision
( – u ) gforth-1.0 “f.s-precision”
value
型変数です。 u は f.s の出力のフィールド幅です。 他の精度の詳細はこの値から導出されます。
maxdepth-.s
( – addr ) gforth-0.2 “maxdepth-dot-s”
変数(variable)。 デフォルトは 9 です。 .s
と f.s
は最大でその数のスタック項目を表示します。
.r
というワードがありますが、 リターン・戻りスタックは「表示されません」。 これは、
フォーマットされた数値出力に使用されるワードです(see Simple numeric output)。
以下のワード群は、 スタックの深さを決定したり、スタックをクリアしたりして、 スタック全体に作用します:
depth
( – +n ) core “depth”
+n は、 +n 自体がスタックに置かれる前にデータ・スタックに置かれていた項目の数です。
fdepth
( – +n ) floating “f-depth”
データ・スタックに積まれた +n は、 浮動小数点スタック上にある(浮動小数点)値の現在の数です。
clearstack
( ... – ) gforth-0.2 “clear-stack”
データ・スタックから全ての項目を削除して破棄します(訳注: データ・スタックを空にします)。
fclearstack
( r0 .. rn – ) gforth-1.0 “f-clearstack”
浮動小数点スタックをクリアします
clearstacks
( ... – ) gforth-0.7 “clear-stacks”
データ・スタックと FP スタックを空にします
以下のワード群はメモリーを調べます。
?
( a-addr – ) tools “question”
アドレス a-addr の内容を現在の基数(base)で表示します。
dump
( addr u – ) tools “dump”
メモリーのアドレス addr から始まる u バイトを表示します。 各行には 16 バイト分の内容が表示されます。 Gforth がオペレーティング・システム上で実行されている場合、 任意の場所にアクセスしようとすると、 Invalid memory address エラーが発生する可能性があります。
forth は、 ワード達 (および、 その後ろでディクショナリー割り当てられたすべての内容) を LIFO 方式で忘れさすことができます。
marker
( "<spaces> name" – ) core-ext “marker”
name という定義を作成します(マーク(mark)と呼ばれる)。 その実行機能(execution semantics)は、
それ自体とその後に定義されたすべてのものを削除します(訳注: 注意: 古典的な forth の forget name
と異なり、
任意のワード以降を「忘れる」コマンドでは無い事に注意。 あらかじめ marker
でマークした位置でしか「忘れる」事はできない)。
この機能の最も一般的な使用法は、 プログラム開発時です。 ソース・ファイルを変更するときは、 定義されているワードをすべて忘れて、 再度ロードします(ソース・ファイルのロード後に定義されたワードもすべて忘れるので、 それも再ロードする必要があります)。 変数への保存やシステム・ワードの破壊などの効果は、 ワードを忘れても元に戻されないことに注意してください。 著者は、 gforth のようなシステムでは、 起動とコンパイルが十分に速いので、 gforth を終了して再起動する方が、 白紙の状態(clean state)で使えるので便利だと思います。
デバッグしているソース・ファイルの先頭で marker
を使用する例を以下に示します。 これにより、
そのファイルの定義は二重定義されないようになります。
[IFDEF] my-code my-code [ENDIF] marker my-code init-included-files \ .. definitions start here \ . \ . \ end
編集/コンパイル/リンク/テスト の開発ループが遅い言語では、 デバッグを容易にするために高度な トレース/ステッピング デバッガが必要になる傾向があります。
高速コンパイル言語でのより良い(より速い)方法は、 適切に選択した場所に表示コード(printing code)を追加し、 プログラムを実行してその出力を見て、 問題が発生した箇所を確認し、 バグが見つかるまでさらに表示コード等を追加することです(訳注: C言語で言うところの printf デバッグすると言っている)。
debugs.fs で提供される単純なデバッグ支援機能は、 このスタイルのデバッグをサポートすることを目的としています。
~~
というワードは、 デバッグ情報(デフォルトではソースの場所とスタックの内容)を出力します。
これは簡単にソース・コードに挿入できます。 Emacs を使用している場合は、 削除も簡単です(Emacs Forth モードで C-x ~
を実行すると、 ~~
を ""(nothing) に query-replace します)。 defer ワード
printdebugdata
と .debugline
は、 ~~
の出力を制御します。
デフォルトのソース位置出力形式は Emacs のコンパイル・モードで適切に動作するため、 C-x `
を使用してソース・レベルでプログラムをステップ実行できます(ステップ実行デバッガーと比較した利点は、 任意の方向にステップ実行でき、
クラッシュが発生した場所や奇妙なデータが発生した箇所を知る事ができるということです)。
~~
( – ) gforth-0.2 “tilde-tilde”
~~
を記述したソース・コードの場所とスタックの内容を .debugline
を使って出力します。
printdebugdata
( – ) gforth-0.2 “print-debug-data”
.debugline
( nfile nline – ) gforth-0.6 “print-debug-line”
nfile nline で示されるソース・コードの場所と追加のデバッグ情報を出力します。 デフォルトの .debugline
は、 printdebugdata
を使用して追加情報を出力します。
debug-fid
( – file-id ) gforth-1.0 “File-id”
デバッグ用の出力先。 デフォルトでは、 当該プロセスの stderr
です。
~~
(およびアサート(assertions))は、 その出現後に同一ファイル内で marker が実行されると、 通常、
間違ったファイル名を出力します。 ~~
(およびアサート(assertions))は、 その出現前に同じファイル内で marker
が実行されると、 ファイル名として「どこか」(somewhere)を出力します。
once
( – ) gforth-1.0 “once”
once
から THEN までの操作を 1 回だけ だけ実行します(訳注: 1度実行されるとその旨を body に書き込むので、
再度実行するには once
を含む定義を「忘れ」(forget)て再度定義(読み込み)する必要がある)
~~bt
( – ) gforth-1.0 “~~bt”
スタック・ダンプとバックトレースを出力
~~1bt
( – ) gforth-1.0 “~~1bt”
スタックダンプとバックトレースを(once
と同様) 1 回だけ実行します
???
( – ) gforth-0.2 “???”
デバッグ・シェルを実行します
WTF??
( – ) gforth-1.0 “WTF??”
バックトレースとスタック・ダンプを使用してデバッグ・シェルを実行します
!!FIXME!!
( – ) gforth-1.0 “!!FIXME!!”
決してこの場所に到達してはいけないワード(訳注: -2605 を throw する)
replace-word
( xt1 xt2 – ) gforth-1.0 “replace-word”
xt2 が xt1 を実行するようにします。 両方ともコロン定義である必要があります。(訳注:
: foo ." foo" ;
: bar ."
bar" ;
foo foo ok
bar bar ok
’ foo ’ bar replace-word ok
foo foo
ok
bar foo ok
~~Variable
( "name" – ) gforth-1.0 “~~Variable”
アクセスごとにワッチされる(~~
する)変数(variable を ~~variable に一時的にリネームする)
~~Value
( n "name" – ) gforth-1.0 “~~Value”
アクセスごとにワッチされる(~~
する) value ( value を 一時的に ~~value にリネームする)
+ltrace
( – ) gforth-1.0 “+ltrace”
行単位のトレースをONにする。
-ltrace
( – ) gforth-1.0 “-ltrace”
行単位のトレースをOFFにする
#loc
( nline nchar "file" – ) gforth-1.0 “#loc”
次のワードの場所を "file" の nline nchar と設定します
特にメンテナンス中に無効になる可能性があると仮定する場合(たとえば、 データ構造の特定のフィールドは決してゼロにならない、 等)、 プログラムを自己チェックするようにすることをお勧めします。 Gforth は、 この目的のために「アサート」(assertions)をサポートしています。 それらは以下のように使用します:
assert( flag )
assert(
と )
の間のコードは flag を計算する必要があります。 すべてが正常な場合は true
、それ以外の場合は false になります。 スタック上の他のものは何も変更しないでください。 アサートの全体的なスタック効果は ( --
)
です。 例:
assert( 1 1 + 2 = ) \ what we learn in school assert( dup 0<> ) \ assert that the top of stack is not zero assert( false ) \ このコードには決して到達してはならない
アサートの必要性は、 その時々によって異なります。 デバッグ中はより多くのチェックが必要ですが、 運用環境では速度をより重視することがあります。 したがって、アサートをオフにすることができます。 その場合、 つまり、 アサートはコメントになります。 アサートの重要性とそのチェックにかかる時間に応じて、 一部のアサートをオフにし、 他のアサートをオンにしたほうがよい場合があります。 Gforth は、この目的のためにいくつかのレベルのアサートを提供します:
assert0(
( – ) gforth-0.2 “assert-zero”
常にオンにする必要がある重要なアサート。
assert1(
( – ) gforth-0.2 “assert-one”
通常のアサート: デフォルトではオンになっています。
assert2(
( – ) gforth-0.2 “assert-two”
デバッグ用のアサート。
assert3(
( – ) gforth-0.2 “assert-three”
通常のデバッグでは有効にしたくないであろう遅いアサート。 主に徹底的なチェックを行うためにオンにします。
assert(
( – ) gforth-0.2 “assert(”
assert1(
と同等です
)
( – ) gforth-0.2 “close-paren”
アサートを終了します。 汎用の終端なので、 他の同様の目的でも使用されます
変数 assert-level
は、オンにする最高のアサートを指定します。 つまり、デフォルトの assert-level
の 1 では、 assert0(
および assert1(
はチェックを実行しますが、 assert2(
および assert3(
) はコメントとして扱われます。
assert-level
の値は、 実行時ではなくコンパイル時に評価されます。 したがって、
実行時にアサートをオンまたはオフにすることはできません。 あなたはコードをコンパイルする前に assert-level
を適切に設定する必要があります。 異なるコード部分を異なる assert-level
でコンパイルできます(例:
信頼に足るライブラリーはレベル 1 で、 新しく作成されたコードはレベル 3 など)。
assert-level
( – a-addr ) gforth-0.2 “assert-level”
変数(variable)。 指定のレベルを超えるすべてのアサートはオフになります。
アサートが失敗すると、 Emacs のコンパイル・モードと互換性のあるメッセージが生成され、 実行が中止(abort)されます(現在は
ABORT"
を使用します。 あなたの興味があれば、 特別な throw コードを導入します。 ただし、
特定の条件をキャッチ(catch
)したい場合は、 おそらくアサートよりも throw
を使用する方が適切です)。
アサート (および ~~
) の出現後に同じファイル内で marker が実行されると、 通常は間違ったファイル名が出力されます。 アサート
(および ~~
) の出現前に同じファイル内で marker が実行された場合、
ファイル名として「どれか」(somewhere)が出力されます。
これらのアサート・ワードの標準 Forth での定義は、 compat/assert.fs で提供されます。
シングルステップ・デバッガーは、 gforth-itc
エンジンでのみ動作します(訳注: OSコマンドラインから gforth
ではなくて gforth-itc で起動する)。
新しいワードを作成するとき、 それが正しく動作するかどうかを確認する必要があることがよくあります。 これを行うには、dbg badword
と入力します。 デバッグ・セッションは以下のようになります:
: badword 0 DO i . LOOP ; ok 2 dbg badword : badword Scanning code... Nesting debugger ready! 400D4738 8049BC4 0 -> [ 2 ] 00002 00000 400D4740 8049F68 DO -> [ 0 ] 400D4744 804A0C8 i -> [ 1 ] 00000 400D4748 400C5E60 . -> 0 [ 0 ] 400D474C 8049D0C LOOP -> [ 0 ] 400D4744 804A0C8 i -> [ 1 ] 00001 400D4748 400C5E60 . -> 1 [ 0 ] 400D474C 8049D0C LOOP -> [ 0 ] 400D4758 804B384 ; -> ok
各行が 1 ステップです。 次のワードを実行して表示するには、 常に Return キーを押す必要があります。 次のワード全体を実行したくない場合は、
nest
に対して n と入力する必要があります。 利用可能なキーの概要は以下のとおりです:
Next; 次のワードを実行する(execute)。
Nest; 次のワードまで 1 ステップずつ辿る。
Unnest; デバッグを停止し(stop)、残りのワードを実行します(execute)。 ネスト(nest)機能でこのワードに到達した場合、 それを呼び出したワードでデバッグを続行します。
Done; デバッグを停止し(stop)、 残りを実行します(execute)。
Stop; 直ちに終了(abort)します。
このメカニズムを使用して大規模なアプリケーションをデバッグすることは、 あなたが関心のある部分が始まる前にプログラム内に非常に深くネストする必要があるため、 非常に困難です。 これにはとても時間がかかります。
これをより直接的に行うには、 BREAK:
コマンドをあなたのソース・コードに追加します。 プログラムの実行が BREAK:
に達すると、 シングル・ステップ・デバッガーが呼び出され、 上記すべての機能が利用できるようになります。
デバッグする部分が複数ある場合、 プログラムが現在どこで停止しているかを把握できると便利です。 これを行うには、 BREAK"
string"
コマンドを使用します。 これは BREAK:
と同様に動作しますが、
「ブレークポイント」に到達したときに文字列が出力される点が異なります。
dbg
( "name" – ) gforth-0.2 “dbg”
break:
( – ) gforth-0.4 “break:”
break"
( ’ccc"’ – ) gforth-0.4 “break"”
あなたのコードに対して広範なテスト(extensive tests)を実行する場合、 そのテストがコードのすべての部分を実行するかどうかを確認する必要が生じることがよくあります。 これを(テスト)カバレッジと呼びます。 ファイル coverage.fs には、 カバレッジと実行頻度を測定するためのツールが含まれています(訳注: gforth に組み込まれて無いので使う時は coverage.fs をロードしてください)。
コード・カバレッジは、 coverage.fs の後にロードされるすべての基本ブロック(逐次実行のコード・シーケンス)にカウント(計数)コードを挿入します。 コードが実行されるたびに、その基本ブロックのカウンター(計数器)がインクリメントされます。 後で、これらの基本ブロックに挿入されたカウント(計数)を含むソース・ファイルを表示できます。
nocov[
( – ) gforth-1.0 “nocov-bracket”
(即実行ワード) 一時的にカバレッジをオフにすることを開始。
]nocov
( – ) gforth-1.0 “bracket-nocov”
(即実行ワード) 一時的にカバレッジをオフにすることを終了。
coverage?
( – f ) gforth-internal “coverage?”
Value: カバレッジ・チェックの オン/オフ
cov+
( – ) gforth-experimental “cov+”
(即実行ワード) ここ(here)にカバレッジ・カウンターを配置します。
?cov+
( flag – flag ) gforth-experimental “?cov+”
(即実行ワード) フラグ(flag)のカバレッジ・カウンター。 カバレッジの出力では、 ?cov
の後ろに 3
つの数字が表示されます。 1 つ目はスタックの最上位が 0 以外だった実行の数です。 2 番目は、 ゼロだった場合の実行数です。 3
番目は実行の合計数です。
.coverage
( – ) gforth-experimental “.coverage”
コードを実行頻度(execution frequencies)とともに表示します。
annotate-cov
( – ) gforth-experimental “annotate-cov”
カバレッジ情報を含むファイルごとに、 実行頻度(execution frequencies)が挿入された .cov
ファイルを作成します。
最初に bw-cover
を使用することをお勧めします(デフォルトの color-cover
を使用すると、
ファイル内にエスケープ シーケンスが含まれます)。
cov%
( – ) gforth-experimental “cov-percent”
coverage.fs の後でロードされ、 少なくとも 1 回実行された基本ブロックの割合を出力します。
.cover-raw
( – ) gforth-experimental “.cover-raw”
生の実行カウントを出力します。
デフォルトでは、 カウント(計数)は(ANSI エスケープ・シーケンスを使用して、)カラーで表示されますが、 bw-cover
を使用すると、 エスケープ・シーケンスを使用せずに括弧で囲まれた形式でカウントを表示できます。
bw-cover
( – ) gforth-1.0 “bw-cover”
実行カウントを(カラー表示ではなく)括弧内に出力します(ソース・コード互換)(訳注: つまり forth のコメントとして出力する。出力をそのまま forth のソース・コードとして利用可能)。
color-cover
( – ) gforth-1.0 “color-cover”
実行カウントをカラーで表示します(デフォルト)。
カバレッジ・カウンター(計数器)をバイナリ形式で保存および再ロードして、 複数のテスト実行にわたるカバレッジ・カウンターを集計できます。
save-cov
( – ) gforth-experimental “save-cov”
カバレッジ・カウンターを保存します。
load-cov
( – ) gforth-experimental “load-cov”
カバレッジ・カウンターをロードします。
Gforth は 2 つのマルチタスカーを提供します。 1 つは従来の協調的なラウンドロビン・マルチタスカー(cooperative round-robin multitasker)で、 もう 1 つはマルチコア・マシン上で複数のスレッドを同時に実行できる pthread ベースのマルチタスカーです。 将来、 Forth マルチタスカーの標準化により、 その意味は変更せずにワードの名前が変更される可能性が高いため、 pthread ベースのマルチタスカーは現在、 実験的な機能としてマークされています。
pthred 、 つまり posix スレッドは、 複数のコア上で並行実行することも、 1 つのコア上でプリエンプティブ・マルチタスクで実行することもできます。 ただし、 以下のワード群の多くは、 従来の協調マルチタスク(traditional cooperative multi-tasker)の場合と同一です。
さらに、 一度に 1 つのタスクのみが何かを変更することを確認したり、 タスク間のコミュニケーションに使用したりするためのワード群もあります。
pause
の呼び出し間でトランザクション(取引)を実行する協調マルチタスク(cooperative-multitasking)はこの環境では機能しないため、
これらのワード群はプリエンプティブ・マルチタスクおよびマルチコア・マルチタスクで必要なものです。
タスクは newtask
または newtask4
を使用して、 指定された量のスタック領域(すべて同じ、 または、
データ・スタックとリターン・スタックと FP スタックとローカル変数スタックそれぞれのサイズを指定)で作成できます。
newtask
( stacksize – task ) gforth-experimental “newtask”
タスク(taskを作成します。 データ・スタックとリターン・スタックと FP
スタックとローカル変数スタックのサイズは全て同じにします(stacksize)(訳注: スタックごとに個別サイズ指定したい時は
newtask4
を使用してください)。
task
( ustacksize "name" – ) gforth-experimental “task”
タスク name を作成します。 各スタック(データ・スタック、 リターン・スタック、 FP スタック、 ローカル変数スタック) サイズは
ustacksize です。
実行時: ( – task )
newtask4
( u-data u-return u-fp u-locals – task ) gforth-experimental “newtask4”
データ・スタック・サイズ u-data、 リターン・スタック・サイズ u-return、 FP スタック・サイズ u-fp、 ローカル変数スタック・サイズ u-locals で task を作成します。
あなたが、 タスクにどのスタック・サイズを使用すればよいかわからない場合は、 メイン・タスクのサイズを流用できます:
stacksize
( – u ) gforth-experimental “stacksize”
u はメイン・タスクのデータ・スタックサイズです。
stacksize4
( – u-data u-return u-fp u-locals ) gforth-experimental “stacksize4”
メイン・タスクのデータ・スタック・サイズと、 リターン・スタック・サイズと、 FP スタック・サイズと、 ローカル変数スタックサイズを、 プッシュします。
タスクは非アクティブ状態(inactive state)で作成されます。 実行するには、 以下のいずれかのワードを使用してアクティブ化(activate)する必要があります:
initiate
( xt task – ) gforth-experimental “initiate”
task に xt を execute させます。 xt から戻ると、 タスクは自動的に終了します(VFX 互換)。 ワンタイム実行可能クロージャ(one-time executable closures)を使用して、 任意のパラメーターをタスクに渡せます。
以下の古い(legacy)ワード、 activate
と pass
は、 initiate
と同じ機能を提供しますが、 インターフェイスが異なります: does>
と同様に、 含まれるコロン定義が 2 つの部分に分割されます:
activate
/pass
より前の部分はタスクをアクティブ化する部分で実行され、
タスクをアクティブ化した後に呼び出し元に戻ります。 activate
/pass
より後の部分は、
アクティブ化されたターゲット・タスク内で実行されます。
activate
( run-time nest-sys1 task – ) gforth-experimental “activate”
指定のタスク(task)に activate
のより後ろにあるコードを実行させ、 activate
を含むワードの呼び出し元に戻ります。 タスクが activate
の後ろにあるコードから戻ると、 タスク自体が終了します。
pass
( x1 .. xn n task – ) gforth-experimental “pass”
現在のタスクのデータ・スタックから x1 .. xn n を取り出し、 x1 .. xn を task
のデータ・スタックにプッシュします。 task に pass
の後ろのコードを実行させ、 pass
を含むワードの呼び出し元に戻ります。 タスクが pass
の後ろにあるコードから戻ると、 タスク自体が終了します。
作成とアクティブ化(activation)をワン・ステップで行うこともできます:
execute-task
( xt – task ) gforth-experimental “execute-task”
メイン・タスクと同じスタック・サイズで新しいタスク task を作成します。 task に xt を execute させます。 xt から戻ると、 タスクは自動的に終了します。
タスクは最後まで実行して終了する以外に、 kill-task
を使用してタスク自身を終了することもできます。 他のタスクは
kill
で終了できます。
kill-task
( – ) gforth-experimental “kill-task”
現在のタスクを終了(terminate)します。
kill
( task – ) gforth-experimental “kill”
指定のタスク(task)を終了(terminate)します。
タスク自体を一時的に停止(temporarily stop)したり、 タスクを停止(be stopped)したりすることもできます:
halt
( task – ) gforth-experimental “halt”
タスクを停止(stop)する
stop
( – ) gforth-experimental “stop”
現在のタスクを停止(stop)し、 イベントを待ちます(再起動(restart)される可能性があります)
stop-ns
( timeout – ) gforth-experimental “stop-ns”
タイムアウト(ナノ秒単位)まで停止(stop)します。 ms
のより良い代替品です
タスクは、 タイムアウトが経過(timeout is over)するか、 別のタスクが以下のワードでウェイクアップ(wake)すると再起動(restart)します:
restart
( task – ) gforth-experimental “restart”
タスクを起動(wake)する
こちらもどうぞ:
pause
( – ) gforth-experimental “pause”
次の待機中のタスクに自発的に切り替えます(pause
は従来の協調(cooperative)タスク・スイッチャー(切替器)です。
pthread マルチタスクでは、 協調するのに pause
は必要ありませんが、 それでも使用できます。 例えば、
何らかの理由でポーリングに頼らなければならないときです)。 これにより、 キュー内のイベントもチェックされます。
Forth では、 すべてのタスクには、 「ユーザー領域」(user area)と呼ばれる、 基本的に同じタスク・ローカル・データがあります(初期の
Forth システムはマルチ・ユーザー・システムであり、 タスクごとに 1 人のユーザーが存在することがよくありました)。 task の結果、
たとえばnewtask
は、そのユーザー領域の開始アドレスです。 各タスクは、 システムによって定義されたユーザー・データ(例:
base
)を取得します。 あなたは以下を使用して追加のユーザー・データを定義できます:
User
( "name" – ) gforth-0.2 “User”
name という名前のユーザー変数(1 セル)を作成します。
name 実行時: ( – addr )
addr
は現在のタスクのユーザー変数のアドレスです。
AUser
( "name" – ) gforth-0.2 “AUser”
name というユーザー変数を作成します( User
とはクロス・コンパイラーでのみ違いが生じます)。
uallot
( n1 – n2 ) gforth-0.3 “uallot”
n1 バイトのユーザー・データを確保します。 確保した領域の先頭をユーザー領域内のオフセット値(n2)として返します。
UValue
( "name" – ) gforth-1.0 “UValue”
name はユーザー value です。
name 実行時: ( – x )
UDefer
( "name" – ) gforth-1.0 “UDefer”
name はタスク・ローカルの deferされたワードです。
Name 実行時: ( ... – ... )
ユーザー・データを扱う場合には、 以下のようなワード群もあります。
up@
( – addr ) new “up-fetch”
addr は現在のタスクのユーザー領域の先頭です(addr は現在のタスクの task 識別子としても機能します)。
user'
( "name" – u ) gforth-experimental “user”’
各タスクのユーザー領域のユーザー変数 name の、 ユーザー領域先頭からのオフセット値を U に返します。
's
( addr1 task – addr2 ) gforth-experimental “’s”
addr1 が現在のタスクのユーザー・データ内のアドレスである場合、 addr2 は 指定のタスク(task)のユーザー・データ内の対応するアドレスです。
協調的(cooperative)マルチタスカーは、 pause
の 2 つの呼び出しの間に相互作用する他のタスクがないことを保証できます。
しかしながら、 pthread は実際には(少なくともマルチコア CPU 上では)並列(concurrent)タスクであるため、
同じリソースにアクセスする際の競合を回避するためのいくつかのテクニックが必要です。
セマフォ(semaphore)を取得できるのは 1 つのスレッドのみであり、 他のすべてのスレッドはセマフォが解放されるまで待機する必要があります。
semaphore
( "name" – ) gforth-experimental “semaphore”
名前付きセマフォ name を作成します
name 実行時: ( – semaphore )
lock
( semaphore – ) gforth-experimental “lock”
セマフォをロック(lock)
unlock
( semaphore – ) gforth-experimental “unlock”
セマフォのロックを解除します(unlock)
同時アクセスを防ぐもう 1 つのアプローチが、 クリティカル・セクションです。 ここではクリティカル・セクションをセマフォで実装しているため、 クリティカル・セクションに使用するセマフォを指定する必要があります。 同一のセマフォを使用するクリティカル・セクション達のみが相互に排他的です。
critical-section
( xt semaphore – ) gforth-experimental “critical-section”
semaphoreをロックした状態で xt を execute します。 xt を離れた後は、 それが例外の throw によるものであっても semaphore のロックが解除されます。
アトミックなハードウェア操作は、 他のタスクが中間状態を参照することなく、 操作全体を実行します。 これらの操作は、 遅い OS プリミティブを使用せずにタスクを同期するために使用できますが、 非アトミックな操作シーケンスと比較すると遅くなる傾向があります。 アトミックな操作は、 アライメントを必要としないハードウェアであっても、 アライメントされたアドレスでのみ正しく機能します。
!@
( u1 a-addr – u2 ) gforth-experimental “store-fetch”
a-addr から 値を u2 に取得し、 そして、 a-addr に 値 u1 を格納する操作を、 アトミック操作として行います。
+!@
( u1 a-addr – u2 ) gforth-experimental “add-store-fetch”
a-addr から 値 u2 を取得し、 a-addr の値に u1 を足し込む操作を、 アトミック操作として行います。
?!@
( unew uold a-addr – uprev ) gforth-experimental “question-store-fetch”
a-addr から取得した値と uold を比較し、 等しい場合はunew を a-addr に格納して uprev には uold をセットします。 等しくない場合は a-addr への格納は行わずに a-addr から取得した値を uprev にセットします。 これらの操作をアトミック操作として行います。
もう 1 つのハードウェア操作はメモリー・バリアーです(memory barrier)。 残念ながら、 最新のハードウェアでは、 メモリー操作を他のメモリー操作と比較して(別のコアから見た場合に)並べ替えることができることが多く、 メモリー・バリアーにより、 タスク実行のある時点でこの並べ替えを抑制します。
barrier
( – ) gforth-experimental “barrier”
バリアー前のすべてのメモリー操作は、 バリアー後のメモリー操作より前に実行されます。
Gforth のメッセージ・キューはアクター・モデルのバリエーションです。
イベントは xt です。 送信タスクは受信タスクにイベントを実行するようにお願いし、 受信タスクは準備ができたらそのメッセージ・キューからイベントを 1 つ実行します。 単回実行クロージャ(Execute-once closures)は、 パラメーターを渡すイベントに使用できます。
send-event
( xt task – ) gforth-experimental “send-event”
タスク IPC: xt を task に送信します。 task で xt が execute されます。 使い捨てクロージャ(one-shot closure)を使用して、 xt でパラメーターを渡します。
複数のタスクが 1 つのタスクにイベントを送信する場合、 イベントは任意の順序で到着する可能性があります。 イベントを受信するには、 受信タスクに以下のいずれかのワードを使います:
?events
( – ) gforth-experimental “question-events”
現在のタスクのメッセージ・キュー内のすべてのイベント・シーケンスを一度に 1 つずつ実行します。
event-loop
( – ) gforth-experimental “event-loop”
イベント・シーケンスを待ち、 イベント・シーケンスが到着したら実行します。 キューにイベント・シーケンスがない場合は待機に戻ります。 このワードは二度と制御を戻しません(This word never returns.)。
event-loop
の代わりに、 タスクが stop
状態になると、 イベントを受信する準備も整い、 そして、
イベントを受信するとタスクが起動(wake)されます。
gforth の Cilk は、 同じ名前のプログラミング言語からインスピレーションを得た、
複数のコアで実行される複数のタスク間で作業を分割するためのフレームワークです。 Cilk を使用する場合は、 require
cilk.fs
としてください。
考え方としては、 並行して解決できる部分問題(subproblems)を特定し、
フレームワークがワーカー・タスクをこれらの部分問題に割り当てるというものです。 特に、 各サブ・タスクに対して spawn
ワードの 1
つを使用します。 最終的には、 部分問題が解決されるまで cilk-sync
で待つ必要があります。
現在、 すべての生成は 1 つのタスクから発生する必要があり、 cilk-sync
はすべての部分問題(subproblems)が完了するまで待機するため、 再帰アルゴリズムに現在の gforth Cilk
を使用するのは簡単ではありません。
オーバーヘッドを避けるために、 部分問題を細かく分割しすぎないでください。 どのくらい細かいかは、
部分問題の実行時間がどの程度均一であるかによって決まりますが、 かなりの実行時間の問題の場合は、 5*cores
個の部分問題(subproblems)にするのが、 おそらく良い出発点となります。
cores
( – u ) cilk “cores”
使用するワーカー・タスクの数を含む値。 デフォルトでは、 これはハードウェア・スレッドの数(SMT/HT の場合)です。
(決定できれば、)それ以外の場合は 1 になります。 別の数を使用したい場合は、 cilk-init
を呼び出す前に
cores
を変更します。
cilk-init
( – ) cilk “cilk-init”
ワーカー・タスクがまだ実行されていない場合は開始します。
spawn
( xt – ) cilk “spawn”
ワーカー・タスクで xt ( – ) を execute します。 使い捨て実行可能クロージャ(one-time
executable closures)を使用して、 ヒープに割り当てられたクロージャを渡し、 spawn
呼び出し側から、
ワーカーで実行されているコードに任意のデータを渡すことができます。
例: ( n r ) [{: n f: r :}h1 code
;] spawn
spawn1
( x xt – ) cilk “spawn1”
Execute xt ( x – ) in a worker task.
spawn2
( x1 x2 xt – ) cilk “spawn2”
Execute xt ( x1 x2 – ) in a worker task.
cilk-sync
( – ) cilk “cilk-sync”
すべての部分問題が完了(complete)するまで待ちます(wait)。
cilk-bye
( – ) cilk “cilk-bye”
すべてのワーカーを終了(terminate)します。
Gforth の C言語インターフェイスは、 Forth スタックからパラメーターを取得してC言語の関数を呼び出す、 C言語の関数を含むラッパー・ライブラリをコンパイルすることによって機能します。 このラッパー・ライブラリーはC言語のコンパイラーによってコンパイルされます。 コンパイル結果はキャッシュされるため、 ラッパー・ライブラリーを変更する必要がある場合、 Gforth は C言語のコンパイルを再実行するだけで済みます。 このビルド処理は自動(automatic)で、 インターフェイス宣言の最後に行われます。 Gforth は、 この処理に libtool と GCC を使用します。
C言語のインターフェイスはほぼ完成した状態で、 コールバックも既に追加されていますが、 構造体については、 独立したスコープを持たない Forth2012 構造体を使用します。 これらの構造体のオフセットは、 SWIG プラグインを使用してヘッダー・ファイルから抽出されます。
C言語の関数関数が宣言されたら(see Declaring C Functions 参照)、 次のように呼び出すことができます: あなたは引数達をスタックにプッシュしてから、 C言語関数のワードを呼び出します。 引数は、 C言語のドキュメントに記載されているのと同じ順序でプッシュする必要があります(つまり、 最初の引数がスタックの最も深いところにあります)。 整数およびポインター引数はデータ・スタックにプッシュする必要があり、 浮動小数点引数は FP スタックにプッシュする必要があります。 これらの引数は、 呼び出されたC言語の関数によって消費されます。
C言語の関数から戻るとき、 戻り値がある場合、 その戻り値は適切なスタックに置きます。 整数の戻り値はデータ・スタックにプッシュされ、 FP の戻り値は
FP スタックにプッシュされ、 void 戻り値の場合は何もプッシュされません。 C言語ではあまり使用されない場合でも、
ほとんどのC言語の関数には戻り値があることに注意してください。 Forth では、 この戻り値を使用しない場合は、
明示的に捨てる(drop
)必要があります。
C言語インターフェイスは、 必要に応じて、 ベストエフォートで C言語の型と Forth の型の間で自動的に変換します(場合によっては、 ある程度の損失が発生する可能性があります)。
例として、 POSIX 関数 lseek()
について考察してみましょう:
off_t lseek(int fd, off_t offset, int whence);
この関数は 3 つの整数引数を受け取り、 整数引数 1 つ返すため、 現在のファイルのオフセットをファイルの先頭に設定するための Forth 呼び出しは以下のようになります:
fd @ 0 SEEK_SET lseek -1 = if ... \ error handling then
あなたは、 off_t
がセルに収まらないため、 lseek に大きなオフセットを渡すことができず、
戻り値の一部しか取得できないのではないかと心配するかもしれません。 その場合、 関数の宣言(see Declaring C Functions)で、off_t 引数と戻り値に 2倍長セルを使用するように宣言し、 結果の Forth ワードに dlseek
などの別の名前を付ける必要があります。 その結果は以下のように呼び出すことができます:
fd @ #0. SEEK_SET dlseek #-1. d= if ... \ error handling then
構造体または共用体の受け渡しは現在、 このインターフェイスではサポートされていません35
可変個の引数を持つ関数の呼び出し(「variadic関数」例: printf()
)は、 引数パターンごとに 1
つの関数呼び出しワードを宣言し、 目的のパターンに適切なワードを呼び出すことによってのみサポートされます。
あなたは、 先の例の lseek
や dlseek
を呼び出す前に、 宣言する必要があります。 宣言は以下の 2
つの部分で構成されます:
関数の C 言語宣言です。 より一般的かつ移植性の高いものとしては、 C 言語の関数の宣言を含むファイルの C 言語スタイルの
#include
です。
パラメーターの Forth 型と、 C言語の関数に対応する Forth ワード名を宣言します。
前述の lseek
と dlseek
の宣言は以下のとおりです:
\c #define _FILE_OFFSET_BITS 64 \c #include <sys/types.h> \c #include <unistd.h> c-function lseek lseek n n n -- n c-function dlseek lseek n d n -- d
宣言の C言語部分は \c
という接頭辞が付けられ、 \c
の行の残りの部分は通常の C 言語のコードです。 C
言語の宣言の行は好きなだけ使用でき、 それ以降のすべての関数宣言で参照できます。
Forth 部分は、 c-function
で各インターフェイス・ワードを宣言し、 その後にワードの Forth 名と、 呼び出される C
言語の関数名と、 ワードのスタック効果が続きます。 スタック効果には、 任意の数のパラメーターのタイプ、 --
、 そして戻り値の「 1
つ」のタイプが含まれます。 可能なタイプは以下のとおりです:
n
単一セル整数
a
アドレス(単一セル)
d
2倍長セル整数
r
浮動小数点数値
func
C言語の関数へのポインター
void
値なし(void 関数の戻り値の型として使用)
可変個の引数(variadic)の C 言語の関数を扱うには、 あなたが使用したいパターンごとに 1 つの Forth ワードを宣言します。 例:
\c #include <stdio.h> c-function printf-nr printf a n r -- n c-function printf-rn printf a r n -- n
注意: C 言語の関数が可変個(variadic)の引数として宣言されている場合(または関数プロトタイプを提供していない場合)、 C 言語インターフェイスには変換先の C 言語の型がないため、 自動変換は行われず、 場合によっては移植性の問題が発生する可能性があることに注意してください。 C 言語の型キャストを波括弧({})で囲んで Forth 型の後に追加できます。 これにより、 例えば、 Forth ではスタック上に存在できない構造体を C 言語の関数へ渡す事も可能になります。
c-function printfll printf a n{(long long)} -- n c-function pass-struct pass_struct a{*(struct foo *)} -- n
C言語では左辺値(lvalues)の型キャスト(typecast)が許可されていないため、 この型キャストは戻り値には使用できません。
\c
( "rest-of-line" – ) gforth-0.7 “backslash-c”
C言語インターフェイスのC言語宣言の1行
c-function
( "forth-name" "c-name" "{type}" "–" "type" – ) gforth-0.7 “c-function”
Forth ワード forth-name を定義します。 Forth-name には指定されたスタック効果({type}
と type)があり、 C言語関数 c-name
を呼び出します。
c-value
( "forth-name" "c-name" "–" "type" – ) gforth-1.0 “c-value”
Forth ワード forth-name を定義します。 Forth-name には指定されたスタック効果(type)があり、
c-name
の C言語からの値を与えます(訳注: スタック効果の通り、 読み取りのみ)。
c-variable
( "forth-name" "c-name" – ) gforth-1.0 “c-variable”
Forth ワード forth-name を定義します。 Forth-name は c-name
のアドレスを返します。
この C言語インターフェイスが機能するために、 実行時に GCC を呼び出し、 動的リンク(dynamic linking)を使用します。 これらの機能が利用できない場合は、 lib.fs と oldlib.fs に、利便性と移植性に劣る他の C 言語インターフェイスが用意されています。 これらのインターフェイスはほとんど文書化されておらず、 また文書化された C言語インターフェイスとほとんど互換性がありません。 あなたが例を見たければ、 lib.fs インターフェイスの例が lib.fs にあります。
C言語の関数へのポインター(たとえば、 C言語で構築された構造体など)を見つけて、 それを Forth プログラムから呼び出したい場合は、
マクロを定義することで上記の構造体を使用できます。 または、 c-funptr
を使用します。
c-funptr
( "forth-name" <{>"c-typecast"<}> "{type}" "–" "type" – ) gforth-1.0 “c-funptr”
Forth ワード Forth-name を定義します。 Forth-name には、 その後続に記述されたスタック効果と、
スタックのTOSに置かれた呼び出されたポインター(the called pointer)(つまり {type} ptr – type )があり、
型キャスト(typecast)または構造体アクセス(c-typecast)を使用して C 言語の関数へのポインター ptr
を呼び出します。
ヘッダー・ファイル func1.h に C言語の関数ポインター型 func1
が定義されており、 これらの関数は 1
つの整数引数を受け取り、 整数の結果を返すことがわかっているとします。 そして、 そのようなポインターを介して関数を呼び出したいとします。
あなたは以下のように定義するだけです
\c #include <func1.h> c-funptr call-func1 {((func1)ptr)} n -- n
そして、 例えば、 以下のようにして func1-addr
が指す関数を呼び出すことができます:
-5 func1-addr call-func1 .
Forth ワード call-func1
は execute
に似ていますが、Forth 実行トークンの代わりに C 言語の
func1
ポインターを使用し、 func1
ポインタ固有である点が異なります。 Forth から呼び出したい C
言語の関数ポインターのタイプごとに、 個別の呼び出しワードを定義する必要があります。
以下のように、 一連の C 言語関数宣言(ライブラリー・インターフェイス)に名前を付けることができます:
c-library lseek-lib \c #define _FILE_OFFSET_BITS 64 ... end-c-library
インターフェイスにこのような名前を付けると、 生成されるファイルの名前にその名前が含まれるようになり、 インターフェイスを 2 回目に使用するときに、 再度生成してコンパイルする代わりに既存のファイルが使用されるため、時間を節約できます。 生成されたファイルにはソース・コードの 128 ビット・ハッシュ(暗号的に安全ではありませんが、 この目的には十分である)が含まれているため、 宣言を変更すると新しいコンパイルが発生します。 通常、 これらのファイルは $HOME/.gforth/architecture/libcc-named にキャッシュされるため、 問題が発生した場合、 または再コンパイルの強制が必要なその他の理由がある場合は、そこにあるファイルを削除してください。
注意: c-library
を使用すると、 一部の設定がリセットされるため、 そのライブラリに関連する他のすべての処理の前に
c-library
を使用する必要があることに注意してください。 c-library
の考え方は、 一般的な使用法としては、
各 c-library
...end-c-library
ユニットを独自のファイルに配置し、
これらのファイルを任意の順序でインクルードできるようにすることです。 C 言語インターフェースを扱う他のすべてのワードは、 ボキャブラリー
c-lib
に隠されており、 c-library
によって検索順序スタックの最上位に置かれ、
end-c-library
によって検索順序スタックの最上位から削除されます。
注意: ライブラリー名はディクショナリーに割り当てられないため、 ディクショナリー内の名前に影響を与えないことに注意してください。
ファイル・システムで使用されるため、 ファイル・システムに適した命名規則を使用する必要があります。
この名前はC言語のシンボルの一部としても使用されますが、 正当なC言語のシンボル名以外の文字はアンダースコアに置き換えられます。 また、
c-library
の後で宣言した関数を end-c-library
を実行する前に呼び出してはなりません。
これらの名前付きライブラリー・インターフェイスの主な利点は、 それらが生成されると、 その生成に使用したツール(特に C 言語のコンパイラーと libtool)が不要になるため、 ツールがインストールされていないマシンでもインターフェイスを使用できることです。 Gforth のビルド・システムはこれらのライブラリーをクロス・コンパイルすることもできるため、 ビルド・ツールがインストールされていないプラットフォームでもライブラリーを利用できます。
c-library-name
( c-addr u – ) gforth-0.7 “c-library-name”
c-addr u という名前で C 言語ライブラリー・インターフェイスを開始。
c-library
( "name" – ) gforth-0.7 “c-library”
c-library-name
のパース対応(parsing)バージョンです
end-c-library
( – ) gforth-0.7 “end-c-library”
直近の C 言語ライブラリー・インターフェイスを終了し、 (必要に応じて)ビルドします。
一部の C 言語の関数を呼び出すには、 その関数を含む特定の OS レベルのライブラリーとリンクする必要があります。 たとえば、sin
関数では、 コマンドライン・スイッチ -lm
を使用して特別なライブラリーをリンクする必要があります。 C 言語インターフェイスでは、
以下のように add-lib
を呼び出して同等のことを行います:
clear-libs s" m" add-lib \c #include <math.h> c-function sin sin r -- r
まず、 (あなたが sin
に必要としない、)以前に宣言された可能性のあるライブラリーをすべてクリアします。 次に m
ライブラリー(実際には libm.so
など) を現在宣言されているライブラリーに追加します。 これは必要なだけ追加できます。 最後に、
上に示したように関数を宣言します。 通常は、 多くの関数宣言に同じライブラリー宣言のセットを使用します。 あなたは、 最初に、 この組を、 ただ 1
つだけ作成する必要があります。
注意: c-library...end-c-library
内で clear-libs
を呼び出してはいけないことに注意してください。 しかしながら、 c-library
は clear-libs
の機能も実行するため、 clear-libs
は必要なく、 そして、 あなたは、 通常は add-lib
呼び出しを
c-library...end-c-library
内に置きたいと思うでしょう。
clear-libs
( – ) gforth-0.7 “clear-libs”
libs のリストをクリア
add-lib
( c-addr u – ) gforth-0.7 “add-lib”
ライブラリー libstring をライブラリーのリストに追加します。ここで、 string は c-addr u で表されます。
add-libpath
( c-addr u – ) gforth-0.7 “add-libpath”
パス string をライブラリー検索パスのリストに追加します。 ここで、string は c-addr u で表されます。
add-framework
( c-addr u – ) gforth-1.0 “add-framework”
フレームワーク libstring をフレームワークのリストに追加します。 ここで、string は c-addr u で表されます。
add-incdir
( c-addr u – ) gforth-1.0 “add-incdir”
パス c-addr u をインクルード検索パスのリストに追加します
add-cflags
( c-addr u – ) gforth-1.0 “add-cflags”
あらゆる種類の cflags をC言語コンパイラーに追加します
add-ldflags
( c-addr u – ) gforth-1.0 “add-ldflags”
リンカー(linker)にフラグを追加
場合によっては、 C言語の関数へ関数のポインターを渡す必要があります。 つまり、
ライブラリーがアプリケーションにコールバックしたい場合です(ポイント先の関数はコールバック関数と呼ばれます)。
既存のC言語の関数のアドレス(lib-sym
で取得。 see Low-Level C Interface Words)を渡すことができますが、 適切な C言語の関数がない場合、 あなたは Forth ワードをC言語の関数として定義したい筈です。 その際は、
下記にて説明する、 コールバックを生成する必要があります:
c-callback
を使用して Forth コードからC言語のコールバックを生成できます。
c-callback
( "forth-name" "{type}" "–" "type" – ) gforth-1.0 “c-callback”
指定のシグネチャ forth-name を使用してコールバック・インスタンス化器(instantiator;インスタンシエーター)を定義します。
コールバック・インスタンス化器 forth-name ( xt -- addr )
は、 xt を受け取り、
そのコールバックを処理する C言語関数の addr を返します。
c-callback-thread
( "forth-name" "{type}" "–" "type" – ) gforth-1.0 “c-callback-thread”
指定のシグネチャ forth-name を使用してコールバック・インスタンス化器を定義します。 コールバック・インスタンス化器
forth-name ( xt -- addr )
は、 xt を受け取り、 そのコールバックを処理する
C言語の関数の addr を返します。 このコールバックは、 別のスレッドから呼び出された場合に安全です
これにより、 (value callback#
までの)多数のコールバック関数がプリコンパイルされます。 C言語の関数プロトタイプは、
Forth シグネチャから推定されます。 これでは不十分な場合は、 Forth 型の後に波括弧({})で囲んで型を追加できます。
c-callback vector4double: f f f f -- void c-callback vector4single: f{float} f{float} f{float} f{float} -- void
文書化されたC言語インターフェイスは、 宣言より C 言語のコードを生成することによって機能します。
特に、 c-function
で宣言されたすべての Forth ワードに対して、 Forth スタックから Forth
データを取得するラッパー関数をC言語で生成し、 これらのデータを引数としてターゲットのC言語の関数を呼び出します。 次に、 C言語のコンパイラーは、
スタックからの Forth 型と、 C言語の関数プロトタイプによって指定されるパラメーターのC言語の型、 の間で暗黙的な変換を実行します。
C言語の関数から戻った後、 戻り値も同様に暗黙的に Forth 型に変換され、 スタックに書き戻されます。
\c
行はそのままC言語のコードに含まれており(ただし、\c
は含まれません)、 C言語のコンパイラーがC言語の型を認識し、
そして、 変換を実行するのに十分な情報を得るために必要な宣言を提供します。
これらのラッパー関数は最終的にコンパイルされ、そして、 Gforth に動的にリンクされ、 そして、 呼び出すことができるようになります。
add-lib
で追加されたライブラリーは、 コンパイラーのコマンド・ラインで -llib
で依存ライブラリーを指定するために使用され、 ラッパー関数がリンクされるときにこれらのライブラリーが動的にリンクされます。
open-lib
( c-addr1 u1 – u2 ) gforth-0.4 “open-lib” \ 訳注: 指定の名前のライブラリーを開く。 u2 にハンドルを返す。 エラーの場合は u2 に 0 を返し、lib-error
で参照できるエラーメッセージをセットする。
lib-sym
( c-addr1 u1 u2 – u3 ) gforth-0.4 “lib-sym” \ 訳注:open-lib
で取得したハンドル u2 を使って シンボル文字列 c-addr1 u1 のシンボル・テーブルのアドレスを u3 に返す。 エラーの場合は u3 に 0 を返し、lib-error
で参照できるエラーメッセージをセットする。
lib-error
( – c-addr u ) gforth-0.7 “lib-error”
最後に失敗した open-lib
または lib-sym
のエラー・メッセージ。
call-c
( ... w – ... ) gforth-0.2 “call-c”
w が指す C言語の関数を呼び出します。 C言語関数はそれ自身でスタックにアクセスする必要があります。 スタック・ポインターはC言語の関数に渡される ptrpair 構造体にエクスポートされ、 その形式でC言語関数から返されます。
SWIG (Simple Wrapper Interface Generator)は、 多くのプログラミング言語でC言語インターフェイスを作成するために使用されています。 Forth モジュールで拡張された SWIG バージョンは、 github にあります。
C言語のヘッダーはパースされ、 前述のC言語インターフェイス関数を使用する Forth のソース・コードに変換されます。
example.h
を使用しています。
example.h
のすべてを変換(translate)するよう指示するだけです:
%module example %insert("include") { #include "example.h" } %include "example.h"
.fsi-c
ファイルを作成します:swig -forth -stackcomments -use-structs -enumcomments -o example-fsi.c
example.i
.fsi-c
ファイルを .fsx
にコンパイルします(x は executable(実行可能)の意味です):gcc -o example.fsx example-fsi.c
.fs
ファイルに取り込むことです:./example.fsx -gforth > example.fs
いくつかの例: SWIG’s Forth Example section
たくさんのインターフェイス・ファイルがこちらにあります: Forth Posix C-Interface と Forth C-Interface Modules
Forth C-Interface Module repository への貢献はいつでも歓迎です!
このバージョンでは、 \c
と c-function
と add-lib
は
c-library
...end-c-library
内でのみ使用できます。 add-lib
は常に
c-library
内の白紙の状態から開始されるため、 ほとんどの場合 clear-libs
を使用する必要はありません。
c-library
...end-c-library
の外側でこれらのワードを使用するプログラムがあった場合は、 あなたは、
それらを c-library
...end-c-library
で囲むだけです。 ただし、 add-lib
のインスタンスをいくつか追加する必要がある場合があります。
Gforth は、 アセンブリ言語でワードを実装する方法(abi-code
… end-code
を使用)と、
任意の実行時の振る舞いを持つ定義ワードを定義する方法(does>
のようなの)を提供し、 ここで、
この実行時の振る舞いを(does>
とは異なり、) Forth ではなくアセンブリ言語で定義します。
ただし、 Gforth のマシン非依存の性質により、 いくつかの問題が生じます。 まず、 Gforth は複数のアーキテクチャー上で実行されるため、
標準のアセンブラーを提供できません。 ただし、 実行されるいくつかのアーキテクチャー用のアセンブラーは提供されています。 さらに言えば、 Gforth
ではシステムに依存しないアセンブラーを使用したり、 ,
や c,
を使用してマシン・コードを直接コンパイルしたりできます。
もう 1 つの問題は、Gforth の仮想マシンのレジスター(スタック・ポインターと仮想マシン命令ポインター)がインストールとエンジンに依存することです。 また、 どのレジスタを自由に使用できるかは、 インストールとエンジンによって異なります。 したがって、 Gforth 仮想マシンのコンテキストで実行するように記述されたコードは、 基本的に、 そのコードが開発されたインストールとエンジンに限定されます(たまたま、 他の場所でも動く可能性はありますが、 それに頼ることはできません)。
幸いなことに、 同じ呼び出し規約(ABI)を持つプラットフォーム上で実行されている Gforth に移植可能(portable)な
abi-code
ワードを Gforth で定義できます。 通常、 これは同じアーキテクチャーと OS の組み合わせへの移植性を意味し、
しばしば OS の境を越えることができます。
assembler
( – ) tools-ext “assembler”
ボキャブラリー: 検索順序スタック(the search order)のTOSのワードリストを assembler ワードリストに置き換えます。
init-asm
( – ) gforth-0.2 “init-asm”
assembler ワードリストを検索順序スタック(the search order)にプッシュします(訳注:つまり assembler ワードリストが検索順序スタックのTOSになる)。
abi-code
( "name" – colon-sys ) gforth-1.0 “abi-code”
C言語プロトタイプ(C-prototype)に対応するプラットフォームの ABI 規則を使用して呼び出されるネイティブ・コード定義を開始します:
Cell *function(Cell *sp, Float **fpp);
ここで、 FP スタック・ポインターは、 FP スタック・ポインターを含むメモリー位置への参照を提供することによって渡され、 (必要な場合)変更された FP スタック・ポインターをそこに格納することによって渡されます。
;abi-code
( – ) gforth-1.0 “semicolon-abi-code”
コロン定義を終了しますが、 実行時に最後に定義されたワード X (create
で作られたワードである必要があります)の変更もして、 C言語プロトタイプに対応するプラットフォームの ABI
規約を使用してネイティブ・コードを呼び出します:
Cell *function(Cell *sp, Float **fpp, Address body);
FP スタック・ポインターは、 FP スタック・ポインターを含むメモリー位置への参照を提供することによって渡され、 変更された FP スタック・ポインターをそこに格納することによって渡されます(必要な場合)。 パラメーター body は X の本体です。
end-code
( colon-sys – ) gforth-0.2 “end-code”
コード定義を終了します。 ABI 呼び出しからの戻り(abi-code
の場合)、 または次の VM
命令へのディスパッチ(code
および ;code
の場合)を自分でアセンブルする必要があることに注意してください。
code
( "name" – colon-sys ) tools-ext “code”
Gforth 仮想マシン(エンジン)のコンテキストで実行されるネイティブ・コード定義を開始します。 このような定義は Gforth
インストール間で移植できないため、 code
の代わりに abi-code
を使用することをお勧めします。
code
定義は、 次の仮想マシン命令へのディスパッチで終了する必要があります。
;code
( compilation. colon-sys1 – colon-sys2 ) tools-ext “semicolon-code”
;code
の後のコードが、 最後に定義されたワード(create
されたワードである必要があります)
の振る舞いになります。 code
の場合と同じ注意事項が適用されるため、 代わりに ;abi-code
を使用することをお勧めします。
flush-icache
( c-addr u – ) gforth-0.2 “flush-icache”
プロセッサの命令キャッシュ(存在する場合)の c-addr と u バイト以降に古いデータが含まれていないことを確認してください。
END-CODE
は flush-icache
を自動的に実行します。 注意(Caveat):
flush-icache
はあなたのインストール環境では機能しない可能性があります。 これは通常、
マシンでダイレクト・スレッドがサポートされておらず(machine.h を確認してください)、
マシンに別個の命令キャッシュがある場合に当てはまります。 このような場合、 flush-icache
は命令キャッシュをフラッシュする代わりに何も行いません。
flush-icache
が正しく動作しない場合、 abi-code
ワードなども(まず確実に)動作しません。
これらのワードの一般的な使用法は、 同等の高レベルの定義ワードから類推することで最も簡単に示すことができます:
: foo abi-code foo <high-level Forth words> <assembler> ; end-code : bar : bar <high-level Forth words> <high-level Forth words> CREATE CREATE <high-level Forth words> <high-level Forth words> DOES> ;code <high-level Forth words> <assembler> ; end-code
abi-code
を使用する場合は、 あなたのプラットフォームの ABI ドキュメントを参照して、
パラメーターがどのように渡されるか(スタック・ポインターをどこで取得するかがわかります)、
戻り値がどのように渡されるか(データ・スタック・ポインターがどこに返されるかがわかります)を確認してください。 ABI のドキュメントでは、
どのレジスターが呼び出し元によって保存されるか(caller-saved)や、 コード内で自由に破棄できるかや、
どのレジスターが呼び出されたワードによって保存される必要があるか(callee-saved)についても説明されています。
あなたはそれらを使用する前に保存し、 後で復元します。 一部のアーキテクチャーと OS
については、適切なセクションで呼び出し規約の各部分の短い概要を示します。 リバース・エンジニアリング志向の人々は、 see
abi-call
を通じてスタック・ポインターの受け渡しと戻りについて知ることもできます。
ほとんどの ABI はレジスターを介してパラメーターを渡しますが、 一部の ABI(特に最も一般的な 386 (別名 IA-32) 呼び出し規約) はアーキテクチャー・スタック上でパラメータを渡します。 共通の ABI はすべてレジスターで戻り値を渡します。
abi-code
を使用する際に知っておく必要があるその他のことは、 Gforth ではデータと FP
スタックの両方が下方向(下位アドレスに向かって)に成長し、 セルあたりのサイズは 1 cells
になり、 FP 値ごとのサイズは
1 floats
になるということです。
386 アーキテクチャーで abi-code
を使用する例を以下に示します:
abi-code my+ ( n1 n2 -- n ) 4 sp d) ax mov \ sp into return reg ax ) cx mov \ tos 4 # ax add \ update sp (pop) cx ax ) add \ sec = sec+tos ret \ return from my+ end-code
この例の AMD64 バリエーションは AMD64 (x86_64) Assembler にあります。
386 で FP 値を扱う例を以下に示します:
abi-code my-f+ ( r1 r2 -- r ) 8 sp d) cx mov \ load address of fp cx ) dx mov \ load fp .fl dx ) fld \ r2 8 # dx add \ update fp .fl dx ) fadd \ r1+r2 .fl dx ) fstp \ store r dx cx ) mov \ store new fp 4 sp d) ax mov \ sp into return reg ret \ return from my-f+ end-code
Gforth のアセンブラーは通常、 後置構文(postfix syntax)を使用します。 つまり、命令名がオペランドの後に続きます。
オペランドは通常の順序(アーキテクチャーのマニュアルで使用されている順序と同じ)で渡されます。 これらはすべて Forth ワードであるため、 スペースで区切る必要があります。 Forth ワードを使用してオペランドを計算することもできます。
通常、 命令名は ,
で終わります。 これにより、 複数の命令を 1 行に配置した場合に、 命令を視覚的に分離しやすくなります。 また、
他の Forth ワード (例: and
) が隠されてしまう(shadowing)のも回避します。
レジスターは通常、 番号で指定します。 たとえば、 (10 進数の) 11
は、 Alpha アーキテクチャー上のレジスター R11
および F11 を指定します(どちらかは命令によって異なります)。 通常の名前も使用できます(例: Alpha の R11 の s2
)。
制御フローは、 通常の Forth コード(see Arbitrary control structures)と同様に、 if,
,
ahead,
, then,
, begin,
, until,
, again,
,
cs-roll
, cs-pick
, else,
, while,
, repeat,
で指定します。 条件は各アセンブラーに固有の方法で指定されます。
このセクションの残りの部分は、 (移植性の高い abi-code
ワードではなく、)主に code
ワードを定義したい人にとって興味深いものです。
注意: Gforth エンジンのレジスター割り当ては、 Gforth バージョン間、 または同じ Gforth
バージョンの異なるコンパイル間でも変更される可能性があることに注意してください(たとえば、 異なる GCC バージョンを使用する場合)。
ABI-CODE
の代わりに CODE
を使用していて、 Gforth のレジスター (スタック・ポインターや TOS
など) を参照したい場合は、 これらのレジスターを参照するための独自のワードを定義して使用することをお勧めします。 そうすれば、
変更されたレジスター割り当てに適応できます。
これらのレジスターの最も一般的な使用法は、 code
定義を次のワードへのディスパッチ(next
ルーチン)で終了することです。 これを行う移植可能な方法は、' noop >code-address
にジャンプすることです(もちろん、
これは next
コードを統合して適切にスケジュールするほど効率的ではありません)。 ABI-CODE
を使用する場合は、
通常のサブルーチンの戻り値をアセンブルするだけです(ただし、 必ずデータ・スタック・ポインターを返すようにしてください)。
Gforth バージョン間のもう 1 つの違いは、 ほとんどのプラットフォームではスタックの最上位が gforth
のメモリーに保持され、
gforth-fast
ではレジスタに保持されることです。 ABI-CODE
定義の場合、
スタック・キャッシュ・レジスターは必ずスタックにフラッシュされるため、 メモリー内のスタックの先頭に確実にアクセスできます。
code
ワードは see
で逆アセンブルできます(see Debugging)。
以下を使用してメモリーのセクションを逆アセンブルできます
discode
( addr u – ) gforth-0.2 “discode”
逆アセンブラーのフック: addr から u バイトのコードを逆アセンブルします。
Gforth には 2 種類の逆アセンブラーがあります。 Forth 逆アセンブラー(一部の CPU で利用可能) と gdb
逆アセンブラー(gdb
および mktemp
を備えたプラットフォームで利用可能)です。
両方が使用可能な場合は、 Forth 逆アセンブラがデフォルトで使用されます。 gdb 逆アセンブラーを使いたい場合は、 以下のようにします
' disasm-gdb is discode
どちらも使用できない場合は、discode
が dump
を実行します。
Forth 逆アセンブラーは通常、 アセンブラーに入力できる出力(つまり、同じ構文など)を生成します。 コメントには追加情報も含まれます。 特に、 命令のアドレスは命令の前のコメントで示されます。
gdb 逆アセンブラーは、 デフォルトの形式(flavour)(386 および AMD64 アーキテクチャの AT&T 構文)で、 gdb
disassemble
コマンドと同じ形式で出力を生成します(see Source and
machine code in Debugging with GDB)。
see
は、 コードの終わりの認識が信頼できないため、 ワードの実際のコードより多めに表示したり少なめに表示したりする場合があります。
十分に表示されない場合は、 discode
を使用できます。 コード・ワードの直後に名前付きワードが続かない場合は、
さらに表示される可能性があります。 他に何かがある場合は、 ワードの後に align latest ,
を付けると、
末尾が認識されるようになります。
Gforth に含まれる 386 アセンブラーは Bernd Paysan によって書かれ、 GPL の下で入手可能で、 元々は bigFORTH の一部でした。
Gforth に含まれる 386 逆アセンブラーは Andrew McKewan によって作成され、 パブリック・ドメインです。
逆アセンブラーは、 Intel のようなプレフィックス構文(Intel-like prefix syntax)でコードを表示します。
アセンブラーは、 AT&T スタイルのパラメーター順序(つまり、宛先が最後;destination last)で後置構文(postfix syntax)を使用します。
アセンブラーには、 Athlon のすべての命令、 つまり 486 コア命令や、Pentium および PPro
拡張機能や、浮動小数点、MMX、3Dnow! が含まれていますが、 ISSE は含まれていません。 これは、16 ビットおよび 32
ビットの統合アセンブラーです。デフォルトは 32 ビットですが、 .86
で 16 ビットに切り替え、.386
で 32
ビットに戻すことができます。
異なる操作サイズを切り替えるためのプレフィックスがいくつかあります。 バイト・アクセスの場合は .b
、 ワード・アクセスの場合は
.w
、 ダブルワード・アクセスの場合は .d
です。 アドレッシング・モードは、 16 ビットアドレスの場合は
.wa
、32 ビットアドレスの場合は .da
で切り替えることができます。 (AL
など、
)バイト・レジスター名のプレフィックスは必要ありません 。
浮動小数点演算の場合、 プレフィックスは .fs
(IEEE single)、.fl
(IEEE
double)、.fx
(extended)、.fw
(word)、 .fd
(double-word)、
.fq
(quad-word)。 デフォルトは .fx
であるため、 Gforth FP 値を扱うときは
.fl
を明示的に指定する必要があります。
MMX オペコードにはサイズのプレフィックスはなく、 Intel アセンブラーと同じように記述されます。 メモリー間での移動の代わりに、 PLDQ/PLDD と PSTQ/PSTD があります。
レジスターには「e」接頭辞がありません。 32 ビット モードでも、 eax は ax と呼ばれます。 即値は、#
を接尾辞として付けることによって示されます(例: 3 #
)。 以下に、 さまざまな構文でのアドレス指定モードの例をいくつか示します:
Gforth Intel (NASM) AT&T (gas) Name .w ax ax %ax register (16 bit) ax eax %eax register (32 bit) 3 # offset 3 $3 immediate 1000 #) byte ptr 1000 1000 displacement bx ) [ebx] (%ebx) base 100 di d) 100[edi] 100(%edi) base+displacement 20 ax *4 i#) 20[eax*4] 20(,%eax,4) (index*scale)+displacement di ax *4 i) [edi][eax*4] (%edi,%eax,4) base+(index*scale) 4 bx cx di) 4[ebx][ecx] 4(%ebx,%ecx) base+index+displacement 12 sp ax *2 di) 12[esp][eax*2] 12(%esp,%eax,2) base+(index*scale)+displacement
D)
と DI)
の代わりに L)
と LI)
を使用して、32 ビット
displacement フィールドを強制できます(後のパッチ適用に役立ちます)。
いくつかの命令の例は以下のとおりです:
ax bx mov \ move ebx,eax 3 # ax mov \ mov eax,3 100 di d) ax mov \ mov eax,100[edi] 4 bx cx di) ax mov \ mov eax,4[ebx][ecx] .w ax bx mov \ mov bx,ax
バイナリ命令では以下の形式がサポートされています:
<reg> <reg> <inst> <n> # <reg> <inst> <mem> <reg> <inst> <reg> <mem> <inst> <n> # <mem> <inst>
シフト/ローテート の構文は以下のとおりです:
<reg/mem> 1 # shl \ shortens to shift without immediate <reg/mem> 4 # shl <reg/mem> cl shl
バイト・バージョンを取得するには、 (movs
などの)文字列命令(string instructions)の前に .b
を付けます。
制御構造ワードの IF
や UNTIL
などの前に、 次の条件のいずれかを指定する必要があります: vs vc
u< u>= 0= 0<> u<= u> 0< 0>= ps pc < >= <= >
(注意: code
ワードなど、 検索パス内で
assembler
が forth
の前にある場合、 これらのワードのほとんどは Forth
ワードの一部を隠してしまう事に注意してください)。 現在、 制御構造ワードは 1 つのスタック項目を使用しているため、 それらをいじるには
cs-roll
の代わりに roll
を使用する必要があります(swap
などを使用することもできます)。
Intel ABI (Linux で使用)に基づいて、abi-code
ワードは 4 sp d)
でデータ・スタック・ポインターを見つけ、 8 sp d)
で FP スタック・ポインターのアドレスを見つけることができます;
データ・スタック・ポインターは ax
で返されます。 Ax
や cx
や dx
は呼び出し元で保存されるため、 ワード内に値を保存する必要はありません。 ret
を使用してワードから戻ることができます。
パラメーターは呼び出し元によってクリーンアップされます。
386 の abi-code
ワードの例については、 Definitions in assembly language
AMD64 アセンブラーは、 386 アセンブラーをわずかに変更したバージョンであり、 構文の大部分を共有しています。 2 つの新しい接頭辞
.q
と .qa
が、 それぞれ 64 ビット・サイズのオペランドやアドレスを選択するために提供されています。 64
ビット・サイズがデフォルトであるため、 通常は他のプレフィックスを使用するだけで済みます。 また、 追加のレジスター・オペランド R8
~ R15
もあります。
レジスターには「e」または「r」プレフィックスがありません。 64 ビット・モードでも、 rax
は ax
と呼ばれます。
すべてのレジスターで最下位バイトを参照するために追加のレジスター・オペランドを使用できます: R8L
〜 -R15L
,
SPL
, BPL
, SIL
, DIL
Linux-AMD64 の呼び出し規則では、 最初の 6 つの整数パラメーターを rdi, rsi, rdx, rcx, r8, r9 で渡し、 結果を
rax , rdx で返します。 最初の 8 つの FP パラメータを xmm0 ~ xmm7 に渡し、 FP 結果を xmm0 ~ xmm1
に返します。 したがって、abi-code
ワードは、 di
でデータ・スタック・ポインターを取得し、 si
で FP スタック・ポインターのアドレスを取得し、 リターン時は ax
にデータ・スタック ポインターをセットします。
呼び出し元が保存する他のレジスターは、 r10, r11 xmm8 ~ xmm15 です。 この呼び出し規約は、 Microsoft 以外の他の OS
でも使用されていると報告されています。
Windows x64 は、 最初の 4 つの整数パラメーターを rcx, rdx, r8, r9 に渡し、 整数の結果を rax に返します。 他の、呼び出し元保存レジスターは r10 と r11 です。
https://uclibc.org/docs/psABI-x86_64.pdf の 21ページによると、 Linux プラットフォームでは、 レジスター AX CX DX SI DI R8 R9 R10 R11 が自由(scratch)に使えます。
AMD64 のアドレッシング・モードは以下のとおりです:
\ ご注意: ワード A を実行すると、レジスターが初期化されていないため、 メモリー・エラーが発生します ;-) ABI-CODE A ( -- ) 500 # AX MOV \ immediate DX AX MOV \ register 200 AX MOV \ direct addressing DX ) AX MOV \ indirect addressing 40 DX D) AX MOV \ base with displacement DX CX I) AX MOV \ scaled index DX CX *4 I) AX MOV \ scaled index 40 DX CX *4 DI) AX MOV \ scaled index with displacement DI AX MOV \ SP Out := SP in RET END-CODE
AMD64 abi-code
ワードの例をいくつか示します:
abi-code my+ ( n1 n2 -- n3 ) \ SP passed in di, returned in ax, address of FP passed in si 8 di d) ax lea \ compute new sp in result reg ( 結果として di+8 → ax つまり drop と同じ) di ) dx mov \ get old tos ( [di] つまり n2 → dx ) dx ax ) add \ add to new tos ( dx + [ax] → [ax] ret end-code
\ Do nothing ABI-CODE aNOP ( -- ) DI ) AX LEA \ SP out := SP in RET END-CODE
\ Drop TOS ABI-CODE aDROP ( n -- ) 8 DI D) AX LEA \ SPout := SPin - 1 RET END-CODE
\ Push 5 on the data stack ABI-CODE aFIVE ( -- 5 ) -8 DI D) AX LEA \ SPout := SPin + 1 5 # AX ) MOV \ TOS := 5 RET END-CODE
\ Push 10 and 20 into data stack ABI-CODE aTOS2 ( -- n n ) -16 DI D) AX LEA \ SPout := SPin + 2 10 # 8 AX D) MOV \ TOS - 1 := 10 20 # AX ) MOV \ TOS := 20 RET END-CODE
\ Get Time Stamp Counter as two 32 bit integers \ The TSC is incremented every CPU clock pulse ABI-CODE aRDTSC ( -- TSCl TSCh ) RDTSC \ DX:AX := TSC $FFFFFFFF # AX AND \ Clear upper 32 bit AX 0xFFFFFFFF # DX AND \ Clear upper 32 bit DX AX R8 MOV \ Tempory save AX -16 DI D) AX LEA \ SPout := SPin + 2 R8 8 AX D) MOV \ TOS-1 := saved AX = TSC low DX AX ) MOV \ TOS := Dx = TSC high RET END-CODE
\ Get Time Stamp Counter as 64 bit integer ABI-CODE RDTSC ( -- TSC ) RDTSC \ DX:AX := TSC $FFFFFFFF # AX AND \ Clear upper 32 bit AX 32 # DX SHL \ Move lower 32 bit DX to upper 32 bit AX DX OR \ Combine AX wit DX in DX -8 DI D) AX LEA \ SPout := SPin + 1 DX AX ) MOV \ TOS := DX RET END-CODE
VARIABLE V \ Assign 4 to variable V ABI-CODE V=4 ( -- ) BX PUSH \ Save BX, used by gforth V # BX MOV \ BX := address of V 4 # BX ) MOV \ Write 4 to V BX POP \ Restore BX DI ) AX LEA \ SPout := SPin RET END-CODE
VARIABLE V \ Assign 5 to variable V ABI-CODE V=5 ( -- ) V # CX MOV \ CX := address of V 5 # CX ) MOV \ Write 5 to V DI ) AX LEA \ SPout := SPin RET END-CODE
ABI-CODE TEST2 ( -- n n ) -16 DI D) AX LEA \ SPout := SPin + 2 5 # CX MOV \ CX := 5 5 # CX CMP 0= IF 1 # 8 AX D) MOV \ If CX = 5 then TOS - 1 := 1 <-- ELSE 2 # 8 AX D) MOV \ else TOS - 1 := 2 THEN 6 # CX CMP 0= IF 3 # AX ) MOV \ If CX = 6 then TOS := 3 ELSE 4 # AX ) MOV \ else TOS := 4 <-- THEN RET END-CODE
\ Do four loops. Expect : ( 4 3 2 1 -- ) ABI-CODE LOOP4 ( -- n n n n ) DI AX MOV \ SPout := SPin 4 # DX MOV \ DX := 4 loop counter BEGIN 8 # AX SUB \ SP := SP + 1 DX AX ) MOV \ TOS := DX 1 # DX SUB \ DX := DX - 1 0= UNTIL RET END-CODE
以下は、FP 値を扱う AMD64 用の例です:
abi-code my-f+ ( r1 r2 -- r ) \ SP passed in di, returned in ax, address of FP passed in si si ) dx mov \ load fp 8 dx d) xmm0 movsd \ r2 dx ) xmm0 addsd \ r1+r2 xmm0 8 dx d) movsd \ store r 8 # si ) add \ update fp di ax mov \ sp into return reg ret end-code
Alpha アセンブラーと逆アセンブラーは、もともとは Bernd Thallner によって書かれました。
レジスター名 a0
〜a5
は、 16 進数を隠してしまう(shadowing)のを避けるために使用できません。
算術命令の即時形式は、 ,
の直前の #
によって区別されます(例: and#,
)(注:
lda,
は算術命令としてカウントされません)。
他のアセンブラーがオプションとみなすオペランドも含めて、 命令にすべてのオペランドを指定する必要があります。 たとえば、br,
の宛先レジスター、 または jmp,
の宛先レジスターとヒントです。
if,
の条件を指定するには、 対応する名前のブランチから最初の b
と末尾の ,
を削除します。
たとえば、以下のようにします:
11 fgt if, \ if F11>0e ... endif,
fbgt,
は fgt
になります。
MIPS アセンブラーは、もともとは Christian Pirker によって書かれました。
現在、 アセンブラーと逆アセンブラーは MIPS32 アーキテクチャーの大部分をカバーしていますが、 FP 命令はサポートしていません。
レジスター名 $a0
〜$a3
は、 16 進数を隠してしまう(shadowing)のを避けるために使用できません。
代わりにレジスター番号 $4
〜$7
を使用してください。
レジスターと即値を区別するものはありません。 即値引数を持つ命令には、 i
接尾辞を付けた明示的なオペコード名を使用します。 例えば。
addu,
の代わりに addiu,
です。
アーキテクチャー・マニュアルで命令の形式が複数指定されている場合(例: jalr,
の場合)、より多くの引数を持つ形式(つまり、jalr,
の場合は 2 つ)を使用してください。 疑問がある場合は、 正しい使用例について
arch/mips/testasm.fs
を参照してください。
MIPS アーキテクチャーの分岐とジャンプには遅延スロット(delay slot)があります。 手動で入力する必要があります(最も簡単な方法は
nop,
を使用することです)。 アセンブラーは(as
とは異なり)自動的に入力しません。 if,
,
ahead,
, until,
, again,
, while,
, else,
,
repeat,
でも遅延スロットが必要です。 begin,
と then,
は分岐ターゲットを指定するだけなので影響を受けません。 ブランチの場合、 ターゲットを指定する引数は相対アドレスです。
遅延スロットのアドレスを追加して絶対アドレスを取得します。
注意: 遅延スロットに分岐やジャンプ(または制御フロー命令)を入れてはいけないことに注意してください。 また、li,
などの疑似演算を遅延スロットに入れることは、 複数の命令に拡張される可能性があるため、お勧めできません。 MIPS I
アーキテクチャーにもロード遅延スロットがあり、 新しい MIPS では依然として mfhi,
および mflo,
の使用に制限があります。 これらの制限を満たすように注意してください。 アセンブラーが自動的に制限を満たしてくれるわけではありません。
いくつかの命令の例は以下のとおりです:
$ra 12 $sp sw, \ sw ra,12(sp) $4 8 $s0 lw, \ lw a0,8(s0) $v0 $0 lui, \ lui v0,0x0 $s0 $s4 $12 addiu, \ addiu s0,s4,0x12 $s0 $s4 $4 addu, \ addu s0,s4,$a0 $ra $t9 jalr, \ jalr t9
if,
などの条件を指定するには、 条件分岐を実行し、 先頭の b
と末尾の ,
を省略します。
例えば以下のようにします:
4 5 eq if, ... \ do something if $4 equals $5 then,
32 ビット MIPS マシンの呼び出し規則では、 最初の 4 つの引数をレジスター $4
〜$7
に渡し、 戻り値に
$v0
〜$v1
を使用します。 これらのレジスターに加えて、 レジスター $t0
~ $t8
は保存・復元せずに上書きしても問題ありません。
jalr,
を使用してダイナミック・ライブラリー・ルーチンを呼び出す場合は、 最初に呼び出される関数のアドレスを $t9
にロードする必要があります。 これは、 相対メモリー・アクセスを行うために位置間接コード(position-indirect
code)によって使用されます。
MIPS32 abi-code
ワードの例を以下に示します:
abi-code my+ ( n1 n2 -- n3 ) \ SP passed in $4, returned in $v0 $t0 4 $4 lw, \ load n1, n2 from stack $t1 0 $4 lw, $t0 $t0 $t1 addu, \ add n1+n2, result in $t0 $t0 4 $4 sw, \ store result (overwriting n1) $ra jr, \ return to caller $v0 $4 4 addiu, \ (delay slot) return uptated SP in $v0 end-code
PowerPC アセンブラーと逆アセンブラーは、 Michal Revucky の貢献に依ります。
このアセンブラーは、 ニーモニック名を「,」で終了する規則に従っていないため、 一部のニーモニック名は通常の Forth ワード (特に:
and or xor fabs
) を隠し(shadow)ます。 そのため、 Forth ワードを使用したい場合は、 最初に、 たとえば
also forward
を使用して、 Forth ワードを表示できるようにする必要があります。
レジスターは番号によって参照されます。 たとえば、9
は、 (命令に応じて、)整数レジスター 9 または FP レジスター 9
を意味します。
レジスターと即値を区別する方法がないため、 add,
だけでなく add,
などの即値用の命令を明示的に使用する必要があります。
アセンブラーと逆アセンブラーは通常、 最も一般的な形式の命令をサポートしますが、 通常は短い形式(特に分岐)はサポートしません。
ARM アセンブラーには、 ARM アーキテクチャー・バージョン 4 のすべての命令と、 アーキテクチャー・バージョン 5 の BLX 命令が含まれています。 Thumb 命令は (まだ) サポートされていません。 また、 コ・プロセッサーもサポートされていません。
アセンブラーは、 「ARM Architecture Reference Manual」で使用されているのと同じオペランド順序を持つ後置構文(postfix syntax)を使用します。 ニーモニックにはカンマが付けられます。
レジスターは r0
〜r15
までの名前で指定され、別名は pc
, lr
, sp
,
ip
, fp
で、これらは利便性のために提供されています。 ip
は「プロシージャ内呼び出しスクラッチ・レジスター」(intra procedure call scratch register)(注意:
r12
) を指し、 命令ポインターを指すわけではないことに注意してください。 sp
は、 Forth
のスタック・ポインターではなく、 ARM ABI スタック・ポインター (r13
) を指します。
条件コードは命令内のどこにでも指定できますが、 ニーモニックの直前に指定すると最も読みやすくなります。 「S」フラグは別個のワードではなく、
命令ニーモニックにエンコードされます。 ステータス・レジスターを更新したい場合は、 add,
の代わりに adds,
を使用してください。
以下の表に、 一般的な命令のオペランドの構文を示します:
Gforth normal assembler description 123 # #123 immediate r12 r12 register r12 4 #LSL r12, LSL #4 shift left by immediate r12 r1 LSL r12, LSL r1 shift left by register r12 4 #LSR r12, LSR #4 shift right by immediate r12 r1 LSR r12, LSR r1 shift right by register r12 4 #ASR r12, ASR #4 arithmetic shift right r12 r1 ASR r12, ASR r1 ... by register r12 4 #ROR r12, ROR #4 rotate right by immediate r12 r1 ROR r12, ROR r1 ... by register r12 RRX r12, RRX rotate right with extend by 1
メモリー・オペランドの構文を以下の表に示します:
Gforth normal assembler description r4 ] [r4] register r4 4 #] [r4, #+4] register with immediate offset r4 -4 #] [r4, #-4] with negative offset r4 r1 +] [r4, +r1] register with register offset r4 r1 -] [r4, -r1] with negated register offset r4 r1 2 #LSL -] [r4, -r1, LSL #2] with negated and shifted offset r4 4 #]! [r4, #+4]! immediate preincrement r4 r1 +]! [r4, +r1]! register preincrement r4 r1 -]! [r4, +r1]! register predecrement r4 r1 2 #LSL +]! [r4, +r1, LSL #2]! shifted preincrement r4 -4 ]# [r4], #-4 immediate postdecrement r4 r1 ]+ [r4], r1 register postincrement r4 r1 ]- [r4], -r1 register postdecrement r4 r1 2 #LSL ]- [r4], -r1, LSL #2 shifted postdecrement ' xyz >body [#] xyz PC-relative addressing
複数のロード/ストア命令のレジスター・リストは、 それぞれ {
および }
というワードを使用して開始および終了します。
{
〜 }
の間に、 レジスター名を 1 つずつリストすることも、 後置演算子 r-r
を使用してレジスター範囲を形成することもできます。 ^
フラグはレジスター・リスト・オペランドではエンコードされませんが、
命令ニーモニックに直接エンコードされます。 つまり、 ^ldm,
と ^stm,
を使用します。
複数のロード/ストアのアドレッシング・モードは命令接尾辞としてエンコードされず、 代わりにアドレッシング・モードのように指定されます。
DA
, IA
, DB
, IB
, DA!
, IA!
,
DB!
, IB!
のいずれかを使います。
以下の表にいくつかの例を示します:
Gforth normal assembler r4 ia { r0 r7 r8 } stm, stmia r4, {r0,r7,r8} r4 db! { r0 r7 r8 } ldm, ldmdb r4!, {r0,r7,r8} sp ia! { r0 r15 r-r } ^ldm, ldmfd sp!, {r0-r15}^
Forth アセンブラーに典型的な制御構造ワードが利用可能です: if,
, ahead,
, then,
,
else,
, begin,
, until,
, again,
, while,
,
repeat,
, repeat-until,
。 条件は以下のワードの前に指定します:
r1 r2 cmp, \ compare r1 and r2 eq if, \ equal? ... \ code executed if r1 == r2 then,
ARM アセンブラーを使用した定義の例:
abi-code my+ ( n1 n2 -- n3 ) \ arm abi: r0=SP, r1=&FP, r2,r3,r12 saved by caller r0 IA! { r2 r3 } ldm, \ pop r2 = n2, r3 = n1 r3 r2 r3 add, \ r3 = n1+n1 r3 r0 -4 #]! str, \ push r3 pc lr mov, \ return to caller, new SP in r0 end-code
別のアセンブラー/逆アセンブラーを提供したい場合は、 そのようなアセンブラーがすでに存在するかどうかを確認するために著者達(anton@mips.complang.tuwien.ac.at)までご連絡ください。 これらを最初から作成する場合は、 著者たちが使用しているものと同様の構文スタイルを使用してください(つまり、 後置や命令名の末尾のカンマ see Common Assembler)。 逆アセンブラーの出力をアセンブラーの有効な入力にし、使用したスタイルと同様のスタイルを維持します。
実装に関するヒント: 最も重要なのは、 すべての手順を含む優れたテスト・スイートを用意することです。 それができたら、 あとは簡単です。 実際のコーディングについては、 arch/mips/disasm.fs を参照して、 アセンブラーと逆アセンブラーの両方でデータを使用し、 冗長性や潜在的なバグを回避する方法についてアイデアを得ることができます。 また、そのファイル (および see Advanced does> usage example) を見て、 逆アセンブラーをファクタリングする方法のアイデアを得ることができます。
逆アセンブラーから始めます。 逆アセンブラーからのデータをアセンブラーで再利用する方が、 その逆よりも簡単だからです。
アセンブラーについては、 arch/alpha/asm.fs を見てください。 これがいかに簡単であるかを示しています。
これらのワードは Gforth の仕組み(Forth サークルでは Forth システムの「肉体的知識」(carnal knowledge)と呼ばれる)を扱っていますが、 著者等は文書化するに当たって、 これらが十分安定(stable)していると考えています。
Gforth 1.0 では、 新しいワード・ヘッダー・レイアウトに切り替えられました。 詳細な説明については、 Bernd Paysan and M. Anton Ertl. The new Gforth header を参照してください。 この論文の公開以後に、xt と nt は本体(body)のようにパラメーター・フィールドを指すように変更されましたが、 それ以外は依然としてこの文献が最新です。
このセクションでは、 データ構造とそれにアクセスするために使用されるワードについてのみ説明します。 ヘッダーには以下のフィールドがあります:
name >f+c >link >cfa >namehm >body
現在、 Gforth には xt/nt/body から各フィールドに到達するために上に示した名前がありますが、 標準の >body
を除けば、 これらは定着した Gforth ワードではありません。 これらの代わりアクセス用ワードを提供しています。
注意:文書化されたアクセス・ワードはヘッダー・レイアウトの再編成後も生き残ることに注意してください。
ワードの中には nt を期待するものもあれば、 xt を期待するものもあります。 nt と xt が両方ともワードの本体(body)を指しているとすると、 その違いは何でしょうか? ほとんどのワードでは、xt と nt は同一ヘッダーを使用するので、 nt=xt となり、 同じ場所を指します。 ただし、 同義語(synonym)(see Aliases)では違いがあります。 以下の例で考えてみましょう
create x synonym y x synonym z y
この場合、z
の nt は z
の本体(body)を指し、 z
の xt は x
の本体(body)を指します。 alias
または forward
(see Forward)
で定義されたワードも、 nt と異なる xt を持ちます。
名前フィールドは可変長で、 name>string
(see Name token) でアクセスします。
>f+c
フィールドには、フラグと名前の長さ(カウント)が含まれます。 name>string
でカウントを読み取り、
以下でフラグを読み取ります。
compile-only?
( nt – flag ) gforth-1.0 “compile-only?”
nt がコンパイル専用(compile-only)としてマークされている場合は true 。
>link
フィールドには、 同じワードリスト内の前のワードへのリンクが含まれます。 name>link
(see Name token) で読み取ることができます。
name と >f+c
と >link
フィールドは noname
ワードには存在しませんが、 それでも
name>string
と name>link
は機能し、 name>string
は 0 0 を返し、
name>link
は 0 を返します。
>cfa
フィールド(別名 コード・フィールド) には、 ワードを execute
するために使用されるコード・アドレスが含まれます。 >code-address
で読み取り、 code-address!
(see Threading Words) で書き込むことができます。
>namehm
フィールドには、 後述するするヘッダー・メソッド・テーブルのアドレスが含まれます。 あなたがこれにアクセスするには、
ヘッダー・メソッド (see Header methods) の実行によるか、 または、 ヘッダー・メソッドへのアクセスによります。
>body
(別名 パラメーター・フィールド)には、 ワードの種類に固有のデータまたはスレッド化コードが含まれます。
その長さはワードの種類によって異なります。 たとえば、 constant
の場合、 定数の値を含むセルが含まれます。
>body
(see The gory details of CREATE..DOES>
) を通じてアクセスできますが、 これは標準の
create
で定義したワードのみです。
新しい Gforth ワード・ヘッダーはオブジェクト指向であり、 以下のメソッド(メソッド・セレクター)をサポートします:
.hm label method overrider field execute set-execute >cfa opt: opt-compile, set-optimizer >hmcompile, to: (to) set-to >hmto extra: >hmextra >int: name>interpret set->int >hm>int >comp: name>compile set->comp >hm>comp >string: name>string set-name>string >hm>string >link: name>link set-name>link >hm>link
これらのワードの多くは定着(stable)した Gforth ワードではありませんが、 Gforth には後述する定着した高レベルのワードがあります。
以下を使用すると、 ワードのヘッダー・メソッドを確認できます
.hm
( nt – ) gforth-1.0 “dot-h-m”
nt のヘッダー・メソッドを出力します
オーバーライダー(overrider)(セッター)ワードは、 最新の定義のメソッド実装を変更します。 クォーテーションまたはクロージャーは、 完了時に以前の最新の定義を復元するため、 最新のものとはみなされず、 以下のようなことができます:
: my2dup over over ; [: drop ]] over over [[ ;] set-optimizer
execute
メソッドは、 パフォーマンス上の理由から、 実際にはヘッダー・メソッド・テーブルではなくヘッダーの >cfa
フィールドに格納されます。 また、 他のメソッドは xt を呼び出すことによって実装されますが、 ネイティブ・コード・アドレスを通じて実装されます。
このメソッドを設定する大まかな方法は以下のとおりです
set-execute
( ca – ) gforth-1.0 “set-execute”
ca のネイティブ・コードにジャンプするように現在のワードを変更します。 また、 compile,
実装を最も一般的な(そして最も遅い)実装に変更します。 より効率的な compile,
実装が必要な場合は、 後で
set-optimizer
を呼び出します。
set-execute
で使用するコード・アドレスを取得するには、 docol:
または
>code-address
などのワードを使用できます。 See Threading Words
set-execute
の代わりに、 xt を受け取る set-does>
(see User-defined Defining Words)もあります。
さらに、 低レベルの code-address!
と definer!
があります。 (see Threading Words)
opt-compile,
メソッドは、 ほとんどの Gforth エンジンで動く compile,
です(gforth-itc
は代わりに ,
を使用します)。 set-optimizer
を使用して、
現在のワードに対する compile,
のより効率的な実装を定義できます((see User-defined compile,
))。 注意: 最終結果は postpone literal postpone execute
と同等でなければならないことに注意してください。
set-optimizer
の使用例として、 以下の constant
の定義を考えてみましょう:
: constant ( n "name" -- ; name: -- n ) create , ['] @ set-does> ; 5 constant five : foo five ; see foo
Forth システムは、 定数の値を変更してはならないことを認識せず、 単に create
されたワード (>body
で変更可能)として見て、 foo
は最初に five
のボティのアドレスをスタックにプッシュし、
その次にそこから値を取得します。 set-optimizer
を使用すると、 constant
の定義を以下のように最適化できます:
: constant ( n "name" -- ; name: -- n ) create , ['] @ set-does> [: >body @ postpone literal ;] set-optimizer ;
いまや、 foo
には、 five
の呼び出しではなく、 即値(literal)の 5 が含まれるようになりました。
注意: set-execute
と set-does>
は、execute
と compile,
が一致していることを確認するために、 set-optimizer
自体を実行することに注意してください。
あなた独自のオプティマイザーを追加するには、 後で追加する必要があります。
defer!
(別名 (to)
メソッド(see User-defined to
and defer@
)は、
defer
で定義されたワードおよび類似のワードに対して defer!
を実装します。 ですが、 これは to
の核心(core)でもあります。 defer!
/(to)
メソッドの一般的なスタック効果は ( val xt
-- )
です。 ここで xt は格納されているワードを示し、 val はそこに格納されている(適切な型の)値です。
たとえば、 以下のように fvalue
を実装できます:
: fvalue-to ( r xt -- ) >body f! ; : fvalue ( r -- ) create f, ['] f@ set-does> ['] fvalue-to set-to ; 5e fvalue foo : bar foo 1e f+ to foo ; see bar
set-optimizer
を使用して、 生成されたコードを改善できます:
: compile-fvalue-to ( xt-value-to -- ) drop ]] >body f! [[ ; : fvalue-to ( r xt -- ) >body f! ; ' compile-fvalue-to set-optimizer : fvalue ( r -- ) create f, ['] f@ set-does> [: >body ]] literal f@ [[ ;] set-optimizer ['] fvalue-to set-to ; 5e fvalue foo : bar foo 1e f+ to foo ; see bar
実際には、 Gforth には、 +TO
など、 実装するための追加の工夫がいくつかあります。
Set-defer@
(see User-defined to
and defer@
) を使用すると、
defer
のようなワードに対して defer@
(see Deferred Words)
メソッドのバリエーションを実装できます。
>hmextra
フィールドは、 追加のデータをヘッダー・メソッド・テーブルに保存する必要がある場合に使用されます。 特に、 それは
set-does>
に渡す xt を保存し(そして does>
は set-does>
を呼び出し)、
そして、 ;abi-code
の後のコードのアドレスを保存します。
これらのメソッドはすべて、 nt ではなく xt を使用しますが、 オーバーライド・ワード(override words)は最新の定義で機能します。
これは、 たとえば、 同義語(synonym)に対して set-optimizer
を使用した場合、
その効果はおそらくあなたが意図したものとは異なることを意味します。 ワードの xt を compile,
する場合、
新たに設定された同義語(synonym)ではなく、 元のワードの opt-compile,
実装が使用されます。
以下のメソッド達は nt を消費します。
name>interpret
メソッドは、 同義語(synonym)や類似のワードを除くほとんどのワードに対して noop
として実装されます。
set->int
( xt – ) gforth-1.0 “set-to-int”
現在のワードの name>interpret (nt -- xt2 )
メソッドの実装を xt に設定します。
name>compile
メソッドは、 nt のコンパイル機能(compilation semantics)を生成します。
set->comp
で変更することでコンパイル機能を変更できますが、 name>compile
のスタック効果のため、
目的のコンパイル機能の xt をプッシュするだけというほど単純ではありません。 一般に、 コンパイル機能の変更は避ける必要があり、 変更する場合は、
immediate
または interpret/compile:
などの高レベル・ワードを使用してください(See Combined Words)。
set->comp
( xt – ) gforth-1.0 “set-to-comp”
現在のワードの name>compile ( nt -- w xt2 )
メソッドの実装を xt に設定します。
immediate?
( nt – flag ) gforth-1.0 “immediate?”
ワード nt がデフォルト以外のコンパイル機能(compilation semantics)を持っている場合は true (これは即時性(immediacy)の定義と完全には一致しませんが、 多くの人はワード語を「即時」(immediate)と呼ぶときはこれを意味しています)。
Name>string
および Name>link
は、 noname ヘッダーから name と >f+c
と link
フィールドを削除できるようにするためのメソッドです。 これらのワードを使用すると意味のある結果が得られます。 通常、
noname
を使用する場合を除き、 これらのメソッドの実装を変更することはありませんが、
あなたがそれでも必要とするなら以下をご覧ください
set-name>string
( xt – ) gforth-1.0 “set-name-to-string”
現在のワードの name>string ( nt -- addr u )
メソッドの実装を xt に設定します。
set-name>link
( xt – ) gforth-1.0 “set-name-to-link”
現在のワードの name>link (nt1 -- nt2|0 )
メソッドの実装を xt に設定します。
ここで使用される用語は、 間接スレッド Forth システム(indirect threaded Forth systems)に由来しています。
間接スレッド Forth システムでは、 ワードの XT はワードの CFA (コード・フィールド・アドレス) によって表されます。 CFA は、
コード・アドレスを含むセルを指します。 コード・アドレスとは、 ワードを呼び出す実行時のアクションを実行するマシン・コードのアドレスです(たとえば、
dovar:
ルーチンは、 ワード(変数)の本体(body)のアドレスをスタックにプッシュします)。
以下のワード群は、 Gforth のコード・フィールドやコード・アドレスやその他のスレッド機能へのアクセスを提供します。 これにより、直接スレッドと間接スレッドの違いは、 多かれ少なかれ抽象化されます。
Gforth 0.7 までは、 ワードの種類を知るのに、 コード・アドレス(さらに、 does>
で定義されたワードの場合は、
>does-code
によって返されるアドレス)で十分でした。 ただし、 Gforth-1.0 以降、 少なくとも、
compile,
や name>compile
のようなワードの振る舞いや実装は、 Header methods
で説明されているように独立して決定できます。
以下のワード達は、 コード・フィールドを作成し、 そして、 同時にヘッダー・メソッドを初期化します:
hmcopy,
( xt – ) gforth-experimental “hmcopy-comma”
ヘッダーの構築中に、 コード・フィールドを割り当て、 コード・フィールドとヘッダー・メソッドを設定するためのプロトタイプとして xt を使用します。
docol,
( – ) gforth-1.0 “docol,”
コロン定義のコード・アドレスを書き込む。
docon,
( – ) gforth-1.0 “docon,”
CONSTANT
のコード・アドレスを書き込む。
dovar,
( – ) gforth-1.0 “dovar,”
CREATE
されたワードのコード・アドレスを書き込む。
douser,
( – ) gforth-1.0 “douser,”
USER
変数のコード・アドレスを書き込む。
dodefer,
( – ) gforth-1.0 “dodefer,”
defer
されたワードのコード・アドレスを書き込む。
dofield,
( – ) gforth-1.0 “dofield,”
field
のコード・アドレスを書き込む。
dovalue,
( – ) gforth-1.0 “dovalue,”
CONSTANT
のコード・アドレスを書き込む。
doabicode,
( – ) gforth-1.0 “doabicode,”
ABI-CODE
定義のコード・アドレスを書き込む。
does>
で定義されたワードの場合、 hmcopy,
を使用してください。
または、 create-from
のような高レベルのワードを使用します(see Creating from a prototype)。
以下のワード群はヘッダー・メソッドが導入される前に設計されたものであるため、 Gforth でさまざまなワード・タイプを処理する最良の(推奨される)方法ではありません。
間接スレッド Forth では、 ' name @
を使用して name のコード・アドレスを取得できます。
Gforth では、 スレッド化方式とは関係なく、 ' name >code-address
で取得できます。
threading-method
( – n ) gforth-0.2 “threading-method”
エンジンが直接スレッドの場合は 0。 注意: これはイメージの存続期間中(lifetime)に変更される可能性があることに注意してください。
>code-address
( xt – c_addr ) gforth-0.2 “>code-address”
c-addr は、 ワード xt のコード・アドレスです。
code-address!
( c_addr xt – ) gforth-obsolete “code-address!”
コード・アドレス c-addr のコード・フィールドを xt に変更します。
さまざまな定義ワードによって生成されるコード・アドレスは、 以下のワード群によって生成されます:
docol:
( – addr ) gforth-0.2 “docol:”
コロン定義のコード・アドレスを書き込む。
docon:
( – addr ) gforth-0.2 “docon:”
CONSTANT
のコード・アドレスを書き込む。
dovar:
( – addr ) gforth-0.2 “dovar:”
CREATE
されたワードのコード・アドレスを書き込む。
douser:
( – addr ) gforth-0.2 “douser:”
USER
変数のコード・アドレスを書き込む。
dodefer:
( – addr ) gforth-0.2 “dodefer:”
defer
されたワードのコード・アドレスを書き込む。
dofield:
( – addr ) gforth-0.2 “dofield:”
field
のコード・アドレスを書き込む。
dovalue:
( – addr ) gforth-0.7 “dovalue:”
CONSTANT
のコード・アドレスを書き込む。
dodoes:
( – addr ) gforth-0.6 “dodoes:”
DOES>
で定義されたワードのコード・アドレス。
doabicode:
( – addr ) gforth-1.0 “doabicode:”
ABI-CODE
定義のコード・アドレスを書き込む。
set-does>
で定義されたワード X の場合、 コード・アドレスは dodoes:
を指し、 そして、
ヘッダー・メソッドの >hmextra
フィールドには、 X の本体(body)アドレスをプッシュした後に呼び出されるワードの
xt が含まれます。
ワードが DOES>
で定義されたワードであるかどうか、 そしてそのワードが execute する Forth
コードを知りたい場合は、>does-code
で以下のことがわかります:
>does-code
( xt1 – xt2 ) gforth-0.2 “>does-code”
xt1 が set-does>
で定義されたワードの子の実行トークンである場合、 xt2 は
set-does>
に渡される xt 、 つまり xt1 実行時に実行されるワードの xt です(ただし、 最初に xt1
の本体(body)アドレスがプッシュされます)。 xt1 が set-does>
で定義されたワードに属していない場合、
xt2 は 0 です。
結果の xt2 を set-does>
(推奨) とともに使用して、 もっとも最新のワードを変更するか以下を使用して変更できます
does-code!
( xt2 xt1 – ) gforth-0.2 “does-code!”
xt1 を xt2 set-does>
で定義されたワードに変更します。
任意のワードを変更するためには…
以下の 2 つのワードは、 >code-address
や >does-code
や
code-address!
や does-code!
を一般化します:
>definer
( xt – definer ) gforth-0.2 “>definer”
definer (definer;定義者)は、 xt の定義方法を示す一意の識別子です。 異なる does>
コードで定義されたワードには、 異なる definer が存在します。 definer は比較や definer!
で使用できます。
definer!
( definer xt – ) gforth-obsolete “definer!”
xt で表されるワードは、 その振る舞いを definer に関連付けられた振る舞いに変更します。
Code-address!
や does-code!
や definer!
は、
opt-compile,
メソッドをそのワード型用のやや汎用的なコンパイラーに更新します(特に、 プリミティブに対しては、
プリミティブ固有の peephole-compile,
(peephole-compile;のぞき穴最適化コンパイル)ではなく、 遅い
general-compile,
(一般コンパイル)メソッドが使用されます)。
Gforth を使用すると、 (そのようなものが存在する場合、)ホスト・オペレーティング・システムのシェルで実行するための任意の文字列をシェルに渡すことができます。
sh
( "..." – ) gforth-0.2 “sh”
行(コマンド・ライン)の残りの部分をシェルコマンドとして実行します。 その後、 ワード $?
によってコマンドの終了ステータスを取得します。
system
( c-addr u – ) gforth-0.2 “system”
c-addr u で指定された文字列をホスト・オペレーティング・システムに渡し、 サブ・シェルで実行します。 その後、 ワード
$?
によってコマンドの終了ステータスが生成されます。 環境変数 GFORTHSYSTEMPREFIX
の値
(またはそのデフォルト値) が文字列の先頭に付加されます(主に、 Cygwin がデフォルトで使用するシェルではなく、 Windows のシェルとして
command.com
の使用をサポートするためです。)。 see Environment variables
sh-get
( c-addr u – c-addr2 u2 ) gforth-1.0 “sh-get”
シェル・コマンド addr u を実行します。 c-addr2 u2 はコマンドの出力です。 終了コードは $?
にあります。 コマンドの出力は sh$ 2@
にもあります。
$?
( – n ) gforth-0.2 “dollar-question”
Value – 最後に実行された system
コマンドによって返された終了ステータス。
getenv
( c-addr1 u1 – c-addr2 u2 ) gforth-0.2 “getenv”
文字列 c-addr1 u1 は環境変数名を指定します。 文字列 c-addr2 u2 は、 ホスト・オペレーティング・システムによる、 その環境変数の展開結果です。 環境変数が存在しない場合、 c-addr2 u2 は長さ 0 文字の文字列を返します(訳注: 存在しない:c-addr2,u2 = 0, 0 存在するけど中身が空: 【有効なアドレス】, 0)
ms
( n – ) facility-ext “ms”
\ 訳注: 指定のミリ秒ウエイトします(エポックでカウントします)。
ns
( d – ) gforth-1.0 “ns”
\ 訳注: 指定のナノ秒ウエイトします(エポックでカウントします)
time&date
( – nsec nmin nhour nday nmonth nyear ) facility-ext “time-and-date”
現在の時刻を報告します。 nsec 、 nmin、 nhour は 0 から数えます。 nmonth は 1 から数えます。
>time&date&tz
( udtime – nsec nmin nhour nday nmonth nyear fdst ndstoff c-addrtz utz ) gforth-1.0 “to-time-and-date”
1970 年 1 月 1 日 0:00Z からの時間を秒単位で現在の時刻に変換します。 nsec、 nmin、 nhourは 0 から数えます。
nmonth は 1 から数えます(訳注: 使い方: 秒単位の値を与える必要があるので、 例えば utime #1000000 ud/mod
rot drop >time&date&tz
とする。 fdst: 夏時間を 採用しているときに負数 、採用していないときに
0、この情報が得られないときに正数。 ndstoff : GMTからのオフセット(秒単位)、 c-addrtz utz : タイムゾーン文字列(例:
JST))
utime
( – dtime ) gforth-0.5 “utime”
エポック(some epoch)以降の現在時刻をマイクロ秒単位で報告します。 #1000000 um/mod nip
を使用して秒に変換します
ntime
( – dtime ) gforth-1.0 “ntime”
エポック(some epoch)以降の現在時刻をナノ秒単位で報告します。
cputime
( – duser dsystem ) gforth-0.5 “cputime”
duser と dsystem は、 Forth システムの開始以降に使用されたユーザー・レベルとシステム・レベルの CPU 時間(子プロセスを除く)をマイクロ秒単位で表したものです(ただし、 粒度はさらに粗くなる可能性があります)。 getrusage コールのないプラットフォームでは、 duser については経過時間(エポック)が報告され、 dsystem については 0 が報告されます。
このセクションでは、 このマニュアルの他の場所で説明されていない標準 Forth のワードをリストします。 いつかは、 これらのワードはそれぞれ適切なセクションに配置されることになるでしょう。
quit
( ?? – ?? ) core “quit”
リターン・スタックを空にし、 ユーザー入力デバイスを入力ソースにして、 インタプリタ状態にして、 テキスト・ インタープリターを開始します。
以下の標準 Forth のワードは現在、 Gforth ではサポートされていません(see Standard conformance):
EDITOR
EMIT?
FORGET
(訳注: forth で書かれたラインエディタである EDITOR
ボキャブラリーはありません。 FORGET はありません。任意の箇所で「忘れる」ことはできません。代わりに marker name
で忘れるポイントを「マーク」しておきます。 name を実行するとそのポイントまで「忘れ」ます。)
Gforth の典型的なエラー・メッセージは以下のようになります:
in file included from \evaluated string/:-1 in file included from ./yyy.fs:1 ./xxx.fs:4: Invalid memory address >>>bar<<< Backtrace: $400E664C @ $400E6664 foo
エラーを特定するメッセージは Invalid memory address
です。 そのエラーはファイル ./xxx.fs
の 4 行目をテキスト解釈(text-interpreting)しているときに発生しました。 その行のエラーが発生したワードを(>>>
と
<<<
で囲んで)指摘します。
エラーを含むファイルは ./yyy.fs の 1 行目でインクルード(included)されており、 yyy.fs はファイル以外からインクルードされています(この場合、 yyy.fs を Gforth へのコマンドライン パラメーターとして指定することにより)。
エラー・メッセージの最後には、 バックトレースとして解釈できるリターン・スタック・ダンプが表示されます(空の可能性があります)。 その一番上の行には
throw
が発生したときのリターン・スタックのTOSが表示され、
その一番下の行には最上位のテキスト・インタープリターのリターン・スタックのすぐ上にあるリターン・スタック・エントリが表示されます。
ほとんどのリターン・スタック・エントリの右側には、 そのリターン・スタック・エントリをリターン・アドレスとしてプッシュしたワードを推測して表示します。
これによりバックトレースが得られます。 この例では、 bar
が foo
を呼び出し、 foo
が
@
を呼び出していることがわかります(そして、 この @
には 「Invalid Memory
address」例外がありました)。
注意: バックトレースは完璧では無いことに注意してください。 どのリターン・スタック・エントリがリターン・アドレスであるかは知りません(そのため、
誤検知が発生する可能性があります)。 また、 (abort"
など、)場合によっては、 リターン・アドレスからはリターン・
アドレスをプッシュしたワードを特定できないため、 リターン・アドレスによってはリターン・スタック・ダンプに名前が表示されません。
リターン・スタック・ダンプは、 特定の throw
が execute されたときのリターン・スタックを表します。
catch
を使用するプログラムでは、 リターン・スタック・ダンプにどの throw
を使用する必要があるかが必ずしも明確ではありません(たとえば、 エラーを示すある throw
がキャッチされ、
そのリカバリ中に別のエラーが発生したとすると、 スタック・ダンプにはどの throw
のを使用するべきでしょうか?)。 Gforth は、
最後に execute された(そして、 その execute からまだ返ってきていない状態で、 ) catch
または
nothrow
の後の最初の throw
のリターン・スタック・ダンプを表示します: 通常、 これはうまく機能します。
正しいバック・トレースを取得するためには、 通常、 エラーが再 throw されない場合は catch
の後に
nothrow
または ['] false catch 2drop
を挿入します。
gforth
エンジン(訳注: OSコマンドラインから gforth で起動)は、 プリミティブが生成した throw
のリターン・スタック・ダンプを行えます(例: invalid memory address(不正なメモリーアクセス), stack
empty(スタックが空) 等)。 gforth-fast
エンジン(訳注: OSコマンドラインから gforth-fast
で起動)は、 直接呼び出された throw
(abort
などを含む)
からのリターン・スタック・ダンプのみを行うことができます。 gforth-fast
エンジンのプリミティブによって例外が発生した場合、
通常はリターン・スタック・ダンプは全く見れません。 しかしながら、 catch
によって例外がキャッチされ(たとえば、
何らかの状態を復元するため)、 その後再び throw
が返される場合、 最初の throw
に対するリターン・スタック・ダンプは見れます。
また、 gforth-fast
は、 ゼロ除算と除算オーバーフローを区別しようとしません。 これは、
どの除算も時間が掛かる処理だからです。
こちらもご覧ください Emacs and Gforth
Forth プログラムを標準プログラムとしてラベル付けしたい場合は、 プログラムがどのワードセットを使用するかを文書化する必要があります。
ans-report.fs ツールを使用すると、アプリケーションでどのワードセットのどのワードが使用されているか、
どの非標準ワードが使用されているかを簡単に判断できます。 チェックしたいプログラムをロードする前に ans-report.fs
をインクルードするだけです。 プログラムをロードした後、 print-ans-report
を使用してレポートを取得できます。
一般的な使用法は、 以下のようにこれをバッチ・ジョブとして実行することです:
gforth ans-report.fs myprog.fs -e "print-ans-report bye"
出力は以下のようになります (compat/control.fs の場合):
The program uses the following words from CORE : : POSTPONE THEN ; immediate ?dup IF 0= from BLOCK-EXT : \ from FILE : (
ans-report.fs は、 Forth-94 と Forth-2012 の両方のワードセットをレポートします。
両方の標準に含まれるワードについては、 接尾辞なしでワードセットが報告されます(例: CORE-EXT
)。 Forth-2012
専用のワードの場合、-2012
接尾辞が付いたワードセットが報告されます(例: CORE-EXT-2012
)。
Forth-94 のみのワード(つまり、Forth-2012 で削除されたワード)についても同様です。
注意: ans-report.fs は、 どのワードが使用されているかをチェックするだけで、 標準に準拠した方法で使用されているかどうかをチェックするわけではないことに注意してください。
一部のワードは、 標準で複数のワードセットで定義されています。 ans-report.fs は、 それらのワードセットのうちの 1
つについてのみレポートし、 それは必ずしもあなたが期待したワードセットとは限りません。 どのワードセットを指定するのが適切かは、
用途によって異なります。 たとえば、S"
のコンパイル機能(compilation semantics)のみを使用する場合、それは
CORE ワードです。 インタプリタ機能(interpretation semantics)も使用する場合、 それは FILE ワードです。
ファイルをロードした後、 スタックに項目が残っていることに気づくことがあります。 Depth-changes.fs ツールを使用すると、 これらのスタック項目がファイル内のどこから来たのかをすばやく見つけることができます。
Depth-changes.fs を使用する最も簡単な方法は、 チェックするファイルの前にこれをインクルードすることです。 例:
gforth depth-changes.fs my-file.fs
これにより、 すべての空行(インタープリター状態)でのデータ・スタックの深さと FP スタックの深さが、
最後の空行(インタープリター状態)での深さと比較されます。 深さが等しくない場合は、 ファイル内の位置とスタックの内容が ~~
(see Debugging) で出力されます。 これは、
指摘行より前の空ではない行内の段落(paragraph)でスタックの深さの変更が発生したことを示します。 ファイルの最後に空行を残しておいて、
最後の段落もチェックされるようにすることをお勧めします。
通常、 空行のみをチェックするのはうまく機能しますが、 空行ではない大きなブロックが存在する場合(大きなテーブルを構築する場合など)、 このブロックのどこでスタックの深さが変更されたのかを知りたい場合があります。 以下を使用すると、 解釈(interpret)されたすべての行をチェックできます
gforth depth-changes.fs -e "' all-lines is depth-changes-filter" my-file.fs
これにより、 各行末ごとにスタックの深さがチェックされます。 したがって、 深さの変更は ~~
によって報告された行で発生しています(それより前の行ではありません)。
注意: これにより、 スタックの深さが変更される場所を示す精度が向上しますが、 多くの、 意図した、 スタックの深さの変更が報告されることが多いことに注意してください(たとえば、 解釈(interpret)された計算が複数の行にまたがる場合)。 一部の行のチェックを抑制するには、 これらの行の末尾にバックスラッシュを置き(その後に空白は続きません)、 以下を使用します
gforth depth-changes.fs -e "' most-lines is depth-changes-filter" my-file.fs
(標準適合度)私達の知る限り、 Gforth は…
ANS Forth システムと Forth-2012 システムに関しては
EMIT?
を除く)
EDITOR
と FORGET
を除く)
Gforth には以下の環境的制限(environmental restrictions)があります:
query
の後に throw
が実行される場合、 Gforth は対応する catch
で有効な入力ソース仕様を常に復元するとは限りません。
さらに、 標準 Forth システムでは、 特定の実装での選択を文書化する必要があります。 この章では、 Forth-94 標準のこれらの要件を満たすことを試みます。 Forth-2012 標準については、 需要がある場合にのみ追加のドキュメントを作成することにしました。 したがって、 このドキュメントで本当に足りないという場合は、 著者達までご連絡ください。
多くの場合、 以下のドキュメント群においては、 特に、 情報がプロセッサに依存する場合、 オペレーティング・システムまたは選択したインストール・オプション、 または、 Gforth のメンテナンス中に変更される可能性があるかどうについては、 情報を直接提供する代わりに、 システムに情報を要求する方法が説明されています。
プロセッサ依存です。 Gforth の整列(alignment)ワードは自然な整列(natural alignment)を実行します(たとえば、 サイズ
8 のデータに対して整列されたアドレスは 8 で割り切れます)。 通常、 非整列アクセス(unaligned accesses)では -23
THROW
が発生します。
EMIT
と非表示文字: ¶文字(character)は C 言語のライブラリー関数(実際にはマクロ) putc
を使用して出力されます。
ACCEPT
と EXPECT
の文字編集: ¶これは、 Emacs のようなキー・バインディングを備えた GNU readline ライブラリー(see Command Line Editing in The GNU Readline Library)をモデルにしています。 Tab を入力するたびに (すべての補完に共通のプレフィックスを生成するのではなく)完全なワード語補完を生成するという点で少し異なります。 See Command-line editing
あなたのコンピュータと表示デバイスの文字セット。 Gforth は 8 ビットクリーンです(ただし、 システム内の他のコンポーネントが問題を引き起こす可能性があります)。
インストールに依存します。 現在、 文字は C言語の unsigned char
で表されます。 (リクエストへのコメントですけども、
)将来的には wchar_t
に切り替える可能性があります。
ASCII NUL 文字を除く任意の文字を名前に使用できます。 照合では大文字と小文字が区別されません(TABLE
を除く)。 照合は C
言語ライブラリー関数 strncasecmp
を使用して実行されますが、 その関数はおそらくロケールの影響を受けます。
たとえば、C
ロケールではアクセントとウムラウトが認識されないため、 そのロケールでは大文字と小文字が区別されて照合されます。
移植性の理由から、 C
ロケールで動作するようにプログラムを作成することが最善です。 そうすれば、 ポーランド人のプログラマ(ISO
Latin-2 でエンコードされた文字を含むワードを使用する可能性がある)と、 フランスのプログラマ(ISO
Latin-1)が作成したライブラリーを同じプログラム内で使用できます(もちろん、 WORDS
は一部のワードに対して愉快な結果を生成します(どのワードでそうなるかは、 使用しているフォントによって異なります))。 また、
あなたのご希望のロケールが他のオペレーティング・システムでは利用できない場合もあります。 Unicode
がいつかこれらの問題を解決してくれることを願っています。
word
がスペース文字を区切り文字として使用して呼び出された場合、 すべての空白文字(C 言語マクロ isspace()
で識別される)が区切り文字になります。 一方、 parse
はスペースを他の区切り文字と同様に扱います。
parse-name
はデフォルトで外部インタープリター (別名テキスト・インタープリター) によって使用され、
すべての空白文字を区切り文字として扱います。
データ・スタックは制御フロー・スタックとして使用されます。 制御フロー・スタック項目のセル単位でのサイズは、 constant
cs-item-size
によって与えられます。 この記事の執筆時点では、 制御フロー・スタック項目はローカル変数リスト(3番目)、
コード内のアドレス(2 番目)、 アイテムを識別するためのタグ(TOS) で構成されています。 タグとして次のタグが使用されています:
defstart
, live-orig
, dead-orig
, dest
,
do-dest
, scopestart
文字 [\]^_'
は、 10 進数値で 36〜41 の「数字」として解釈されます(訳注: 基数による)。
それより大きな「数字」の多くは(直接)入力する方法がありません。
ACCEPT
および EXPECT
で入力が終了した後の表示: ¶入力した文字列の末尾にカーソルが移動します。 Return キーを使用して入力を終了する場合は、 スペースが入力されます。
ABORT"
の 例外中止(exception abort)シーケンス: ¶エラー文字列は変数 "error
に保存され、 -2 throw
が実行されます。
対話入力の場合は、 C-m (CR) および C-j (LF) で行を終了します。 通常、 これらの文字は、 Enter キーまたは Return キーを入力したときに生成されます。
s" /counted-string" environment? drop .
で得られます。 現在、 すべてのプラットフォームで 255
文字ですが、 これは変更される可能性があります。
constant /line
によって与えられます。 現在 255 文字です。
MAX-U / 8 (s" max-u" environment? drop 8 u/ u.
)
ENVIRONMENT?
の最大文字列長(文字単位): ¶MAX-U / 8 (s" max-u" environment? drop 8 u/ u.
)
ユーザー入力デバイスは標準入力です。 現時点では、 Gforth 内から変更する方法はありません。 ただし、 入力は通常、Gforth を起動するOSコマンド・ラインでリダイレクトできます。
EMIT
および TYPE
は、値 outfile-id
(デフォルトでは stdout
)
に格納されているファイル ID に出力します。 Gforth は、ユーザー出力デバイスが端末の場合はバッファなしの出力を使用します。 それ以外の場合、
出力はバッファリングされます。
わざわざここで文書化する必要ある?(What are we expected to document here?)
s" address-units-bits" environment? drop .
で得られます。 現在はすべてのプラットフォームで 8
プロセッサに依存します。 現在のすべてのプラットフォームでは 2 進数は 2 の補数表現です。
インストールに依存します。 MAX-N
と MAX-U
と MAX-D
と MAX-UD
の環境クエリ(environmental queries)を作成します。 符号なし(および正)型の下限は 0 です。 2 の補数マシンおよび 1
の補数マシンの符号付き型の下限は、 その上限に 1 を加算することで計算できます。
Forth データ空間全体が書き込み可能です。
WORD
のバッファのサイズ: ¶PAD HERE - .
で得られます。 32 ビット マシンでは 104 文字。 バッファは、 数値表示出力文字列(pictured
numeric output string)と共有されます。 PAD
の上書きが許容される場合、
そのサイズは残りの辞書スペースと同じになりますが、 実用になるのはカウンタ付き文字列に収まる範囲程度です。
1 cells .
で得られます。
1 chars .
で得られます。 現在のすべてのプラットフォームで 1
さまざまです。 lp@ tib - .
を使用して、 特定の時点でのサイズを確認できます。 これは、
現在のファイルを含むファイルのローカル変数スタックおよび TIB と共有されます。 コマンド・ライン・オプション -l
を使用して、
Gforth 起動時に TIB とローカル変数スタックのスペースの量を変更できます。
PAD HERE - .
で得られます。 32 ビット マシンでは 104 文字。 バッファは WORD
と共有されます。
PAD
によって返されるスクラッチ領域のサイズ: ¶ディクショナリー・スペースの残りまるごと。 unused pad here - - .
で得られます。
ディクショナリーの検索では大文字と小文字が区別されません(TABLE
を除く)。 ただし、 上記 character-set
extensions で説明したように、 非 ASCII 文字のマッチングは使用しているロケールによって決まります。 デフォルトの C
ロケールでは、 すべての非 ASCII 文字は大文字と小文字を区別して照合されます。
インタープリター状態では ok
で、 コンパイル状態では compiled
です。
通常の除算ワード / mod /mod */ */mod
は、フロア除算(floored division)を実行します (Gforth
のデフォルトのインストールを使用の場合)。 s" floored" environment? drop .
でこれを確認できます。
特定の除算の四捨五入が必要なプログラムを作成する場合は、 移植性を高めるために fm/mod
または sm/rem
を使用するのが最適です。
STATE
の値: ¶-1.
2 の補数マシンでは、 1倍長の場合は 2**bits-per-cell 、 2倍長の場合はセルあたり 4**bits-per-cell
を法として演算(modulo)が実行されます(符号付き型では適切なマッピングを使用)。 ゼロによる除算は通常、 -55 throw
(Floating-point unidentified fault) または -10 throw
(divide by zero)
を引き起こします。 整数除算のオーバーフローにより、 -55 throw
や -10 throw
、 または
-11 throw
が発生する可能性があります。 gforth-fast
エンジン(OSコマンドラインから
gforth-fast
で起動)の除算オーバーフローやゼロ除算では、 例外が生成されずに偽の結果が返される可能性があります。
DOES>
の後ろで見つかる(可視)かどうか: ¶いいえ(No)。
-13 throw
(Undefined word)
-19 throw
(Word name too long)
スタックやコード・スペースやヘッダー・スペースはアクセス可能です。 マシン・コード空間は通常、読み取り可能です。 他のアドレスにアクセスすると、
オペレーティング・システムに応じた結果が得られます。 まともなシステムの場合: -9 throw
(Invalid memory
address)
これは通常はキャッチされません。 一部のワード(制御フロー・ワードなど)はチェックを実行し、 ABORT"
または -12
THROW
(Argument type mismatch) となります。
実行トークンは、 ワードのインタープリター機能(interpretation semantics)を表します。 Gforth
はすべてのワードのインタープリター機能を定義します。 標準でインタプリタ機能が定義されていないが、 実行機能(execution
semantics)が定義されているワード(LEAVE
を除く)については、 インタープリター機能が実行機能を実行します。
標準でインタープリター機能が定義されていないが、 コンパイル機能(compilation semantics)(および
LEAVE
)が定義されているワードの場合、 インタプリタ機能はコンパイル機能を実行します。
一部の単語はコンパイル専用(compile-only)としてマークされており、 '
はこれらのワードに対して警告を出します。
一部のプラットフォームでは、 これにより -10 throw
(Division by zero) が生成されます。 他のシステムでは、
通常、 これにより -55 throw
(Floating-point unidentified fault) が生成されます。
オペレーティング・システムやインストール時の設定や Gforth の起動時の設定に応じて、 これはメモリー管理ハードウェアによって、
チェックされたり、チェックされなかったり。 これがチェックされている場合、 通常はオーバーフローが発生するするやいなや、
(プラットフォームとオーバーフローを達成した方法によって異なりますが、) -3 throw
(Stack overflow) または
-5 throw
(Return stack overflow) または -9 throw
(Invalid memory
address) を受け取ります。 これがチェックされていない場合、 オーバーフローは通常、 原因不明の不正なメモリー・アクセスを引き起こし、
-9 throw
(Invalid memory address) または -23 throw
(Address
alignment exception) を生成します。 また、ALLOCATE
とそのファミリーの内部データ構造も破壊し、
これらのワードにさまざまなエラーが発生する可能性があります。
他のリターン・スタック・オーバーフローと同様。
ディクショナリーで利用可能なメモリーを超えるメモリーを(allot
で直接、 または ,
や create
などで間接的に)割り当てようとすると、 -8 throw
(Dictionary overflow) が発生します。
ディクショナリーの末尾を超えてメモリにアクセスしようとすると、 結果はスタック・オーバーフローと同様になります。
Gforth は全てのワードのインタープリター機能(interpretation semantics)を定義します。 標準で実行機能(execution
semantics)が定義されているワード(LEAVE
を除く)については、 インタープリター機能が実行機能を実行します。
標準ではインタプリタ機能が定義されていないが、 コンパイル機能(compilation semantics)(および
LEAVE
)が定義されているワードの場合、 インタープリター機能はコンパイル機能。
一部のワードはコンパイル専用(compile-only)としてマークされており、
テキスト解釈(text-interpreting)すると警告が表示されます。
これらは書き込み可能なメモリーに配置されており、 変更することができます。
-17 throw
(Pictured numeric ouput string overflow)
PARSE
はオーバーフロー不可能です。 WORD
はオーバーフローをチェックしません。
2 の補数マシンでは、 1倍長演算の場合は 2**bits-per-cell 、2倍長演算の場合は 4**bits-per-cell
を法(modulo)として演算が実行されます(符号付き型は適切なマッピングを使用)。 ゼロ除算は通常、-10 throw
(divide
by zero) または -55 throw
(floating point unidentified fault) を引き起こします。
除算のオーバーフローにより、 -10 throw
(divide by zero) または -55 throw
(floating point unidentified fault) が発生したり、 -11 throw
(result out of
range) が発生したりする可能性があります。 Gforth-fast
エンジン(訳注: OSコマンドラインから
Gforth-fast
で起動)では、 除算のオーバーフローまたはゼロによる除算の際に、 暗黙的に偽の結果を生成する可能性があります。
Convert
と >number
は現在、 何も警告を出さずにオーバーフローします。
データ・スタックは、 各ワード execute 後、 外部インタープリター(別名 テキスト・インタプリタ)によってチェックされます。
アンダーフローした場合は、 -4 throw
(Stack underflow) が発出されます。 それとは別に、
オペレーティング・システムやインストール時の設定や(OSからの gforth)起動時の設定に応じて、 スタックがチェックされるかどうかが異なります。
チェックで検出された場合、 通常は、プラットフォームや、 どのスタックがどの程度アンダーフローしたかに応じて、 通常は -4 throw
(Stack underflow) または -6 throw
(Return stack underflow) または -9
throw
(Invalid memory address) が発生します。 注意: システムが(MMU を介した)チェックを使用している場合でも、
リアクションをトリガーするには、 プログラムがかなりの数のスタック項目をアンダーフローする必要がある場合があることに注意してください(その理由は、
MMU 絡みチェックがページ・サイズの粒度で動作するためです)。
チェックが行われない場合、アンダーフローによる症状はオーバーフローによる症状と似ています。 アンバランスなリターン スタック エラーは
-9 throw
(Invalid memory address) や不正な命令(通常は -260
throw
)など、さまざまな症状を引き起こす可能性があります。
Create
とその子孫は -16 throw
(Attempt to use zero-length string as
a name;長さ 0 の文字列を名前として使用しようとします)を発出します。 '
のようなワードは検索しても見つからない可能性があります。 注意: nextname
を使用して長さゼロの名前を作成できることに注意してください(ホントにそうすべきかどうかをよく確認してください)。
>IN
が指し示す値が入力バッファの長さより大きい: ¶次にパース・ワードを呼び出すと、 長さ 0 の文字列が返されます。
RECURSE
が DOES>
の後に現れた: ¶DOES>
の後のコードへの再帰呼び出しをコンパイルします。
RESTORE-INPUT
の引数の入力ソースが現在の入力ソースと異なります: ¶-12 THROW
注意: 入力ファイルが閉じられると(たとえば、 ファイルの終わりに達したため、 その source-id
は再利用される可能性があることに注意してください。 したがって、 閉じられたファイルを参照する入力ソース仕様(input source
specification)を復元(restore)すると、 -12 THROW
ではなく、 予期しない結果が発生する可能性があります。
将来的には、 Gforth は現在の入力ソース以外から入力ソースの仕様を復元(input source specifications)できるようになる可能性があります。
allot
による割り当て解除はチェックされません。 これにより、通常、 メモリ・アクセス・フォールト(memory access
faults)や不正な命令の実行(execution of illegal instructions)が発生します。
プロセッサに依存します。 通常、 -23 throw
(Address alignment exception) が発生します。
アライメントがオンになっている 486 以降のプロセッサ上の Linux-Intel では、 アライメントが正しくないと -9 throw
(Invalid memory address) が発生します。 報告によると、
アライメント制限があるのに違反を報告しない一部のプロセッサがあるとのことです。
,
, C,
: ¶他のアライメント・エラーと同様。
PICK
および ROLL
を実行:他のスタック・アンダーフローと同様。
チェックされていません。 カウンタ付きループのワード群は、 リターン・スタック項目の先頭がループ制御パラメーターであると単純に想定し、 それに応じて動作します。
IMMEDIATE
): ¶abort" last word was headerless"
.
TO
で使用される VALUE
で名前が定義されていません: ¶-32 throw
(Invalid name argument) (名前がローカル変数であるか、 CONSTANT
によって定義されている場合を除きます。 後者の場合は、 定数が変更されるだけです)。
'
, POSTPONE
, [']
, [COMPILE]
): ¶-13 throw
(Undefined word)
DO
, ?DO
, WITHIN
): ¶Gforth は、 それらが同じタイプであるかのように振る舞います。 つまり、 あなたは、 すべてのパラメーターを符号付きなどとして解釈することで、振る舞いを予測できます。
POSTPONE
または [COMPILE]
が TO
に適用されました: ¶: X POSTPONE TO ; IMMEDIATE
と仮定します。 X
は TO
のコンパイル機能(compilation semantics)を実行します。
WORD
によって返されるカウンタ付き文字列よりも長い文字列: ¶チェックされていません。 文字列は問題ありませんが、 当然ながら、 そのカウントにはその長さの最下位ビット達のみが含まれます。
LSHIFT
、RSHIFT
): ¶プロセッサに依存します。 一般的な振る舞いは、 0 を返し、 シフト・カウントの下位ビット達のみを使用することです。
CREATE
によって定義されたワードではない: ¶>BODY
は、 ワードの定義方法に関係なく、 ワードの PFA を生成します。
DOES>
は、 定義方法に関係なく、 最後に定義されたワードの実行機能(execution semantics)を変更します。
たとえば、 CONSTANT DOES>
は CREATE , DOES>
と同等です。
<#
〜 #>
の外で不適切に使用されているワード:チェックされていません。 いつものように、 メモリー障害が発生することが予想されます。
PAD
を使用した非標準ワード: ¶無し。
OS のコマンド・ライン処理後、 Gforth は対話モードになり、 Gforth に対話的にコマンドを与えることができます。 実際に利用できる機能は、Gforth の呼び出し方法によって異なります。
UNUSED .
は残りのディクショナリー空間を与えます。 合計ディクショナリー空間は、 Gforth の起動時に -m
スイッチで指定できます(see Invoking Gforth)。
s" RETURN-STACK-CELLS" environment? drop .
を使用して、
セル単位での合計リターン・スタック空間を計算できます。 あなたは gforth 起動時に -r
スイッチを使用して指定できます(see Invoking Gforth)。
s" STACK-CELLS" environment? drop .
を使用して、 セル単位での合計データ・スタック空間を計算できます。
あなたは gforth 起動時に -d
スイッチを使用して指定できます(see Invoking Gforth)。
起動直後に here first-start - .
と入力します。 この記事の執筆時点では、 32 ビット システムでは 80080
(バイト) になります(訳注: Gforth 0.7.9_20240418 amd64 では 649000 aus(address unit))
LIST
使用時の表示書式: ¶最初にスクリーン番号が表示され、次に 1 行当たり 64 文字からなる 16 行が表示され、 各行の前に行番号が表示されます。
\
の影響を受ける行の長さ: ¶64 文字。
通常、 OS 由来の値 (-512 ~ -2048) の throw
が返されます。 ブロック・ファイルの長さが十分でない場合、
足りない部分は空白を返します。
通常、 OS 由来の値 (-512 ~ -2048) の throw
が返されます。
-35 throw
(Invalid block number)
BLK
の値を直接書き換えた場合: ¶入力ストリームは、 同じ位置で別のブロックに切り替えられます。 非ブロック入力の解釈(interpret)時に BLK
への値の格納が行われた場合、 ブロックの終了時にシステムがかなり混乱することでしょう。
UPDATE
を実行した: ¶UPDATE
は何もしません。
(今のところ) 制限はありません。
あなたのディスク容量によります。
THROW
コード値: ¶コード -256〜-511 は、 シグナルに使用されます。 OS シグナル番号から throw
コードへのマッピングは、-256−signal です。 コード -512〜-2047 は、OS
エラー(ファイルおよびメモリー割り当て操作)に使用されます。 OS エラー番号から throw
コードへのマッピングは、-512−errno
です。 このマッピングの副作用の 1 つは、 未定義の OS エラーにより、
奇妙な番号のメッセージが生成されることです。 例: -1000 THROW
の場合、 私のシステムでは Unknown
error 488
が発生します。
EKEY
): ¶ASCII 文字に対応するキーは ASCII 文字としてエンコードされます。 他のキーは次の定数でエンコードされます: k-left
,
k-right
, k-up
, k-down
, k-home
, k-end
,
k1
, k2
, k3
, k4
, k5
, k6
, k7
,
k8
, k9
, k10
, k11
, k12
, k-winch
,
k-eof
システムに依存します。 ワード MS
に関しては、 時間はマイクロ秒単位で指定されます。 OS
とハードウェアがこれをどの程度うまく実装するかは別の問題です。
MS
の execute で期待される再現性: ¶システムに依存します。 Unix では、多くのことが負荷に依存します。 システムの負荷が軽く、 Gforth がスワップアウトされないほど遅延が短い場合、 パフォーマンスは許容範囲内です。 MS-DOS やその他のシングルタスク・システムではパフォーマンスは良好です。
R/O
と R/W
と BIN
は期待どおりに機能します。 W/O
は
C言語のファイル・オープニング・モード w
(または wb
) に変換され、 ファイルが存在する場合はクリアされ、
存在しない場合は作成されます( open-file
、 create-file
両方とも)。 Unix では、
create-file
は、 あなたの umask によって変更された 666 権限を持つファイルを作成します。
file ワード群は例外を発出しません(たぶん、 不正なアドレスまたは不正なファイル ID を渡した場合のメモリー・アクセス失敗(memory access faults)例外が発生します)。
システムに依存します。 Gforth は C言語の改行文字を行終端文字として使用します。 実際の文字コードが何であるかはシステムによって異なります。
システムに依存します。 Gforth はあなたの OS のファイル名形式を使用するだけです。
FILE-STATUS
によって返される情報: ¶FILE-STATUS
は、 ファイルに許可されている最も強力なファイル・アクセス・モードを返します。 それは R/O
または W/O
または R/W
のいずれかです。 ファイルにアクセスできない場合は、 R/O BIN
が返されます。 BIN
は、 返されたモードと一緒に適用されます。
例外の発出により、 残されたすべてのファイルはクローズされます。
ファイルおよびメモリー割り当てワード群によって返される ior は throw コードとして意図されています。 通常、これらは OS エラーの -512〜-2047 の範囲内にあります。 OS エラー番号から ior へのマッピングは -512−errno です。
リターン・スタックと、 ローカル変数/TIB スタックと、 オープン可能なファイルの数によって制限されます。 この制限内である限り問題は起きないはずです。
/line
で得られます。 現在は 255
デフォルトでは、 ブロックは現在の作業ディレクトリ内のファイル blocks.fb 内でアクセスされます。 ファイルは
USE
で切り替えることができます。
S"
によって提供される文字列バッファの数: ¶利用可能なメモリー量だけ。 文字列は、 ALLOCATE で割り当てられたメモリー・ブロックに無期限に保存されます。
S"
によって使用される文字列バッファのサイズ: ¶/line
でられます。 現在 255
REPOSITION-FILE
は通常どおり実行されます。 その後、 FILE-POSITION
は
REPOSITION-FILE
で指定された値を返します(訳注: それがEOFを超えた値であっても)。
EOF 、 つまりゼロ文字(zero characters)が読み取られ、 エラーは報告されません。
INCLUDE-FILE
): ¶適切な例外が throw される可能性もありますが、 メモリー障害またはその他の問題が発生する可能性の方が高いです。
INCLUDE-FILE
, INCLUDED
): ¶問題を発見した操作によって生成された ior が throw されます。
INCLUDED
): ¶open-file
によって生成された ior が throw されます。
マップされていない正当なブロック番号はありません。 一部のオペレーティング・システムでは、 大きな番号のブロックを書き込むとファイル・システムがオーバーフローし、 その結果エラー・メッセージが表示される場合があります。
blk
がゼロ以外の場合に source-id
を使用した: ¶source-id
は自身の機能を実行します。 通常、 ブロックをロードしたソースの ID が表示されます。
(作者たちが考えたのより良いアイデアがあれば教えてください)
システムに依存します。 C言語の double 型 です。
REPRESENT
の結果: ¶システムに依存します。 REPRESENT
は C言語のライブラリー関数 ecvt()
を使用して実装されているので、
この点においてその振る舞いを継承します。
丸めの振る舞いは、 ホストしている C 言語のコンパイラーから継承されます。 IEEE-FP ベースの(つまり、ほとんどの)システムは、 デフォルトで最も近い値に丸められ、 偶数に丸める(つまり、 仮数の最後のビットを 0 にする)ことによって決着させます。
s" FLOATING-STACK" environment? drop .
は、 浮動小数点スタックの合計サイズ(float
単位)を示します。 これは、 gforth 起動時にコマンドライン・オプション -f
を使用して指定できます(see Invoking Gforth)。
1 floats
で得られます。
df@
または df!
が 2倍長 float にアライメントされていないアドレスで使用されています: ¶システムに依存します。 通常、 他のアライメント違反と同様に -23 THROW
が発生します。
f@
または f!
が float にアライメントされてないアドレスで使用されています: ¶システムに依存します。 通常、 他のアライメント違反と同様に -23 THROW
が発生します。
システムに依存します。 -43 throw
(floating point overflow) または -54 throw
(floating point underflow) または -41 throw
(floating point inexact
result;浮動小数点の不正確な結果) または -55 THROW
(Floating-point unidentified
fault;浮動小数点の未確認の障害) を発出する可能性があり、 または、 例えば無限大(Infinity)を表す特別な値が生成される可能性があります。
sf@
または sf!
が1倍長浮動小数点数にアライメントされていないアドレスで使用されています: ¶システムに依存します。 通常、 他のアライメント違反と同様にアライメント違反が発生します。
base
が 10 進数ではありません (REPRESENT
、F.
、FE.
、FS.
): ¶それでもなお、 浮動小数点数は 10 進数に変換されます。
FATAN2
): ¶システムに依存します。 FATAN2
は、 C言語のライブラリー関数 atan2()
を使用して実装されます。
FTAN
を使用する場合、 cos(r1) はゼロです: ¶システムに依存します。 いずれにせよ、通常、 r1 の cos は小さな誤差によりゼロにはならず、 tan は非常に大きい(または非常に小さい)ものの有限の数になります。
D>F
で 2倍長整数から不動小数点数に変換しようとしますが、 浮動小数点数として正確に表すことができません: ¶結果は最も近い浮動小数点数に丸められます。
プラットフォームに依存します。 Infinity または NaN または -42 throw
(floating point divide
by zero) または -55 throw
(Floating-point unidentified fault)
が生成される可能性があります。
DF!
、DF@
、SF!
、SF@
): ¶システムに依存します。 IEEE-FP ベースのシステムでは、 数値は infinity (無限大)に変換されます。
FACOSH
): ¶プラットフォームに依存します。 IEEE-FP システムでは通常、 NaN が生成されます。
FLNP1
): ¶プラットフォームに依存します。 IEEE-FP システムでは通常、NaN (または float=-1 の場合は negative infinity(負の無限大))が生成されます。
FLN
, FLOG
): ¶プラットフォームに依存します。 IEEE-FP システムでは通常、 NaN (または float=0 の場合はnegative infinity(負の無限大))が生成されます。
FASINH
, FSQRT
): ¶プラットフォームに依存します。 fsqrt
の場合、 これは通常 NaN を返します。 fasinh
の場合、一部のプラットフォームでは NaN が生成され、 その他のプラットフォームでは数値が生成されます(C ライブラリーのバグかな?)。
FACOS
, FASIN
, FATANH
): ¶プラットフォームに依存します。 IEEE-FP システムは通常、 NaN を生成します。
F>D
の d で表すことができません: ¶プラットフォームに依存します。 通常、 2倍帳の数値が生成されますが、 エラーは報告されません。
f.
、fe.
、fs.
): ¶数値出力領域の Precision
文字が使用されます。 precision
が高すぎる場合、 これらのワードは
here
に近いデータまたはコードを破壊します。
;CODE
や CODE
に続く入力シーケンスの終端: ¶END-CODE
;CODE
および CODE
に続く入力の処理方法: ¶ASSEMBLER
ボキャブラリーが検索順序(the search order)スタックにプッシュされ、
入力はテキスト・インタープリターによって(開始時に)インタープリター状態で処理されます。
EDITOR
および ASSEMBLER
の検索順序: ¶Search-Order ワードセット参照
SEE
による表示形式と表示ソース: ¶see
のソースは、 内部インタープリターによって使用される実行可能コードです。 現在の see
は、 Forth
ソース・コード(および一部のプラットフォームではプリミティブのアセンブリ・コード)を可能な限り表示しようとします。
FORGET
): ¶(まだ)実装されていません。
CS-PICK
, CS-ROLL
): ¶通常、 これにより、 説明的なエラー・メッセージを含む abort"
が表示されます(将来的には -22 throw
(Control structure mismatch) に変更される可能性があります)。 また、
メモリー・アクセス・エラーが発生する可能性もあります。 残念なことに、 この曖昧な状態はキャッチできません。
FORGET
が見つかりません: ¶(まだ)実装されていません。
CREATE
によって定義されていません: ¶この点において、 ;CODE
は DOES>
と同様に動作します。 つまり、 定義方法に関係なく、
最後に定義されたワードの実行機能(execution semantics)を変更します。
POSTPONE
が [IF]
に適用されました: ¶: X POSTPONE [IF] ; IMMEDIATE
定義後の X
は [IF]
と同等です。
[ELSE]
または [THEN]
に到達する前に入力ソースの末尾に到達しました: ¶次の外部入力ソースでも同一の条件付きコンパイル状態を継続します。 現時点では、 これに関するユーザーへの警告はありません。
FORGET
): ¶(まだ)実装されていません。
ワードは、 定義の先頭時点でコンパイル・ワードリストであるワードリストに追加されます。 名前フィールドへの変更(たとえば
immediate
)またはコード・フィールドへの変更(例えば DOES>
の execute)は、
それらが変更可能であれば、 コンパイル・ワードリストに関係なく、 もっとも最新に定義されたワードに適用されます。
previous
): ¶abort" Vocstack empty"
also
): ¶abort" Vocstack full"
このマニュアルの残りの部分を読み進めると、 標準ワードのドキュメントと、 いくつかの魅力的な Gforth 拡張のドキュメントがあります。 あなたは、 「標準に限定すべきか、 それとも gforth での拡張を使用すべきでしょうか?」(Should I restrict myself to the standard, or should I use the extensions?)
その答えは、 あなたが取り組んでいるプログラムの目標(goal)によって異なります:
プログラムを Gforth のみに制限しても問題ない場合、 Gforth 拡張を使用しない理由はありません。 移植性を保ちたい別のプログラムでこれらのパーツを再利用したい場合に備えて、 標準を守るのが簡単なところでは標準を守るのは良い考えです。
プログラムを他の Forth システムに移植できるようにしたい場合は、 以下の点を考慮する必要があります:
これらの考慮事項を実行するには、 何が標準で何が標準ではないかを知る必要があります。 このマニュアルには通常、 何が非標準であるかが記載されていますが、 信頼できる情報源は standard document です。 Appendix A of the Standard (Rationale) は、技術委員会の思考プロセスについての貴重な洞察を提供します。
注意: Forth システム間の移植性だけが移植性の問題ではないことにも注意してください。 異なるプラットフォーム (プロセッサと OS の組み合わせ)間の移植性の問題もあります。
C または C++ またはその他の言語で書かれているアプリケーションのスクリプト言語として Forth を使用することを好む人もいます。
Forth システム ATLAST は、 Forth システムをアプリケーションに組み込むための機能を提供します。 残念ながら、 これにはいくつかの欠点があります。 最も重要なのは、 標準 Forth に基づいておらず、 明らかに死んでいる(つまり、 これ以降開発されておらず、 サポートされていない)ことです。 この分野で Gforth が提供する機能は ATLAST の機能からインスピレーションを得ているため、 ATLAST からの切り替えは難しくはありません。
また、 著者達は、 いつか標準化されたインターフェイスに到達できるように、 他の Forth システムで簡単に実装できるようにインターフェイスを設計することも試みました。 このような標準インターフェイスを使用すると、 C 言語のコードを書き直すことなく他の Forth システムに置き換えることができます。
Gforth インタープリターを埋め込むには、 ライブラリー libgforth.a
または libgforth.so
とリンクします(コンパイラーにオプション -lgforth
を指定するか、 他のエンジンのいずれかの場合は
-lgforth-fast
または -lgforth-itc
または -lgforth-ditc
を指定します)。 このライブラリ内のインターフェイスに属するすべてのグローバル・シンボルには、 接頭辞 gforth_
が付いています。
共通のインターフェイスが出現した場合、 接頭辞 forth_
を付けた #define
を通じて関数を利用できる可能性もあります。
#include <gforth.h>
を使用して、 Forth 型の宣言と、 インターフェイス関数と、
変数をインクルードすることができます。
いまや、 あなたは gforth_main
を呼び出すか、 以下のコンポーネントを使用して、 Gforth
セッションを実行できるようになりました:
Cell gforth_main(int argc, char **argv, char **env) { Cell retvalue=gforth_start(argc, argv); if(retvalue == -56) { /* throw-code for quit */ retvalue = gforth_bootmessage(); // show boot message if(retvalue == -56) retvalue = gforth_quit(); // run quit loop } gforth_cleanup(); gforth_printmetrics(); // gforth_free_dict(); // if you want to restart, do this return retvalue; }
Forth インタープリタと対話するには、 Xt gforth_find(Char * name)
と Cell
gforth_execute(Xt xt)
があります。
(未記述)(さらなるドキュメントをここに置く必要があります。)
Cell
, UCell
: データ・スタック項目
Float
: FPスタック項目
Address
、Xt
、Label
: メモリーと、Forthワードと、VM内部のForth命令への、
ポインター・タイプ。
void *gforth_engine(Xt *, stackpointers *); Cell gforth_main(int argc, char **argv, char **env); int gforth_args(int argc, char **argv, char **path, char **imagename); ImageHeader* gforth_loader(char* imagename, char* path); user_area* gforth_stacks(Cell dsize, Cell rsize, Cell fsize, Cell lsize); void gforth_free_stacks(user_area* t); void gforth_setstacks(user_area * t); void gforth_free_dict(); Cell gforth_go(Xt* ip0); Cell gforth_boot(int argc, char** argv, char* path); void gforth_bootmessage(); Cell gforth_start(int argc, char ** argv); Cell gforth_quit(); Xt gforth_find(Char * name); Cell gforth_execute(Xt xt); void gforth_cleanup(); void gforth_printmetrics(); void gforth_setwinch();
Gforth には、 Goran Rydqvist による forth.el の改良版である gforth.el が付属しています(TILE パッケージ を利用しています)。 改善点は以下のとおり:
info-lookup
機能のサポート。
これらの機能の基本的な説明を取得するには、 forth-mode
に入り、 C-h m と入力します。
さらに、 Gforth は Emacs を全力サポートします。 エラー・メッセージや、 (~~
からの)デバッグ出力や、 失敗した
assert のメッセージで指定されるソース・コードの場所は、 Emacs のコンパイル・モード(see Running Compilations under Emacs in Emacs Manual)に適切な形式になっているため、
エラーまたはその他のメッセージに対応するソースの場所は数回のキーストロークだけでアクセスできます(例えば、 次のエラーは C-x `
カーソル下のエラーは C-c C-c)。
さらに加えて、 このマニュアルに記載されているワードについては、 C-h TAB
を使用して用語集のエントリをすばやく検索できます(info-lookup-symbol
, see Documentation Commands in Emacs Manual)。 この機能には Emacs 20.3 以降が必要で、
:
を含むワードに対しては機能しません。
gforth.el の機能を Emacs で利用できるようにするには、 .emacs ファイルに次の行を追加します(訳注: 2024年時点では .emacs.d/init.el ):
(autoload 'forth-mode "gforth.el") (setq auto-mode-alist (cons '("\\.fs\\'" . forth-mode) auto-mode-alist)) (autoload 'forth-block-mode "gforth.el") (setq auto-mode-alist (cons '("\\.fb\\'" . forth-block-mode) auto-mode-alist)) (add-hook 'forth-mode-hook (function (lambda () ;; customize variables here: (setq forth-indent-level 4) (setq forth-minor-indent-level 2) (setq forth-hilight-level 3) ;;; ... ))) ;; 訳注: とりあえずインストール( .emacs.d/init.el ): ;; /path/to/gforth-dev フォルダに gforth.el gforth.elc があるとして (add-to-list 'load-path "/path/to/gforth-dev") (require 'forth-mode "gforth")
etags.fs を require
すると、 その後に定義されるすべてのワードの定義を含む新しい TAGS
ファイル(see Tags Tables in Emacs Manual)が生成されます。 あなたは
M-. を使用してワードのソースを見つけることができます。 注意: Emacs は複数の TAG
ファイルを同時に使用できることに注意してください(たとえば、 1 つは Gforth ソース用、 もう 1 つはあなたのプログラム用、 などです。
see Selecting a Tags Table in Emacs Manual)。
事前にロード(preload)されるワードの TAGS ファイルは $(datadir)/gforth/$(VERSION)/TAGS
です(例えば /usr/local/share/gforth/0.2.0/TAGS)。 etags.fs
で最良の動作を得るには、 require
などの前後の両方に定義を置かないようにすべきです。 そうしないと、
tags-search
のようなコマンドによって同一ファイルが何度もアクセスされることになります。
gforth.el には、カスタム・ソース・ハイライティング・エンジンが付属しています。 ファイルを forth-mode
で開くと、 そのファイルは完全にパースされ、 キーワードやコメントや文字列などに face を割り当てます。 ファイルを編集している間、
変更されたリージョンはその場(on-the-fly)でパース・更新されます。
Emacs の変数 ‘forth-hilight-level’ を使用して、 装飾レベルを 0 (まったく強調表示なし) から 3 (デフォルト) に変更します。 強調表示レベルを 0 に設定した場合でも、 パーサーはバックグラウンドで動作し、 テキストの領域が「コンパイル」(compiled)されているか「解釈」(interpreted)されているかに関する情報を収集します。 これらの情報は、 自動インデントが適切に機能するために必要です。 コンピュータが遅すぎてパース処理できない場合は、 Emacs の変数 ‘forth-disable-parser’ を非 nil に設定します。 ただし、 これは自動インデント・エンジンのスマートさに影響します。
しばしば Forth のソース・コードでは、 強調表示する必要がある新しい機能として、 新しい制御構造や定義ワードなどが定義される場合があります。 変数
‘forth-custom-words’ を使用して、 forth-mode
で追加のワードや構造を強調表示させることができます。
詳細については、‘forth-words’ の docstring を参照してください(Emacs では、C-h v out-words
と入力します)。
‘forth-custom-words’ は .emacs.d/init.el ファイル内でカスタマイズすることを目的としています。 ファイル固有の方法でハイライトをカスタマイズするには、 あなたのソース・ファイルの最後のEmacsローカル変数セクションで ‘forth-local-words’ を設定します(see Variables in Emacs Manual)。
Example:
0 [IF] Local Variables: forth-local-words: ((("t:") definition-starter (font-lock-keyword-face . 1) "[ \t\n]" t name (font-lock-function-name-face . 3)) ((";t") definition-ender (font-lock-keyword-face . 1))) End: [THEN]
TAB を入力するか、C-m で改行するたびに、 forth-mode
は自動的にスマートな方法で行をインデントしようとします。
簡単なカスタマイズは、 .emacs.d/init.el ファイルで ‘forth-indent-level’ と ‘forth-minor-indent-level’ を設定することで実現できます。 歴史的な理由により、 gforth.el はデフォルトでは 4 の倍数の桁数インデントされます。 より伝統的な 3 桁のインデントを使用するには、 .emacs.d/init.el に以下の行を追加します:
(add-hook 'forth-mode-hook (function (lambda () ;; customize variables here: (setq forth-indent-level 3) (setq forth-minor-indent-level 1) )))
インデントでデフォルト以外のワードを認識したい場合は、 .emacs.d/init.el で ‘forth-custom-indent-words’ を設定してカスタマイズします。 詳細については、 ‘forth-indent-words’ の docstring を参照してください(Emacs では、C-h v four-indent-words と入力します)。
ファイル固有の方法でインデントをカスタマイズするには、 ソースファイルの最後のEmacsローカル変数セクションで ‘forth-local-indent-words’ を設定します(see Variables in Emacs Manual)。
Example:
0 [IF] Local Variables: forth-local-indent-words: ((("t:") (0 . 2) (0 . 2)) ((";t") (-2 . 0) (0 . -2))) End: [THEN]
forth-mode
は、 最初の行の長さが 1023 文字を超えているかどうかをチェックして、 ブロック・ファイルを自動検出します。
次に、 ファイルを通常のテキスト形式に変換しようと試みます。 あなたがファイルを保存すると、
通常のストリーム・ソース・ファイルとしてディスクに書き込まれます。
ブロック・ファイルへ書き込みたい場合は、 forth-blocks-mode
を使用します。 これは forth-mode
からすべての機能を継承しており、 さらにいくつかの追加機能があります:
注意すべき制限がいくつかあります。 タブ文字または改行文字を含むブロック・ファイルを開くと、 ファイルがディスクに書き戻されるときに、 これらの文字はスペースに変換されます。 ブロック・ファイルの読み取り中にタブまたは改行が検出された場合、 エラーがエコーエリアに表示されます。 なので、 読み取り中に Emacs のベルが鳴ったら、‘*Messages*’ バッファを見てください。
詳細については、 C-h v forth-blocks-mode と入力して、 forth-blocks-mode
の
docstring を参照してください。
イメージ・ファイルは、 Forth のディクショナリーのイメージ、 つまり、 ディクショナリ内に存在するコンパイルされた Forth
コードとデータを含むファイルです。 慣例により、 イメージ・ファイルには拡張子 .fi
を使用します。
gforthmi
(see gforthmi) または savesystem
(see Non-Relocatable Image Files) で作成されたイメージには、 オリジナルのイメージが含まれます。 つまり、
著作権法(copyright law)に従うと、 それはオリジナルのイメージの派生物となります。
Gforth は GNU GPL に基づいて配布されるため、 新しく作成されたイメージも GNU GPL に当てはまります。 特に、これは、 あなたがイメージを配布する場合、 あなた自身が書いたものも含め、 あなたのイメージのすべてのソースを利用可能にする必要があることを意味します。 詳細については、 GNU General Public License (Section 3) を参照してください。
cross
(see cross.fs) を使用してイメージを作成した場合、 そのイメージには、
指定したソースからコンパイルされたコードのみが含まれます。 これらのソースのいずれも GPL に準拠していない場合、
上記の条件はこのイメージには適用されません。 ただし、 そのイメージに GPL に基づくエンジン(gforth バイナリ)が必要な場合に、
そのイメージに GPL の条件を適用したくない場合、 それらが一体にならないよう、 せいぜい単なる集合体にすぎない方法で両方を配布する必要があります。
Gforth は、 (エンジン内の) リミティブだけでなく、 Forth で書かれた定義でも構成されます。 Forth コンパイラー自体もこれらの定義に含まれているため、 エンジンと Forth ソースだけでシステムを起動することはできません。したがって、 Forth コードをほぼ実行可能な形式のイメージ・ファイルとして提供します。 Gforth が起動すると、 C言語のルーチンがイメージ・ファイルをメモリーにロードし、 オプションでアドレスを再配置し、 イメージ・ファイル内の情報に従ってメモリー(スタックなど)を設定し、 (最終的に) Forth コードの実行を開始します。
デフォルトのイメージ・ファイルは gforth.fi (GFORTHPATH
上の何処か)です。 -i
または --image-file
または --appl-image
オプションを使用すると、
別のイメージを使用できます(see Invoking Gforth)。 例:
gforth-fast -i myimage.fi
イメージ・ファイルにはさまざまなバリエーションがあり、 イメージ・ファイルの生成を容易にするという目標と、 イメージ・ファイルを移植できるようにするという目標の間のさまざまな妥協点を表しています。
Win32Forth 3.4 と、 Mitch Bradley の cforth
は実行時に再配置を使用します。 これにより、
以下で説明する複雑な問題の多くが回避されます(イメージ・ファイルは、 特に手間をかけることなくデータを再配置できます)が、 しかし、
パフォーマンスが低下し(メモリー・アクセスごとに 1 回の追加)、 そして、 Forth
とライブラリー呼び出しまたは他のプログラムの間でアドレスを渡すことが困難になります。
対照的に、 Gforth ローダーはイメージのロード時に再配置を実行します。 また、 ローダーは、 プリミティブ呼び出しを表すトークンを適切なコード・フィールド・アドレス(直接スレッドの場合はコード・アドレス)に置き換える必要があります。
イメージ・ファイルには、 再配置の程度が異なる 3 種類があります。 つまり、 再配置不可能なイメージ・ファイルと、 データ再配置可能なイメージ・ファイルと、 完全に再配置可能なイメージ・ファイルです。
これらのイメージ・ファイルのバリエーションには、 いくつかの共通の制限があります。 それらは、 イメージ・ファイル・ローダーの設計によって引き起こされます:
ALLOCATE
されたメモリー・チャンク(およびそれらへのポインター)を表すことができないことを意味します。 スタックの内容も表現されません。
アドレスを含む複雑な計算が実行される場合、 結果はイメージ・ファイルで表現できません。 このような計算を使用するアプリケーションがいくつか思い浮かびます:
table
または
wordlist
を使用する場合、 システムの起動時にハッシュ・テーブルが自動的に再計算されるため、問題はありません。
あなた独自のハッシュ・テーブルを使用する場合は、 同様のことをあなた自身で行う必要があります。
XOR
されたアドレスを使用する二重リンク・リストの小さい実装があります。
このようなリストをイメージ・ファイル内で単一リンクとして表現し、 起動時に二重リンク表現を復元することもできます36。
docol:
のようなランタイム・ルーチンのコード・アドレスは、
イメージ・ファイルでは表現できません(直接スレッド実装ではトークンがマシン・コードに置き換えられるため)。 回避策として、 実行時に
>code-address
を使用して適切なワードの実行トークンからこれらのアドレスを計算します(kernel/getdoers.fs の docol:
とそのファミリーの定義を参照してください)。
CODE
ワードを再配置可能イメージ・ファイルに含めることはできません。
回避策は、アドレスを何らかの相対形式(たとえば、 レジスタに存在する CFA を基準とした相対形式)で表現するか、
アドレスが切り刻んでない(mangle)形式で格納されている場所からロードすることです。
これらのファイルは、 ディクショナリーの単純なメモリー・ダンプです。 これらは、 作成された実行可能ファイル(つまり、gforth ファイル)に固有のものです。さらに悪いことに、 それらは、 イメージが作成されたときにディクショナリーが存在していた場所に固有のものです。 ええ、はい、 次回 Gforth を起動するときにディクショナリが同じ場所に存在するという保証はありません。 したがって、 再配置不可能なイメージが次回動作するという保証もありません(ただし、 Gforth はクラッシュする代わりにエラーを出します)。 実際、 アドレス空間のランダム化(が有効になっている) OS では、 再配置不可能なイメージが機能する可能性は低いです。
savesystem
を使用して、 再配置不可能なイメージ・ファイルを作成できます。例:
gforth app.fs -e "savesystem app.fi bye"
savesystem
( "image" – ) gforth-0.2 “savesystem”
これらのファイルには、 再配置可能なデータ・アドレスが含まれていますが、 (トークンではなく)固定コード・アドレスが含まれています。 これらは、
作成された実行可能ファイル(つまり、gforth ファイル)に固有のものです。 また、
動的ネイティブ・コード生成も無効になります(通常、 速度が 2 倍になります)。 GFORTHD
環境変数を通じて使用するエンジンを
gforthmi (see gforthmi) に渡すと、 データ再配置可能なイメージを取得できます。
GFORTHD="/usr/bin/gforth-fast --no-dynamic" gforthmi myimage.fi source.fs
注意: ここでイメージが機能するには --no-dynamic
が必要であることに注意してください(そうしないと、
イメージに保存されていない動的に生成されたコードへの参照が含まれてしまいます)。
これらのイメージ・ファイルには、 再配置可能なデータ・アドレスとコード・アドレスのトークンが含まれています。 これらは、 同じマシン上で異なるバイナリ(デバッグの有無など)で使用でき、 同一データ形式(バイト・オーダー、 セル・サイズ、 浮動小数点形式)を持つ複数のマシン間でも使用でき、 動的なネイティブ・コード生成で動作します。 ただし、 これらは通常、 作成された Gforth のバージョンに固有です。 ファイル gforth.fi と kernl*.fi は完全に再配置可能です。
完全に再配置可能なイメージ・ファイルを作成するには 2 つの方法があります:
通常は gforthmi を使用します。 gforth options
で Gforth
を呼び出してロードするすべてのものを含むイメージ file を作成したい場合は、以下のように言うだけです:
gforthmi file options
たとえば、 通常のものに加えてファイル asm.fs がロードされたイメージ asm.fi を作成したい場合は、 以下のように実行できます:
gforthmi asm.fi asm.fs
gforthmi は sh スクリプトとして実装され、 次のように動作します: 異なるアドレスに対して 2 つの再配置不可能なイメージを生成し、 それらを比較します。 その出力はこの比較を反映しています: まず、 再配置不可能なイメージ・ファイルを生成する 2 つの Gforth 呼び出しの出力(存在する場合)が表示され、 次に比較プログラムの出力が表示されます: データ・アドレスに使用されるオフセットとコードアドレスに使用されるオフセットを表示します。 データ・アドレスに使用されるオフセットと使用されるオフセットが表示されます。 さらに、 イメージ・ファイル内で正しく表現できないセルごとに、 以下のような行が表示されます:
78DC BFFFFA50 BFFFFA40
これは、 forthstart
からのオフセット $78dc に、一方の入力イメージには $bffffa50 が含まれ、
もう一方の入力イメージには $bffffa40 が含まれていることを意味します。 これらのセルは出力イメージでは正しく表現できないため、
ディクショナリ内のこれらの場所を調べて、 これらのセルが死んでいること (つまり、書き込まれる前に読み取られていないこと)を確認する必要があります。
イメージ・ファイル名の前に --application
オプションを挿入すると、 --image-file
オプションの代わりに --appl-image
オプションを使用したイメージを取得します(see Invoking Gforth)。 (イメージ名をコマンドとして入力して、 )このようなイメージを Unix 上で実行すると、 Gforth エンジンは、
オプションをエンジン・オプションとして解釈しようとはせず、 すべてのオプションをイメージに渡します。
引数を指定せずに gforthmi と入力すると、 いくつかの使用説明が表示されます。
ちょっとだけ説明すると、 渡された gforth-options 処理後、 savesystem
と bye
というワードが現れる必要があります。 gforth 実行可能ファイルの特別な二重間接スレッド化(doubly indirect
threaded)バージョンは、 再配置不可能なイメージの作成に使用されます。 この実行可能ファイルの正確なファイル名は、 環境変数
GFORTHD
(デフォルト: gforth-ditc) を介して渡すことができます。
二重間接スレッド化ではないバージョンを渡すと、 完全に再配置可能なイメージではなく、
データ再配置可能なイメージ(see Data-Relocatable Image Files)を取得します。 これは、
コード・アドレス・オフセットがないためです。 通常の gforth 実行可能ファイルは、 再配置可能イメージの作成に使用されます。
この実行可能ファイルの正確なファイル名は、 環境変数 GFORTH
を介して渡すことができます。
cross
という、 Forth風のプログラミング言語を受け入れるバッチ・コンパイラーを使用することもできます(see Cross Compiler)。
cross
を使用すると、
イメージ・ファイルの生成に使用したものとは異なるデータ・サイズとデータ形式を持つマシン用のイメージ・ファイルを作成できます。これを使用して、 Forth
コンパイラーを含まないアプリケーション・イメージを作成することもできます。 これらの機能は、
プログラミング上の制限と不便を許容することで利用可能になります。 たとえば、 コードを再配置可能にするには、 アドレスを特別なワード
(A!
、 A,
など) を使用してメモリーに保存する必要があります。
サイズのコマンド・ライン・フラグを指定して Gforth を呼び出すと(see Invoking Gforth)、
指定したサイズがディクショナリーに保存されます。 savesystem
でディクショナリーを保存するか、 gforthmi
でイメージを作成すると、 このサイズが結果のイメージ・ファイルのデフォルトになります。 たとえば、 以下の例では、 1MB
のディクショナリーを備えた完全に再配置可能なバージョンの gforth.fi が作成されます。
gforthmi gforth.fi -m 1M
つまり、 ディクショナリーとイメージの、 スタックのデフォルト・サイズを設定したい場合は、 イメージの作成時に適切なオプションを指定して gforthmi を呼び出すだけです。
注意: キャッシュに優しい振る舞い(つまり、 良好なパフォーマンス)のために、 スタック達をわずかに異なるサイズにする必要があります。 キャッシュ・ラインを 2K としましょう。 例えば、 デフォルトのスタックサイズは、 2K で割ると剰余(mod)は次のようになっています: データ・スタック 16k (mod 2k=0); FPスタック 15.5k (mod 2k=1.5k); リターン・スタック 15k(mod 2k=1k); ローカル変数スタック 14.5k (mod 2k=0.5k)
-i
フラグを使用したデフォルトの gforth.fi の代わりに、 イメージ・ファイル image を使用して
Gforth を呼び出すことができます(see Invoking Gforth):
gforth -i image
オペレーティング・システムが #! ...
形式の行によるスクリプトの開始をサポートしている場合は、
イメージ・ファイル名を入力するだけで、 そのイメージ・ファイルで Gforth を起動できます(ファイル拡張子 .fi
は単なる慣例であることに注意してください)。 つまり、 イメージ・ファイル image を使用して Gforth を実行するには、
gforth -i image
の代わりに image と入力するだけです。 これが機能するのは、すべての
.fi
ファイルが以下の形式の行で始まるためです:
#! /usr/local/bin/gforth-0.4.0 -i
この行で指定された Gforth エンジンのファイルとパス名は、 そのエンジンがビルドされた特定の Gforth 実行可能ファイルです。 つまり、
gforthmi が実行されたときの環境変数 GFORTH
の値です。
同じシェル機能を利用して、 Forth ソース・ファイルを実行可能ファイルにできます。 たとえば、 以下のテキストをファイルに配置します:
#! /usr/local/bin/gforth ." Hello, world" CR bye
そして、 ファイルを実行可能にすると(Unix の場合は chmod +x)、 コマンド・ラインから直接実行できます。 シーケンス #!
は 2 つの側面から使用されます。 まず、 オペレーティング・システムによって「マジック・シーケンス」として認識され37、 次に Gforth
によってコメント文字として扱われます。 2 番目の使用法のため、#!
と実行可能ファイルへのパスの間に空白が必要です(さらに、 一部の
Unix ではシーケンス #! /
が必要です)。
ほとんどの Unix システム(Linux を含む)は、 バイナリ名の後に 1 つのオプションだけをサポートします。 それでは足りない場合は、 以下のトリックを使用できます:
#! /bin/sh : ## ; 0 [if] exec gforth -m 10M -d 1M $0 "$@" [then] ." Hello, world" cr bye \ caution: this prevents (further) processing of "$@"
最初に、 このスクリプトはシェル・スクリプトとして解釈され、 最初の 2 行が(ほとんど)コメントとして扱われ、 次に 3 行目が実行されると、
このスクリプト ($0
) をパラメーターとして、 そのパラメーターを追加パラメーター ("$@"
) として gforth
を呼び出します。 さらに次に、 このスクリプトは Forth スクリプトとして解釈され、 最初にコロン定義 ##
が定義され、 次に
[then]
までのすべてが無視され、 最後にそれに続く Forth コードが処理されます。 あなたは以下を使用することもできます
#0 [if]
2 行目ですが、 これは Gforth-0.7.0 以降でのみ機能します。
gforthmi のアプローチは最も高速で、 シェル・ベースのアプローチは最も遅くなります(追加のシェルを起動する必要があるため)。
シェル・アプローチの追加の利点は、 Gforth バイナリ が $PATH
内にある限り、 Gforth
バイナリがどこにあるかを知る必要がないことです。
#!
( – ) gforth-0.2 “hash-bang”
\
の alias です。
defer されたワード 'cold
を使用して、 イメージの起動シーケンスに独自の初期化を追加できます。 'cold
は、 イメージ固有のコマンドライン処理 (つまり、 ファイルのロードと (-e
) 文字列の評価)が開始される直前に呼び出されます。
初期化を追加するシーケンスは通常以下のようになります:
:noname Defers 'cold \ do other initialization stuff (e.g., rehashing wordlists) ... \ your stuff ; IS 'cold
'cold
の後、 Gforth はイメージ用のオプションを処理し(see Invoking Gforth)、 別の defer
されたワードである bootmessage
を実行します。 これは通常は、 Gforth の起動メッセージを出力し、
他には何も行いません。
したがって、 ターンキー・イメージ(つまり、 拡張された Forth システムではなくアプリケーション用のイメージ)を作成したい場合は、 以下のいくつかの方法でこれを行うことができます:
'cold
にフックします。 その場合、 エンジンが OS
コマンド・ライン・オプションを処理しないように、 gforthmi --application
(see gforthmi)を使用してイメージをビルドすることも必要になるでしょう。 その後、 next-arg
を使用してあなた独自のコマンドライン処理を実行できます。
process-option
にフックします。
options
ボキャブラリーでワードを定義します。
bootmessage
にフックします。
いずれの場合も、 あなたは、 おそらくこれらのフックで execute するワードを普通に終了さずに、 bye
または
throw
を使用したいでしょう。 そうしないと、 Gforth 起動プロセスが続行され、 最終的に Forth
コマンド・ラインがユーザーに表示されます。
'cold
( – ) gforth-0.2 “tick-cold”
OS コマンドライン引数を解釈(interpret)する直前に何かするためのフック( defer されたワード)。 あなたが実行したい幾つかの初期化を行うためにあります。 通常、 あなたが実行したいいくつかの初期化も行います。
bootmessage
( – ) gforth-0.4 “bootmessage”
OS コマンドライン引数を解釈(interpret)した直後のフック(deferされたワード)。 通常は Gforth 起動メッセージを出力します(訳注: つまり、 あなたが何かしらワードをセットすると起動メッセージは出力されなくなります)。
process-option
( addr u – true / addr u false ) gforth-0.7 “process-option”
オプション addr u を処理し、 オプションが処理された場合は true を返します。 未処理のオプションは
required
を通じてファイルとしてロードされます。
Gforth を使用したプログラミングにはこの章を読む必要はありません。 あなたが Gforth のソース・コード内を探索するのに役立つかもしれません。
このセクションのアイデアは次の論文にも掲載されています: Bernd Paysan, ANS fig/GNU/??? Forth (in German), Forth-Tagung ’93; M. Anton Ertl, A Portable Forth Engine, EuroForth ’93; M. Anton Ertl, Threaded code variations and optimizations (extended version), Forth-Tagung ’02.
Gforth プロジェクトの重要な目標の一つは、 様々なPCで使えることです。 fig-Forth と、 程度は低いですが F83 は、 当時人気のあったいくつかのプロセッサ向けにエンジンをアセンブリ言語で手動でコーディングすることで、 この目標を達成しました。 このアプローチは非常に労働集約的であり、 コンピュータ・アーキテクチャーの進歩につれ短命に終わってしまいます。
たとえば Mitch Bradley (cforth)や、 Mikael Patel (TILE)や、 Dirk Zoller (pfe) など、 C 言語でコーディングすることでこの問題を回避したモノもあります。 UNIX マシンのアーキテクチャーは多種多様であるため、 このアプローチは UNIX ベースの Forth で特に人気があります。 残念ながら、 C 言語での実装は、 効率の目標や、 従来の手法の使用とうまく調和しません。 間接または直接のスレッド化は C 言語では表現できず、 C 言語で利用できる最速の手法であるスイッチ・スレッディングは大幅に遅くなります。 C 言語 のもう 1 つの問題は、 2倍長整数の演算を表現するのが非常に面倒なことです。
幸いなことに、 これらの制限のない移植可能な言語が存在します。 それは、GNU C 言語コンパイラーによって処理される C 言語のバージョンである
GNU Cです(see Extensions to the C Language Family in GNU
C Manual)。 値としてのラベル機能(see Labels as Values in GNU
C Manual)により、 直接および間接的なスレッド化が可能になり、 その long long
型(see Double-Word Integers in GNU C Manual)は、 多くのシステムで Forth の 2倍長整数に対応します。
GNU C は、 すべての重要な(および多くの重要でない) UNIX マシン や VMS や MS-DOS を実行する 80386 や Amiga や
Atari ST で無料で利用できるため、 GNU C で書かれた Forth はこれらすべてのマシンで実行できます。
ポータブルな言語で記述すると、 アセンブリよりも遅いコードが生成されるという評判があります。 Forth エンジンでは、 コンパイラーによって生成されたコードを繰り返し確認し、 ソース・コードを適切に変更することで、 コンパイラーによって引き起こされる非効率性のほとんどを排除しています。
しかしながら、 プログラマはレジスター割り当てに移植可能な影響を及ぼすことができないため、 レジスターが不足しているマシンでは非効率が発生します。
一部のマシンでは速度を向上させるために、 明示的なレジスター宣言を使用しています(see Variables in Specified Registers in GNU C Manual)。 これらは、 構成フラグ
--enable-force-reg
(gcc
スイッチ -DFORCE_REG
) を使用してオンにします。
残念ながら、 この機能はマシンだけでなくコンパイラーのバージョンにも依存します。 一部のマシンでは、 特定の明示的なレジスター宣言が使用されると、
一部のコンパイラー・バージョンでは不正なコードを生成します。 したがって、 デフォルトでは -DFORCE_REG
は使用されません。
GNU C の「ラベルとしての値」の拡張機能(gcc-2.0
以降で有効;see Labels as Values in GNU C Manual)により、 &&label
と書くことにより、
label のアドレスを取得できるようになります。 このアドレスは、 goto *address
のようなステートメントで使用できます。 つまり、 goto *&&x
は goto x
と同じです。
この機能を使用すると、間接スレッド化された NEXT
は以下のようになります:
cfa = *ip++; ca = *cfa; goto *ca;
上記の言葉に馴染みのない人のために説明すると、 ip
は Forth 命令ポインタです。 cfa
(コード・フィールド・アドレス) は標準 Forth の実行トークンに対応するもので次に実行されるワードのコード・フィールドを指します。
そこからフェッチされた ca
(コード・アドレス)は、
プリミティブやコロン定義ハンドラー(docol
)などの実行可能コードを指します。
直接スレッド化はさらに簡単です:
ca = *ip++; goto *ca;
もちろん、 我々は、 NEXT
と NEXT1
(cfa を取得した後の NEXT
の部分) というマクロに、
全体をきちんとパッケージ化しています。
スケジューリングについては少々複雑です。 パイプライン・プロセッサとスーパースカラ・プロセッサ、 つまり RISC と一部の最新の CISC マシンは、
ある命令の結果が出るのを待つ間に別の命令を処理できます。 通常、 コンパイラーは、
これらの遅延スロットを有効に使用できるように命令を並べ替え(スケジューリング)します。 ただし、 我々の最初の挑戦では、
コンパイラーはプリミティブのスケジューリングをうまく機能させられませんでした。 たとえば、 +
は以下のように実装されます
n=sp[0]+sp[1]; sp++; sp[0]=n; NEXT;
NEXT
は厳密に他のコードの後に配置されます。 つまり、 スケジューリングはまずありえません。
ちょっと考えると問題が明らかになってきます: コンパイラーは、 sp
と ip
が異なるアドレスを指していることを認識できないため(そして、 たとえ認識が可能だったとしても、 私たちが使用した gcc
のバージョンはそれを知りません。)、 ストア上の cfa の負荷を TOS に移動できませんでした。 実際、
スタックのTOSまたはTOS付近に対するコードが実行された場合、 これらのポインターは同一になる可能性があります。 速度を重視して、 私達は、 この、
おそらく未使用の「認識機能」を禁止した上でコンパイラーのスケジューリングを支援します(NEXT
を幾つかの部分、 すなわち、
NEXT_P0
と NEXT_P1
と NEXT_P2
に分割します): そうすると、 +
は以下のようになります:
NEXT_P0; n=sp[0]+sp[1]; sp++; NEXT_P1; sp[0]=n; NEXT_P2;
さまざまな操作を NEXT の部分間でいくつかの方法で分散するさまざまな仕組みがあります。 一般に、 異なる仕組みは異なるプロセッサ上で最高のパフォーマンスを発揮します。 私たちは、 ほとんどのアーキテクチャーに対して、 そのアーキテクチャーのほとんどのプロセッサで良好に動作する仕組みを使用しています。 将来的には、 ベンチマークを実施し、 インストール時に仕組みを選択するように切り替える可能性があります。
スレッド化された forth コードは、 プリミティブ(+
のような単純なマシン・コード・ルーチン)と非プリミティブ(コロン定義、 変数、
定数など)への参照で構成されます。 非プリミティブの特定の種類(変数など)では、 コード・ルーチン(dovar
など) は 1
つだけですが、 各変数は各変数それぞれのデータへの個別の参照が必要です。
伝統的に、 Forth は間接スレッド化コードとして実装されてきました。 これにより、 非プリミティブの参照は 1 つのセルのみの使用で済ませられるためです(基本的に、 データをポイントし、 ポイントした先でコード・アドレスを見つけます)。
しかしながら Gforth (0.6.0 以降) のスレッド化コードは、 非プリミティブに 2 つのセルを使用します。 1 つはコード・アドレス用で、
もう 1 つはデータ・アドレス用です。 データ・ポインターは、 コード・アドレスで表される仮想マシン命令の直接の引数です。
すべてのコード・アドレスが単純なプリミティブを指しているため、 これを「プリミティブ指向」スレッド化コードと呼びます。 たとえば、 変数の場合、
コード・アドレスは lit
用です(99
などの整数リテラルにも使用されます)。
プリミティブ指向スレッド化コードにより、 ディスパッチ手法として(より高速な)直接スレッド化を完全に移植可能な状態で使用できます(0.6.0 より前の Gforth の直接スレッド化コードにはアーキテクチャー固有のコードが必要でした)。 また、386 実装で直接スレッド・コードによる I キャッシュの一貫性に関連するパフォーマンスの問題も解消され、 追加の最適化が可能になります。
ただし、 問題があります。 execute
の xt パラメーターは 1 つのセルしか占有できないため、
「コードとデータ・アドレス」を持つ非プリミティブをどのように渡せばよいのでしょうか? これに対する私達の答えは、 execute
や単一セル xt を使用する他のワードに間接スレッド化ディスパッチを使用することです。 したがって、
コロン定義内の通常のスレッド化コードは直接スレッドを使用し、 データ・スタック上の xt にディスパッチする execute
や同様のワードは間接スレッド化コードを使用します。 これを 「ハイブリッド 直接/間接 スレッド化コード」と呼びます。
gforth
エンジンと gforth-fast
エンジンは、 ハイブリッド 直接/間接
スレッド化コードを使用します。 つまり、 これらのエンジンでは ,
を使用して xt をコンパイルできないことを意味します。
代わりに、 compile,
を使用する必要があります。
,
を使用して xt をコンパイルする場合は、 gforth-itc
エンジンを使用します。 このエンジンは、
単純な古い間接スレッド化コードを使用します。 しかし、 それでもプリミティブ指向のスタイルでコンパイルされるため、 ,
の代わりに
compile,
を使用することはできません(たとえば、 ] word1 word2 ... [
で xt
達のテーブルを生成する場合)。 それを行いたい場合は、 gforth-itc
を使用して ' , is
compile,
を実行する必要があります。 あなたのプログラムでは、 threading-method
を使用して、 ハイブリッド
直接/間接 スレッド化エンジンで実行されているか、 純粋な間接スレッド化エンジンで実行されているかを確認できます。
gforth
エンジンと gforth-fast
エンジンは、 別の最適化、 つまり、
レプリケーションを伴う動的スーパー命令(Dynamic superinstructions with replication)を使用します。
例として、 以下のコロン定義について考えてみましょう:
: squared ( n1 -- n2 ) dup * ;
Gforth はこれを以下のスレッド化コード・シーケンスにコンパイルします
dup * ;s
simple-see
(see Examining compiled code)を使用して、
コロン定義のスレッド化コードを確認します。
通常の直接スレッド化コードでは、 上記のプリミティブごとに 1 つのセルを占めるコード・アドレスがあります。
各コード・アドレスはマシン・コード・ルーチンを指し、 インタープリターはプリミティブを実行するためにこのマシン・コードにジャンプします。 上記 3
つのプリミティブのルーチンは以下のとおりです(386 の gforth-fast
の場合):
Code dup ( $804B950 ) add esi , # -4 \ $83 $C6 $FC ( $804B953 ) add ebx , # 4 \ $83 $C3 $4 ( $804B956 ) mov dword ptr 4 [esi] , ecx \ $89 $4E $4 ( $804B959 ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code Code * ( $804ACC4 ) mov eax , dword ptr 4 [esi] \ $8B $46 $4 ( $804ACC7 ) add esi , # 4 \ $83 $C6 $4 ( $804ACCA ) add ebx , # 4 \ $83 $C3 $4 ( $804ACCD ) imul ecx , eax \ $F $AF $C8 ( $804ACD0 ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code Code ;s ( $804A693 ) mov eax , dword ptr [edi] \ $8B $7 ( $804A695 ) add edi , # 4 \ $83 $C7 $4 ( $804A698 ) lea ebx , dword ptr 4 [eax] \ $8D $58 $4 ( $804A69B ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code
動的なスーパー命令とレプリケーションを使用すると、 コンパイラーはスレッド化されたコードを配置しないだけでなく、 コード断片のコピーも行い、 通常は各コード断片最後で行うジャンプを行いません。
( $4057D27D ) add esi , # -4 \ $83 $C6 $FC ( $4057D280 ) add ebx , # 4 \ $83 $C3 $4 ( $4057D283 ) mov dword ptr 4 [esi] , ecx \ $89 $4E $4 ( $4057D286 ) mov eax , dword ptr 4 [esi] \ $8B $46 $4 ( $4057D289 ) add esi , # 4 \ $83 $C6 $4 ( $4057D28C ) add ebx , # 4 \ $83 $C3 $4 ( $4057D28F ) imul ecx , eax \ $F $AF $C8 ( $4057D292 ) mov eax , dword ptr [edi] \ $8B $7 ( $4057D294 ) add edi , # 4 \ $83 $C7 $4 ( $4057D297 ) lea ebx , dword ptr 4 [eax] \ $8D $58 $4 ( $4057D29A ) jmp dword ptr FC [ebx] \ $FF $63 $FC
スレッド化コードの制御フローに変更が発生した場合(;s
など)にのみジャンプが追加されます。 この最適化により、
これらのジャンプの多くが排除され、 残りの部分がより予測可能になります。 高速化はプロセッサとアプリケーションによって異なります。 Athlon
および Pentium III では、 この最適化により通常 2 倍の速度向上が得られます。
直接スレッド化コード内のコード・アドレスは、 コピーされたマシン・コード内の適切なポイントを指すように設定されます。 この例では以下のようになります:
primitive code address dup $4057D27D * $4057D286 ;s $4057D292
したがって、 このコード部分の任意の場所にスレッド化コードからのジャンプ先が存在する可能性があります。 これにより、 逆コンパイルもかなり簡素化されます。
See-code
(see Examining compiled code)は、 動的スーパー命令(dynamic
superinstructions)のネイティブ・コードと混在するスレッド化コードを示します。 最近では、
動的に生成されたネイティブ・コードに追加の最適化が適用されているため、 特定の AMD64 インストール上の gforth-fast
での
see-code squared
の出力は以下のようになります:
$7FB689C678C8 dup 1->2 7FB68990C1B2: mov r15,r8 $7FB689C678D0 * 2->1 7FB68990C1B5: imul r8,r15 $7FB689C678D8 ;s 1->1 7FB68990C1B9: mov rbx,[r14] 7FB68990C1BC: add r14,$08 7FB68990C1C0: mov rax,[rbx] 7FB68990C1C3: jmp eax
--no-dynamic を使用してこの最適化を無効にできます。 --no-super を使用すると、 ジャンプを排除せずにコピーを使用できます(つまり、 動的レプリケーション、 しかしスーパー命令は使用しません)。 これは分岐予測のみに利点をもたらします。 パフォーマンスへの影響は CPU によって異なります。 Athlon および Pentium III では、 レプリケーションを伴う動的スーパー命令(dynamic superinstructions with replication)よりも高速化が若干損なわれます。
これらのオプションの用途の 1 つは、 スレッド化されたコードにパッチを適用する場合です。 スーパー命令を使用すると、 ディスパッチ・ジャンプの多くが削除されるため、 パッチを当てても効果がなくなることがよくあります。 これらのオプションは、 すべてのディスパッチ・ジャンプを保持します。
一部のマシンでは動的スーパー命令(dynamic superinstructions)は安全ではないため、 デフォルトで無効になっています。 しかしながら、 あなたが冒険してみたい場合は、 --dynamic を使用して有効にすることができます。
Forth エンジンの最も複雑な部分の 1 つは dodoes
です。つまり、 CREATE
...DOES>
ペアで定義されたすべてのワードによって実行されるコードの塊(chunk)です。 実際のところプリミティブ指向のコードでは、
CREATE
...DOES>
ペアで定義されたすべてのワードによって実行されるコードの塊(chunk)は、 ワードの xt
が execute
された場合にのみ必要です。 ここでの主な問題は、 実行される Forth コード、 つまり DOES>
の後のコード(the DOES>
-code) をどのようにして見つけるかということです。 この解決策は 2 つあります:
fig-Forth では、 コード・フィールドは dodoes
を直接指しており、 DOES>
コード・アドレスはコード・アドレスの後のセル(つまり CFA cell+
)に格納されます。 この解決策は、 Forth-79
以降のすべての標準では違法であるように見えるかもしれません。 fig-Forth ではこのアドレスがボディ内にあるためです(標準では違法です)。
しかしながら、 すべてのワードに対してコード・フィールドを大きくすることで、 この解決策は再び有効になります。 私たちはこのアプローチを採用しています。
ほとんどの場合、 セルを未使用のままにしておくのはちょっと無駄ですが、 私たちが対象としているマシンでは、 これはほとんど問題になりません。
プリミティブは移植可能な言語(portable language)で実装されているため、 最早プリミティブの数を最小限に抑える必要はありません。 逆に、 多くのプリミティブを持つことには速度という利点があります。 プリミティブでのエラーの数を減らし、 プリミティブのプログラミングを容易にするために、 プリミティブ・ジェネレーター (prims2x.fs 別名 Vmgen see Introduction in Vmgen)というツールを提供しており、 それは、 スタック効果の表記からプリミティブの C言語コードの大部分(場合によってはすべて)を自動的に生成します。 プリミティブのソースの形式は以下のとおりです:
Forth-name ( stack-effect ) category [pronounce] [""
glossary entry""
] C code [:
Forth code]
角括弧(brackets)内の項目はオプションです。
カテゴリ(category)と発音(pronounce)と用語集(glossary)のフィールドはドキュメントを生成するためにあり、 Forth
コード(Forth code)は GNU C を持たないマシンでの手動実装のためにあります。 たとえば、 プリミティブ +
のソースは以下のとおりです:
+ ( n1 n2 -- n ) core plus n = n1+n2;
これは仕様のように見えますが、 実際には n = n1+n2
は C言語のコードです。 私たちのプリミティブ生成ツールは、
スタック効果の表記から多くの情報を抽出します38: それらは、 スタックからポップされた項目とスタックにプッシュされた項目の数、 その型、
および C言語のコード内で参照される名前 です。 その次に、 各プリミティブの C言語コードの prelude と postlude を生成します。
+
の最終的な C言語コードは以下のようになります:
I_plus: /* + ( n1 n2 -- n ) */ /* label, stack effect */ /* */ /* documentation */ NAME("+") /* debugging output (with -DDEBUG) */ { DEF_CA /* definition of variable ca (indirect threading) */ Cell n1; /* definitions of variables */ Cell n2; Cell n; NEXT_P0; /* NEXT part 0 */ n1 = (Cell) sp[1]; /* input */ n2 = (Cell) TOS; sp += 1; /* stack adjustment */ { n = n1+n2; /* C code taken from the source */ } NEXT_P1; /* NEXT part 1 */ TOS = (Cell)n; /* output */ NEXT_P2; /* NEXT part 2 */ }
これは長くて非効率に見えますが、 GNU C コンパイラーは非常に適切に最適化して、 例えば R3000 や HP RISC マシンなどで
+
に最適なコードを生成します。 n
達を定義してもコードは生成されませんし、 また、
それら中間ストレージとして使用してもコストはかかりません。
この例では示されていない他の最適化もあります。 単純な変数間の代入は通常はコストがかかりません(コピー伝播)。 スタック項目の 1
つがプリミティブによって使用されていない場合(drop
など)、
コンパイラーはスタックのその部分の負荷を削除します(デッド・コードの削除)。 一方、 コンパイラーが実行しない処理もいくつかあり、 そのため、 それらは
prims2x.fs によって実行されます。 コンパイラは、 スタック項目を元の場所に格納するコードを最適化しません(例:
over
).
プリミティブのプログラミングは通常は簡単ですが、 最も顕著な ?dup
だけでなく、 NEXT
に(常に)フローが流れていかないワードについても、 プログラマがジェネレーターのアクションを考慮する必要があるケースがいくつかあります。
詳細について(原文未記述)
Forth エンジンなどのスタック・マシン・エミュレータの重要な最適化は、 1 つ以上の最上位側スタック項目をレジスターに保持することです。
ワードにスタック効果 ( in1...inx -- out1...outy )
がある場合、 上位の
n 項目をレジスターに保持します:
特に、 レジスターの数が十分であれば、 1 つの項目をレジスターに保持しておくことは決して不利にはなりません。 ?branch
や定数や変数やリテラルや i
などの頻繁に使用されるワードにとって、 2 つの項目をレジスターに保持することは不利になります。
したがって、 ジェネレーターは、 レジスターに 0 個または 1 つの項目を保持するコードのみを生成します。 生成された
C言語のコードは両方のケースをカバーします。 これらの選択肢の選択は、 C言語のコンパイル時にスイッチ -DUSE_TOS
を使用して行われます。 +
の C言語コードの TOS
は、 1 項目の場合は単なる変数名ですが、 それ以外の場合は
sp[0]
に展開されるマクロです。 注意: GNU C コンパイラーは TOS
のような単純な変数をレジスターに保持しようと試み、 十分な数のレジスターがあれば、 それは通常は成功することに注意してください。
プリミティブ・ジェネレーターは、 浮動小数点数スタックの TOS 最適化も実行します(-DUSE_FTOS
)。 浮動小数点数演算の場合、
この最適化の利点はさらに大きくなります。 浮動小数点数演算は、 ほとんどのプロセッサで非常に時間がかかりますが、
その結果が利用されない間は他の演算と並行して実行できます。 FP-TOS がレジスターに保持されている場合、 他の演算との並行しての実行が機能します。
スタック上、 つまりメモリー内に保持されている場合、 メモリーへのストアは浮動小数点数演算の結果を待つ必要があり、
プリミティブの実行時間が大幅に長くなります。
TOS の最適化により、 プリミティブの自動生成がちょっぴり複雑になります。 sp[0]
のすべての出現を TOS
に置き換えるだけでは十分ではありません。 考慮すべき特殊なケースがいくつかあります:
dup ( w -- w w )
で、 TOS 最適化がオンになっている場合、
ジェネレーターはスタック上の項目の元の場所へのストアを削除してはなりません。
(-- out1...outy)
の形式のスタック効果を持つプリミティブは、 開始時に TOS
をスタックに保存する必要があります。 同様に、 スタック効果 in1...inx --)
を持つプリミティブは、
最後にスタックから TOS をロードする必要があります。 ただし、 null スタック効果 --
の場合、
ストアやロードは生成されません。
コンパイラーとフラグ設定を使用してマシン上のプリミティブに対してどのようなアセンブリ・コードが生成されるかを確認するには、 make
engine.s
と入力し、結果のファイル engine.s を確認します。 あるいは、 一部のアーキテクチャでは
see
を使用してプリミティブのコードを逆アセンブルすることもできます。
RISC では、Gforth エンジンは限りなく最適な状態に近くなります。 逆に、 通常、 RISC以外では、 顕著に高速なスレッド化コード・エンジンを作成することは不可能です。
386 アーキテクチャー・プロセッサのようにレジスターの数が不足しているマシンでは、 明示的なレジスター宣言があっても gcc
が人間ほどにはレジスターを利用しないため、 人間による改善が可能です。 たとえば、Bernd Beuster は Forth
システムの断片をアセンブリ言語で作成し、 486 用に手動で調整しました。 このシステムは、 486DX2/66 での Sieve ベンチマークでは、
-DFORCE_REG
を使用して gcc-2.6.3
でコンパイルした Gforth よりも 1.19 倍高速です。
この状況は gcc-2.95 と gforth-0.4.9 で改善されました。 これで、
最も重要な仮想マシンのレジスターが実際のレジスターに収まるようになり(更に、 TOS 最適化を使用する余裕さえあります)、 結果として以前の結果よりも
1.14 高速化されました。 また、動的スーパー命令によりさらに高速化が実現されます(ただし、 486 では約 1.2 倍にすぎません)。
アセンブリ言語で実装する潜在的な利点は、 Forth システムでは必ずしも現実のモノにはなりません。 Gforth
をアセンブリ言語で書かれたシステムと比較しました: Gforth-0.5.9 (直接スレッド化、 gcc-2.95.1
、
-DFORCE_REG
でコンパイル)、 Gforth-0.5.9 の Win32Forth 版 1.2093
(新しいバージョンははるかに高速であると報告されています))、 LMI の NT Forth (1994年5月のベータ版)、
Eforth(スレッド化されたコードののぞき穴(peephole; 別名 針の穴(pinhole))最適化の有無)。 また、Gforth を
C言語で書かれた 3 つのシステムと比較しました: PFE-0.9.14 (Linux のデフォルト構成で gcc-2.6.3
でコンパイル: -O2 -fomit-frame-pointer -DUSE_REGS -DUNROLL_NEXT
)、 ThisForth
Beta (gcc-2.6.3 -O3 -fomit-frame-pointer
でコンパイル; ThisForth
はスレッド化されたコードののぞき穴最適化を採用)、 TILE (make opt
でコンパイル)。 Linux 上の 486DX2/66
で、 Gforth と PFE と ThisForth と TILE のベンチマークを実行しました。 Kenneth O’Heskin は、
Windows NT で同様のメモリー・パフォーマンスを備えた 486DX2/66 上の Win32Forth と NT Forth
の結果を親切に提供してくれました。 Marcel Hendrix は Eforth を Linux に移植し、 ベンチマークを実行できるように拡張し、
覗き穴・オプティマイザーを追加してベンチマークを実行し、 結果を報告しました。
私たちは 4 つの小さなベンチマークを使用しました。 ユビキタスのふるい。 バブルソートと行列乗算はスタンフォード整数ベンチマーク由来で、 Martin Fraeman によって Forth に変換されました。 TILE Forth パッケージに含まれているバージョンを使用しましたが、 データ・セット・サイズが大きくなりました。 そして、 呼び出しパフォーマンス(calling performance)のベンチマークのための再帰的フィボナッチ数計算です。 以下の表は、 ベンチマークにかかる時間を Gforth にかかった時間で換算したものです(つまり、 Gforth が他のシステムに対して達成した高速化係数を示しています)。
relative Win32- NT eforth This- time Gforth Forth Forth eforth +opt PFE Forth TILE sieve 1.00 2.16 1.78 2.16 1.32 2.46 4.96 13.37 bubble 1.00 1.93 2.07 2.18 1.29 2.21 5.70 matmul 1.00 1.92 1.76 1.90 0.96 2.06 5.32 fib 1.00 2.32 2.03 1.86 1.31 2.64 4.55 6.54
あなたは、 アセンブリ言語で書かれたシステムと比較したとき、 Gforth の優れたパフォーマンスに驚かれるかもしれません。
これら他のシステムのパフォーマンスが期待外れである重要な理由の 1 つは、 おそらく、 それらが 486
用に最適化して書かれていないことです(たとえば、lods
命令を使用している)。 さらに、 Win32Forth は、 Forth
イメージを再配置するために快適ではありますがコストのかかる方法を使用します: これは cforth
と同様に、
実行時に実際のアドレスが計算され、 NEXT
毎に 2 つのアドレス計算が行われます(see Image File Background)。
PFE や ThisForth や TILE に対する Gforth の高速化は、 前者達のシステムが標準 C に対して自ら課した制限によって簡単に説明できます: これにより、 効率的なスレッド化が不可能になります(ただし、 観測された PFE の実装では GNU C 拡張機能が使用されています see Defining Global Register Variables in GNU C Manual)。 さらに言えば、 現在の C コンパイラーは、 ThisForth や TILE ソースの他の側面を最適化するのに苦労しています。
386 アーキテクチャー・プロセッサ上の Gforth のパフォーマンスは、 使用する gcc
のバージョンによって大きく異なります。
たとえば、gcc-2.5.8
は、 それ自身では仮想マシン・レジスターを実マシン・レジスターに割り当てることに失敗し、 かつ、
明示的なレジスター宣言では正しく動作しないため、 (Sieve ベンチマークを実行している 486DX2/66
では、)上記で測定したエンジンよりも大幅に遅いエンジンが出来上がります。
注意: ここで紹介したリリース以降、 Win32Forth のリリースがいくつかあるため、 上記の結果は現在の Win32Forth のパフォーマンスを予測する値がほとんどない可能性があることに注意してください(i486DX2/66 での現在のリリースの結果報告をお待ちしています)。
Translating Forth to Efficient C by M. Anton Ertl and Martin Maierhofer (presented at EuroForth ’95) では、 Gforth の間接スレッド化バージョンが、 Win32Forth や NT Forth や PFE や ThisForth や、 いくつかのネイティブ・コード・システムと比較されます。 そのバージョンの Gforth は、 486 ではここで使用されているバージョンよりも遅くなります。 これらの測定値の新しいバージョンは https://www.complang.tuwien.ac.at/forth/performance.html で見つけることができます。 あなたは Benchres でさまざまなマシン上の Gforth の数値を見つけることができます。
クロス・コンパイラーは、 Forth カーネルをブート・ストラップするために使用されます。 Gforth は、 外部インタープリターやコンパイラなどの重要な部分を含め、 ほとんどが Forth で書かれているため、 開始するにはコンパイル済みの Forth コードが必要です。 クロス・コンパイラを使用すると、 他のアーキテクチャー用の新しいイメージを作成でき、 別の Forth システムで実行することもできます。
クロス・コンパイラは、 Forth そっくりの言語を使用しますが、 Forth ではありません。 主な違いは、 Forth コードは定義後に実行できるのに対し、 cross によってコンパイルされたコードは通常は実行できないことです。 これは、 コンパイルしているコードは通常、 コンパイルしているコンピュータとは異なるコンピュータ用であるためです。
Makefile はすでにセットアップされており、 簡単な make コマンドで新しいアーキテクチャ用のカーネルを作成できるようになります。 GCC
でコンパイルされた仮想マシンを使用する汎用カーネルは、 make
を使用した通常のビルド・プロセスで作成されます。 たとえば、 8086
プロセッサ用の埋め込み Gforth 実行可能ファイル(embedded Gforth executable)(DOS マシン上で実行)を作成するには、
以下のようにタイプします
make kernl-8086.fi
これにより、 arch/8086 ディレクトリのマシンの説明(machine description)が使用されて、 新しいカーネルが作成されます。 マシン・ファイルは以下のようになります:
\ Parameter for target systems 06oct92py 4 Constant cell \ cell size in bytes 2 Constant cell<< \ cell shift to bytes 5 Constant cell>bit \ cell shift to bits 8 Constant bits/char \ bits per character 8 Constant bits/byte \ bits per byte [default: 8] 8 Constant float \ bytes per float 8 Constant /maxalign \ maximum alignment in bytes false Constant bigendian \ byte order ( true=big, false=little ) include machpc.fs \ feature list
この部分はクロス・コンパイラにとって必須であり、 機能リスト(feature list)は、 ターゲットがこれらの機能をサポートしているかどうかに応じて、 いくつかの機能を条件付きでコンパイルしたりしなかったりするためにカーネルによって使用される。
あなた独自のプリミティブを定義する場合、 またはアセンブラーを使用する場合、 またはブート・プロセスを機能させるために特別な非標準の準備が必要な場合は、
オプションの機能(feature)がいくつかあります。 asm-include
にはアセンブラーが含まれ、
prims-include
にはプリミティブが含まれ、 >boot
はブートの準備をします。
: asm-include ." Include assembler" cr s" arch/8086/asm.fs" included ; : prims-include ." Include primitives" cr s" arch/8086/prim.fs" included ; : >boot ." Prepare booting" cr s" ' boot >body into-forth 1+ !" evaluate ;
これらのワードは、 ファイル kernel/main.fs をクロス・コンパイルする時に一種のマクロとして使用されます。 これらのマクロを使用する代わりに、 新しいカーネル・プロジェクト・ファイルを作成することも可能ですが、 より複雑になります。
kernel/main.fs はスタック上にマシン記述ファイル名(machine description file
name)を期待します。 クロス・コンパイラ自体(cross.fs)は、 mach-file
がカウンタ付き文字列をスタックに残すか、 または machine-file
がファイル名のアドレスとカウントのペアをスタックに残すかのいずれかを想定します。
機能リスト(feature list)は通常 SetValue
を使用して制御されますが、
複数のプロジェクトで使用される汎用ファイルは代わりに DefaultValue
を使用できます。 どちらのワードも、
値が定義されていない場合は Value
のように動作しますが、 値が定義されている場合は SetValue
は
to
のように動作し、 値が定義されている場合は DefaultValue
は何も設定しません。
\ generic mach file for pc gforth 03sep97jaw true DefaultValue NIL \ relocating >ENVIRON true DefaultValue file \ controls the presence of the \ file access wordset true DefaultValue OS \ flag to indicate a operating system true DefaultValue prims \ true: primitives are c-code true DefaultValue floating \ floating point wordset is present true DefaultValue glocals \ gforth locals are present \ will be loaded true DefaultValue dcomps \ double number comparisons true DefaultValue hash \ hashing primitives are loaded/present true DefaultValue xconds \ used together with glocals, \ special conditionals supporting gforths' \ local variables true DefaultValue header \ save a header information true DefaultValue backtrace \ enables backtrace code false DefaultValue ec false DefaultValue crlf cell 2 = [IF] &32 [ELSE] &256 [THEN] KB DefaultValue kernel-size &16 KB DefaultValue stack-size &15 KB &512 + DefaultValue fstack-size &15 KB DefaultValue rstack-size &14 KB &512 + DefaultValue lstack-size
MINOS2 は、 mini-oof2.fs のオブジェクト・モデルで書かれた GUI ライブラリです。 これには 2 つの主要なクラス階層があります:
actor
( – class ) minos2 “actor”
コンポーネントに結び付けられたされたアクションのクラス。
widget
( – class ) minos2 “widget”
ビジュアル・コンポーネントのクラス
actor
methods: ¶caller-w
( – optr ) minos2 “caller-w”
アクターを埋め込むウィジェットへのポインター
active-w
( – optr ) minos2 “active-w”
アクターを埋め込むアクティブなサブウィジェットへのポインター
act-name$
( – addr u ) minos2 “act-name-string”
デバッグ支援: アクターの名前
clicked
( rx ry bmask n – ) minos2 “clicked”
処理されたクリック(達)
scrolled
( axis dir – ) minos2 “scrolled”
スクロール処理
touchdown
( $rxy*n bmask – ) minos2 “touchdown”
生のクリック・ダウン
touchup
( $rxy*n bmask – ) minos2 “touchup”
生のクリック・アップ
ukeyed
( addr u – ) minos2 “ukeyed”
キーイベント。 印刷可能な Unicode 文字の文字列
ekeyed
( ekey – ) minos2 “ekeyed”
キーイベント。 印刷不可能なキー
?inside
( rx ry – act / 0 ) minos2 “query-inside”
座標がウィジェット内にあるかどうかをチェック。
focus
( – ) minos2 “focus”
ウィジェットにフォーカスする
defocus
( – ) minos2 “defocus”
ウィジェットをフォーカスから外す
entered
( – ) minos2 “entered”
カーソルがウィジェット領域に入ると反応します
left
( – ) minos2 “left”
カーソルがウィジェット領域から離れると反応します
show
( – ) minos2 “show”
ウィジェットが表示されます
hide
( – ) minos2 “hide”
ウィジェットを非表示にします
get
( – something ) minos2 “get”
ウィジェットの背後にある値のゲッター
set
( something – ) minos2 “set”
ウィジェットの背後にある値のセッター
show-you
( – ) minos2 “show-you”
ウィジェットを表示させる
widget
methods: ¶parent-w
( – optr ) minos2 “parent-w”
親ウィジェットへのポインター
act
( – optr ) minos2 “act”
アクターへのポインター
name$
( – addr u ) minos2 “name-string”
デバッグと検索用のウィジェット名
x
( – r ) minos2 “x”
ウィジェット x 座標
y
( – r ) minos2 “y”
ウィジェットの y 座標
w
( – r ) minos2 “w”
ウィジェットの幅
h
( – r ) minos2 “h”
ベースラインより上のウィジェットの高さ
d
( – r ) minos2 “d”
ベースラインより下のウィジェットの深さ
gap
( – r ) minos2 “gap”
行間の隙間
baseline
( – r ) minos2 “baseline”
行ごとの最小スキップ
kerning
( – r ) minos2 “kerning”
カーニングを追加
raise
( – r ) minos2 “raise”
ボックスを 上げる/下げる
border
( – r ) minos2 “border”
周囲の境界線、全方向
borderv
( – r ) minos2 “borderv”
垂直境界線のオフセット
bordert
( – r ) minos2 “bordert”
上枠のオフセット
borderl
( – r ) minos2 “borderl”
左境界オフセット
w-color
( – r ) minos2 “w-color”
ウィジェットのカラー・インデックス(カラーマップ内)(存在する場合)
draw-init
( – ) minos2 “draw-init”
draw 初期化
draw
( – ) minos2 “draw”
ウィジェットを描く
split
( firstflag rstart1 rx – o rstart2 ) minos2 “split”
ウィジェットを段落を植字するための部分(parts for typesetting paragraphs)に分割します
lastfit
( – ) minos2 “lastfit”
最後のウィジェット要素をボックスに収めます(fit)
hglue
( – rtyp rsub radd ) minos2 “hglue”
水平グルーを計算
dglue
( – rtyp rsub radd ) minos2 “dglue”
ベースラインの下の垂直グルーを計算
vglue
( – rtyp rsub radd ) minos2 “vglue”
ベースラインより上の垂直グルーを計算
hglue@
( – rtyp rsub radd ) minos2 “hglue-fetch”
hglue
のキャッシュされたバリエーション
dglue@
( – rtyp rsub radd ) minos2 “dglue-fetch”
dglue
のキャッシュされたバリエーション
vglue@
( – rtyp rsub radd ) minos2 “vglue-fetch”
vglue
のキャッシュされたバリエーション
xywh
( – rx0 ry0 rw rh ) minos2 “xywh”
ウィジェット境界(bound)ボックス、左上隅から開始
xywhd
( – rx ry rw rh rd ) minos2 “xywhd”
左のベースライン点から始まるウィジェット境界(bound)ボックス
!resize
( rx ry rw rh rd – ) minos2 “store-resize”
ウィジェットのサイズ変更
!size
( – ) minos2 “store-size”
ウィジェットにサイズを自己決定させます
dispose-widget
( – ) minos2 “dispose-widget”
ウィジェットを削除
.widget
( – ) minos2 “print-widget”
デバッグ: ウィジェットに関する情報を出力
par-split
( rw – ) minos2 “par-split”
段落を幅 rw で分割します
resized
( – ) minos2 “resized”
ウィジェットのサイズが変更されました
コンポーネントは、 段落区切りを含め、 LaTeX に似た box&glue モデルを使用して構成されます。 簡素化と移植性のため、 MINOS2 は 1 つのウィンドウのみをサポートし、 レンダリングには OpenGL を使用します。
MINOS2 はさらに、 animation
クラスを使用したアニメーションをサポートします。
カラー・インデックス・テクスチャはさまざまなカラー・スキームに使用され、 隣接するスキーム間の遷移をアニメーション化することもできます。
>animate
( rdelta addr xt – ) minos2 “to-animate”
rdelta のタイムアウトが期限切れになるまで、 スタック効果 ( addr r0..1 -- )
を指定して
xt を繰り返し呼び出して、 新しいアニメーションを作成します。 最後の呼び出しは常に引数 1e を使用して行われます。
名前付きカラー・インデックスを作成し、 現在アクティブなカラー・スキームのカラー値を割り当てることができます。
color:
( rgba "name" – ) minos2 “color:”
rgba で初期化された(おそらく共有される)カラー・インデックスを作成します。
new-color:
( rgba "name" – ) minos2 “new-color:”
rgba で初期化された一意(unique)のカラー・インデックスを作成します。
text-color:
( rgba "name" – ) minos2 “text-color:”
rgba で初期化された一意のテキスト・カラー・インデックスを作成します。 対応する絵文字の色は白に設定されます。
text-emoji-color:
( rgbatext rgbaemoji "name" – ) minos2 “text-emoji-color:”
rgbatext で初期化された一意のテキスト・カラー・インデックスを作成します。 対応する絵文字の色は rgbaemoji に設定されます。
fade-color:
( rgba1 rgba2 "name" – ) minos2 “fade-color:”
rgba1 と rgba2 で初期化されたテキスト・カラー・インデックスの一意のペアを作成します。 対応する絵文字の色は白に設定されます。 インデックスをあるインデックスから次のインデックスにゆっくりと移動すると、 オブジェクトは再描画時に線形補間(linear interpolation)を使用してその色を変更します。
text-emoji-fade-color:
( rgbatext1 ~2 rgbaemoji1 ~2 "name" – ) minos2 “text-emoji-fade-color:”
rgbatext1 と ~2 で初期化されたテキスト・カラー・インデックスの一意のペアを作成します。 対応する絵文字カラー・ペアは rgbaemoji1 から ~2 に設定されます。 インデックスをあるインデックスから次のインデックスにゆっくりと移動すると、 オブジェクトは再描画時に線形補間を使用してその色を変更します。
re-color
( rgba "name" – ) minos2 “re-color”
現在のカラースキームの名前付きカラーインデックス "name" に値 rgba を割り当てます。
re-text-color
( rgba "name" – ) minos2 “re-text-color”
現在のカラー・スキームの名前付きテキスト・カラー・インデックス "name" に値 rgba を割り当てます。
re-emoji-color
( rgbatext rgbaemoji "name" – ) minos2 “re-emoji-color”
現在のカラー・スキームの名前付きテキスト・カラー・インデックスと名前付き絵文字カラー・インデックス "name" に値 rgbatext と rgbaemoji を割り当てます。
re-fade-color
( rgba1 rgba2 "name" – ) minos2 “re-fade-color”
現在のカラー・スキーム内の名前付きカラー・インデックスのペア "name" に値 rgba1 および rgba2 を割り当てます。
re-text-emoji-fade-color
( rgbatext1 ~2 rgbaemoji1 ~2 "name" – ) minos2 “re-text-emoji-fade-color”
現在のカラー・スキーム内の名前付きカラー・インデックスのペア "name" にそれぞれ値 rgbatext1 と ~2 を割り当てます。 rgbaemoji1 と ~2.
多くの特定のオブジェクトに対して、 それらのオブジェクトでのみ機能する早期結び付けメソッド(early bound method)があります
vp-top
( o:vp – ) minos2 “vp-top”
ビューポートを一番上までスクロール
vp-bottom
( o:vp – ) minos2 “vp-bottom”
ビューポートを一番下までスクロール
vp-left
( o:vp – ) minos2 “vp-left”
ビューポートを左にスクロール
vp-right
( o:vp – ) minos2 “vp-right”
ビューポートを右にスクロール
vp-reslide
( o:vp – ) minos2 “vp-reslide”
スクロール後にビューポートのスライダーを調整します
vp-needed
( xt – ) minos2 “vp-needed”
ビューポートの vp-need でニーズ(needs)を収集します
チュートリアルは小さなファイルであり、 MINOS2 がちょびっとずつ示されています。 共通フレームワークの場合、 最初にファイル minos2/tutorial/tutorial.fs をロードする必要があります。 コマンドライン引数内の他のすべてのチュートリアルは、 そのファイル内に含まれます。 スクロール・ホイールまたは 前/次 のマウス・ボタンや、 ウィンドウの左端または右端をクリックすると、 読み込まれたさまざまなチュートリアル間を移動できます。
つまり、 buttons チュートリアルをロードするには以下のようにして Gforth を開始します。
gforth minos2/tutorial/tutorial.fs buttons.fs
利用可能なチュートリアル:
既知のバグは、 Gforth ディストリビューションのファイル BUGS に記載されています。
バグを見つけた場合は、 https://savannah.gnu.org/bugs/?func=addbug&group=gforth を通じてバグ・レポートを送信してください。
uname -a
がこの情報を報告します)。
configure
出力または config.cache)。
バグ報告の詳細なガイドについては、 How to Report Bugs in GNU C Manual を参照してください。
Gforth プロジェクトは、 1992 年の半ば Bernd Paysan と Anton Ertl によって開始されました。 3番目の主要な著者は Jens Wilke でした。 Neal Crook はマニュアルに多大な貢献をしました。 アセンブラーと逆アセンブラーは、 Andrew McKewan と Christian Pirker と Bernd Thallner と Michal Revucky によって提供されました。 Lennart Benschop (1993 年半ばの Gforth の最初のユーザーの 1 人) と Stuart Ramsden は、 継続的なフィードバックで私たちにインスピレーションを与えてくれました。 Lennart Benshop は glosgen.fs を寄稿し、 Stuart Ramsden は C 言語ライブラリーの呼び出しの自動サポートに取り組んできました。 Paul Kleinrubatscher, Christian Pirker, Dirk Zoller, Marcel Hendrix, John Wavrik, Barrie Stott, Marc de Groot, Jorge Acerada, Bruce Hoyt, Robert Epprecht, Dennis Ruffer, David N からも有益なコメントが寄せられました。 Gforth-0.2.1 のリリース以来、 他の多くの人からも有益なコメントがありました。 みんなありがとう。 ここにあなたの名前を載せられなくてごめんなさい(でも、 メールボックスを漁ってあなたの名前を抽出するのは私のTODOリストに入っています)。
また、 Gforth は、私たちが使用したツール (GCC や CVS やautoconf など)の作成者と、 インターネットの作成者に多くの恩恵を受けています。 Gforth はインターネット上で開発されたため、 開発の最初の 4 年間、 作成者は物理的に会うことはありませんでした。
Gforth は、 bigFORTH (1993) と fig-Forth から派生したものです。 もちろん、Gforth の設計の重要な部分は標準 Forth によって規定されました。
Bernd Paysan が書いた bigFORTH は、 主に Dietrich Weineck によって作成された、 Atari ST 用の VolksForth の未発表32ビット・ネイティブ・コード版である TurboForth の子孫です。
VolksForth は、 80 年代半ばに C64 (そこでは UltraForth と呼ばれていました) 用に Klaus Schleisiek と Bernd Pennemann と Georg Rehfeld と Dietrich Weineck によって作成され、 1986 年に Atari ST に移植されました。
Bill Ragsdale が率いるチームは、 1979 年に多くのプロセッサに fig-Forth を実装しました。 Robert Selzer と Bill Ragsdale は、 microForth に基づいて 6502 用のオリジナルの fig-Forth 実装を開発しました。
microForth の主な設計者は Dean Sanderson でした。 microForth は、 FORTH, Inc. の最初の既製製品でした。 これは 1976 年に 1802 用に開発され、 その後 8080 や 6800 や Z80 に実装されました。
初期の Forth システムはすべてカスタム・メイドで、 通常は 60 年代後半に(Charles Moore 本人曰く)「Forth を発見した」(discover Forth) Charles Moore によって作られました。 最初の完全な Forth は 1971 年に存在しました。
このセクションの情報の一部は、HOPL-II 会議で発表され、 SIGPLAN Notices 28(3), 1993 年にプレプリントされた、 Elizabeth D. Rather と Donald R. Colburn と Charles H. Moore による The Evolution of Forth からのものです。そこにはフォースに関する歴史的および系図的な情報が含まれています。 より一般的な (そしてグラフィカルな) Forth 家系図については、 https://www.complang.tuwien.ac.at/forth/family-tree/, Forth Family Tree and Timeline を参照してください。
Copyright © 2000,2001,2002 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.
A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.
You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements.”
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (C) year your name. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''.
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with...Texts.” line with this:
with the Invariant Sections being list their titles, with the Front-Cover Texts being list, and with the Back-Cover Texts being list.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
Copyright © 2007 Free Software Foundation, Inc. http://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program—to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers’ and authors’ protection, the GPL clearly explains that there is no warranty for this free software. For both users’ and authors’ sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users’ freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work’s System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work’s users, your or third parties’ legal rights to forbid circumvention of technological measures.
You may convey verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation’s users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party’s predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor’s “contributor version”.
A contributor’s “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor’s essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient’s use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
one line to give the program's name and a brief idea of what it does. Copyright (C) year name of author This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
program Copyright (C) year name of author This program comes with ABSOLUTELY NO WARRANTY; for details type ‘show w’. This is free software, and you are welcome to redistribute it under certain conditions; type ‘show c’ for details.
The hypothetical commands ‘show w’ and ‘show c’ should show the appropriate parts of the General Public License. Of course, your program’s commands might be different; for a GUI interface, you would use an “about box”.
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see http://www.gnu.org/licenses/.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read http://www.gnu.org/philosophy/why-not-lgpl.html.
この索引は、 このマニュアル内に glossary 項目がある Forth ワードのリストです。 各ワードはそのスタック効果とワード・セットとともにリストされます。
Jump to: | -
,
;
:
!
?
.
'
(
)
[
]
{
}
@
*
/
\
#
%
`
+
<
=
>
|
~
$
0
1
2
A B C D E F G H I J K L M N O P Q R S T U V W X Y |
---|
Jump to: | -
,
;
:
!
?
.
'
(
)
[
]
{
}
@
*
/
\
#
%
`
+
<
=
>
|
~
$
0
1
2
A B C D E F G H I J K L M N O P Q R S T U V W X Y |
---|
この索引にリストされているすべての項目が本文中にそのまま記載されているわけではありません。 このインデックスは、 ワード索引にリストされているすべてのワードを省略形式で転載しています(ここではワードの名前のみがリストされています)。
Jump to: | -
,
;
:
!
?
.
'
"
(
)
[
]
{
}
@
*
/
\
&
#
%
`
+
<
=
>
|
~
$
0
1
2
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z イ オ ク セ メ 受 子 |
---|
Jump to: | -
,
;
:
!
?
.
'
"
(
)
[
]
{
}
@
*
/
\
&
#
%
`
+
<
=
>
|
~
$
0
1
2
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z イ オ ク セ メ 受 子 |
---|
ただし、 1998 年に主要な商用 Forth ベンダーがネイティブ・コード・コンパイラーに切り替えたとき、 その基準は引き上げられてしまいました。
つまり、 ユーザーのホーム・ディレクトリに保存されます。
この表記法は、 後値記法(postfix notation) または RPN (逆ポーランド記法) と呼ばれます。
したがって、 ワード名には )
を使用しないことをお勧めします。
見つかったかどうかはわかりませんが、いまのところ、 見つからなかったと仮定します。 訳注: 12 というワードを定義することもでき、その場合はワード 12 の方が数値 12 より優先される
それは完全には真実ではありません。 キーボードの上矢印キーを押すと、 以前のコマンドにスクロールして戻り、 編集して再入力できます。
実際には、 いくつか微妙な違いがあります – The Text Interpreter
For example, /usr/local/share/gforth...
この一緒にした表記から、 浮動小数点数だけを分離するだけで、
簡単に分離された表記にできます。 例えば: ( n r1 ur2 -- r3 )
は ( n u -- ) ( F: r1
r2 -- r3 )
になります
「ディクショナリー」(辞書)という用語は、 従来の辞書と同じように名前を検索するために使用される、 ワード・リストやヘッダーに組み込まれた検索データ構造を指すために使用されることがあります
正確には、 インタープリター機能(interpretation semantics)(see Interpretation and Compilation Semantics)を持っていません
ええ、まぁ、 移植可能な方法ではできません、 ぐらいな
next-case
は、 他の case
ワード群とは異なり、
名前にハイフン(-
)が含まれています。 VFX Forth には値をドロップする nextcase
があるので、これと区別するためです。
まあ、
多くの場合、 それは可能ではありますが、 標準的な移植可能な方法では変更できません。 Value
を使用する方が安全です
(続きを読んでください)まあ、多くの場合変更できますが、標準的な移植可能な方法では変更できません。 Value
(続きを読んでください)
厳密に言えば、 compile,
が xt
をコード領域内の何かに変換するために使用するメカニズムは実装に依存します。 スレッド実装は実行トークンを直接吐き出す場合がありますが、
別の実装はネイティブ・コード・シーケンスを吐き出す場合があります
このデータ領域への読み取りと書き込みは両方とも正当です。
研究課題: この例は
Value
と TO
をあなた独自に実装するための出発点として使って見ましょう。 もし、 あなたが行き詰まった場合は、
'
と [']
の振る舞いを調べてください。
標準の用語では、 「現在の定義に追加する」と言います
標準の用語でいうと、 デフォルトのインタープリター機能はその実行機能です。 デフォルトのコンパイル機能は、 その実行機能を現在の定義の実行機能に追加します
このトピックに関する議論の詳細は M. Anton Ertl,
State
-smartness—Why
it is Evil and How to Exorcise it, EuroForth ’98. をご覧ください。
標準 Forth には、
インタープリター機能(interpretation semantics)が未定義のワード(例: r@
)や、
定義された実行機能(execution semantics)が無いワード(例: s"
)や、 およびそのどちらも持たないワード(例:
if
)があります。 ただし、 インタープリター機能(interpretation semantics)と実行機能(execution
semantics)の両方が定義されている場合、 それらは同一でであるため、 それらを同一のものとして扱います
これは、 ワードのコンパイル機能(compilation
semantics)に依存します。 ワードにデフォルトのコンパイル機能(compilation semantics)がある場合、 xt は
compile,
を表します。 それ以外の場合(たとえば、 即実行ワードの場合)、 xt は execute
を表します
最近の RFI の回答では、 ワードのコンパイルはコンパイル状態でのみ実行する必要があるとしているため、 この例はすべての標準システムで動作することは保証されませんが、 ちゃんとした(decent)システムであれば動作します
このセクションは Introducing the Text Interpreter の拡大バージョンです
テキスト・インタープリターがキーボードからの入力を処理している時、
このメモリー領域はターミナル入力バッファー(terminal input buffer (TIB))と呼ばれ、 (既に廃止されていますが)
ワード TIB
および ワード #TIB
によってアドレス指定されます
言い換えると、 テキスト・インタープリターは、 パース領域が空になるまで、 パース領域からの文字列をパースすることによって、 入力バッファーの内容を処理します
これがワードのパースの仕組みです
研究課題: 3
を 4
に置き換えたらどうなるでしょうか?
buffer
の標準 Forth での定義は、 ディスク I/O を発生させないことを目的としています。
以前の block
コマンドにより、 特定のブロックに関連付けられたデータがすでにブロック・バッファーに格納されている場合、
buffer
はそのブロック・バッファーを返し、 ブロックの既存のコンテンツが利用可能になります。 それ以外の場合、
buffer
は単にそのブロックに新しい空のブロック・バッファーを割り当てます
コンパイラー構築用語では「すべての場所はローカル変数の定義によって支配される」と言う
この機能は「拡張レコード」(extended records)とも呼ばれます。 これは、 オベロン・プログラミング言語が成した主な革新です。 言い換えれば、 この機能を Modula-2 に追加することで、 Wirth は新しい言語を作成し、 新しいコンパイラの記述等を行いました。 この機能を Forth に追加するには、 数行のコードが必要なだけでした。
さらに、
catch
を呼び出すワードや、 objects.fs
をロードする前に定義されているワードについては、
catch
を再定義したように、 これらも再定義する必要があります: : catch this >r catch r>
to-this ;
「メソッド・マップ」(method map)は著者自作用語です。 C++ 用語では、 仮想関数テーブル と言います。
この批評の長いバージョンは、Anton Ertl の On Standardizing Object-Oriented Forth Extensions (Forth Dimensions, May 1997) にあります。
あなたが C 言語コンパイラーの呼び出し規則を知っていれば、 通常は何らかの方法でそのような関数を呼び出すことはできますが、 その方法は通常、 プラットフォーム間で移植可能ではなく、 場合によっては C 言語コンパイラー間でさえ移植可能ではありません。
ただし、 著者の意見としては、 (実装がどうであれ)二重リンク・リストを使用する前によく考えるべきです。
Unix シェルは実際には 2 種類のファイルを認識します。 それは実行可能ファイルとデータ・ファイルです。 データ・ファイルは「インタープリター行」で指定されたインタープリターによって処理されます。 このインタープリター行は、 シーケンス #! で始まるデータ・ファイルの最初の行です。 インタープリター行で指定できる文字数には小さな制限(例: 32)がある場合があります。
データと浮動小数点スタックが別々に存在する場合でも、1 つのスタック表記法を使用します。 個別の表記は、 統一された表記から簡単に生成できます。