Skip to content

Terra API リファレンス

リスト

terralib内のAPIで配列を返すものは、常にListオブジェクトを返します。これは、Luaコード内で使用するためのより完全なリストデータ型です。

List型は通常のLuaテーブルで、以下の追加メソッドを持っています:

  1. Luaのtableグローバルの全メソッド
  2. SMLのリスト型に基づいた高階関数のリスト

これにより、Terraオブジェクトをメタプログラムで操作しやすくなります。

lua
local List = require("terralist")

List() -- 空のリスト
List { 1,2,3 } -- 要素3つのリスト

テーブルで初期化された新しいリストを作成します。

Listには次の関数もあります:

lua
-- Luaのstring.subに相当、リストの部分取得
list:sub(i,j)

-- リストを反転
list:rev() : List[A]

-- すべての要素に関数fnを適用
list:app(fn : A -> B) : {}

-- すべての要素にmapを適用し、新しいリストを返す
list:map(fn : A -> B) : List[B]

-- 関数fn(e)が真である要素のみの新しいリスト
list:filter(fn : A -> boolean) : List[A]

-- すべての要素にmapを適用し、結果のリストを連結
list:flatmap(fn : A -> List[B]) : List[B]

-- 条件を満たす最初の要素を見つける
list:find(fn : A -> boolean) : A?

-- k,v = fn(e)を各要素に適用し、同じkの値vをグループ化
list:partition(fn : A -> {K,V}) : Map[ K,List[V] ]

-- 再帰的に初期値initとリストの各要素にfnを適用
list:fold(init : B,fn : {B,A} -> B) -> B

-- リストの各要素にfnを再帰的に適用
list:reduce(fn : {B,A} -> B) -> B

-- リストのいずれかの要素が条件を満たすか
list:exists(fn : A -> boolean) : boolean

-- リストのすべての要素が条件を満たすか
list:all(fn : A -> boolean) : boolean

高階関数を取るすべての関数には、関数にリストのインデックスも提供するiバリアントもあります:

lua
list:mapi(fn : {int,A} -> B) -> List[B]

リスト関数のmapのようなものは、関数を引数として取る高階関数です。 高階List関数の引数となる関数には以下のいずれかを指定できます:

  1. Luaの通常の関数
  2. 演算子の文字列(例:"+"src/terralist.luaのopテーブルを参照)
  3. オブジェクト上で呼び出すフィールドまたはメソッドを指定する文字列

例:

lua
local mylist = List { a,b,c }
mylist:map("foo") -- 各要素に対してフィールドを選択:a.foo, b.foo, c.foo, など
                  -- a.fooが関数の場合、メソッドa:foo()として扱われる

高階関数への追加引数は、これらの関数に渡されます。これは、Luaのインライン関数の記述が冗長であるため、インライン関数を避けるためのものです。

lua
local List = require("terralist")
List:isclassof(exp)

expがリストであればtrueを返します。

lua
terralib.israwlist(l)

lが連続する整数キー1からNまで(他のキーなし)のテーブルであればtrueを返します。

Terra リフレクションAPI

すべてのTerraエンティティは、ファーストクラスのLuaオブジェクトでもあります。これにはTerraの関数グローバル変数が含まれます。クォートは、Terraのクォーテーション構文(バッククォートやquote)によって返されるオブジェクトで、Terra関数内にはまだ含まれていないコードの断片を表します。シンボルは変数に固有の名前を与えるもので、新しいパラメータやローカル変数を定義する際に使用されます。

Terra関数がLuaオブジェクトに変換できない値を返す場合、Terraのになります。これはLuaからアクセス可能なラッパーです(内部的にはLuaJITの"cdata"オブジェクトです)。

各オブジェクトには、操作用のLua APIが提供されています。例えば、関数の逆アセンブルを行うterrafn:disas()や、型の特性を問い合わせるtyp:isarithmetic()といったメソッドがあります。

ジェネリック

lua
tostring(terraobj)
print(terraobj)

すべてのTerraオブジェクトにはデバッグ用の文字列表現が備わっています。

lua
terralib.islist(t)
terralib.isfunction(t)
terralib.types.istype(t)
terralib.isquote(t)
terralib.issymbol(t)
terralib.ismacro(t)
terralib.isglobalvar(t)
terralib.islabel(t)
terralib.isoverloadedfunction(t)

特定のオブジェクトがTerraのクラス型であるかをチェックします。

lua
terralib.type(o)

type(o)の拡張版で、次のように定義されています:

lua
function terralib.type(t)
   if terralib.isfunction(t) then return "terrafunction"
   elseif terralib.types.istype(t) then return "terratype"
   elseif terralib.ismacro(t) then return "terramacro"
   elseif terralib.isglobalvar(t) then return "terraglobalvariable"
   elseif terralib.isquote(t) then return "terraquote"
   elseif terralib.istree(t) then return "terratree"
   elseif terralib.islist(t) then return "list"
   elseif terralib.issymbol(t) then return "terrasymbol"
   elseif terralib.isfunction(t) then return "terrafunction"
   elseif terralib.islabel(t) then return "terralabel"
   elseif terralib.isoverloadedfunction(t) then return "overloadedterrafunction"
   else return type(t) end
end
lua
memoized_fn = terralib.memoize(function(a,b,c,...) ... end)

関数の結果をメモ化します。ある引数セットで関数が初めて呼び出されると、その引数での戻り値が計算されキャッシュされます。同じ引数での以後の呼び出し(Luaの等価性による)では、そのキャッシュされた値が返されます。テンプレート化された値(例:同じTに対して毎回同じVector(T)型を返すべき場合)を生成する際に便利です。

関数

Terra関数は、Terraコードのエントリーポイントです。関数は定義済みまたは未定義である可能性があります (myfunction:isdefined())。未定義の関数は型が既知ですが、実装がまだ提供されていません。関数が初めて実行されるまで、myfunction:resetdefinition(another_function)を使用して定義を変更できます。

lua
[local] terra myfunctionname :: type_expression
[local] terra myfunctionname :: {int,bool} -> {int}

Terra関数の宣言。新しい未定義の関数を作成し、Lua変数myfunctionnameに保存します。 オプションのlocalキーワードを使用する場合、myfunctionnameは最初に新しいローカルLua変数として定義されます。localキーワードを使用しない場合、myfunctionnameはテーブル指定子(例:a.b.c)として使うこともできます。

lua
[local] terra myfunctionname(arg0 : type0,
                             ...
                             argN : typeN)
        [...]
end

Terra関数の定義。指定されたコード本体を使用してmyfunctionnameを定義します。myfunctionnameが未定義の場合は既存の関数宣言に定義を追加し、それ以外の場合は新しい関数宣言を作成して定義を追加します。

lua
local func = terralib.externfunction(function_name,function_type)

外部で定義された関数にバインドされたTerra関数を作成します。例:

lua
local atoi = terralib.externfunction("atoi",{rawstring} -> {int})
lua
myfunction(arg0,...,argN)

myfunctionはTerra関数です。Luaからmyfunctionを呼び出します。未定義の関数に対して呼び出しを行うとエラーになります。引数はLua値からTerraへの変換規則を使用してTerraに変換され、戻り値はTerra値からLuaへの変換規則に基づいて変換されます。

lua
local b = func:isdefined()

関数に定義が存在する場合はtrueを返します。関数を定義するには、func:adddefinitionfunc:resetdefinition、または関数定義構文terra func(...) ... endを使用します。

lua
local b = func:isextern()

この関数がprintfのような外部シンボルにバインドされている場合はtrueを返します。外部関数はterralib.includecでC関数をインポートするか、terralib.externfunctionを呼び出して作成されます。

lua
func:adddefinition(another_function)

funcの定義をanother_functionの現在の定義に設定します。another_functionは定義されている必要があり、funcは未定義でなければなりません。funcanother_functionの型は一致している必要があります。

lua
func:resetdefinition(another_function)

funcの定義をanother_functionの現在の定義に設定(またはリセット)します。another_functionは定義されている必要があります。funcは定義済みまたは未定義のどちらでもかまいません。すでにコンパイルされている関数に対してこのメソッドを呼び出すとエラーになります。

lua
func:printstats()

この関数のコンパイルおよびJITにかかった時間などの統計を出力します。このメソッドを呼び出すと、関数がコンパイルされます。

lua
func:disas()

すべての関数定義をx86アセンブリや最適化されたLLVM形式に逆アセンブルして出力します。パフォーマンスデバッグに役立ちます。このメソッドを呼び出すと関数がコンパイルされます。

lua
func:printpretty([quote_per_line=true])

この関数のコードを視覚的に表現して出力します。デフォルトでは、元のコードの各部分を別の行として表示します。quote_per_linefalseに設定すると、読みやすいようにコードがまとめて表示されます。

lua
r0, ..., rn = myfunction(arg0, ... argN)

Luaからmyfunctionを呼び出します。引数は、Terra値とLua値を相互に変換するための規則に従って変換され、戻り値も同様にLua値に変換されます。このメソッドの呼び出しにより、関数が機械コードにコンパイルされます。

lua
func:compile()

関数を機械コードにコンパイルします。この関数が必要とするすべての関数やグローバル変数も定義されていることを確認します。

lua
function_type = func:gettype()

関数のを返します。function_type.parametersはパラメータの型リスト、function_type.returntypeは戻り値の型です。複数の値を返す関数の場合、戻り値の型はタプルになります。

lua
func:getpointer()

この関数の機械コードを指すLuaJITのctypeオブジェクトを返します。呼び出すと関数がコンパイルされます。

lua
str = func:getname()
func:setname(str)

関数の見やすい名前を取得または設定します。生成されたコードを表示する際に役立ちますが、関数の動作には影響しません。

lua
func:setinlined(bool)

trueの場合、この関数は常にインライン化されます。falseの場合、インライン化されません。デフォルトではLLVMの関数インライナーの裁量によりインライン化されます。

lua
func:setoptimized(bool)

すべてのTerra関数はデフォルトで最適化されます(Clangの-O3に相当)。最適化を無効にするにはfalseを渡します(Clangの-O0に相当)。

lua
func:setcallingconv(string)

関数の呼び出し規約を設定します。デフォルトではLLVMのデフォルトの呼び出し規約が使用されます。有効な値は、LLVMのテキストベースのアセンブリ言語で指定できるものと同じです(執筆時点では公式のLLVMドキュメントは不完全で、特にターゲット固有の呼び出し規約についてはソースコードを直接参照する必要がある場合があります)。

型 (Types)

型オブジェクトはTerraオブジェクトの型を表す一級のLua値です。Terraの組み込み型システムは、Cのような低レベル言語の型システムに似ています。型コンストラクタ(例:&int)は有効なLua式であり、Terraの型オブジェクトを返します。リンクリストのような再帰型をサポートするために、構造体はメンバーやメソッドが完全に指定される前に宣言できます。構造体が宣言されただけで定義されていない場合、それは「不完全」とみなされ、値としては使用できません。ただし、ポインタ算術が不要な限り、不完全型へのポインタは使用可能です。型が完全に指定される必要がある場合(例:コンパイルされた関数で使用される、またはその型のグローバル変数を割り当てる場合)に、「完全」となります。この時点で型の完全な定義が必要です。

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
bool
float double

基本型(プリミティブ型)。

&typ

typへのポインタを構築します。

typ[N]

typ型のインスタンスをN個持つ配列を構築します。Nは正の整数である必要があります。

lua
vector(typ,N)

typ型のインスタンスをN個持つベクトルを構築します。Nは整数で、typはプリミティブ型である必要があります。これらの型はSSEのようなベクトル命令セットの抽象化です。

lua
parameters -> returntype

関数ポインタを構築します。parametersおよびreturnsは、型のリスト(例:{int,int})または単一の型(例:int)で指定できます。複数の値を返す場合、戻り値の型としてタプルが使用されます。

戻り値の型がvoidの場合は、空のタプル{}を使用します。

lua
struct { field0 : type0, ..., fieldN : typeN }

ユーザー定義型またはエキソタイプを構築します。各structの呼び出しは独自の型を生成します(指名型システムを使用)。詳細はエキソタイプを参照してください。

lua
tuple(type0, type1, ..., typeN)

タプルを構築します。これはstructの特別な型で、type0...typeNの値をobj._0...obj._Nのフィールドとして保持します。通常の構造体と異なり、同じ引数を使ったtupleの各呼び出しは同じ型を返します。

lua
terralib.types.istype(t)

tが型であればtrueを返します。

lua
type:isprimitive()

typeがプリミティブ型(上記参照)であればtrueを返します。

lua
type:isintegral()

typeが整数型であればtrueを返します。

lua
type:isfloat()

typefloatまたはdoubleであればtrueを返します。

lua
type:isarithmetic()

typeが整数型または浮動小数点型であればtrueを返します。

lua
type:islogical()

typeboolであればtrueを返します(将来的に、ベクトル命令内のフラグに近いサイズのブール型をサポートする予定)。

lua
type:canbeord()

typeorおよびand演算に使用できる場合(つまり整数型や論理型だが浮動小数点型ではない場合)、trueを返します。

lua
type:ispointer()

typeがポインタであればtrueを返します。type.typeは指している型です。

lua
type:isarray()

typeが配列であればtrueを返します。type.Nは長さを、type.typeは要素の型を表します。

lua
type:isfunction()

typeが関数(関数ポインタではない)であればtrueを返します。type.parametersはパラメータ型のリスト、type.returntypeは戻り値の型です。複数の値を返す関数の場合、戻り値の型はその値のtupleになります。

lua
type:isstruct()

type構造体であればtrueを返します。

lua
type:ispointertostruct()

typeが構造体へのポインタであればtrueを返します。

lua
type:ispointertofunction()

typeが関数へのポインタであればtrueを返します。

lua
type:isaggregate()

typeが配列または構造体であればtrueを返します(任意の型を保持できる型です)。

lua
type:iscomplete()

typeが完全に定義され、コードで使用できる状態であればtrueを返します。非集約型では常にtrueです。集約型の場合、その内部に含まれるすべての型が定義されていればtrueとなります。type:complete()を呼び出すと強制的に型が完全になります。

lua
type:isvector()

typeがベクトルであればtrueを返します。type.Nは長さ、type.typeは要素の型です。

lua
type:isunit()

typeが空のタプルであればtrueを返します。空のタプルは、戻り値を持たない関数の戻り値型としても使用されます。

lua
type:(isprimitive|isintegral|isarithmetic|islogical|canbeord)orvector()

typeが指定されたプロパティを持つプリミティブ型である場合、またはそのプリミティブ型を要素とするベクトル型である場合にtrueを返します。

lua
type:complete()

型を強制的に完全にします。構造体の場合、構造体のレイアウトを計算し(定義されていれば__getentries__staticinitializeを呼び出します)、この型が参照するすべての型を再帰的に完全にします。

lua
type:printpretty()

型を出力し、構造体の場合はそのメンバーも含めて表示します。

lua
terralib.sizeof(terratype)

ffi.sizeofのラッパーです。terratypeを完全にして、そのサイズ(バイト単位)を返します。

lua
terralib.offsetof(terratype,field)

ffi.offsetofのラッパーです。terratypeを完全にして、terratype内のfieldのオフセット(バイト単位)を返します。

lua
terralib.types.pointer(typ, [addrspace])

実験的機能&typの代替で、LLVMアドレス空間を指定することが可能です。非ゼロアドレス空間の意味はターゲット固有です。

Quotes

Quotes(引用句)は、Terraの引用演算子(バッククォートおよびquote ... in ... end)によって返されるLuaオブジェクトです。これは関数にまだ挿入されていないTerraコードの断片(文や式)を表します。エスケープ演算子([...]およびescape ... emit ... end)を使うと、引用句を周囲のTerraコードに挿入できます。Quotesには、1つの式のみを生成する短い形式と、文や式を生成する長い形式があります。

lua
quotation = `terraexpr
-- `記号で引用句を作成

引用句の短い形式です。バッククォート演算子により、1つのTerra式を含む引用句が作成されます。terraexprには任意のTerra式を指定できます。terraexpr内にエスケープが含まれる場合、それは式が構築される際に評価されます。

lua
quote
    terrastmts
end

引用句の長い形式です。quote演算子により、複数のTerra文を含む引用句が作成されます。この引用句は、Terraコード内で式または文が有効な場所に挿入できます。式のコンテキストで使用される場合、その型は空のタプルになります。

lua
quote
    terrastmts
in
    terraexp1,terraexp2,...,terraexpN
end

長いquote構文には、いくつかの式を生成するin文をオプションで含めることができます。このquoteがTerraコード内で式として挿入される場合、これらの式から構築されたタプルが値として返されます。

lua
local a = quote
    var a : int = foo()
    var b : int = bar()
in
    a + b + b
end
terra f()
    var c : int = [a] -- 'a'はint型
end
lua
terralib.isquote(t)

tが引用句であればtrueを返します。

lua
typ = quoteobj:gettype()

この引用句のTerra型を返します。

lua
typ = quoteobj:astype()

この引用句をTerra型オブジェクトとして解釈しようとします。通常、型を引数として受け取るマクロで使用されます(例:sizeof([&int]))。この関数は、quoteオブジェクトを型に変換します(例:&int)。

lua
bool = quoteobj:islvalue()

この引用句が代入の左辺(l-value)として使用可能であればtrueを返します。

lua
luaval = quoteobj:asvalue()

この引用句を単純なLua値として解釈しようとします。通常、定数を引数として受け取るマクロで使用されます。特定の値のみで動作し、Constant式として利用可能な場合に限られます。生成コードに複雑なデータ構造を渡す場合、マクロよりもエスケープを使用することを検討してください。

lua
quoteobj:printpretty()

この引用句内のコードの視覚表現を出力します。引用句は関数に挿入されるまで型チェックが行われないため、関数の型なしの表現が出力されます。

Symbol

Symbol(シンボル)は、Terraの識別子の抽象的な表現です。変数の使用、定義、関数の引数、フィールド名、メソッド名、ラベルなど、識別子が期待される場所で使用できます(エスケープも参照)。LISPのgensym関数で返されるシンボルに似ています。

lua
terralib.issymbol(s)

sがシンボルであればtrueを返します。

lua
symbol(typ,[displayname])

新しいシンボルを構築します。このシンボルは他のシンボルと一意になります。typはシンボルの型で、displaynameはエラーメッセージで表示される際のオプションの名前です。

Values

LuaJITのFFI APIのラッパーを提供しており、Luaから直接Terraオブジェクトを割り当てたり操作したりできます。

lua
terralib.typeof(obj)

objのTerra型を返します。objは、以前にTerra APIを使用して割り当てられたLuaJIT ctypeである必要があります。または、Terra関数の戻り値として返されるものです。

lua
terralib.new(terratype,[init])

LuaJITのffi.newのラッパーです。型terratypeの新しいオブジェクトを割り当てます。initはオプションの初期化子であり、Terra値とLua値の間での変換ルールに従います。このオブジェクトはLuaから到達不可能になるとガベージコレクションされます。

lua
terralib.cast(terratype,obj)

ffi.castのラッパーです。objterratypeに変換し、Terra値とLua値の間での変換ルールに従います。

グローバル変数

グローバル変数は、すべてのTerra関数で共有されるTerra値です。

lua
global(type,[init,name,isextern,isconstant,addrspace])
global(init,[name,isextern,isconstant,addrspace])

typeと初期値initで新しいグローバル変数を作成します。typeまたはinitのどちらか一方は必須です。typeが指定されない場合は、initから型を推定します。initが指定されない場合、グローバル変数は未初期化のままです。initは通常の変換ルールに従ってTerra値に変換されます。initが指定された場合、型は完了されます。

initにはQuoteも指定でき、これは定数式として扱われ、グローバル変数の初期化に使用されます。nameはデバッグ用の名前です。

isexterntrueの場合、このグローバル変数は外部で定義された変数nameにバインドされます。

isconstanttrueの場合、グローバル変数の内容は定数として扱われます。

addrspacenilでない場合、グローバルは対応するLLVMアドレス空間に配置されます。非ゼロのアドレス空間の意味はターゲットによって異なるため注意が必要です。

lua
globalvar:getpointer()

メモリ内でこのグローバル変数へのポインタであるctypeオブジェクトを返します。型を完了します。

lua
globalvar:get()

このグローバル変数の値をLuaJITのctypeオブジェクトとして取得します。型を完了します。

lua
globalvar:set(v)

vを通常の変換ルールに従ってTerra値に変換し、グローバル変数に設定します。型を完了します。

lua
globalvar:setname(str)
str = globalvar:getname()

このグローバル変数のデバッグ名を設定または取得します。これはデバッグに役立ちますが、グローバル変数の動作には影響しません。

lua
typ = globalvar:gettype()

グローバル変数のTerra型を取得します。

lua
globalvar:setinitializer(init)

このグローバル変数の初期化式を設定または変更します。グローバル変数がコンパイルされる前にのみ有効です。例えば、クラスのvtableを格納するグローバル変数の場合、クラスにメソッドを追加するたびに値を追加することができます。

定数

Terra定数は、Terraコードで使用される定数値を表します。例えば、sin関数のルックアップテーブルを作成する場合、まずLuaで値を計算し、これらの値を格納する定数のTerra配列を作成することができます。コンパイラはこの配列が定数であることを認識して、より積極的な最適化を行うことができます。

lua
constant([type],init)

新しい定数を作成します。initは通常の変換ルールに従ってTerra値に変換されます。オプションのtypeが指定されると、initはその型に明示的に変換されます。型を完了します。

initにはTerraのquoteオブジェクトも指定可能です。この場合、quoteは_定数初期化式_として扱われます。

lua
local complexobject = constant(`Complex { 3, 4 })

定数式は、Terra式のサブセットであり、その値がコンパイル後に確定されるもので、LLVMの定数式の概念におおむね対応します。例えば、関数ポインタの値のように、事前に確定できないものも含まれます。

lua
terra a() end
terra b() end
terra c() end
-- a, b, cを含む関数ポインタの配列
local functionarray = const(`array(a,b,c))
lua
terralib.isconstant(obj)

objがTerra定数であればtrueを返します。

ラベル

ラベルは、goto文などで使用できる抽象的なコード位置です。シンボルのように、ラベルの値を使ってプログラムでコード位置を生成できます。

lua
terralib.islabel(l)

lがラベルであればtrueを返します。

lua
label([displayname])

新しいラベルを構築します。このラベルは他のラベルと一意で、displaynameが同じでも異なるラベルとみなされます。displaynameはエラーメッセージに表示されるオプションの名前です。

マクロ

マクロを使用すると、型チェック中にコンパイラにカスタムの動作を挿入できます。コンパイル時に実行されるため、コンパイラに戻る際は非同期コンパイルを考慮する必要があります。

lua
macro(function(arg0,arg1,...,argN) [...] end)

新しいマクロを作成します。この関数はTerraコード内の各呼び出し時にコンパイル時に呼び出されます。各引数は、マクロに対して引数として渡されるTerraのquoteです。例えば、mymacro(a,b,foo())の呼び出しは、マクロへの引数として3つのquoteを生成します。マクロは単一の値を返し、その値はコンパイル時の変換ルールを使用してTerraオブジェクトに変換されます。

lua
terralib.ismacro(t)

tがマクロであればtrueを返します。

ビルトインマクロ

Terraには以下のビルトインマクロが用意されています。

lua
terralib.intrinsic(name, type)

指定したnametypeに対応するLLVMの組み込み関数を呼び出すTerra関数を返します。たとえば、LLVMはsqrtに以下のような組み込み関数を提供しています。

lua
local sqrt = terralib.intrinsic("llvm.sqrt.f32", float -> float)

これでsqrtを呼び出せるようになり、ターゲットプラットフォーム向けに効率的なコードが生成されます。

なお、使用できる組み込み関数のセットはLLVMのバージョンやターゲットプラットフォームによって異なり、Terra側で制御することはできません。

lua
terralib.attrload(addr, attrs)

addrのアドレスからattrs属性付きでデータを読み込みます。attrsは以下のキーを含むリテラルテーブルで指定します。

  • nontemporal(任意): trueの場合、読み込みが非一時的になります。
  • align(任意): addrのアライメントを指定します。
  • isvolatile(任意): trueの場合、addrの内容が揮発性であると見なされます。

以下の例では、attrload123を返します。

lua
var i = 123
terralib.attrload(&i, { align = 1 })
lua
terralib.attrstore(addr, value, attrs)

addrのアドレスにvalueの値をattrs属性付きで書き込みます。属性の指定方法はattrloadと同じです。

lua
terralib.fence(attrs)

実験的機能。フェンス操作を発行します。指定した属性によって、フェンスを境にした原子命令の順序変更を防ぎます。この操作の意味はLLVMによって決まります。

以下の属性が指定可能です(使用できる属性のセットは各原子操作によって異なります)。

lua
terralib.cmpxchg(addr, cmp, new, attrs)

実験的機能。アドレスaddrでの原子比較交換(cmpxchg)操作を実行します。addrの値がcmpと同じ場合、newの値が書き込まれます。違う場合、値は変更されません。addrの元の値と交換が成功したかどうかを示すブール値のタプルを返します。

使用できる属性は次の通りです(原子操作によって使用できる属性が異なります)。

  • syncscope(任意): LLVM syncscope
  • success_ordering(必須): 交換が成功した場合に適用するLLVM memory ordering
  • failure_ordering(必須): 交換が失敗した場合に適用するLLVMメモリ順序。
  • align(任意): addrのアライメント。attrloadと異なり、alignの値はaddrの内容のサイズ以上でなければなりません(詳細はこちら)。
  • isvolatile(任意): trueの場合、addrの内容が揮発性であると見なされます。
  • isweak(任意): trueの場合、偽の失敗を許可します。交換条件が一致しても書き込まれないことがあります。

以下の例では、最初のcmpxchgは失敗し、{1, false}を返しますが、2番目は成功し{1, true}を返します。最終的なiの値は4になります。

lua
var i = 1
terralib.cmpxchg(&i, 2, 3, {success_ordering = "acq_rel", failure_ordering = "monotonic"})
terralib.cmpxchg(&i, 1, 4, {success_ordering = "acq_rel", failure_ordering = "monotonic"})
lua
terralib.atomicrmw(op, addr, value, atomicattrs)

実験的機能addrのアドレスでvalue値と演算子opを使用した原子読み書き操作(RMW)を実行します。操作は原子的に行われます。addrの元の値を返します。

使用可能な操作はLLVMのドキュメントに指定されています。faddおよびfsub操作は浮動小数点型を必要とし、ほとんどの操作は整数型またはポインタ型を必要とします。使用できる操作のセットはLLVMのバージョンやターゲットプラットフォームに依存することがあります。

指定可能な属性は次の通りです(原子操作によって使用できる属性が異なります)。

  • syncscope(任意): LLVM syncscope
  • ordering(必須): LLVM memory ordering
  • align(任意): addrのアライメント。attrloadとは異なり、alignの値はaddrの内容のサイズ以上でなければなりません(詳細はこちら)。
  • isvolatile(任意): trueの場合、addrの内容が揮発性と見なされます。

以下の例では、atomicrmwi21を書き込み、元の値1を返します(シングルスレッドの場合)。

lua
var i = 1
terralib.atomicrmw("add", &i, 20, {ordering = "acq_rel"})

Exotypes(構造体)

Terraでは、ユーザー定義の集約型を「exotype」と呼びます。これは、Lua APIを使ってTerraの外部で定義されるためです。この設計は、ユーザー定義型の振る舞いを定義するための基本的なメカニズムを提供することを目的としており、言語固有のポリシーを強制することはありません。このため、JavaやC++のようなポリシーベースのクラスシステムを、これらのメカニズムを基にライブラリとして作成することができます。簡潔さと親しみやすさのために、これらの型を言語内で「struct」というキーワードで表現します。

また、一般的なケースに対しては、exotypeの定義用のシンタックスシュガーも提供しています。このセクションでは、まずLua API自体について説明し、その後、シンタックスシュガーがどのようにAPIに変換されるかを示します。

この設計の背景については、公開資料で詳しく説明しています。

Lua API

新しいユーザー定義型は次の呼び出しで作成します。

lua
mystruct = terralib.types.newstruct([displayname])

displaynameはエラーメッセージに表示される任意の名前ですが、newstructを呼び出すたびに名前に関係なくユニークな型が作成されます(Terraでは指名型システムが使用されています)。この型はTerraプログラム内で次のように利用できます。

lua
terra foo()
    var a : mystruct -- mystruct型のインスタンス
end

この型がTerraプログラムで使用される際のメモリレイアウトと動作は、型のmetamethodsテーブルにプロパティ関数を設定することで定義されます。

lua
mystruct.metamethods.myproperty = function ...

Terraの型チェッカーが型に関する情報を知る必要があるとき、型のmetamethodsテーブルにあるプロパティ関数を呼び出します。プロパティが設定されていない場合、デフォルトの動作が適用されます。各プロパティの詳細については個別に説明されます。

metamethodsで設定できるフィールドは以下の通りです。

lua
entries = __getentries(self)

構造体内のフィールドを計算によって決定する_Lua_関数です。構造体内のエントリリストが最初に必要になると、コンパイラは__getentries関数を1度だけ呼び出します。この呼び出し時点では型がまだ完全でないため、このメソッドで型の完全性が必要な操作を行うとエラーになります。entriesはフィールドエントリのListです。各フィールドエントリは以下のいずれかです:

  • { field = stringorsymbol, type = terratype } 形式のテーブルで、名前付きフィールドを指定します。
  • {stringorsymbol, terratype} 形式のテーブルで、名前付きフィールドを指定します。
  • 一連のListフィールドエントリで、同じメモリを共有するunionとして割り当てられます。

デフォルトでは、__getentriesself.entriesテーブルを返します。これはstruct定義シンタックスで設定されます。

lua
method = __getmethod(self, methodname)

構造体のメソッドを検索する_Lua_関数です。コンパイラがmystruct:mymethod(...)のメソッド呼び出しやmystruct.mymethodの静的メソッド参照を検出したときに呼び出されます。mymethodは文字列またはsymbolで指定可能です。このメタメソッドは、この型でのmethodnameの静的な呼び出しごとにコンパイラによって呼び出されます。同じmethodnameに対して複数回呼ばれる可能性があるため、高コストの操作は呼び出しごとにメモ化して使い回すべきです。

methodには、Terra関数、Lua関数、またはコンパイル時に実行されるマクロを指定できます。

たとえば、__getmethodmethodを返す場合、Terraコード内の式myobj:mymethod(arg0, ...argN)は、myobjの型がTの場合に[method](myobj, arg0, ..., argN)に変換されます。

もしmyobjの型が&Tの場合は、[method](@myobj, arg0, ..., argN)に展開されます。メソッドが呼び出された際にmyobjの型がTであり、形式パラメータの型が&Tである場合、引数はアドレスを取得してポインタに自動的に変換されます。この「メソッド受信キャスト」により、メソッド呼び出しでオブジェクトを変更可能にします。

デフォルトでは、__getmethod(self, methodname)self.methods[methodname]を返し、これはメソッド定義のシンタックスシュガーで設定されます。メソッドテーブルにメソッドが含まれていない場合、型チェッカーは以下で説明する__methodmissingを呼び出します。

lua
__staticinitialize(self)

型が完全になった後、コンパイラがユーザー定義コードに戻る前に呼ばれる_Lua_関数です。型が完全であるため、terralib.offsetofを使ってオフセットを調べたり、仮想関数テーブル(vtables)を作成したりといった、完全な型を必要とする操作を行うことができます。構造体内のエントリに対する静的初期化子は、構造体自体の静的初期化子よりも先に実行されます。

lua
castedexp = __cast(from, to, exp)

型間の変換を定義する_Lua_関数です。fromexpの型で、toは必要な型です。mystruct型について、__castfromまたはtoのどちらかがmystructまたは&mystruct型の場合に呼び出されます。有効な変換がある場合、このメソッドはcastedexpexptoに変換する式)を返すべきです。変換できない場合は、error関数を使って説明的なエラーメッセージを出力する必要があります。Terraコンパイラは、適用可能な__castメタメソッドを見つけるまで試行します(つまり、errorを呼び出さないものを探します)。

lua
__for(iterable, body)

実験的機能。 指定した型を反復処理するためのループを生成する_Lua_関数です。iterableの値は指定された型の値を生成する式になります。bodyはループ変数を受け取り、1回のループ処理を実行するLua関数です。iterablebodyの引数は複数回の評価から保護される必要があります。 __forメタメソッドの結果は引用句(quote)でなければなりません。

例として、単純なRange型の実装は以下のようになります:

lua
struct Range {
    a : int
    b : int
}
Range.metamethods.__for = function(iter, body)
    return quote
        var it = iter
        for i = it.a, it.b do
            [body(i)]
        end
    end
end
lua
__methodmissing(mymethod, myobj, arg1, ..., argN)

メソッドがmyobj:mymethod(arg0, ..., argN)として呼び出され、__getmethodが設定されていない場合、メソッドテーブルにmymethodが存在しなければマクロ__methodmissingが呼び出されます。このマクロはメソッド呼び出しの代わりに使用されるTerraのquoteを返すべきです。

lua
__entrymissing(entryname, myobj)

myobjentrynameフィールドを含まない場合、型チェッカーがmyobj.entryname式を検出すると__entrymissingが呼び出されます。これはマクロでなければならず、フィールドの代わりに使用されるTerraのquoteを返す必要があります。

カスタム演算子:

lua
__sub, __add, __mul, __div, __mod, __lt, __le, __gt, __ge,
__eq, __ne, __and, __or, __not, __xor, __lshift, __rshift,
__select, __apply

これらはTerraメソッドまたはマクロとして指定できます。指定した型でこれらの演算子が使用されたときに呼び出されます。__applyは関数適用に、__selectterralib.selectに使用されます。二項演算子の場合、少なくとも引数の1つがmystruct型である必要があります。カスタム演算子のインターフェースは十分なテストが行われておらず、変更される可能性があります。

lua
__typename(self)

型の名前を生成する_Lua_関数です。この名前はエラーメッセージやtostringで使用されます。

構文シュガー

lua
[local] struct mystruct

構造体の宣言 mystructがまだTerraの構造体でない場合、terralib.types.newstruct("mystruct")を呼び出し、新しい構造体を作成してLua変数mystructに格納します。mystructが既に構造体の場合は、変更は行いません。オプションのlocalキーワードが使用される場合、mystructはまず新しいローカルのLua変数として定義されます。localキーワードなしで使用する場合、mystructはテーブルの指定子(例: a.b.c)にすることができます。

lua
[local] struct mystruct {
    field0 : type0;
    ...
    union {
        fieldUnion0 : type1;
        fieldUnion1 : type2;
    }
    ...
    fieldN : typeN;
}

構造体の定義 mystructがまだ構造体でない場合、構造体の宣言の動作に従い新しい構造体を作成します。次に、定義の本体で指定されたフィールドと型を使用して構造体のentriesテーブルを埋めます。unionブロックを使用して、一連のフィールドがメモリ内の同じ位置を共有することを指定できます。mystructが以前に定義されている場合、再定義はエラーになります。

lua
terra mystruct:mymethod(arg0 : type0,..., argN : typeN)
    ...
end

メソッド定義 mystruct.methods.mymethodがTerra関数でない場合、それを作成します。その後、メソッド定義を追加します。&mystruct型のselfという仮引数が、仮引数リストの先頭に追加されます。

オーバーロード関数

オーバーロード関数は通常の関数とは別のオブジェクトであり、API呼び出しを使用して作成します。

lua
local addone = terralib.overloadedfunction("addone",
              { terra(a : int) return a + 1 end,
                terra(a : double) return a + 1 end })

また、後でメソッドを追加することもできます。

lua
addone:adddefinition(terra(a : float) return a + 1 end)

通常の関数とは異なり、オーバーロード関数はLuaから直接呼び出すことはできません。

lua
overloaded_func:getdefinitions()

この関数の定義のリストを返します。

エスケープ(Escapes)

エスケープはマルチステージプログラミングから取り入れられた特殊な構文で、Luaを使用してTerraの式を生成できるようにします。エスケープは角括弧(ブラケット)構文を使って作成され、1つのLua式(例: [ 4 + 5 ])を含みます。このLua式は、周囲のTerraコードが_定義_されるときに評価されます(注: これは、関数が_コンパイル_されるときに実行されるマクロとは異なります)。エスケープはTerraコードのレキシカルスコープ内で評価され、周囲のLuaスコープの識別子に加え、Terraコード内で定義された識別子も含まれます。これらの識別子はLuaコード内でシンボルとして表現されます。以下のエスケープ例では、

lua
terra foo(a : int)
    var b = 4
    return [dosomething(a,b)]
end

dosomethingに渡される引数abは、Terraコード内で定義された変数への参照としてのシンボルになります。

また、識別子やテーブル選択のエスケープに対して構文シュガーも提供されています。例えば、Terraの式identはエスケープ[ident]として扱われ、テーブルの選択a.b.cは、abがLuaテーブルである場合にエスケープ[a.b.c]として扱われます。

lua
terra foo()
    return [luaexpr],4
end

[luaexpr]は単一式のエスケープです。luaexprは関数が_定義_されるときにLuaの値に評価される単一のLua式です。評価されたLua式は、コンパイル時の変換ルールを使用してTerraオブジェクトに変換されます。変換がTerra値のリストを生成した場合、リストは1つの値に切り詰められます。

lua
terra foo()
    bar(3,4,[luaexpr])
end

[luaexpr]は複数式のエスケープであり、式リストの最後の式として発生します。単一式エスケープと同様の動作をしますが、luaexprの変換が複数のTerra式を生成した場合、これらの値は式リストの末尾に追加されます(この場合、barの呼び出しの引数リストに追加されます)。

lua
terra foo()
    [luaexpr]
    return 4
end

[luaexpr]は文のエスケープです。この形式は複数式エスケープと同様の動作をしますが、Terra文のクオートを返すことも可能です。luaexprの変換がTerra値のリストを生成した場合、すべてが現在のブロックに挿入されます。

lua
terra foo([luaexpr] : int)
    var [luaexpr] = 4
    mystruct.[luaexpr]
end

[luaexpr]は識別子のエスケープの例です。luaexprシンボルを生成する必要があります。フィールド選択子(a.[luaexpr])、メソッド(a:[luaexpr]())またはラベル(goto [luaexpr])に対して、luaexprは文字列も生成できます。この形式は、プログラム的に識別子を定義することを可能にします。シンボルが明示的に定義された型を持つ場合、変数の型が明示的に指定されていない限り、変数はシンボルの型を取ります。たとえば、シンボルを構築する場合(foo = symbol(int))、var [foo]int型となり、var [foo] : floatfloat型になります。

lua
terra foo(a : int, [luaexpr])
end

[luaexpr]は識別子リストのエスケープです。この場合、単一識別子のエスケープと同様に動作しますが、明示的に型指定されたシンボルのリストも返すことができ、パラメータリストに追加されます。

TerraでCを使用する

TerraはClangフロントエンドを利用して、TerraコードをCとの後方互換性を持つようにしています。この機能は現在、Cヘッダーファイルから関数、型、およびenumをインポートすることが可能です。また、次のように数値のみのマクロ定義もインポートできます。

c
#define FOO 1

ただし、現時点ではグローバル変数や定数のインポートはサポートされていません。これについては将来の改善を予定しています。

lua
table = terralib.includecstring(code,[args,target])

文字列codeをCコードとしてインポートします。含まれるC関数の名前をTerraの関数オブジェクトに、Cの型(typedefなど)をTerraのに対応するLuaテーブルを返します。Lua変数terralib.includepathを使って、追加のヘッダー検索パスを指定できます。これはセミコロンで区切られたディレクトリのリストです。argsはオプションで、Clangに渡すフラグのリスト(例:includecstring(code, "-I", ".."))です。targetは指定されたターゲット用にヘッダーを正しくインポートするためのターゲットオブジェクトです。

lua
table = terralib.includec(filename,[args,target])

includecstringと似ていますが、Cコードがfilenameから読み込まれます。これはClangのデフォルトのヘッダーファイル検索パスを使用します。...を使用して、Clangに追加の引数を渡すことができます(さらに検索するディレクトリを含めることも可能です)。

lua
terralib.linklibrary(filename)

ファイルfilenameにある動的ライブラリを読み込みます。includecでインポートしたヘッダーファイルに含まれる宣言の定義がTerraが実行されている実行ファイルにリンクされていない場合、linklibraryで定義を動的に読み込む必要があります。この状況は、外部ライブラリをterraのREPL/ドライバーアプリケーションで使用するときに発生します。

lua
local llvmobj = terralib.linkllvm(filename)
local sym = llvmobj:extern(functionname,functiontype)

LLVMビットコードファイルfilename.bc拡張子でリンクし、clangまたはclang++で生成します:

sh
clang++ -O3 -emit-llvm -c mycode.cpp -o mybitcode.bc

このコードは機械語ではなくビットコードとして読み込まれるため、より強力な最適化(例:関数呼び出しのインライン化)が可能ですが、機械語にコンパイルするためTerraでの初期化に時間がかかります。このビットコードファイルから関数を抽出するには、関数名とTerra相当の型(例:int -> int)を指定してllvmobj:externメソッドを呼び出します。

Luaの値とTerraの値の変換

Terraコードをコンパイルまたは実行する際、TerraとLua間で値を変換する必要があります。内部的には、この変換はLuaJITの外国関数インターフェース(FFI)を基にして実装され、Luaから直接C関数の呼び出しやC値の使用が可能です。Terraの型システムはCに似ているため、このインフラストラクチャをほとんど再利用できます。

既知の型のLua値をTerra値に変換する

Lua値をTerraに変換する際、期待される型がわかっている場合(例:terralib.castterralib.constantで型が指定されているとき)は、LuaJITの変換セマンティクスに従い、各Terra型に相当するC型に変換されます。

型が不明なLua値をTerra値に変換する

Lua値がエスケープを介してTerraコードから直接使用されたり、型指定なしでTerra値が作成される(例:terralib.constant(3))場合、オブジェクトの型を推定しようとします。成功した場合、通常の変換が適用されます。type(value)が次の場合に適用される型は次の通りです:

  • cdata -- Terra APIから以前に割り当てられた、またはTerraコードから返されたものであれば、オブジェクトのctypeに相当するTerra型に変換されます。
  • number -- floor(value) == valueかつ値がintに収まる場合はint、そうでなければdouble型とみなされます。
  • boolean -- bool型になります。
  • string -- rawstring(すなわち&int8)に変換されます。将来的には特別な文字列型を追加する可能性があります。
  • その他 -- 型を推定できません。オブジェクトの型がわかっている場合は、terralib.cast関数を使用して指定することができます。

コンパイル時の変換

Luaの値がTerra関数内のエスケープ演算子の結果として使用される場合、以下の追加変換が許可されます:

  • グローバル変数 — 値がTerraコード内でグローバル変数へのlvalue参照となります。
  • シンボル — シンボルを使って定義された変数へのlvalue参照になります。変数がスコープ内にない場合、コンパイル時エラーが発生します。
  • 引用 — 引用内で定義されたコードがTerraコードにスプライスされます。引用がステートメントのみを含む場合、ステートメントの位置にのみスプライス可能です。
  • 定数 — 定数がTerraコードにスプライスされます。
  • Lua関数 — 関数呼び出しで使用される場合、Lua関数はterralib.castで戻り値のないTerra関数型に変換され、実際のパラメータのTerra型が使用されます。関数呼び出し以外ではエラーとなります。
  • マクロ — 関数呼び出しとして使用される場合、コンパイル時に実行され、結果はコンパイル時変換ルールでTerraに変換され、指定された位置にスプライスされます。
  • — マクロ呼び出しの引数として使用される場合、そのまま渡され、arg:astype()呼び出しが値を返します。関数呼び出しとして使用された場合(例:[&int](v))、その型への明示的なキャストとして動作します。
  • リスト またはterralib.israwlistで分類された生リスト — リストの各メンバーはコンパイル時変換を用いて再帰的にLua値に変換されます(リストの変換は除く)。ステートメントや複数の式がある場合、リストのすべての値がスプライスされます。単一の式のみが許可される場合、リストは1つの値に切り詰められます。
  • cdata 集合体(構造体や配列) — Lua cdata集合体のTerra型TがTerraコード内で直接参照される場合、その値はLuaで割り当てられたメモリにある集合体へのlvalue参照となります。
  • その他 — 値はまず標準的なLuaからTerraへの変換ルールでTerra値に変換され、結果の値が_定数_としてスプライスされます。

Terra値からLua値への変換

Terra値をLua値に変換する場合(例えば関数呼び出しの結果から)、LuaJITのC型からLuaオブジェクトへの変換セマンティクスに従います。各Terra型に相当するC型に置き換えられ、cdataオブジェクトの場合はTerraの値APIで利用可能です。

Terraコードの読み込み

これらの関数を使って、実行時に混在するTerra-Luaコードのチャンクを読み込むことができます。

lua
terralib.load(readerfn)

C APIのterra_loadと同等のLua関数です。readerfnはLuaのload関数と同じ動作をします。

lua
terralib.loadstring(s)

C APIのterra_loadstringと同等のLua関数です。

lua
terralib.loadfile(filename)

C APIのterra_loadfileと同等のLua関数です。

lua
require(modulename)

Terraコードモジュールmodulenameを読み込みます。Luaのpackage.loadersにTerraコードをモジュールとしてロードするための追加コードローダーが登録されています。requireはまず、以前にrequireを使ってロードされたmodulenameがあるかを確認し、存在する場合はそれを返します。存在しない場合は、package.terrapathでモジュールを検索します。package.terrapathはセミコロンで区切られたテンプレートリストです。例:

lua
"lib/?.t;./?.t"

modulenameは、.をディレクトリセパレータ/に置き換えてパスに変換されます。そして、ファイルが見つかるまで各テンプレートが試されます。例えば、このパスを使用すると、require("foo.bar")lib/foo/bar.tまたはfoo/bar.tをロードしようとします。ファイルが見つかると、そのファイルに対するterralib.loadfile呼び出しの結果が返されます。デフォルトでは、package.terrapathは環境変数TERRA_PATHに設定されます。TERRA_PATHが設定されていない場合、package.terrapathにはデフォルトのパス(./?.t)が含まれます。TERRA_PATH内の;;は存在する場合、デフォルトパスに置き換えられます。

通常のLuaコードもrequireを使ってインポートされることに注意してください。package.path(環境変数LUA_PATH)は純粋なLuaコードのロードに使用され、package.terrapath(環境変数TERRA_PATH)はLua-Terraコードのロードに使用されます。

コンパイルAPI

Terraコードの保存

lua
terralib.saveobj(filename [, filetype], functiontable[, arguments, target, optimize])

Terraコードを外部ファイル形式(オブジェクトファイルや実行ファイルなど)に保存します。filetypeには、"object"(オブジェクトファイル *.o)、"asm"(アセンブリファイル *.s)、"bitcode"(LLVMビットコード *.bc)、"llvmir"(LLVMテキストIR *.ll)、または"executable"(拡張子なし)のいずれかを指定できます。filetypeが省略された場合、拡張子から自動的に推測されます。

functiontableは文字列からTerra関数へのテーブルで、これらの関数が指定された名前で出力されるコードに含まれます。argumentsはリンク時に渡される追加のフラグリストで、filetype"executable"の場合に使用されます。filenamenilの場合、ファイルはメモリ内に書き込まれ、Lua文字列として返されます。

別のアーキテクチャ用にオブジェクトをクロスコンパイルする場合は、ターゲットアーキテクチャを説明するtargetオブジェクトを指定できます。指定がない場合、saveobjはネイティブアーキテクチャを使用します。

デフォルトでは、saveobjはClangの-O3相当の最適化でコードをコンパイルします。optimizeの設定で最適化を無効にしたり、安全でない高速数学の最適化を追加で有効にしたりできます。optimizeの値は次の通りです:

  • true または false:最適化を有効/無効にする(-O3相当)。デフォルトは有効です。高速数学最適化は含まれません。
  • {optimize = ..., fastmath = ...}:最適化プロファイルを指定するテーブル。optimizeキーは前述のtrueまたはfalse(指定しない場合はデフォルトでtrue)。fastmathの可能な値は以下で説明します。

fastmathキーには以下のいずれかの値を指定できます:

  • true または false:すべてのLLVM高速数学フラグを有効/無効にします(デフォルトはfalse)。
  • "flag":単一の高速数学フラグを指定し、それだけを有効化。他のフラグは無効。
  • {"flag1", "flag2"}:複数のフラグをリストとして指定し、リスト内のフラグをすべて有効化。他のフラグは無効。

LLVMの高速数学フラグの一覧はこちらから確認できます。利用可能なフラグはLLVMのバージョンに依存し、Terraの管理外です。

例:

lua
terralib.saveobj("a.o", {main=main}, nil, nil, false) -- 最適化を無効化。
terralib.saveobj("a.o", {main=main}, nil, nil, {fastmath=true}) -- 全ての高速数学最適化を有効化。
terralib.saveobj("a.o", {main=main}, nil, nil, {fastmath={"contract", "nnan"}}) -- contractとnnanを有効化。

ターゲット

terralib.saveobjterralib.includecは、コードを異なるアーキテクチャ用にコンパイルするためのターゲットオブジェクトをオプションで受け取ります。これによりクロスコンパイルが可能になります。例えば、x86マシンを使ってRaspberry Pi用にARMコードをコンパイルするには、次のようにターゲットオブジェクトを作成します:

lua
local armtarget = terralib.newtarget {
    Triple = "armv6-unknown-linux-gnueabi"; -- LLVMターゲットトリプル
    CPU = "arm1176jzf-s";,  -- LLVM CPU名
    Features = ""; -- LLVMの特徴文字列
    FloatABIHard = true; -- ARM用に浮動小数点レジスタを使用
}

テーブル内のTripleフィールド以外のエントリはオプションです。設定する文字列に関する詳細情報は、clangのドキュメントで確認できます。

デバッグ

Terraにはコードのデバッグやパフォーマンスチューニングに役立つライブラリ関数がいくつか用意されています。currenttimeinseconds以外のデバッグ機能は、OSXおよびLinuxでのみ使用可能です。

lua
terralib.currenttimeinseconds()

過去のある時点からの経過時間を秒で返すLua関数です。Terraコードのパフォーマンスチューニングに便利です。

lua
terra terralib.traceback(uctx : &opaque)

Terraコードから呼び出してスタックトレースを出力するTerra関数です。uctxnilの場合は現在のスタックを出力します。uctxucontext_tオブジェクト(ucontext.h参照)へのポインタとしても指定でき、そのコンテキストのスタックトレースを表示します。デフォルトでは、プログラムがセグメンテーションフォールトを起こした際にこの情報が出力されます。

lua
terra terralib.backtrace(addresses : &&opaque, naddr : uint64, ip : &opaque, frameaddress : &opaque)

低レベルのインターフェースで、マシンスタックからリターンアドレスを取得します。addressesは少なくともnaddrポインタ分の容量があるバッファへのポインタである必要があります。ipは現在の命令のアドレスで、最初のエントリとしてaddressesに格納され、frameaddressはベースポインタの値にする必要があります。addressesにはスタック上のリターンアドレスが格納されます。正しく機能させるにはデバッグモードを有効化(-gオプション)する必要があります。

lua
terra terralib.disas(addr : &opaque, nbytes : uint64, ninst : uint64)

低レベルの逆アセンブラインターフェースです。addrから始まる命令の逆アセンブルを出力します。nbytesバイト分またはninst命令分のいずれか多くなる方の命令が出力されます。

lua
terra terralib.lookupsymbol(ip : &opaque, addr : &&opaque, size : &uint64, name : &rawstring, namelength : &uint64) : bool

任意の命令のポインタipを基に、Terra関数に関する情報を検索しようと試みます。成功するとtrueを返し、addrに関数の開始アドレス、sizeに関数のバイト数が格納されます。nameには関数名を最大namemax文字まで固定幅文字列として格納します。

lua
terra terralib.lookupline(fnaddr : &opaque, ip : &opaque, filename : &rawstring, namelength : &uint64, line : &uint64) : bool

命令のポインタipと、それを含む関数の開始アドレスfnaddrを指定して、Terra命令に関する情報を検索しようと試みます。成功するとtrueを返し、lineに命令がある行番号、filenameにファイル名が最大namemax文字まで格納されます。

Cコード内にTerraを埋め込む

Luaと同様に、Terraは既存のコードに埋め込むことを意図して設計されています。TerraのC APIは、Terra-Luaプログラムを実行するためのエントリーポイントとして機能します。実際、terraの実行ファイルとREPLは、このC APIのクライアントに過ぎません。TerraのC APIは、LuaのAPIを拡張しており、Terra固有の関数が追加されています。クライアントはまずlua_Stateオブジェクトを作成し、terra_initを呼び出してTerra拡張を初期化します。Terraには、lua_loadに相当する関数(例:terra_loadfile)が用意されており、これらの関数は入力をTerra-Luaコードとして扱います。

lua
int terra_init(lua_State * L);

lua_State Lの内部Terra状態を初期化します。Lはすでに初期化されたlua_Stateである必要があります。

lua
typedef struct { /* 初期値は0 */
    int verbose; /* デバッグ出力の冗長性を設定します。
                    0(デバッグ出力なし)から2(非常に冗長)までの
                    値が有効です。 */
    int debug;   /* Terraコンパイラでデバッグ情報を有効にします。
                    スタックトレースにベースポインタと行番号情報が
                    表示されるようになります。 */
} terra_Options;
int terra_initwithoptions(lua_State * L, terra_Options * options);

lua_State Lの内部Terra状態を初期化します。Lはすでに初期化されたlua_Stateである必要があります。terra_Optionsには追加の設定オプションが含まれます。

lua
int terra_load(lua_State *L,
               lua_Reader reader,
               void *data,
               const char *chunkname);

TerraとLuaの両方を含むチャンクを読み込みます。これはlua_loadのTerra版であり、引数や動作はlua_loadと同じですが、入力をTerra拡張を含むLuaプログラムとして解釈します。現時点では、Lua-Terraコードのバイナリ形式はなく、入力はテキストでなければなりません。

lua
int terra_loadfile(lua_State * L, const char * file);

ファイルをTerra-Luaチャンクとして読み込みます。luaL_loadfileのTerra版です。

lua
int terra_loadbuffer(lua_State * L,
                         const char *buf,
                         size_t size,
                         const char *name);

バッファをTerra-Luaチャンクとして読み込みます。luaL_loadbufferのTerra版です。

lua
int terra_loadstring(lua_State *L, const char *s);

文字列sをTerra-Luaチャンクとして読み込みます。luaL_loadstringのTerra版です。

lua
terra_dofile(L, file)

ファイルfileを読み込み、実行します。以下のコードと同等です。

lua
(terra_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
lua
terra_dostring(L, s)

文字列sを読み込み、実行します。以下のコードと同等です。

lua
(terra_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))

Luaに埋め込む新しい言語の作成

Terraシステムの言語拡張により、カスタムLua文や式を作成して独自の埋め込み言語を実装することができます。各言語は、言語内の文や式の開始を示すエントリーポイントキーワードを登録します。TerraパーサがLua式や文の先頭でこれらのキーワードのいずれかを検出すると、パーサの制御がその言語に切り替わり、トークンを抽象構文木(AST)またはその他の中間表現に解析します。ASTを作成した後、言語はTerraパーサに戻り、実行時に文や式を実行するためのコンストラクタ関数を返します。

このガイドでは、簡単なスタンドアロンの例を用いて言語拡張の基本を紹介し、Terraへの登録方法を示します。その後、この例を拡張してLua環境と相互作用する方法を説明します。ガイドの最後には、言語拡張インターフェースとレキサーインターフェースの詳細な仕様が記載されています。

簡単な例

まず、Luaに単純な言語拡張を追加し、数値のリストを合計する言語を作成します。この構文は sum 1,2,3 done のような形で、実行すると数値を合計して 6 を出力します。言語拡張はLuaのテーブルを使って定義されます。以下がその言語のテーブルです。

lua
local sumlanguage = {
  name = "sumlanguage"; -- デバッグ用の名前
  -- 式の開始トークンとなるキーワードリスト
  entrypoints = {"sum"};
  keywords = {"done"}; -- この言語固有のキーワードリスト
   -- Terraパーサから呼び出されてこの言語に入ります
  expression = function(self,lex)
    -- 実装がここに入ります
  end;
}

ここでは、entrypointsリストに "sum" を追加しています。これは、このトークンが式の先頭に現れた際にTerraが制御をこの言語に渡すようにするためです。また、式の終わりを示す "done" もキーワードとしてリストに追加しています。Terraパーサがsumトークンを見つけたときに、expression関数が呼び出され、レキサーへのインターフェース lex が引数として渡されます。実装は以下の通りです。

lua
expression = function(self,lex)
  local sum = 0
  lex:expect("sum") -- 最初のトークンは "sum" であるべき
  if not lex:matches("done") then
    repeat
      -- 数値を解析してその値を返します
      local v = lex:expect(lex.number).value
      sum = sum + v
    -- コンマがあれば、それを消費して続行
    until not lex:nextif(",")
  end

  lex:expect("done")
  -- この式がLuaで評価されるときに実行される関数を返します
  return function(environment_function)
    return sum
  end
end

lex オブジェクトを使ってトークンと対話しています。このインターフェースは後述します。この文は数値の定数のみを許可しているため、解析中に合計を計算できます。最後に、コンストラクタ関数 を返し、この文が実行されるたびにこの関数が呼び出されます。Luaコードでの使用例は以下の通りです。

lua
print(sum 1,2,3 done) -- 6を出力

この例のコードは tests/lib/sumlanguage.t にあり、その使用例は tests/sumlanguage1.t にあります。

言語の読み込みと実行

言語拡張を使用するためには、それを インポート する必要があります。 言語拡張の仕組みには、言語拡張をロードするための import 文が含まれています。

lua
import "lib/sumlanguage" -- 新しいパース規則を有効化
result = sum 1,2,3 done

import 文は パース時 に評価されるため、引数は文字列リテラルでなければなりません。パーサはこの文字列リテラルを使って require を呼び出し、言語拡張ファイルを読み込みます。 指定されたファイルは、言語を記述したLuaテーブルを return する必要があります。

lua
local sumlanguage = { ... } -- テーブルを記入
return sumlanguage

インポートされた言語は、インポート文が現れたローカルスコープ内でのみ有効です。

lua
do
    import "lib/sumlanguage"
    result = sum 1,2,3 done -- OK、スコープ内
    if result == 6 then
        result = sum 4,5 done -- OK、まだスコープ内
    end
end
result = sum 6,7 done -- エラー! sumlanguageはスコープ外

複数の言語を同じスコープにインポートすることも可能ですが、entrypoints が重複しない必要があります。 もしentrypoints が重複する場合、import 文が異なるスコープで発生すれば同じファイル内でインポートできます。

Luaシンボルとの相互作用

Terraの利点の一つは、Luaと同じレキシカルスコープを共有することにより、Terraの関数を簡単にパラメータ化できる点です。拡張言語もまたLuaの静的スコープにアクセスできます。では、定数の数値だけでなくLuaの変数もサポートするように、sum言語を拡張してみましょう。

lua
local a = 4
print(sum a,3 done) -- 7を出力します

これを行うには、expression関数内のコードを修正する必要があります。

lua
expression = function(self,lex)
  local sum = 0
  local variables = terralib.newlist()
  lex:expect("sum")
  if not lex:matches("done") then
    repeat
      if lex:matches(lex.name) then -- 変数の場合
        local name = lex:next().value
        -- Terraのパーサーに指示
        -- Luaの変数 'name' にアクセスすることを伝える
        lex:ref(name)
        -- 変数名をリストに追加
        variables:insert(name)
      else
        sum = sum + lex:expect(lex.number).value
      end
    until not lex:nextif(",")
  end
  lex:expect("done")
  return function(environment_function)
    -- ローカル環境をキャプチャ
    -- 変数名から値へのテーブル
    local env = environment_function()
    local mysum = sum
    for i,v in ipairs(variables) do
      mysum = mysum + env[v]
    end
    return mysum
  end
end

これで、式は変数名(lex.name)を含むことができます。定数とは異なり、変数の値は解析時にわからないため、実行前に合計を計算することはできません。代わりに、変数名を保存(variables:insert(name))し、Terraのパーサーに実行時にその変数の値が必要であることを伝えます(lex:ref(name))。_コンストラクタ_では、environment_functionパラメータを呼び出すことでローカルなレキシカル環境をキャプチャし、その環境内で変数の値を参照して合計を計算します。lex:ref(name)を呼び出すことが重要です。もしこれを呼び出さなければ、この環境テーブルには必要な変数が含まれません。

Luaの再帰的な解析

場合によっては、言語の途中でLuaのパーサーに戻り、Luaの式全体を解析したいことがあります。例えば、Terraの型はLuaの式です。

lua
var a : int = 3

この例では、intは実際にはLuaの式です。

lex:luaexpr()メソッドはLuaの式を解析します。このメソッドは、式を実装するLua関数を返します。この関数はローカルなレキシカル環境を受け取り、その環境での式の値を返します。例として、単一引数のLua関数を簡潔に指定する方法を追加してみましょう。def(a) expのように、ここでaは単一の引数、expはLuaの式です。これはPythonのlambdaステートメントに似ています。以下にその言語拡張を示します。

lua
{
  name = "def";
  entrypoints = {"def"};
  keywords = {};
  expression = function(self,lex)
    lex:expect("def")
    lex:expect("(")
    local formal = lex:expect(lex.name).value
    lex:expect(")")
    local expfn = lex:luaexpr()
    return function(environment_function)
      -- 結果を返します。単一引数のlua関数です。
      return function(actual)
        local env = environment_function()
        -- 仮引数を環境内の実引数にバインド
        env[formal] = actual
        -- 環境内で式を評価
        return expfn(env)
      end
    end
  end;
}

この例の完全なコードはtests/lib/def.tおよびtests/def1.tにあります。

ステートメントの拡張

式の構文を拡張するだけでなく、ステートメントやローカル変数の宣言に対しても新しい構文を定義できます。

lua
terra foo() end -- 新しいステートメント
local terra foo() end -- 新しいローカル変数の宣言

これは、言語テーブルにstatementおよびlocalstatement関数を指定することで行います。これらの関数は、expression関数と同様に動作しますが、定義する名前のリストをオプションで返すことができます。ファイルtest/lib/def.tには、この方法を使ってdefコンストラクタでステートメントをサポートする方法が示されています。

lua
def foo(a) luaexpr -- グローバル変数 foo を定義
local def bar(a) luaexpr -- ローカル変数 bar を定義

Prattパーサによる高レベルの解析

直接的に字句解析器インターフェースを使ってパーサを書くのは手間がかかります。解析を簡単にするためのシンプルな方法の一つに、Prattパーサ(トップダウンの優先度解析、詳細はこちら)があります。この方法は、特に複数の優先度レベルがある式に対して便利です。Lexerインターフェース上に構築されたライブラリを提供しており、APIのドキュメントと共にtests/lib/parsing.tで見つけることができます。このライブラリを使用した拡張の例はtests/lib/pratttest.t、およびそれを使用したプログラムの例はtests/pratttest1.tにあります。

言語とLexer API

このセクションでは、言語を定義し、lexerオブジェクトとやり取りするためのAPIについて詳しく説明します。

言語テーブル

言語拡張は、次のフィールドを持つLuaテーブルによって定義されます。

lua
name

デバッグに使用する言語の名前

lua
entrypoints

この言語での項を開始できるキーワードを指定するLuaリストです。これらのキーワードは、TerraやLuaのキーワードであってはならず、他の読み込まれた言語のエントリーポイントと重複してはなりません(将来的には、言語を読み込む際にエントリーポイントの名前を変更して競合を解決できるようにする可能性があります)。これらのキーワードは有効なLua識別子でなければなりません(すなわち、英数字で構成され、数字で始まってはなりません)。将来的には任意の演算子(例:+=)を許可することを検討しています。

lua
keywords

言語内で使用する追加のキーワードを指定するLuaリストです。エントリーポイントと同様に、これらも有効な識別子である必要があります。LuaやTerra内でキーワードとされるものは常にあなたの言語でもキーワードとして扱われるため、ここにリストする必要はありません。

lua
expression

(オプション)Luaの式の先頭にエントリーポイントキーワードが出現した場合に呼び出されるLuaメソッドfunction(self,lexer)です。selfは言語オブジェクト、lexerはトークンの取得やエラーの報告を行うためにTerraの字句解析器とやり取りするためのLuaオブジェクトです。そのAPIについては以下で説明します。このexpressionメソッドは、コンストラクタ関数function(environment_function)を返すべきです。このコンストラクタは式が評価されるたびに呼び出され、その式がLuaコード内で現れる際の値を返す必要があります。引数environment_functionは、呼び出されると変数名から値へのLuaテーブルとしてローカルなレキシカル環境を返す関数です。

lua
statement

(オプション)Lua _ステートメント_の先頭にエントリーポイントキーワードが出現した場合に呼び出されるLuaメソッドfunction(self,lexer)です。expressionと同様にコンストラクタ関数を返します。さらに、変数に対する代入のリストとして二つ目の引数を返すことができます。たとえば、値{ "a", "b", {"c","d"} }はLuaステートメントa,b,c.d = constructor(...)のように振る舞います。

lua
localstatement

(オプション)localステートメントの先頭にエントリーポイントキーワードが出現した場合(例:local terra foo() end)に呼び出されるLuaメソッドfunction(self,lexer)です。statementと同様に、このメソッドも名前のリスト(例:{"a","b"})を返すことができます。この場合、これらの名前はローカル変数として定義され、local a, b = constructor(...)のように振る舞います。

トークン

言語のメソッドには、Terraの_字句解析器_(lexer)へのインターフェースであるlexerが与えられます。これを使用してトークンのストリームを調べたり、エラーを報告したりできます。_トークン_は以下のフィールドを持つLuaテーブルです。

lua
token.type

トークンの種類。キーワードや演算子の場合、これは単なる文字列です(例:"and""+")。lexer.namelexer.numberlexer.stringは、それぞれ識別子(例:myvar)、数値(例:3)、文字列(例:"my string")を示します。lexer.eofはトークンストリームの終端を示します。

lua
token.value

名前、文字列、数値の場合、これは具体的な値になります(例:3.3)。数値は、Luaの数値として表現される場合(浮動小数点数または32ビット整数)と、64ビット整数では[u]int64_tのcdata型として表現されます。

lua
token.valuetype

数値の場合、これは解析されたリテラルのTerra型です。3int型、3.3double型、3.ffloat型、3ULLuint64型、3LLint64型、3Uuint型になります。

lua
token.linenumber

このトークンが出現した行番号です(先読みトークンには利用できません)。

lua
token.offset

このトークンが発生したファイルの先頭からの文字数のオフセットです(先読みトークンには利用できません)。

Lexer

lexerオブジェクトは、以下のフィールドとメソッドを提供します。lexer自体は解析中のみ有効であり、たとえばコンストラクタ関数から呼び出すことはできません。

lua
lexer:cur()

現在の_トークン_を返します。位置は変更しません。

lua
lexer:lookahead()

現在のトークンの次の_トークン_を返します。位置は変更しません。実装を簡単にするため、1つのトークンのみ先読みが許可されています。

lua
lexer:matches(tokentype)

lexer:cur().type == tokentype の短縮形

lua
lexer:lookaheadmatches(tokentype)

lexer:lookahead().type == tokentype の短縮形

lua
lexer:next()

現在のトークンを返し、次のトークンに進みます。

lua
lexer:nextif(tokentype)

tokentypeが現在のトークンのtypeと一致する場合、そのトークンを返してlexerを進めます。そうでない場合はfalseを返し、lexerは進みません。多くの代替案を解析したいときに便利です。

lua
lexer:expect(tokentype)

tokentypeが現在のトークンのタイプと一致する場合、そのトークンを返しlexerを進めます。そうでない場合、解析を停止してエラーを発生させます。どのトークンが出現すべきかがわかっている場合に便利です。

lua
lexer:expectmatch(tokentype,openingtokentype,linenumber)

expectと同様ですが、マッチしたトークンに対してより良いエラーレポートを提供します。たとえば、リストの閉じカッコ}を解析する際にlexer:expectmatch('}','{',lineno)を呼び出すと、対応しない括弧や開閉行が報告されます。

lua
lexer.source

ファイル名またはストリームの識別子を含む文字列(将来のエラーレポートに便利)

lua
lexer:error(msg)

解析エラーを報告して処理を中断します。msgは文字列です。返り値はありません。

lua
lexer:errorexpected(msg)

msgという文字列が予期されていましたが、現れなかったことを報告します。返り値はありません。

lua
lexer:ref(name)

nameは文字列で、Terraのパーサーに対し、言語がLuaの変数nameを参照する可能性があることを示します。関心のある自由な識別子について、この関数を必ず呼び出す必要があります。そうしないと、その識別子は_コンストラクタ_関数に渡されるレキシカル環境に現れない可能性があります。参照しない識別子に対して呼び出しても安全ですが、効率が低下します。

lua
lexer:luaexpr()

トークンストリームから単一のLua式を解析します。これを使用して言語の式に対しLuaの解析に戻ることができます。たとえば、Terraはこれを使用して型を解析します(型は単なるLua式です):var a : aluaexpression(4) = 3。この関数は、function(lexicalenv)という関数を返し、現在のレキシカルスコープのテーブル(コンストラクタのenvironment_functionから返されるものなど)を受け取り、そのスコープで評価された式の値を返します。この関数は、Lua式をASTに解析するためのものではありません。現在、Lua式をASTに解析するには、自分でパーサーを書く必要があります。将来的には、言語でLua/Terraの文法の一部を選択して使用できるライブラリを追加する予定です。

lua
lexer:luastats()

トークンストリームから一連のLuaステートメントを解析し、ブロックの終了キーワード(endelseelseifなど)に達するまで処理します。これを利用すると、Luaのすべてのパーサを再実装することなく、Luaを拡張したドメイン固有言語を構築するのに役立ちます。

lua
lexer:terraexpr()

トークンストリームから単一のTerra式を解析します。これを使用すると、Terraを拡張したドメイン固有言語を再実装することなく構築するのに役立ちます。

lua
lexer:terrastats()

トークンストリームから一連のTerraステートメントを解析し、ブロックの終了キーワード(endelseelseifなど)に達するまで処理します。これを利用して、Terraを拡張したドメイン固有言語を構築するのに役立ちます。

中間表現と抽象構文記述言語 (ASDL)

抽象構文記述言語 (ASDL) は、コンパイラの中間表現(IR)やツリーやグラフベースのデータ構造を簡潔に記述する方法です。代数的データ型に似ていますが、一貫したクロス言語仕様を提供します。Pythonのコンパイラではその文法を記述するためにASDLが使用され、Terra内部でもTerraコードを表現するために使用されています。

ASDL仕様を解析するためのLuaライブラリを提供しており、ドメイン固有言語の構築に役立つIRや他のデータ構造を実装できます。このライブラリを使用すると、ASDL仕様を解析し、IRを構築するためのLuaクラス(実際には特別に定義されたメタテーブル)を作成できます。このライブラリはIRの構築用のコンストラクタ付きでクラスを自動的に設定し、通常のLuaメソッド定義を使って追加メソッドをクラスに追加できます。

lua
local asdl = require 'asdl'

ASDLパッケージはTerraに付属しています。

lua
context = asdl.NewContext()

ASDLクラスはコンテキスト内で定義されます。異なるコンテキストは何も共有しません。コンテキスト内の各クラスには一意の名前が必要です。

ASDLクラスの作成

lua
local Types = asdl.NewContext()

Types:Define [[

   # 2つのメンバーを持つ単純なレコード型を定義
   Real = (number mantissa, number exp)
   #       ^~~~ フィールド型         ^~~~~ フィールド名

   # タグ付きユニオン(バリアント、判別付きユニオン、合計型)
   # をいくつかのオプションのデータ型で定義。
   # この例では型 Stm は3つのサブタイプを持ちます。
   Stm = Compound(Stm head, Stm next)
       | Assign(string lval, Exp rval)
   # '*' はフィールドがリストオブジェクトであることを指定
   # '?' はフィールドがオプション(nilまたはその型)であることを指定
       | Print(Exp* args, string? format)

   Exp = Id(string name)
       | Num(number v)
       | Op(Exp lhs, BinOp op, Exp rhs)

   # タグ付きユニオンで()を省略すると、シングルトン値を作成
   BinOp = Plus | Minus
]]

型はLuaのプリミティブ(type(v)が返すnumber、table、function、string、booleanなど)、他のASDL型、またはcontext:Externで登録された任意の関数でチェックされた型が使用できます。

外部型を使用するには、その型に対して名前を登録し、その型のオブジェクトに対してtrueを返す関数を指定します。

lua
Types:Extern("File",function(obj)
   return io.type(obj) == "file"
end)

ASDLクラスの使用

lua
local exp = Types.Num(1)
local assign = Types.Assign("x",exp)
local real = Types.Real(3,4)

local List = require 'terralist'
local p = Types.Print(List {exp})

値はクラスを関数として呼び出すことで作成されます。引数は構築時に正しい型であるかチェックされ、間違った型の場合には警告が表示されます。

フィールドはコンストラクタによって初期化されます。

lua
print(exp.v) -- 1

デフォルトでクラスには文字列表現があります。

lua
print(assign) -- Assign(lval = x,rval = Num(v = 1))

メンバーシップを:isclassofで確認することができます。

lua
assert(Types.Assign:isclassof(assign))
assert(Types.Stm:isclassof(assign))
assert(Types.Exp:isclassof(assign) == false)

シングルトンはクラスではなく値です。

lua
assert(Types.BinOp:isclassof(Types.Plus))

クラスはその値のメタテーブルであり、Class.__index = Classを持ちます。

lua
assert(getmetatable(assign) == Types.Assign)

タグ付きユニオンには文字列フィールド.kindがあり、ユニオン内でその値がどのバリアントであるかを識別します。

lua
assert(assign.kind == "Assign")

ASDLクラスにメソッドを追加

クラスに追加のメソッドを定義して、追加の機能を持たせることができます。

lua
function Types.Id:eval(env)
  return env[self.name]
end
function Types.Num:eval(env)
  return self.v
end
function Types.Op:eval(env)
  local op = self.op
  local lhs = self.lhs:eval(env)
  local rhs = self.rhs:eval(env)
  if op.kind == "Plus" then
     return lhs + rhs
  elseif op.kind == "Minus" then
     return lhs - rhs
  end
end

local s = Types.Op(Types.Num(1),Types.Plus,Types.Num(2))
assert(s:eval({}) == 3)

また、スーパークラスにメソッドを定義すると、サブクラスにもそのメソッドが定義されます。

lua
function Types.Stm:foo()
  print("foo")
end

assign:foo()

注意: メタテーブルの構造を簡単に保つために、これは連鎖テーブルで実装されていません。このデザイン上、スーパークラスでのメソッド定義はサブクラスにもコピーされるため、親のメソッドは子のメソッドより先に定義しなければなりません。そうでなければ、親のメソッドが子のメソッドを上書きしてしまいます。

すでに定義済みのメソッド(例えば __tostring)をオーバーライドする必要がある場合は、最初にスーパークラスでそれをnilに設定してください。

lua
Types.Stm.__tostring = nil
function Types.Stm:__tostring()
  return "<Stm>"
end

名前空間

ASDLの拡張として、名前空間を定義するためにmoduleキーワードを使用できます。これにより、コンパイラで多くの異なるExpTypeを扱う際に役立ちます。

lua
Types:Define [[
   module Foo {
      Bar = (number a)
      Baz = (Bar b)
   }
   Outside = (Foo.Baz x)
]]
local a = Types.Foo.Bar(3)

ユニーク

もう一つの拡張として、任意の具体型をuniqueとしてマークすることができます。unique型は構築時にメモ化され、同じ引数(Luaの等価性の範囲内で)で構築された場合は、同じLuaオブジェクトが再び返されます。これは、リスト(*)やオプション(?)を含む型にも対応しています。

lua
Types:Define [[
   module U {
      Exp = Id(string name) unique
          | Num(number v) unique
   }
]]
assert(Types.U.Id("foo") == Types.U.Id("foo"))