38 行の Lua プログラムで Forth インタプリタを作る（内部インタプリタ 6 行、外部インタプリタ 32 行）

# はじめに
前回の文章、「[6 行の Lua プログラムで、Forth の内部インタプリタを作る](http://qiita.com/iigura/items/855954ad000fe1da6c59)」では、内部インタプリタを作成しました。作成した内部インタプリタの動作確認として、実際の Forth プログラムを実行させましたが、ハンドコンパイルする必要がありました。

今回は外部インタプリタを作成します。これにより、ハンドコンパイルは不要となります。また、外部インタプリタを eval として、read-eval-print-loop を構成可能です。

追記：ソースコードを [GitHub（https://github.com/iigura/tinyLuaForth）](https://github.com/iigura/tinyLuaForth)に置きました。興味のある方は実際に動かしてみて下さい。

注：当初は「35 行の Lua プログラムで Forth インタプリタを作る（内部インタプリタ 6 行、外部インタプリタ 29 行）」というタイトルで掲載しておりましたが、トークンの評価順序が一般の Forth とは異なっていました。今回はそれを修正しました。結果、外部インタプリタが 3 行増加し、合計 38 行となりました（ただし、辞書の方は見直しを行い、30 行から 2 行減って全 28 行となりました）。関連して、トークンの評価順序に関する部分についての記述も修正しました。その他、説明不足な部分についても文章を追加し、誤字脱字等の修正も行いました。

# 外部インタプリタの仕事
外部インタプリタには 2 つのモードがあり、それぞれのモードで実現すべき機能が異なります。ひとつはインタプリタモードで、これはワードを起動する等して、計算などの処理を実行するモードです。もうひとつはコンパイルモードで、ワードの定義などはこのモードで実施されます。

## インタプリタモードで行うべきこと
インタプリタモードでは、入力された文字列（トークン）により、以下のいずれかの処理を行います。

1. 登録済みのワードと同名の文字列であっとき：　その文字列をワード名とし、該当ワードを検索・実行します。
2. 同名のワードがない場合：　数値として解釈することを試みます。数値であると判断された場合には、その値をデータスタックにプッシュします。
3. それ以外の場合：　未定義のワードとしてエラーを出します（が、この場合どのように処理するのかは各処理系に委ねられています）。

例えば、 5 dup * . という文字列が入力された場合は、

| トークン  | スタック | 説明 |
|:--------:|:-------|:-----|
| 5 | | 5 というワードは存在せず、数値として解釈できるのでスタックに積む|
| dup | 5 | "dup" というワードを探して実行する（＝スタックトップを複製する）|
| * | 5 5 | "*" というワードを探して実行する（＝スタックの上から 2 つ取り出し、それらの積をプッシュする）|
| . | 25 | "." というワードを探して実行する（＝スタックトップの値を表示する）|

と処理され、最終的に 25 という数値が画面に表示されます。

## コンパイルモードで行うべきこと
コンパイルモードでも、トークンの解釈の順序はインタプリタモードと同じです。しかし、コンパイルモードでは、それらのワードや数値を実行したりスタックに積むのではなく、現在コンパイル中のワードのパラメタフィールドに、コンパイルした結果を順次書き込んでいきます。具体的には、以下の処理を行います。

* ワード名が入力されたとき：そのワードがイミディエイト属性（即時実行特性）を持つならば、そのまま実行し、そうでなければ、該当ワードへのポインタをパラメタフィールドの末尾に配置します。
* 数値が入力されたとき：　ワード "lit" をコンパイルし、その直後に該当の数値を配置します。

例として、 : square dup * ; という文字列を処理してみます。外部インタプリタはインタプリタモードにて起動するため、最初は ":" というワードを実行することとなります。

":" を実行すると、

1. 新しいワードの領域が作成され、
2. 処理系へ入力された文字列から次のトークンを読出し、それをワード名として設定し、
3. そのワードのコンピレーションフィールドに docol を設定し、
4. コンパイルモードへと移行、

します。

あとは、 * dup というワードがコンパイルされ（＝各ワードへのポインタがパラメタフィールドに順次追加され）、最後に ";" というワードを処理することとなります。

";" というワードはイミディエイト属性を持つワードですので、コンパイルモードであろうが、ワード ";" を直ちに実行します（つまり、単に記述しただけでは、ワード ; はコンパイルされません）。

ワード ";" では、

1. ワード semis をコンパイルし、
2. インタプリタモードへと移行、

します。

結果として、コンピレーションフィールドに docol が設定され、パラメタフィールドにはコンパイルされた dup, * , semis という列を持つ square というワードが作成されます。

# 外部インタプリタの設計
ここで作成する外部インタプリタも、[Ochs の mini-forth に倣い](http://angg.twu.net/miniforth-article.html)、モードを文字列で持つようにします。

それぞれのモードで実行される処理は action テーブルで持つこととします。また、各モードにおいて、ワードの場合と数値の場合で処理を変更する必要があります。そのため、該当のトークンの値を、ワード名ならば文字列、数値として解釈できるならばその数値とすると、action テーブルは以下のように記述できます。

```lua:action_frame.lua
action={}
action.interpret={
    number=function(inNumber) データスタックへプッシュ end,
    string=function(inWordName) ワードを検索し、実行する end
}
action.compile={
    number=function(inNumber)
               ワード lit をコンパイルし、
               定義中のワードのパラメタフィールドの末尾に該当の数値を配置
           end,
    string=function(inWordName)
               inWordName を検索し、
               そのワードがイミディエイト属性を持つならば、そのワードを実行、
　　　　　　　　　そうでなければ、定義中のワードのパラメタフィールドの末尾にそのワードへのポインタを配置
           end
}
```

# 32 行で作る外部インタプリタ
以下、今回作成した外部インタプリタのコードを示します。このコードは、もともとの趣旨より、前回の[記事](http://qiita.com/iigura/items/855954ad000fe1da6c59) 同様、行数が少なくなるように圧縮してコーディングしています。

関数 outerInterpreter() は入力された 1 行文の文字列を受け取り、エラーがなければ nil を返し、もしエラーが発生した場合はそのトークン（文字列）を返すこととします。

```lua:outerInterpreter.lua
require "innerInterpreter"
local dataStack,returnStack={},{}
local makePush=function(t) return function(v) table.insert(t,v) end end
local makePop =function(t) return function() return table.remove(t) end end
DS={Push=makePush(dataStack),   Pop=makePop(dataStack)}
RS={Push=makePush(returnStack), Pop=makePop(returnStack)}
local execWord=function(inWordName) innerInterpreter({Dict[inWordName]}) end
local action={};
action.interpret={number=DS.Push, string=execWord}
action.compile  ={
    number=function(inValue) local ins=table.insert
        ins(NewWord.param,Dict["lit"]); ins(NewWord.param,inValue)
    end,
    string=function(inWordName)
        if Dict[inWordName].immediate then execWord(inWordName)
        else table.insert(NewWord.param,Dict[inWordName]) end
    end
}
function GetToken() local token
    token,scanPos=string.match(Line,"^([ \t]*)()", scanPos)
    token,scanPos=string.match(Line,"^([^ \t]*)()",scanPos)
    return token~="" and token or nil   -- token!="" ? token : nil
end
Mode="interpret"
outerInterpreter=function(inLine)
    Line=inLine; scanPos=1; local token=GetToken()
    while token do local tokVal = Dict[token] and token or tonumber(token)
        if tokVal then action[Mode][type(tokVal)](tokVal); token=GetToken()
        else return token end
    end
    return nil
end
```

# tinyDict による動作テスト
[前回](http://qiita.com/iigura/items/855954ad000fe1da6c59)および今回作成した、全 38 行からなる Forth システムが動作するのか、テストしてみましょう。Forth の場合、インタプリタだけでは何もできませんので、28 行のテスト用辞書も用意しましたので、以下に示します。

```lua:tinyDict.lua
Dict={}
Dict.Add=function(inWord) Dict[inWord.name]=inWord end
local docol=function() local newThread=IP.thread[IP.index-1].param
    RS.Push(IP); IP={thread=newThread,index=1} end
local readIP=function() return IP.thread[IP.index] end
local incIP=function() IP.index=IP.index+1 end
local compile=function(inWordName) table.insert(NewWord.param,Dict[inWordName]) end
local compileEmptySlot=function() table.insert(NewWord.param,"EMPTY_SLOT") end
Dict.Add { name=":", immediate=true, code=function()
    NewWord={code=docol, name=GetToken(), param={}}
    Dict[NewWord.name]=NewWord; Mode="compile" end }
Dict.Add { name="semis", code=function() IP=RS.Pop() end }
Dict.Add { name=";", immediate=true, code=function()
    compile("semis"); Dict[NewWord.name]=NewWord; Mode="interpret" end }
Dict.Add { name="0branch", code=function()
    if DS.Pop()==0 then IP.index=IP.index+readIP() else incIP() end end }
Dict.Add { name="if", immediate=true, code=function()
    compile("0branch"); RS.Push(#NewWord.param+1); compileEmptySlot() end }
Dict.Add { name="then", immediate=true, code=function()
    local p=RS.Pop(); NewWord.param[p]=#NewWord.param+1-p end }
Dict.Add { name="lit", code=function() DS.Push(readIP()); incIP() end }
Dict.Add { name="dup", code=function() local t=DS.Pop(); DS.Push(t); DS.Push(t) end }
Dict.Add { name="-", code=function() local a,b=DS.Pop(),DS.Pop(); DS.Push(b-a) end }
Dict.Add { name="*", code=function() local a,b=DS.Pop(),DS.Pop(); DS.Push(a*b) end }
Dict.Add { name=">", code=function() local a,b=DS.Pop(),DS.Pop();
    if b>a then DS.Push(-1) else DS.Push(0) end end }
Dict.Add { name=".", code=function() local t=DS.Pop(); io.write(" "); io.write(t) end }
Dict.Add { name="cr", code=function() print() end }
```

これを使って、通常の計算と、再帰を使って階乗を計算するワードを定義し、階乗計算を行ってみます。

```lua:ex_outerInterpreter.txt
> dofile "outerInterpreter.lua"
> dofile "tinyDict.lua"
> -- 5 dup * を計算してみます。最後の cr は改行を行うワードです（. だけだと改行しないので）
> outerInterpreter("5 dup * . cr")
 25
nil
> -- 最後の nil は全てのトークンが正しく評価できたという意味です。

> -- 次に階乗計算するワード fact を定義します
> outerInterpreter(": fact dup 1 > if dup 1 - fact * then ;")
nil

> -- fact のテスト　5!=120 となれば OK.
> outerInterpreter("5 fact . cr")
 120
nil
```

# おまけの REPL
毎回、外部インタプリタを起動するのも面倒なので、read-eval-print-loop (REPL)を行うスクリプト tinyLuaForth.lua を載せておきます。使い方ですが、読み込みたい辞書ファイルの名前を引数として起動し、空行入力で終了します（当初掲載していたバージョンではプロンプトがありませんでしたが、修正にあたり、本バージョンではプロンプトを表示するようにしました。プロンプトは > としたかったのですが、Lua のものと混同しやすいため、あえて -> というものにしています）。

```lua:tinyLuaForth.lua
require "outerInterpreter"
tinyLuaForth=function(inDictFilePath)
    print "--- tiny LuaForth 1.2 ---"; dofile(inDictFilePath)
    while true do io.write("-> ")
        local line=io.read(); if line=="" then print("bye."); break end
        local result=outerInterpreter(line)
        print(result==nil  and " ok." or "unknown word '"..result.."'")
    end
end
```

使い方のサンプルです。

```lua:ex_tinyLuaForth.txt
> dofile "tinyLuaForth.lua"
> tinyLuaForth("tinyDict.lua")
--- tiny LuaForth 1.1 ---
-> 5 dup * .
 25 ok.
-> : square dup * ;
 ok.
-> 5 square .
 25 ok.
-> : cube dup square * ;
 ok.
-> 5 cube .
 125 ok.
-> : fact dup 1 > if dup 1 - fact * then ;
 ok.
-> 5 fact .
 120 ok.
->
bye.
> 
```
外部インタプリタから戻って来ると print("ok.") を実行しているので、今回はワード cr の実行は必要ありません。

# まとめ
前回と今回にわたり、Eduardo Ochs の "[Boostrapping a Forth in 40 lines of Lua code](http://angg.twu.net/miniforth-article.html)" に触発されて、40 行程度の Forth システムを、できるだけ素直な実装で作成してみました。より素直な実装方法や、エレガントな実装方法がありましたら、コメントなどいただけると助かります。

今回、作成した Lua による Forth インタプリタには、標準の Forth とは異なる部分があります。例えば、辞書に連想配列を使用しているため、そのままでは FORGET が実現できません。一方、モダンな部分も自然に実装されており、ひとつは、Lua で取り扱うことができる情報であれば何でもプッシュ・ポップできる点です（もちろん、トークンの解釈部分のを修正する必要がありますが…）。これらを踏まえて、Lua で作る Forth というものも考えてみたく思います。それはもしかすると、factor とか mops という言語に近いものになるのかもしれませんが、個人的には、もっとゆるく、Lua で実装するが故に、素直にかつ簡単に実装できる範囲で Lua Forth というものを捉えていければ、と思っています。

# 後日談
実際に Lua で Forth 系言語を作成してみました。以下の記事もご覧になっていただければ幸いです。

* [F/L というオレオレ言語の製作を通じて思ったこと・感じたこと](https://qiita.com/iigura/items/6d17910969c9866058b4)
* [メイキングオブ・プログラミング言語 F/L](https://qiita.com/iigura/items/756e07bafdd5dd361657)
* [言語作成の習作：プログラミング言語 F/L](https://qiita.com/iigura/items/02b2d9c23043b6876b20)
* [F/L でのプログラミング](https://qiita.com/iigura/items/fe7a69e53cb8b1fd2d7a)
* [F/L のワード一覧（辞書ごとのリファレンス）](https://qiita.com/iigura/items/d8413b256012c8f828bd)