2009年4月23日

Factorに入門する(18) syntax残り

[ Factor プログラミング言語 ]

18回目。Factor Language Reference: syntaxも終盤です。

Commentは!または#!、行末までコメント。

リテラルは第4回でとりあげたのでとばします。

そして、parse time evaluation。parsing wordsの話か、と思いきや、そうじゃなくて、「パーズ時に評価する」という特殊なことをできるそうな。ソースコード上、"<<"と">>"に囲まれた部分は、パース時(つまりコンパイル前)に評価されます。"rearly-used feature"だそうなので、今回はとばしておきます。

超短くて中身がほぼないですが、今回はここまで。

2009年4月21日

Factorに入門する(17) top level formsの実行タイミング

[ Factor プログラミング言語 ]

収拾付かないFactor入門も17番目。まだまだ全体の景色が見えないので、まとめにははいりませんよ。今日はtop level formsをしっかり理解して帰ってください(オレが)。

top level formsとは、ソースコードのうちdefinitions以外の部分です。どこに書いてあっても、ソースコードすべてがパースされた「後」に実行されます。試してみましょう。

こういうソースファイルを書きます。fooを定義する前にfooを呼ぶコードです。

IN: test
USING: io ;

foo

: foo ( -- ) "test:foo called." print ;

実行すると

P" temp.factor"
4: foo
      ^
No word named ``foo'' found in current vocabulary search path

パース時のエラーになってます。パースして、コンパイルされて、それから実行されるのですね、たぶん。

ではこういうのはどうでしょうか。

USING: io math prettyprint kernel ;
IN: test

3 4 + .

: + ( x y -- ) 2drop "test:+ called. dropped values! HAHAHA" ;

こいつを実行すると

7

普通にmathの+が呼ばれます。サーチの順序ではtestが先に入っていますが、パース時点ではまたtest:+が定義されていないからですね。そういうときには、DEFER:で、先にwordの空定義を作っておくことができます。Pascalのforwardみたいなもんですが、FactorのDEFER:では、呼ぶとエラーになるwordがまず定義されます。

まずは、DEFER:で+を宣言して、しかし定義はしないコードを書いてみます。

USING: io math prettyprint kernel ;
IN: test
DEFER: +

3 4 + .

実行結果。

Calling a deferred word before it has been defined

DEFER:した+がまだ定義されていない、と怒られました。

+の呼び出しの「後」に、vocaburary "test"の+を定義して、実行してみましょう。

USING: io math prettyprint kernel ;
IN: test
DEFER: +

3 4 + .

: + ( x y -- str ) 2drop "test:+ called. dropped the values! HAHAHA"  ;
"test:+ called. dropped the values! HAHAHA"

ということで、test内で+を定義したのより後にtop level formが実行されていることが確認できました。

ドキュメントには、前回疑問に思った"ソースファイル中でinがとれない"ということについても書いてありました。

Top-level forms do not have access to the in and use variables that were set at parse time, nor do they run inside with-compilation-unit; so meta-programming might require extra work in a top-level form compared with a parsing word.

さらに、変数スコープもtop-levelではダイナミックなあたらしいものが生成されるそうで、実行後にそのスコープはなくなってしまうそうな。このあたりの詳細は、スコープのところをやるときにつっこんでみることにします。

2009年4月20日

Factorに入門する(16) vocaburary search path

[ Factor プログラミング言語 ]

Factor入門 16th。今日もLanguage referenceからsyntaxの続きで、"vocabulary search path"を地味にみていきます。Factorのparserがトークンを読むにあたって、vocabulary search pathに入ってるvocabularyの順番で、その名前で定義されているwordを探しに行きます。

vocaburary search path : use

vocaburary search pathは、変数useに連想配列の形で入っているとのことで、調べてみます。

( scratchpad ) USE: vocabs.parser
( scratchpad ) use get .
V{
    H{
        { "change-reg" change-reg }
        { "change-ref" change-ref }
        { ">>number" >>number }
        { "change-in1" change-in1 }
        { "change-in2" change-in2 }
        { ">>minute" >>minute }
(以下略)

useの中身は、すでにwordの名前からwordの実体への連想配列になっていて、vocaburary名は直接出てこないようです。

wordを定義する先のvocaburary

新しいwordを定義する場合、それもどこかのvocabularyに属させないといけません。対話環境(listener)ではvocabulary "scratchpad"が使われるので気にしなくてもよいですが、通常定義するときはIN:をつかって、これから定義するvocabularyをどこに属させるのか決めます。このIN:で定義されている最新のvocabulary名は、変数inで取り出せます。

( scratchpad ) USE: vocabs.parser
( scratchpad ) in get .
"scratchpad"

ところがこれ、ファイルを作ってやるとこうなります。

test.factor:

USING: vocabs.parser namespaces io prettyprint ;
in get .

IN: test-temp
in get .

実行結果

$ /Applications/factor/factor temp.factor 
f
f
$ 

IN: が効いているのはパース中で、in get . が実行されるときはコンパイル後、って違いなんだろうと思いますが、いまのところは完全に理解できていません。第19回くらいに明らかになるかもしれません。

USE: USING:

さて、IN:は新規にwordを定義するvocabularyを指定しますが、サーチパスに追加するにはUSE:またはUSING:を使います。USE:はひとつだけ、USING:は、;(セミコロン)があらわれるまでの複数vocabularyを指定できます。順番に、search pathの先頭に追加されていきます。

USE:のいろいろなバリエーションとしてQUALIFIED:, FROM: EXCLUDE:...などいろいろあります。たとえば、QUALIFIED:で読み込まれたwordは、prefixつきになります。名前の衝突を避けるのに使うのでしょうか。例えばこんなです。

( scratchpad ) QUALIFIED: math
( scratchpad ) 5 9 math:*

--- Data stack:
45
( scratchpad ) 

useの中身の連想配列のキー側に"math:*"が入っていて、value側にはword '*'の実体をいれることでこれを実現しているようです。

それから、private wordを定義する方法があります。

( scratchpad ) IN: myvocab
( myvocab ) <PRIVATE
( myvocab.private ) : my-word ( -- ) "it's private" print ;
( myvocab.private ) PRIVATE>
( myvocab ) 

単に、現在のinにたいして".private"をつけたものをinにセットするだけです。documentにもこう書かれています。

Private words can be defined; note that this is just a convention and they can be called from other vocabularies anyway

auto-use?

さて、ここまでは、サーチパスにあるvocabularyからword定義を探す前提でしたが、他のメカニズムもあります。

もし、サーチパスからtokenの名前がつけられたwordが見つからなかった場合には、ロードされているすべてのvocabularyから探しにいきます。

このとき、auto-use?がtかつ、ロードされているvocaburaryのひとつだけで該当するwordがみつかれば、それをサーチパスにくわえてparsingが続行されます。parse後、そのwordが属するvocaburaryが表示されます。

( scratchpad ) t auto-use? set
( scratchpad - auto ) 10 <vector>
1: Note:
Added "vectors" vocabulary to search path

--- Data stack:
V{ }
( scratchpad - auto ) 

開発中にうっかりUSING:を忘れたときのために使うもの、だそうです。

auto-use?がfだとerrorになりますが、キャッチしてなんとかできるようです。errorの仕組みがまだ分かっていないので、詳細はまた今度、です。

もうひとつはっきり分からないことがあって、"loaded vocaburary"の意味です。サーチパスにはいっている場合、useに名前とwordの連想配列が保持されている、のは分かります。USING:していないけど、イメージに入っているものがloaded vocaburaryなんでしょう。ではイメージにvocaburaryがloadされるタイミングは? とか、useのスコープは? とか、useはいつ使われる? (パース時のみ?)とか、この疑問の先にはおそらくいろいろ分からないことがぶらさがっていますが、今回はとりあげません。

word定義のshadowing

さて、vocaburary search pathの最後は、shadowingの話です。Factorではvocaburaryが異なれば同じ名前のwordが定義できますが、その名前のスコープについて、です。これは実例をみるのがはやいとおもわれるので、Factor documentを見ながら自分でも書いてみましょう。

ソースコード

IN: foo
USING: math io prettyprint ;

: + ( x y -- z ) "foo:+ called" print + ;

"checkpoint1: " print
3 4 + .

USE: foo
"checkpoint2: " print
3 4 + .

実行結果

checkpoint1: 
7
checkpoint2: 
foo:+ called
7

最初にfooで+を定義しています。このとき、inはfooなので、新しい+はfooに定義されます。その中で使われている+は、USING: で指定されているmathの中にあるので、そちらが優先されます。そのすぐした、トップレベルに書いた+も、mathのものが使われます。

しかし、USE: fooをすれば、その前でUSING:されていたmathの+は隠されて、fooの+が呼ばれます。しかしfoo:+の定義の中で呼ばれる+は、相変わらずmath:+です。

USING:とIN:の順序を逆にするとどうでしょうか。

USING: math io prettyprint ; 
IN: foo 

: + ( x y -- z ) "foo:+ called" print + ;

"checkpoint1: " print
! ここで呼ばれる+は、fooの+。
3 4 + . 

USE: foo
"checkpoint2: " print
3 4 + .
checkpoint1: 
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
(以下いつまでも続く)

後に書いたIN:のほうが優先されるわけですな。

2009年4月17日

Factorに入門する(15) Parser algorithm

[ Factor プログラミング言語 ]

今回からしばらく、Factorのドキュメントの中からLanguage referenceを地道にみていきます。地を這うような内容が、当分続くことになると思います。飽きたら3Dオブジェクトをまわしたりしてみるかもしれません。

Conventionsは以前とりあげたので、今回はdocumentのなかから、syntax(文法)の記事をみていきましょう。 Factorには2種類のsyntaxがあって、それがdefinition syntaxとliteral syntaxですが、ここではliteral syntaxについてとりげられています。本日はまずparser algorithだけとりあげます。

Parser algorithm

FactorはForthの末裔ですので、基本は、"空白でくぎられたトークン"の集まりです。そして大文字小文字は区別されます。 パーサはトークンを読んで、それが数値なのか、ふつうのwordなのか、parsing wordなのかをみていきます。 パース結果はparse treeに追加されていきます。

1文字目がダブルクオーテーション(")のときは、その後に空白があいていなくてもparsing word " が実行され、文字列が読み込まれます。なるほど、これだけ特別扱いなんですね。それ以外の場合、通常のwordであればparse treeに追加され、parsing wordであれば実行されます。

以前parsing wordに挑戦して、 意外に奥が深くて負けましたが、 コンセプトは単純ですね。通常のwordはすぐには実行されず、parsing wordsはその場で実行される。Forthでいうimmediate wordに似ています。

parsing wordはその性質上どんな文法のものでも扱えますが、デフォルトのものはsyntaxボキャブラリにあります。

2009年4月 8日

Factorに入門する(14) 好きな場所に自分のvocaburaryをつくる

[ Factor プログラミング言語 ]

Factorの門 その14。今回は軽めのネタ。

前々回・前回でvocaburaryをつくりテストをかきコードをかきドキュメントを書き、ってやりましたが、この場所は基本的に固定されてました。Macで/Applicationsの下にfactorをいれたのであれば、ここ。

/Applications/factor/work/

scaffoldで作るvocaburaryはこの下に作成されます。もちろん、好きなところに作ってもいいんですが、そうするといろいろ不便です。vocaburaryのロードパスに含まれないので、USE:で簡単にロードできなかったり、helpも探してくれなかったり、emacsやGUIのいろいろなサポートを受け付けられなかったり。

というわけで、デフォルト以外の場所にvocaburary root(こういう用語なんです)を追加する方法です。

ドキュメントに"Workng with code outside of the Factor source tree"という項があって、そこに書いてあります。

方法は3つ:

  1. FACTOR_ROOTS環境変数に追加パスを設定する。
  2. ~/.factor-rootsに追加パスを設定する。
  3. add-vocab-rootで設定する。
2番目の方法では、たとえばこんなふうに書きます。

/Users/skoji/Projects/factor/
/mnt/share/factor-codes/

こうしておくと、vocaburary作成時に、resource:workなど以外にここに記述したパスがrootとして使えるようになります。