[SikiLanguage] [式鬼言語航海日誌] 2007-09-02
2007/09/02_000000まいど。夏の盛も過ぎて、すっかり涼しくなりましたね。
「前は初夏も始めだったのにねぇ」
まあ、期間がかかりましたが進捗ありましたよ。基本的なメソッド呼び出しまで意味付けできました。
忘れないうちにメモ書きしておきます。頭の中を整理するために書いていますので、判り辛いのはご容赦下さい。
「図も使うと分かり易いンだけどねぇ」
そこはこのWikiエンジンの限界ということで……
まず、全体の流れとしては RootとなるCellに文字列を与える。 Rootが文字列をパースして、命令列に変換 Rootの中のパースエンジンが文字列を命令列に変換してRootのhead列に保存 トップレベル(=Root)のスコープで空行が出て来たらパースを中断 パースを中断したタイミングで、解釈済の命令列を実行 Rootのhead列を逆さにしながらbody列に全て移動 body列の命令を実行 body列が無くなったら実行を中断してパース再開 上記を繰り返し 文字列をパースしきったら終了
といった感じです。6月のアイディアを具体化したものですね。
「ブロックごとに実行していく感じかね」
そうですね。塊ごとに逐次的に実行していく感じですね。
まだ実装はしていませんが、究極的にはRootのパースルールを変更する命令列も用意したいと考えています。
で、次が命令列の挙動について。
命令列……式鬼言語ではCellと呼んでいますけど、大きく分けると
トークン(id): 名前空間から変数を持って来て戻り値として継続のhead列に(戻り値として)渡したあと、 メソッド呼び出しをスタートする。 オブジェクト:自分自身を戻り値として継続のhead列に(戻り値として)渡す。 その他命令列:それぞれ固有の処理を行う。戻り値も色々。
といった内容になります。
「判り辛いねぇ。色々と前提をすっ飛ばしているだろ。この説明」
……そうですね。まあ、まずはメモ書きということで……オブジェクトとその他命令列はまあ、何とかわかるのではないかと思います。手続き型言語でいうと、オブジェクトはリテラルの、命令列はコマンドの、それぞれの挙動に近いですからね。
で、最後のトークン(id)は、まとめて言うと、
変数の名前解決 --> 変数に保存されているCell(or トークンそのもの)を利用してメソッド呼び出しを処理
というCellということになります。
「さっぱり判らん。例を出せ、例を」
そうですね。まあ、実際の例を出しても判らないかもしれませんが……
:test1 :test2 .. :test3 <== {!'test'} test1 test2 .. test3
というソースを例に説明しましょう。
まず、Root(というCell)は、この文字列を途中までパースします。
|Transport| test1 |Transport| test2 .. |Transport| test3 |Skewer| |Transport| <InlineExecutor> |Copier| test1 test2 .. test3
|~~|というのは命令列のCellです。ここでは
- |Transport|:次に実行するはずの命令列を実行せずにそのまま継続に(戻り値として)渡す
- |Skewer|:メソッド呼び出しのベースになるCellを検索するして継続に(戻り値として)渡す
- |Copier|:継続に(戻り値として)積まれているCellを2つ取り出して、片方の内容をもう片方にコピーする
という挙動になります。
<InlineExecutor>というのも命令列なのですが、「{!~~}の中身を実行する」という命令列になります。
つまりは、「test1 test2 test3というCellをそれぞれ継承元とする引数を持つメソッドを定義する」という命令列になります。
命令列はRootのbody列に積まれていますので、順次実行していくと
Root::body |Transport| test1 |Transport| test2 .. |Transport| test3 |Skewer| |Transport| <InlineExecutor> |Copier| Root::tail
Root::body |Transport| test2 .. |Transport| test3 |Skewer| |Transport| <InlineExecutor> |Copier| Root::tail test1
Root::body .. |Transport| test3 |Skewer| |Transport| <InlineExecutor> |Copier| Root::tail test1 test2
Root::body |Transport| test3 |Skewer| |Transport| <InlineExecutor> |Copier| Root::tail test1 test2 ..
Root::body |Skewer| |Transport| <InlineExecutor> |Copier| Root::tail test1 test2 .. test3
Root::body |Transport| <InlineExecutor> |Copier| Root::tail <test1 test2 .. test3 メソッドのベースとなるCell>
Root::body |Copier| Root::tail <test1 test2 .. test3メソッドのベースとなるCell> <InlineExecutor>
Root::body Root::tail <test1 test2 .. test3メソッド>
といった感じです。
「ここで文字列のパースが再開されるわけだね」
はい。Rootのbody列を使い切りましたので、再開します。パースする前に、tailに積まれたCellはクリアされます。
パースが終了するとこんな感じですね。
Root::body test1 test2 .. test3 Root::tail
「そのまンまだね」
はい。ただ、bodyに積まれたのは命令列ではなくてトークン(id)ということに注意してください。では逐次実行してみましょう。
Root::body (|Trigger|) test2 .. test3 Root::tail test1
ここで|Trigger|という命令が新たに積まれていることに注意してください。()で括られているのは、実際にはbodyに積まれないで直接実行されるためです。
|Trigger|という命令は tailに積まれたCellを新しく積まれた順に検索してメソッドを探す 該当するメソッドが存在する場合はメソッドを実行する 該当するメソッドが存在しない場合は何もしない
という命令です。いわゆるメソッド呼び出しですね。
「ふむ……メソッドを名前で呼び出すンじゃなくてCellで呼び出す、つうことか」
はい。トークンを処理するときはその都度メソッド呼び出しが発生します。パフォーマンス的なインパクトはありますが、代わりに柔軟性が手に入ります。まあ、それは後ほど。
test1 test2 .. まではメソッドがありませんので、そのままtail列に積まれます。test3まで積むと、先ほど定義したメソッドが要求するCellとスタックに積まれたCell(の継承元)が一致しますので、ようやっとメソッドが実行されます。
Root::body (|Trigger|) Root::tail test1 test2 .. test3
Root::body (<test1 test2 .. test3メソッド>) Root::tail test1 test2 .. test3
メソッドは'test'という文字列を返します。
Root::body Root::tail test1 test2 .. test3 'test'
で、めでたく終了しました。
「長かったねぇ。結局、引数は使わなかったね」
ええ、まだ実装が追い付いていないところがありまして……
引数については
- tailに積んである引数を、順番入替え/コピー/削除する命令列がある
- tailから降ろす/参照するときに、Closure(Delegater)の評価を行う
という特徴があります。このあたりは実装が終わったころにもう一度ということで。
制作・著作: 野分(nowake) at fiercewinds.net (Creative Commons 表示-継承 2.1 日本)