Squirrel_Lang/ch2_1

Last-modified: 2008-07-31 (木) 12:25:39

戻る

スレッド

Squirrelは協調スレッド(コルーチンとも呼ばれる)をサポートしている。
協調スレッドは実行中に中断でき、呼び出し元に値を返すことができるサブルーチンである。後にこの実行は、中断した場所から再開することができる。

一見すると、Squirrelのスレッドはジェネレータと同じように見えるかもしれない。事実、その挙動は非常に似ている。
しかし、ジェネレータは呼び出し元のスタック上で動作し、ローカルなルーチンスタックだけを持つのに対して、スレッドは独自の実行スタックとグローバルテーブルとエラーハンドラを持つ。
これによってスレッドはネストされた呼び出しを中断でき、独自のエラーポリシーを持つことができる。

スレッドの使用

スレッドは組み込み関数newthread(func)を通じて作成される。
この関数は引数をしてSquirrel関数を取り、それを新しいスレッドオブジェクトにバインドする(これがスレッドの本体となる)。
戻り値となったスレッドオブジェクトは初期状態はidleである。このスレッドは関数threadobj.call()によって開始することができる。メソッドcallに渡される引数は、そのスレッドの関数へ渡される。

スレッドは関数suspend()を呼び出すことで中断することができる。このとき、スレッドを起こした(または開始させた)関数に戻る。(もしsuspend()に1引数が渡されたなら、それは呼び出し元への戻り値となる。引数がないなら、戻り値はnullとなる)。
中断されたスレッドは関数threadobj.wakeupを呼び出すことで再開することができる。このとき、以前のsuspendの位置に戻る(もし、wakeup()に1引数が渡されたなら、それは関数suspend()への戻り値となる。引数がないなら、戻り値はnullとなる)。
[この段落の訳にかなり自信がない…]

そのメイン関数が明示的なreturn命令や関数本体の末尾によって終了するか、実行中に取り扱えない例外が発生したときに、スレッドは終了する。

function coroutine_test(a, b)
{
    ::print(a + " " + b + "\n");
    local ret = ::suspend("suspend 1");
    ::print("the coroutine says " + ret + "\n");
    ret = ::suspend("suspend 2");
    ::print("the coroutine says " + ret + "\n");
    ret = ::suspend("suspend 3");
    ::print("the coroutine says " + ret + "\n");
    return "I'm done"
}
local coro = ::newthread(coroutine_test);
local susparam = coro.call("test", "coroutine"); // コルーチンの開始
local i = 1;
do
{
    ::print("suspend passed (" + susparam + ")\n")
    susparam = coro.wakeup("ciao " + i);
    ++i;
} while (coro.getstatus() == "suspended")
::print("return passed (" + susparam + ")\n")

このプログラムの結果は次のようになる。

test coroutine
suspend passed (suspend 1)
the coroutine says ciao 1
suspend passed (suspend 2)
the coroutine says ciao 2
suspend passed (suspend 3)
the coroutine says ciao 3
return passed (I'm done).

次の興味深い例ではスレッドと末尾再帰を組み合わせている。

function state1()
{
    ::suspend("state1");
    return state2(); // 末尾呼び出し
}
function state2()
{
    ::suspend("state2");
    return state3(); // 末尾呼び出し
}
function state3()
{
    ::suspend("state3");
    return state1(); // 末尾呼び出し
}
local statethread = ::newthread(state1)
::print(statethread.call()+"\n");
for(local i = 0; i < 10000; i++)
    ::print(statethread.wakeup() + "\n");

弱参照

弱参照によって、オブジェクト自身の生存期間の影響を受けることなく、オブジェクトを参照するオブジェクトを作成することができるようになる。
Squirrelにおいて、弱参照オブジェクトはファーストクラスオブジェクトであり、組み込みメソッドobj.weakref()を通して作成される。
nullを除く全ての型はメソッドweakref()を実装している。しかし、真偽型、整数型、浮動小数点数型において、そのメソッドは単純にそのオブジェクト自身を返す(これらの型は常に値渡し(passed by value)されるため)。

弱参照オブジェクトがコンテナ(テーブルスロット、配列、クラス、インスタンス)に代入されたときの取り扱いは他のオブジェクトと異なる。
弱参照オブジェクトを持つコンテナスロットをフェッチしたとき、それは常に(弱参照オブジェクト自体ではなく)弱参照オブジェクトが差す値を返す。
これによって、プログラマはこれらが弱参照かどうかという事実を気にする必要がなくなる。
弱参照オブジェクトが差す値が破壊されたとき、弱参照オブジェクトは自動的にnullにセットされる。

local t = {}
local a = ["first", "second", "third"]
// 弱参照オブジェクトを作成して、それをテーブルのスロットへ代入する
t.thearray = a.weakref();

テーブルスロットthearrayは配列への弱参照オブジェクトとなる。
次の例は"first"を出力する。テーブル(や他の全てのコンテナ)は常に弱参照オブジェクトが差すオブジェクトを返すからである。

print(t.thearray[0]);

配列を差す強参照はローカル変数aが所有する。よって、次の例によってaに整数が代入されると、配列は破壊される。

a = 123;

弱参照オブジェクトが差すオブジェクトが破壊されたとき、弱参照オブジェクトはnullを差すようになるため、次の例では"null"が出力される。

::print(typeof(t.thearray))

弱参照オブジェクトの明示的な取り扱い

弱参照オブジェクトがローカル変数へ代入された場合は、弱参照オブジェクトとして取り扱われる。

local t = {}
local weakobj = t.weakref();

次の例は"weakref"と出力される。

::print(typeof(weakobj))

弱参照オブジェクトが差すオブジェクトは組み込みメソッドweakref.ref()を通して得ることができる。

次の例は"table"と出力される。

::print(typeof(weakobj.ref()))

委譲

Squirrelは暗黙の委譲をサポートしている。
全てのテーブルとユーザデータは親テーブル(委譲)を持つことができる。親テーブルは通常のテーブルであり、その子に対する特別な振る舞いを定義することができる。
テーブル(もしくはユーザデータ)が、対応するスロットを持たないキーを指定されたとき、インタプリタは自動的にその親へgetまたはsetの処理を委譲する。

Entity <- {}
function Entity::DoStuff()
{
    ::print(_name);
}
local newentity = delegate Entity : {
    _name = "I'm the new entity"
}
newentity.DoStuff(); // prints "I'm the new entity"

テーブルの親はキーワードparentを通じて検索される。parentは「擬似スロット」である。スロットparentがセットされていないとき、かわりに委譲命令を使用しなければならない。

local thedelegate = newentity.parent;

メタメソッド

メタメソッドは言語の意味的な様相をカスタマイズすることを可能にするメカニズムである。これらのメソッドは通常の関数で、テーブルparent(委譲)またはクラス宣言に配置されている。
メタメソッドを定義するだけで、テーブルとクラスインスタンスの振る舞いの様相を大きく変更することが可能となる。(インスタンスではなく)クラスオブジェクトは、_newmemberと_inheritedという2つのメタメソッドのみをサポートする。

例えば、2つのテーブルに対し通常の == とは違う関係の演算子を用いるとき、VMはテーブルの親がメソッド_cmpを持っているかどうかを調べる。
もし持っているならそれを呼び出して、2つのテーブルの関係を決定する。

local comparable = {
    _cmp = function (other)
    {
        if (name < other.name) return -1;
        if (name > other.name) return 1;
        return 0;
    }
}
local a = delegate comparable : { name = "Alberto" };
local b = delegate comparable : { name = "Wouter" };
if (a > b)
    print("a>b")
else
    print("b<=a");

クラスに対して、上記のコードは次のようになる。

class Comparable {
    constructor(n)
    {
        name = n;
    }
    function _cmp(other)
    {
        if (name < other.name) return -1;
        if (name > other.name) return 1;
        return 0;
    }
    name = null;
}
local a = Comparable("Alberto");
local b = Comparable("Wouter");
if (a > b)
    print("a>b")
else
    print("b<=a");

_set

インデックス idx がこのオブジェクトまたは委譲チェーンに存在しないときに呼び出される。

function _set(idx, val) // val を返す

_get

インデックス idx がこのオブジェクトまたは委譲チェーンに存在しないときに呼び出される。

function _get(idx) // フェッチされた値を返す

_newslot

テーブルに新規スロットを追加するときに呼び出される。

function _newslot(key, val) // val を返す

もし、対象となるテーブル上にスロットが既に存在しているなら、"new slot" 演算子を用いたとしてもこのメソッドは呼び出されない。

_delslot

テーブルからスロットを削除するときに呼び出される。

このメソッドが呼び出されるなら、Squirrelはそのスロットの削除を行わない。

function _delslot(key)

_add

演算子 +

function _add(op) // this + op の結果を返す

_sub

(_addのような)演算子 -

_mul

(_addのような)演算子 *

_div

(_addのような)演算子 /

_modulo

(_addのような)演算子 %

_unm

単項演算子 -

function _unm()

_typeof

テーブル、ユーザデータ、クラスインスタンス上の演算子 typeof によって呼び出される。

function _typeof() // 文字列としてthisの型を返す

_cmp

演算子 < > <= >=をエミュレートするために呼び出される。

function _cmp(other)

次のような整数を返す。

>0if this > other
0if this == other
<0if this < other

_call

テーブル、ユーザデータ、クラスインスタンスが呼び出されたときに、呼び出される。

function _call(original_this, params…)

_cloned

テーブル、ユーザデータ、クラスインスタンスが複製(クローン)されたときに、複製先から呼び出される。

function _cloned(original)

_nexti

テーブル、ユーザデータ、クラスインスタンスがforeachループによって反復されたときに、呼び出される。

function _nexti(previdx)

previdxがnullなら、最初の反復であることを意味する。この関数は「次」の値のインデックスを返さなければならない。

_tostring

文字列連結中や表示関数がテーブル、ユーザデータ、クラスインスタンスを表示するときに呼び出される。
このメソッドはAPI関数sq_tostring()によっても呼び出される。

function _tostring()

オブジェクトの文字列表現を返さなければならない。

_inherited

クラスオブジェクトを_inheritedを実装しているクラスから継承させるときに呼び出される。

function _inherited(attributes)

戻り値は無視される。

_newmember

クラス本体で宣言される各メンバに対して(宣言時に)呼び出される。

function _newmember(index, value, attributes)

もし、この関数が実装されているなら、メンバはクラスに追加されない。

組み込み関数

Squirrel VMは組み込み補助関数を持つ。

グローバルシンボル

array(size, [fill])

指定サイズの配列を作成して返す。
もし、オプション引数fillが指定されていたら、新配列のスロットはこの値で埋められる。もしfillが省略されているなら、nullが代わりに用いられる。

seterrorhandler(func)

実行時エラーハンドラをセットする。

setdebughook(hook_func)

デバッグフックをセットする。

enabledebuginfo(enable)

コンパイル時のデバッグ行情報生成を有効/無効にする。enableがnullでないなら有効、enableがnullなら無効である。

getroottable()

VMのルートテーブルを返す。

assert(exp)

もし、expがnullなら例外を投げる。

print(x)

xを標準出力に表示する。

compilestring(string, [buffername])

Squirrelスクリプトを含んでいる文字列をコンパイルして関数に変換し、それを返す。

local compiledscript = compilestring("::print(\"ciao\")");
// run the script
compiledscript();

collectgarbage()

ガーベージコレクタを呼び出し、発見し(て削除し)た循環参照の数を返す

type(obj)

メタメソッド_typeofを呼び出さずに、オブジェクトの「生」の型を返す。

getstackinfos(level)

指定されたコールスタックレベルのスタック情報を返す。次のようなフォーマットのテーブルを返す。

{
    func = "DoStuff",       // 関数名
    src = "test.nut",       // ソースファイル名
    line = 10,              // 行番号
    locals = {              // ローカル変数のテーブル
        a = 10,
        testy = "I'm a string"
    }
}

level = 0は現在の関数、level = 1は呼び出し元、といったようになる。もしそのスタックレベルが存在しないなら、この関数はnullを返す。

newthread(threadfunc)

強調スレッドオブジェクト(コルーチン)を新たに作成し、それを返す。

デフォルト委譲

nullとユーザデータを除いて、全てのSquirrelオブジェクトは、そのオブジェクト自体からの情報の操作と取得のための関数の集合を含んだデフォルト委譲を持つ。

整数

  • tofloat()
    値を浮動小数点数に変換して、それを返す
  • tostring()
    値を文字列に変換して、それを返す
  • tointeger()
    整数値を返す(ダミー関数)
  • tochar()
    整数によって表現された文字を含む文字列を返す
  • weakref()
    ダミー関数。整数自体を返す

浮動小数点数

  • tofloat()
    浮動小数点数の値を返す(ダミー関数)
  • tointeger()
    値を整数に変換して、それを返す
  • tostring()
    値を文字列に変換して、それを返す
  • tochar()
    浮動小数点数の整数部によって表現された文字を含む文字列を返す
  • weakref()
    ダミー関数。浮動小数点数自体を返す

真偽値

  • tofloat()
    trueなら1.0を、falseなら0.0を返す
  • tointeger()
    trueなら1を、falseなら0を返す
  • tostring()
    trueなら"true"を、falseなら"false"を返す
  • weakref()
    ダミー関数。真偽値自体を返す

文字列

  • len()
    文字列の長さを返す
  • tointeger()
    文字列を整数に変換して、それを返す
  • tofloat()
    文字列を浮動小数点数に変換して、それを返す
  • tostring()
    文字列を返す(ダミー関数)
  • slice(start, [end])
    新規文字列として部分文字列を返す。startからendまで(endは含まれない)をコピーする。
    • もしstartが負なら、添字は (文字列長 + start) として計算される。
    • もしendが負なら、添字は (文字列長 + end) として計算される。
    • もしendが省略されたなら、endは文字列長であるとする
  • find(substr, [startidx])
    startidxの位置から部分文字列substrを検索し、最初に発見した部分文字列の添字を返す。
    • もしstartidxが省略されたなら、検索は文字列の先頭から行われる。
    • もしsubstrが発見できなければ、この関数はnullを返す
  • tolower()
    文字列を小文字(lowercase)にした文字列を返す(元の文字列は変化しない)
  • toupper()
    文字列を大文字(uppercase)にした文字列を返す(元の文字列は変化しない)
  • weakref()
    このオブジェクトへの弱参照を返す

テーブル

  • len()
    テーブル中のスロットの数を返す
  • rawget(key)
    委譲を用いずにスロットkeyから値を得る
  • rawset(key, val)
    委譲を用いずにスロットkeyに値をセットする。もしスロットが存在しないなら、作成される
  • rawdelete()
    委譲を用いずにスロットkeyを削除し、その値を返す。もしスロットが存在しないなら、nullが返される
  • rawin(key)
    もしスロットkeyが存在するなら、trueを返す。
    この関数は演算子inと同じ動作をするが、委譲を行わない。
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    メタメソッド_tostringを呼び出す。それが失敗したなら"(insatnce : pointer)"を返す

配列

  • len()
    配列の長さを返す
  • append(val)
    値valを配列の末尾に追加する
  • extend(array)
    与えられた配列の全要素を追加することで、この配列を拡張する
  • pop()
    配列末尾から値を削除してその値を返す
  • top()
    もっとも大きい添字を持つ配列の値を返す
  • insert(idx, val)
    値valを配列の位置idxに挿入する
  • remove(idx)
    配列の位置idxの値を削除する
  • resize(size, [fill])
    配列をリサイズする。(もし以前のサイズよりも指定サイズが大きい場合)もしオプション引数fillが指定されているなら、新しい配列のスロットをその値で埋める。
    もし引数fillが省略されているなら、nullが使用される
  • sort([compare_func])
    配列をソートする。カスタム比較関数をオプションとして渡すことができる。この関数プロトタイプは次の通り。
    function custom_compare(a, b)
    {
      if (a > b) return 1
      else if (a < b) return -1
      return 0;
    }
  • reverse()
    配列要素の位置を反転する
  • slice(start, [end])
    新規配列として部分配列を返す。startからendまで(endは含まれない)をコピーする。
    • もしstartが負なら、添字は (配列長 + start) として計算される。
    • もしendが負なら、添字は (配列長 + end) として計算される。
    • もしendが省略されたなら、endは配列長であるとする
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(array : pointer)"を返す

関数

  • call(_this, args…)
    指定された環境(this)オブジェクトと引数で関数を呼ぶ
  • pcall(_this, args…)
    指定された環境(this)オブジェクトと引数で関数を呼ぶ。
    失敗時にこの関数はエラーコールバックを呼ばない(pcallは'protected call'の略)。
  • acall(array_args)
    指定された環境(this)オブジェクトと引数で関数を呼ぶ。
    この関数は呼び出し先の関数に渡された引数を含む配列を受け入れる。
  • pacall(array_args)
    指定された環境(this)オブジェクトと引数で関数を呼ぶ
    この関数は呼び出し先の関数に渡された引数を含む配列を受け入れる。
    失敗時にこの関数はエラーコールバックを呼ばない(pacallは'protected array call'の略)。
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(closure : pointer)"を返す
  • bindenv(env)
    この関数(クロージャ)を複製し、環境オブジェクトをオブジェクトenv(テーブル、クラス、インスタンス)で束縛する。
    新規関数の引数には常にenvがセットされるようになる。
    作成された関数は、その環境オブジェクトを弱参照として保持するので、その生存期間の制御をすることができないので、注意すること。

クラス

  • instance()
    クラスの新たなインスタンスを返す。
    この関数はインスタンスのコンストラクタを呼ばない。
    コンストラクタは明示的に呼ばれなければならない(例えば、class_inst.constructor(class_inst) )
  • getattributes(membername)
    指定されたメンバの属性を返す。
    引数membernameがnullなら、この関数はクラス属性を返す
  • getattributes(membername, attr)
    指定されたメンバの属性をセットし、以前の属性値を返す。
    引数membernameがnullなら、この関数はクラス属性をセットする
  • rawin(key)
    もしスロットkeyが存在するなら、trueを返す。
    この関数は演算子inと同じ動作をするが、委譲を行わない。
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(class : pointer)"を返す

クラスインスタンス

  • getclass()
    このインスタンスを作成したクラスを返す
  • rawin(key)
    もしスロットkeyが存在するならtrueを返す。
    この関数は演算子inと同じ動作をするが、委譲を行わない。
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    メタメソッド_tostringを呼び出す。それが失敗したなら"(insatnce : pointer)"を返す

ジェネレータ

  • getstatus()
    ジェネレータの状態を示す文字列("idle", "running", "suspended")を返す
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(generator : pointer)"を返す

スレッド

  • call(...)
    引数を指定してスレッドを開始する
  • wakeup([wakeupval])
    中断されているスレッドを再開する。
    スレッドを中断したときの関数(通常suspended())に対する戻り値として用いるためのオプション引数が指定可能である。
  • getstatus()
    スレッドの状態を示す文字列("idle", "running", "suspended")を返す
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(thread : pointer)"を返す

弱参照

  • ref()
    弱参照が指しているオブジェクトを返す。もしそのオブジェクトが既に削除されているならnullを返す
  • weakref()
    このオブジェクトへの弱参照を返す
  • tostring()
    "(weakref : pointer)"を返す