C/C++プログラマー向けのTerra-Lua対応表
TerraのセマンティクスはC/C++に非常に近いですが、Luaとの密接な統合のために、同じ内容でも異なる方法で記述される場合があります。このクイックリファレンスシートでは、C++のコードスニペットと、それに相当するLua-Terraのスニペットを並べて表示し、Terraでのプログラミング方法を学びやすくしています。必要に応じて、メタプログラミングの構文も第3の列で示します。
この内容は、C++クイックリファレンスに基づいています。他に追加したい例があれば、Pull Requestを送ってください。
文脈
cpp
// 関数/グローバル宣言の文脈:
typedef int MyInt;
MyInt x;
int f() {
// C++コードの文脈:
MyInt bar = x + 1;
// ~~~~~ C++型の文脈
return bar;
}
struct S {
// 構造体定義の文脈:
int a;
// ~~~ 型の文脈
float b;
};
lua
-- Lua文脈 (ここでは任意のLuaコードが書ける)
MyInt = int -- Lua変数 'MyInt' への代入
x = global(MyInt)
terra f()
-- Terra文脈
var bar : MyInt = x + 1
-- ~~~~~ _Lua_ 文脈, ここでは任意のLuaコードが記述可能
-- ただし、それがTerra型として評価される必要がある
return bar
end
struct S {
a : int
-- ~~~ _Lua_ 文脈, Terra型として評価される
b : float
}
-- メタプログラミングによるLua-Terraは、
-- 追加で文脈が変わる箇所を作り出します。
function g() return `4+5 end
-- ~~~~ Terra文脈, クォートは
-- LuaからTerra式を生成する
terra h()
var baz = [ g() ]
-- ~~~~~~~ Lua文脈, エスケープは
-- Luaに入り、Terra式として評価される
end
プリプロセッサ
複数ファイルの使用
cpp
#include "myfile.h"
void f() {
myfunction();
}
lua
local myfile = require("myfile")
-- Luaのrequireを使って他のLuaファイルを読み込む
-- Terra関数はテーブルmyfileに格納できる
terra f()
myfile.myfunction()
end
C関数の使用
cpp
#include <stdio.h>
#include <malloc.h>
int main() {
printf("hello, world\n");
}
lua
local C = terralib.includecstring [[
#include<stdio.h>
#include<malloc.h>
]]
-- 単一のファイルには terralib.includec("stdio.h") を使用可能
-- Cは関数 (例: C.printf) や型 (例: C.FILE) を格納するテーブル
...
terra hello()
C.printf("hello, world\n")
end
プリプロセッサマクロの等価
cpp
#define X (3+3)
lua
local X = `3+3
-- Lua変数はTerra関数内で置き換え可能な値を保持できる
-- クォート (`) によりLuaから直接Terra式を生成
マクロ関数
cpp
#define F(a,b) a + b
lua
local F = macro(function(a,b)
return `a + b
end)
条件付きコンパイル
cpp
// #ifdef を使用して関数の定義を制御
#ifdef __WIN32
char * getOS() { return "Windows"; }
#else
char * getOS() { return "Linux"; }
#endif
lua
-- Luaを使用してTerra関数の定義を制御
if terralib.os == "Windows" then
terra getOS() return "Windows" end
else
terra getOS() return "Linux" end
end
リテラル
cpp
255, 0377, 0xff
2147483647LL, 0x7ffffffful
123.0, 1.23e2
"strings\n"
'a'
"hello" "world"
true, false // ブーリアン
lua
255, 0377, 0xff
2147483647LL, 0x7fffffffULL -- long数値のリテラルはLuaJITに準拠
123.0, 1.23e2
"strings\n" または 'strings\n' または [[strings\n]] -- Lua文字列に対応
("a")[0] -- char用の組み込みリテラルはないので、文字列をインデックス化する(または関数を作成)
[ "hello".."world" ] -- Luaにエスケープして文字列を連結
true, false
宣言と型コンストラクタ
変数の宣言
cpp
void f() {
int x;
int y = 255;
auto z = 255;
}
lua
terra f() {
var x
var y : int = 255
var z = 255
end
lua
x = symbol(int)
y = symbol(int)
z = symbol(int)
local zdeclare = quote
var [z] = 255
end
terra f()
var [x]
var [y]
[zdeclare]
return x + y + z
end
整数型のサイズ指定
cpp
short s; long l;
lua
var s : int16, l : int64
非整数の基本型
cpp
char c = 'a';
float f; double d;
bool b;
lua
var c : int8 = ('a')[0]
var f : float, d : double
var b : bool
複数宣言
cpp
int a = 1, b = 2, c = 3;
lua
var a : int, b : int, c : int = 1, 2, 3
配列
cpp
int a[10];
int a[] = {0, 1, 2};
float a[] = {0, 1, 2};
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
lua
var a : int[10];
var a : int[3] = array(0, 1, 2)
-- 'array' は式であり、
-- C++のような初期化子ではない
var a = arrayof([float], 0, 1, 2)
-- arrayofを使用して、初期化に使用する式とは異なる型を指定
var a : (int[3])[2] = array(array(1, 2, 3), array(4, 5, 6));
ポインタ
cpp
int* p;
char * s = "hello";
void* p = NULL;
lua
var p : &int
-- '&' は「アドレス」を意味し、&int は「int型のアドレス」
var s : rawstring = "hello"
-- rawstring は &int8 と等価
var p : &opaque = nil
-- opaque はポインタ内のvoidを置き換える
cpp
Vec3& r = v;
r.x
lua
var r : &Vec3 = &v
-- 参照 (references) は存在しない
r.x
-- '.' はポインタの '->' と同じように機能
タイプ定義 (typedef)
cpp
typedef String char*;
lua
local String = &int8
-- typedefはLuaでは単なる代入
-- Terra型はLua値であるため
Const
cpp
const int c = 3;
lua
var c = 3
-- constは変数に対して存在しない
列挙型 (Enum)
cpp
enum weekend {SAT, SUN};
weekend f() {
return SAT;
}
lua
-- Terraには列挙型が存在しないため、メタプログラミングで再現
local function Enum(...)
local t = { type = int }
for i, name in ipairs({...}) do
-- C++に合わせて0ベースに設定
t[name] = i - 1
end
return t
end
weekend = Enum("SAT", "SUN")
terra f() : weekend.type
return weekend.SAT
end
グローバル変数
cpp
int x = 3;
const int x = 3;
int x[] = { 3, 4, 5 };
const int x[] = { 3, 4, 5 };
void f() {
}
lua
-- Lua関数はTerra定数を構築する
x = global(int)
x = constant(int, 3)
x = global(int, `array(3, 4, 5))
x = constant(int, `array(3, 4, 5))
terra f()
end
lua
-- 定数のテーブルを作成可能
sin_values = {}
N = 32
for i = 1, N do
sin_values[i] =
math.sin(2 * math.pi * (i - 1) / N)
end
-- 定数テーブルをコード内に埋め込み
sin_table = constant(`arrayof(float, sin_values))
記憶クラス (Storage Classes)
cpp
int x;
static int y;
static void g() {
return x + y;
}
void f() {
static int z = 0;
return g();
}
extern int w;
lua
-- 'saveobj' コールで公開/非公開シンボルを指定
x = global(int)
y = global(int)
terra g()
return x + y
end
terra f()
return g()
end
-- x と f のみがシンボルとして公開される
-- ただし y と g は内部的に含まれる(使用されているため)
terralib.saveobj("out.o", { x = x, f = f})
cpp
void f() {
static int z = 0;
return z;
}
lua
-- 関数内変数の'direct static' 相当は存在しない
-- ただし Lua の 'do' と 'end' を使用して
-- グローバルの字句スコープを制御可能
do
local z = global(int, 0)
terra f()
return z
end
end
文 (Statements)
代入
cpp
x = y;
x += y;
lua
x = y
x = x + y -- Luaでは '+=’ のような演算子は存在しない
宣言
cpp
int x;
lua
var x : int
セミコロン
cpp
x = y; y = z;
lua
-- 明確にするためにオプションで使用可能
x = y; y = z;
ブロック
cpp
void f() {
{
printf("hi\n");
printf("hi\n");
printf("hi\n");
}
}
lua
terra f()
do
C.printf("hi\n")
C.printf("hi\n")
C.printf("hi\n")
end
end
lua
local stats = {
`C.printf("hi\n"),
`C.printf("hi\n"),
`C.printf("hi\n")
}
terra f()
do
[stats]
end
end
条件分岐
cpp
if (x) { <statements> }
else if (y) { <statements> }
else { <statement> }
lua
if x then <statements>
elseif y then <statements>
else <statements> end
ループ
cpp
while(x) {
<statements>
}
lua
while x do
<statements>
end
cpp
for(x; y; z;) {
<statements>
}
lua
x;
while y do
<statements>
z;
end
cpp
for(int i = 0; i < 100; i++) {
<statements>
}
lua
for i = 0, 100 do
-- 注意: [0, 100) の範囲
<statements>
end
cpp
do {
<statements>
} while(b);
lua
repeat
<statements>
until ~b
スイッチ (Switch)
cpp
switch(x) {
case X1: a;
case X2: b;
default: c;
}
lua
switch x do
case X1 then a
case X2 then b
else
c
end
制御構造 (Control Flow)
cpp
break;
return;
lua
break
return
-- 注意: break/return は
-- ブロックの終端である必要がある
例外 (Exceptions)
cpp
try { x; }
lua
-- Terraでは例外はサポートされていないため、複雑さを回避
関数
関数の定義
cpp
int f(int x, int y) {
return x + y;
}
lua
terra f(x : int, y : int): int
-- :int 戻り値の型は任意
-- 再帰しない関数では省略可能
return x + y
end
lua
local args = {symbol(int), symbol(int)}
terra f([args])
var s = 0
escape
for _, a in ipairs(args) do
emit quote
s = s + a
end
end
end
return s
end
cpp
void f() {
} // 戻り値なし
lua
terra f() : {} -- 空のタプルはvoidを意味する
end
関数の宣言
cpp
int f(int x, int y);
void g();
lua
terra f :: {int, int} -> int
-- ~~~~~~~~~~~~~~~~ 関数の型
terra g :: {} -> {}
~~ ~~ 空のタプルはvoid/引数なしを意味
lua
local args = {int, int}
local ret = int
local type = args -> ret
local void = {} -> {}
terra f :: type
terra g :: void
インライン化 (Inlining)
cpp
inline void f();
lua
f :: {} -> {}
f:setinlined(true)
-- 実際には __alwaysinline__ と同等
演算子 (Operators)
cpp
struct T {};
T operator+(T x, T y) {
}
lua
struct T {}
terra T.metamethods.__add(x : T, y : T)
end
-- 常に左辺型 'T' に関連付けられる
オーバーロード (Overloading)
cpp
int max(int a, int b) {
return (a > b) ? a : b;
}
float max(float a, float b) {
return (a > b) ? a : b;
}
lua
max = terralib.overloadedfunction("max" {
terra(a : int, b : int)
return terralib.select(a > b, a, b)
end,
terra(a : float, b : float)
return terralib.select(a > b, a, b)
end
})
式 (Expressions)
C++と基本的に同じ意味論:
クイックリファレンスより「演算子は優先順位でグループ化され、最高優先順位から始まる。単項演算子と代入は右から左、その他は左から右に評価される。優先順位は評価の順序に影響を与えない(未定義)。配列の範囲外、無効なポインタなどのランタイムチェックは存在しない。」
名前空間の操作
cpp
namespace N {
void f() {}
}
void g() {
N::f()
}
lua
local N = {}
N.f = terra() end
terra g()
N.f()
end
-- N は単なる Lua テーブル
-- Terraコード内では N.f は N["f"] に置き換えられる
ポインタとメンバー
cpp
&x
*p
t.x
p->x
lua
&x
@p
t.x
p.x -- '.' は '->' のように動作
lua
&[<luaexp>]
@[<luaexp>]
t.[("xyz"):sub(1,1)]
--~~~~~~~~~~~~~~~~~ 任意のLua式 (文字列を結果とする)
p.["x"]
--~~~~~ 同様
配列のインデックスと関数呼び出し
cpp
void g(int* a, int i, T t) {
a[i]
f(x, y)
t(x, y)
}
lua
terra g(a : &int, i : int, t : T)
a[i]
f(a, i)
t(a, i)
end
lua
local
terra g(a : &int, i : int, t : T)
a[i]
escape
local args = { a, i }
emit quote
f([args])
t([args])
end
end
end
更新操作 (Updates)
cpp
x++, ++x
x--, --x
lua
-- 存在しない
-- ステートメントを使用
x = x + 1
ランタイム型情報 (RTTI)
cpp
typeid(x)
dynamic_cast<T>(x)
lua
-- 組み込みの相当機能はなし、自作可能
local nextid = 0
local function addtypeid(T)
T.entries:insert(1, { "_typeid", int })
T.metamethods._typeid = nextid
terra T:init()
self._typeid = nextid
end
nextid = nextid + 1
end
terra typeid(v : &opaque)
-- 最初のメンバーを抽出
var typeid = @[&int](v)
return typeid
end
local function dynamic_cast(T)
local tid = T.metamethods._typeid
return terra(v : &opaque)
var id = typeid(v)
if id == tid then
return [&T](v)
end
return nil
end
end
dynamic_cast = terralib.memoize(dynamic_cast)
struct A {
a : int
}
struct B {
a : float
}
addtypeid(A)
addtypeid(B)
C = terralib.includec("stdio.h")
terra f(v : &opaque)
var a = [dynamic_cast(A)](v)
var b = [dynamic_cast(B)](v)
if a ~= nil then
C.printf("A\n")
elseif b ~= nil then
C.printf("B\n")
end
end
terra g()
var a : A
var b : B
a:init()
b:init()
f(&a)
f(&b)
end
キャスト (Casts)
cpp
(T) x
(T*) x
lua
[T](x)
[&T](x)
-- Terra型 'T' を関数のように適用している
-- '&T' のような型コンストラクタはLua式であるため
-- 一般的にエスケープ '[T]' を使用する必要がある
lua
local PT = &int
terra f(a : &opaque) : PT
return PT(a)
end
サイズ取得 (Sizeof)
cpp
sizeof(T)
sizeof(t)
lua
sizeof(T)
sizeof([(`t):gettype()])
メモリ確保 (Allocation)
cpp
new T //malloc, use a std.t metatype, or build your own
lua
[&T](C.malloc(sizeof(T)))
算術演算子 (Arithmetic)
cpp
-x
+x //存在しない
x * y
x / y
x % y
x + y
x - y
lua
-x
x -- '+' のプレフィックスはない
x * y
x / y
x % y
x + y -- ポインタにも対応
x - y -- ポインタにも対応
lua
local plus = "+"
terra two()
return operator(plus, 1, 2)
end
比較演算子 (Comparisons)
cpp
x < y
x <= y
x > y
x >= y
x == y
x != y
lua
x < y
x <= y
x > y
x >= y
x == y
x ~= y
論理およびビット演算子 (Logical and Bitwise Operators)
cpp
~x
lua
not x -- 整数のビット単位反転
cpp
!x
lua
not b -- ブール値の論理否定
cpp
x << y
x >> y
lua
x << y
x >> y
cpp
x && y
x || y
lua
b and d -- ブール値の論理 'and'
b or d -- 短絡評価 (short circuits)
cpp
x & y
x | y
lua
x and y -- 整数のビット単位 'and'
x or y -- 短絡評価なし
cpp
x ^ y
lua
x ^ y
その他 (Other Stuff)
cpp
x ? y : z
lua
terralib.select(x, y, z) -- 短絡評価なし
cpp
throw x; // 例外はない。longjmp, setjmpの使用を検討
lua
-- 例外はなし
-- longjmp, setjmp の使用を検討
テンプレート (Templates)
cpp
// 全ての型に対して f をオーバーロード
template <class T>
T f(T t) {
}
lua
function f(T)
return terra(t : T) : T
end
end
-- ユニークな 'T' ごとに1つの関数のみ生成
f = terralib.memoize(f)
cpp
// 型パラメータ T を持つクラス
template <class T>
class X {
T myt;
void foo(T t) {
myt = t;
}
};
lua
function X(T)
local struct X {
myt : T
}
terra X:foo(t : T)
self.myt = t
end
return X
end
-- ユニークな 'T' ごとに1つの構造体のみ生成
X = terralib.memoize(X)
cpp
// "int型のX" オブジェクト
X<int> x;
lua
var x : X(int)
名前空間 (Namespaces)
cpp
namespace N {class T {};}
lua
N = {} -- Luaテーブル
struct N.T {}
lua
N = {}
local struct mystruct {}
N["T"] = mystruct
cpp
// 名前空間N内のTを使用
N::T t;
lua
-- テーブルN内のTにアクセス
var t : N.T
lua
local key = "T"
terra f()
var t : N[key]
end
cpp
using N::T;
lua
local T = N.T
cpp
using namespace N;
lua
-- グローバル環境にNをマージ
for name, value in pairs(N) do
_G[name] = value
end
Cライブラリの使用
cpp
int main() {
printf("hello, world\n")
}
lua
C = terralib.includec("stdio.h")
terra main()
C.printf("hello, world\n")
end
cpp
int * a = (int*)malloc(10*sizeof(int))
lua
var a = [&int](malloc(10 * sizeof(int)))
オフラインコンパイラの使用
cpp
int main() {
}
-- shell
$ cc -o main.o main.cpp
lua
terra main()
end
terralib.saveobj("main.o",
{ main = main })
-- ~~~~~~~~~~~~~~~ エクスポートされる関数のテーブル
cpp
$ cc -o main -lfoo main.cpp
lua
terralib.saveobj("main",
{ main = main }, {"-lfoo"})
-- ~~~~~~~~~
-- 追加のリンカ引数
cpp
$ cc -shared -o libmain.so main.cpp
lua
terralib.saveobj("libmain.so",
{ main = main })
Cライブラリ
cpp
#include <mylib.h>
int main() {
mylib_myfunction();
}
-- shell:
$ cc -I mylibinclude main.cpp libmylib.so -o main -
lua
terralib.includepath = "mylibinclude;" .. terralib.includepath
C = terralib.includec("mylib.h")
terralib.linklibrary("libmylib.so")
terra main()
C.mylib_myfunction()
end
-- またはオフライン用:
terralib.saveobj("main", { main = main },
{"libmylib.so"})
-- ~~~~~~~~~~~~~~~ 追加のリンカ引数