]>
Rosetta スクリプト・マニュアル
Rosetta スクリプト・マニュアル
目次
§概略
概要
Rosetta スクリプトは EntisGLS4 で提供されるスクリプト言語で、ゲーム中のスクリプト処理の他、テキスト、画像、音声、動画などのデータを簡易に処理することを目的としたインタプリタです。
基本的な言語仕様は JavaScript との互換性をある程度備えています。
しかしながら、JavaScript は型記述の面で曖昧であり、コードの読みづらさがあるほか、クラスを言語がサポートしていないため、オブジェクト指向プログラミングにおけるコーディングの煩雑性などの欠点があります。Rosetta ではこれを解決するための拡張を行い、Java ライクな記述が可能になっています。
実行速度は、詞葉 objected モードとほぼ同程度の速度となります。
EntisGLS3 での詞葉スクリプトに対する、EntisGLS4 での Rosetta スクリプトという位置づけの言語ですが、詞葉とは異なり実行中の状態を保存する機能などは提供されませんので、セーブ・ロード処理が必要な場合には、スクリプト上で実装する必要があります。
はじめに
Rosetta は以下のような JavaScript ライクなコードを実行できます。
// JavaScript ライクなクラス
function MyClass()
{
this.a = 1 ;
this.b = 2 ;
}
// メンバの定義(※文末のセミコロン必須)
MyClass.prototype.hoge = function( c )
{
return this.a * c + this.b ;
} ;
var foo = new MyClass() ;
foo.hoge( 3 ) ;
また、同様に以下の Java ライクなコードを実行できます。
// Java ライクなクラス
class MyClass
{
public int m_a = 1 ;
public int m_b = 2 ;
public int hoge( int c )
{
return m_a * c + m_b ;
}
}
MyClass foo = new MyClass() ;
foo.hoge( 3 ) ;
Rosetta は JavaScript ライクなコードを実行できますが、JavaScript と完全な互換性を有しているわけではありません。
提供されるクラスの違いの他に、特に以下の点については注意が必要です。
1. 文末には常にセミコロンが必要
2. Integer 型が存在し、整数同士の除算は整数となる
3. シングルクォーテーションは文字列ではなく文字コードを表現する
4. ローカル変数空間は関数単位ではなく、波括弧 { } 範囲内
また、Java に対して特に以下の点が異なるので注意が必要です。
1. 整数・数値型間で暗黙の型変換が行われる
2. final 修飾子は存在しない
3. 派生クラスで親クラスのメンバ変数と同名の変数を定義できない
4. 配列リテラルは [ ] 角括弧で囲んで記述する
基本型
Rosetta には以下の基本型が提供されています。
Integer Number String Array HashMap Function Object
boolean byte short char int long float double
Uint8Pointer Int8Pointer Uint16Pointer Int16Pointer
Uint32Pointer Int32Pointer Int64Pointer
Float32Pointer Float64Pointer
byte, short, char, int, long, float, double は Java コードと一定の互換性を持たせるために提供されている型で、実体は Integer, Number オブジェクトです。
一連の
XxxPointer 型はポインタを表現する型で、JavaScript の型付き配列型に似ています。
Rosetta では
Type[] のように Java と同様の記法で配列を表現できますが、これは Array で実装されており、Java とは異なります。Java の配列に近いのはポインタ型となります。
boolean 型
boolean 型はブール値型を提供します。
boolean 型のリテラルには true, false があります。
Integer 型
Integer 型は64ビットの符号あり整数を表現します。
byte, short, char, int, long も Integer オブジェクトによって実装されます。
Integer は64ビット整数値の他に、有効ビット数と符号の有無を保持しており、型キャストの際には、有効数ビットと符号ビットによって再構成されます。
例えば、
(byte) 0xff
は、-1 と評価され、
(byte) 0xffffffffffffff7f
は、127 と評価されます。
byte は符号あり8ビット整数ですので、下位7ビットと、符号ビットの組み合わせられた値がキャストされた値となります。
リテラルの表現は、C 言語や Java と同様です。
シングルクォーテーションで囲んだ文字は、文字コードを表現する数値となります。
以下のリテラルはすべて同じ値です。
97
0141
0x61
'a'
Number 型
Number 型は64ビット浮動小数点、または32ビット符号あり整数を表現します。
float, double も Number オブジェクトによって実装され、64ビット浮動小数点となります。(ArrayBuffer 上の float メモリ表現(構造体等)は32ビットとなります)
リテラルの表現は、C 言語や Java と同様です。
123.
0.123
123.456
123E-2
1.23e+2
String 型
String 型は文字列を表現します。
リテラルの表現は Java と同様に、ダブルクォーテーションで囲んで記述します。
文字列中のバックスラッシュや引用符記号などはバックスラッシュを伴って記述します。
表記 | 16進 | 内容 |
\a | 07H | ベル(BEL) |
\b | 08H | 後進(BS) |
\t | 09H | 水平タブ(HT) |
\n | 0AH | 改行(LF) |
\v | 0BH | 垂直タブ(VT) |
\f | 0CH | 書式送り(FF) |
\r | 0DH | 行復帰(CR) |
\? | 3FH | ? 記号 |
\\ | 5CH | バックスラッシュ(日本語環境では半角円記号) |
\" | 22H | ダブルクォーテーション |
\' | 27H | シングルクォーテーション |
\x### | ### | 文字コード指定(16進表記) |
\### | ### | 文字コード指定(8進表記) |
String 型は JavaScript や Java の String と似ていますが、setString メソッドによって内容を変更できるため、関数の引数に渡して文字列を受け取ることが出来ます。
void foo( String s )
{
s.setString( "foo" ) ;
}
String str = "hoge" ;
foo( str ) ;
System.console().printf( "%s\n", str ) ; // foo
また比較演算はポインタの比較ではなく、内容文字列の比較となります。(JavaScript と同等)
String str1 = "hoge" ;
String str2 = "ho" + "ge" ;
if ( str1 == str2 )
{
System.console().printf( "equals\n" ) ;
}
Array 型
Array 型は配列を提供します。
Java 同様に
Type[] のように記述することによって型付きの配列型を表現できます。
リテラルの表現は、JavaScript 同様、角括弧 [] 内にコンマで区切って要素を列挙します。
整数配列リテラルを型付き配列型へ代入する場合には、要素の型を明示する必要があります。
Integer[] a = [ 1, 2, 3 ] ; // OK
int[] b = (int[]) [ 1, 2, 3 ] ; // 要キャスト
int[] c = [ (int) 1, (int) 2, (int) 3 ] ;
多重配列も同様に記述できます。
Integer[][] a = [ [ 1 ], [ 2, 3 ], [ 4, 5, 6 ] ] ;
int[][] b = (int[][]) [ [ 1 ], [ 2, 3 ], [ 4, 5, 6 ] ] ;
Java のように new
Type[] によって配列オブジェクトを生成できます。
String[] a = new String[] ;
配列長(制限値)を指定して生成することも出来ます。
String[] a = new String[100] ;
配列長を指定した場合でも、配列の初期サイズは 0 です。
配列は自動的に伸長されますが、制限値を超えた場合にはエラーとなります。
String[] a = new String[100] ;
System.console().printf( "%d\n", a.length() ) ; // 0
a[9] = "abc" ;
System.console().printf( "%d\n", a.length() ) ; // 10
a[100] = "xyz" ; // Error
HashMap 型
HashMap 型は、Java の HashMap とほぼ同等のクラスで、JavaScript の Object に似ています。
リテラルの表現は、JavaScript 同様、波括弧 { } 内にコンマで区切って要素を列挙します。各要素は、コロンで区切って左辺に要素名、右辺に値を記述します。
HashMap a = { name : "abc", value : 1000 } ;
また、HashMap のコンストラクタは要素型 class を受け取ることができ、要素への代入時に型チェック/変換を行うことができます。
class Foo {}
class Hoge extends Foo {}
HashMap a = new HashMap( Foo ) ;
a.abc = new Hoge() ; // OK
a.def = new String() ; // Error
a["ghi"] = new Foo() ; // OK
要素型を指定した型として記述することもできます。
HashMap<Foo> a = new HashMap<Foo>() ;
Function 型
Function 型は HashMap の派生型として定義されており、記述上は無名関数を受け取る変数の型として使用されます。
Function f = function( int a, int b ) : int
{
return a * b ;
} ;
関数は、f(a,b) のように記述することにより呼び出すことが出来ます。
また、.* 演算子を用い C++ ライクにメンバ関数として呼び出すことが出来ます。
class Foo
{
public int m_a = 3 ;
public int m_b = 5 ;
}
Function func = function( int a, int b )
{
return m_a * a + m_b * b ;
} ;
Foo foo = new Foo() ;
(foo .* func)( 7, 11 ) ;
無名関数内部から、その関数を生成した時の名前空間を参照できます。
Function getFunc( int x )
{
int a = 123 + x ;
return function( int b ) { return a * b ; } ;
}
Function foo = getFunc( 456 ) ;
System.console().printf( "%d\n", foo( 2 ) ) ; // 1158
その為、以下のように関数内のローカル変数で、生成した関数を保持していると循環参照となり永遠に解放されないため注意が必要です。
Function getFunc( int x )
{
int a = 123 + x ;
Function f = function( int b ) { return a * b ; } ;
return f ;
}
可能であれば、関数を保持しているローカル変数は、必要でなくなったら null を代入し、解放可能にすることが推奨されます。
また以下のような構文でプロトタイプ型付きの関数型を記述できます。
Function<double,MyClass,String,int>
f = function( String s, int i ) : double -> MyClass
{
return m_memberOfMyClass ;
} ;
Function 型の1つ目の引数は関数の返り値型で、voidを指定することも出来ます。function 式では、引数の後に「:」記号に続けて記述します。
2つ目の引数は関数のメンバとなるクラスを指定します。function 式は返り値型の後に「->」に続けて記述します。省略した場合には Object となります。
3つ目以降は関数の引数の型となります。
プロトタイプ型付きの Function 変数には、プロトタイプの一致する関数しか代入できません。
なお、文末には必ずセミコロンが必要ですので、function 式の文末にセミコロンを忘れないように注意が必要です。
Object 型
Object は Java の Object とほぼ同等のクラスで、すべてのオブジェクトの基底クラスです。
すべてのオブジェクトは Object にキャストして受け取ることが可能です。
変数宣言時の Object は var と同じ意味です。
var obj = new Foo() ;
Object obj = new Foo() ;
しかし、var の使用は推奨されません。
クラス
Rosetta のクラス定義は Java と似ています。
class Foo
{
public int m_a = 3 ;
public int m_b = 5 ;
}
クラスの派生も定義出来ます。
class Bar extends Foo
{
public int m_c ;
public Bar( int c )
{
super() ;
m_c = c ;
}
}
Rosetta の構築関数では、明示的に super() を記述しなければ親クラスの構築関数は呼び出されないので注意が必要です。(但し、構築関数が呼び出される前に、メンバ変数は定義された初期値で初期化されます)
※インスタンス生成のメカニズムは Java より JavaScript に似ています。
インスタンスの生成プロセスでは、まずプロトタイプが複製されます。
プロトタイプとはメンバ変数の定義で記述された初期値で構成されたインスタンスです。初期値はメンバ定義時の評価値で、インスタンス生成時に式が都度評価されるわけではありません。
次に構築関数が呼び出されます。この時、親クラスの構築関数は明示しない限り呼び出されません。但し派生クラスで構築関数をオーバーロードしていない場合、親クラスの構築関数が呼び出されます。
将来のバージョンでは暗黙に親クラスの構築関数が呼び出されるよう変更されるかもしれません。互換性のため、必要であれば親クラスの構築関数の呼び出しを明示することが推奨されます。
また、Java の interface に相当するクラスを多重派生することも出来ます。
class Hoge
{
public abstract void hoge() ;
}
class Bar extends Foo implements Hoge
{
public void hoge()
{
System.console().printf( "Bar.hoge\n" ) ;
}
}
ポインタ型
Rosetta のポインタ型は、JavaScript の型付配列と似ています。
基本的な使用法は、ArrayBuffer で確保されたメモリへ関連付け、配列の要素としてメモリを参照します。
ArrayBuffer buf = new ArrayBuffer( 100 * 4 ) ;
Int32Pointer p = new Int32Pointer( buf ) ;
p[i] = foo( i ) ;
以下のようにコンストラクタに配列長を指定すると、自動的にメモリが確保されます。
Int32Pointer p = new Int32Pointer( 100 ) ;
ポインタが JavaScript の型付配列と異なるのは、ポインタを加減算できる点です。
配列長
n のポインタには、0~
n の値を加算できます。ただし、(p+n)[0] は p[n] 同様、範囲外指標エラーとなります。
p-- や p+=n+1 は加減算の時点でエラーとなります。(範囲外の値となったポインタは null と評価されます)
ループでポインタを変化させながら使用する際には注意が必要です。(p+=n の後の p-- は問題ありません)
またポインタ型は代入の際、ポインタオブジェクトへのポインタではなく、ポインタ値そのものが複製されます。
Int32Pointer p1 = new Int32Pointer( 3 ) ;
p1[0] = 100 ;
p1[1] = 200 ;
p1[2] = 300 ;
Int32Pointer p2 = p1 ;
Int32Pointer p3 = p1 + 2 ;
p2 += 1 ;
System.console().printf( "%d,%d,%d\n", p1[0], p2[0], p3[0] ) ; // 100,200,300
すべてのポインタ型は Uint8Pointer へ変換できます。
Int32Pointer p1 = new Int32Pointer( 100 ) ;
Uint8Pointer p2 = p1 ;
しかしそれ以外のポインタへの変換はできませんので、コンストラクタを呼び出す必要があります。
(※ポインタの型変換のみのコンストラクタは、コンパイルされた場合には単にポインタ値がコピーされるだけです)
Int16Pointer p1 = new Int16Pointer( 100 ) ;
Uint32Pointer p2 = new Uint32Pointer( p1 ) ;
ポインタに半端なアドレスを指定するとアライメントエラーとなり例外がスローされます。
Uint8Pointer p1 = new Uint8Pointer( 10 ) ;
Uint32Pointer p2 = new Uint32Pointer( p1 + 1 ) ; // Error!
整数ポインタが指すメモリへ数値を書き込むとき、数値は最近値の整数へ丸められ、整数は有効なビット数だけ下位ビットが書き込まれます。
尚、書き込み数値のエンディアンは実行環境に依存します。
Int8Pointer p1 = new Int8Pointer( 10 ) ;
Uint8Pointer p2 = new Uint8Pointer( 10 ) ;
p1[0] = 128 ;
p1[1] = 255 ;
p2[0] = -1 ;
p2[1] = 257 ;
System.console().printf( "%d,%d,%d,%d\n", p1[0], p1[1], p2[0], p2[1] ) ; // -128, -1, 255, 1
構造体
Rosetta では C/C++ 言語と同じような構造体が利用できます。
構造体はクラスとは異なり、ArrayBuffer 上のメモリの構造を直接記述します。
struct Hoge
{
public int a = 1 ;
public short b, c ;
}
構造体変数はポインタ型変数とよく似ており、ArrayBuffer で確保されたメモリへ関連付けて使用することが出来ます。
ArrayBuffer buf = new ArrayBuffer( Structure.sizeof(Hoge) ) ;
Hoge p = new Hoge( buf ) ;
以下のように配列長を指定すると、自動的にメモリが確保されます。
配列長を省略した場合には、長さ1の配列が確保されます。
Hoge p = new Hoge( 100 ) ;
Hoge q = new Hoge() ;
ポインタ型同様、加減算することが出来ます。
加減算と [] 括弧での要素参照はよく似ていますが、[] 括弧の場合、有効長が1に制限されている点、加減算の場合の有効範囲は 0~n に対し、[] 括弧の場合 0~(n-1) などの違いがあります。
またポインタ型同様、代入の際には構造体ポインタ値そのものが複製されます。
Hoge p1 = new Hoge( 3 ) ;
p1.a = 100 ;
p1[1].b = 200 ;
p1[2].c = 300 ;
Hoge p2 = p1[1] ;
Hoge p3 = p1 + 2 ;
System.console().printf( "%d,%d,%d\n", p1.a, p2.b, p3.c ) ; // 100,200,300
Hoge p4 = p1[1] ;
Hoge p5 = p1 + 1 ;
p4[0].a = 110 ; // OK
p4[1].a = 120 ; // 指標範囲外エラー!
p5[1].a = 120 ; // OK
構造体のメンバには配列を定義することが出来ます。
配列メンバはポインタ型(構造体の場合は構造体の型)として評価されます。
また、ポインタの有効長はメンバの配列長に制限されます。
struct Hoge
{
public int a ;
public char b[10] ;
public short c ;
}
Hoge hoge = new Hoge() ;
Uint16Pointer s = hoge.b ;
s[5] = 'a' ; // OK
s[10] = 'b' ; // 指標範囲外エラー!
ポインタ型同様、Uint8Pointer へ変換できますが、それ以外の変換はコンストラクタを呼び出す必要があります。
Hoge hoge = new Hoge() ;
Uint8Pointer p1 = hoge ;
Uint32Pointer p2 = new Uint32Pointer( hoge ) ;
構造体は派生することが出来、派生のメカニズムは C++ とよく似ています。
struct Foo
{
public int a, b ;
}
struct Bar
{
public int c, d ;
}
struct Hoge extends Foo, Bar
{
public int e ;
}
Hoge hoge = new Hoge() ;
Foo foo = hoge ;
Bar bar = hoge ;
foo.a = 1 ;
bar.c = 2 ;
hoge.e = 3 ;
Int32Pointer p = new Int32Pointer( hoge ) ;
System.console().printf( "%d,%d,%d\n", p[0], p[2] p[4] ) ; // 1,2,3
構造体のメンバ関数は仮想関数ではありません。
struct Foo
{
public void put( void )
{
System.console().printf( "foo\n" ) ;
}
}
struct Bar extends Foo
{
public void put( void )
{
System.console().printf( "bar\n" ) ;
}
}
Bar bar = new Bar() ;
Foo foo = bar ;
bar.put() ; // bar
foo.put() ; // foo
構造体の構築関数の記述には注意が必要です。
構造体変数はポインタ変数であり、構築関数が明示的にメモリ確保を行う必要があります。
struct Point3D
{
public int x, y, z ;
public Point3D( int x, int y, int z )
{
super( 1 ) ; // 配列長 1 のメモリを確保
this.x = x ;
this.y = y ;
this.z = z ;
}
}
なお、構築関数から super() が呼び出された場合、構造体の情報は保持されているので、派生クラスでも有効です。
struct Point4D extends Point3D
{
public int w ;
public Point4D( int x, int y, int z, int w )
{
super( x, y, z ) ;
this.w = w ; // このメモリまで確保されている
}
}
また、構造体の消滅関数(finalize)は無効です。
定義済みリテラル
Rosetta には JavaScript と同様に以下のリテラルが定義されています。
undefined
未定義データを表現します。
Rosetta の undefined は JavaScript ほど厳密ではなく(またグローバルオブジェクトではなく値の表現であり)、x === undefined の代わりに、x === null を使用してもかまいません。
しかし、(String) キャストでは "undefined" に変換されます。
null
Object の null ポインタを表現します。
true
boolean の true 値を表現します。
false
boolean の false 値を表現します。
void 型
C++ 同様 Rosetta では void を関数の返り値型や、引数として指定できます。
関数の返り値の場合、返り値が存在しないことを、引数の場合、引数が存在しないことを意味します。
void foo( void )
{
// 返り値も引数も無い関数
}
this と super
Rosetta では Java 同様 this や super を使用できます。
this は文字通りその関数が属しているクラスのオブジェクトを示します。
super は this のコンストラクタから親クラスのコンストラクタを呼び出したり、this メンバ関数内から親クラスの関数を呼び出すのに使用します。
※Rosetta では親クラスのコンストラクタは super で明示しない限り呼び出されません。
class Foo extends Hoge
{
public Foo()
{
super( 1, 2, 3 ) ; // Hoge のコンストラクタを呼び出す
this.m_value = 100 ; // Foo か Hoge かその親クラスのメンバ変数
}
public int getHoge()
{
return super.getHoge() * 2 ; // Hoge の getHoge 関数を呼び出す
}
}
演算子
Rosetta では以下の演算子が利用可能です。
expr ( arg-list ) |
関数呼び出し |
expr [] |
配列型 |
expr [ expr ] |
配列要素、又はメンバ参照 |
type-expr :: member |
メンバ、又はクラスの static メンバ参照 |
expr ++ |
インクリメント |
expr -- |
デクリメント |
new type-expr ( arg-list ) |
オブジェクトインスタンス生成 |
type-expr ( arg-list ) |
オブジェクトインスタンス生成(非推奨) |
function ( arg-list ) [: type-expr ]opt [-> class-name ]opt { statement-list } |
無名関数生成 |
++ expr |
インクリメント |
-- expr |
デクリメント |
( type-expr ) expr |
型変換 |
+ expr |
単項プラス |
- expr |
算術否定(マイナス) |
~ expr |
論理否定(ビット毎の1の補数) |
! expr |
論理否定 |
expr * expr |
乗算 |
expr / expr |
除算 |
expr % expr |
剰余 |
expr + expr |
加算 |
expr - expr |
減算 |
expr & expr |
論理積(ビット毎の論理積) |
expr | expr |
論理和(ビット毎の論理和) |
expr ^ expr |
排他的論理和(ビット毎の排他的論理和) |
expr >>> expr |
論理右シフト |
expr << expr |
算術左シフト |
expr >> expr |
算術右シフト |
expr == expr |
同値判定 |
expr != expr |
非同値判定 |
expr <= expr |
同値又は劣勢値判定 |
expr < expr |
劣勢値判定 |
expr >= expr |
同値又は優勢値判定 |
expr > expr |
優勢値判定 |
expr === expr |
実体オブジェクトの一致判定(ポインタ同値判定) |
expr !== expr |
実体オブジェクトの不一致判定(ポインタ非同値判定) |
expr instanceof type-expr |
左辺オブジェクトの型適合判定 |
lvalue = expr |
代入 |
lvalue *= expr |
乗算代入 |
lvalue /= expr |
除算代入 |
lvalue %= expr |
剰余代入 |
lvalue += expr |
加算代入 |
lvalue -= expr |
減算代入 |
lvalue &= expr |
論理積代入 |
lvalue |= expr |
論理和代入 |
lvalue ^= expr |
排他的論理和代入 |
lvalue >>>= expr |
論理右シフト代入 |
lvalue <<= expr |
算術左シフト代入 |
lvalue >>= expr |
算術右シフト代入 |
表の上の行のほうが優先度が高く、縦罫線が繋がっている行は同じ優先度です。
expr は任意の式を、
type-expr は任意の型を表現する式を、
arg-list は関数引数リストを、
lvalue は左辺式を、
member はメンバ名を表します。
JavaScript や Java と異なる演算子があることに注意してください。
function 演算子の [~]
opt は省略可能を意味します。
またインスタンスを生成する new 演算子は省略可能ですが非推奨です。これは将来構文を拡張した際に互換性を保証しないためです(new の省略を正式な仕様とするかは未定です)。
スレッドと同期
Rosetta のスレッドと同期処理は Java のそれとほぼ同等です。
class MyRunnable extends Runnable
{
public void run()
{
System.console().printf( "run\n" ) ;
}
}
Thread thread = new Thread( new MyRunnable() ) ;
thread.start() ;
thread.join() ;
Object クラスには Java 同様 notify, notifyAll, wait 関数が定義されており、また synchronized 構文と組み合わせて使用することにより同期を取ることが可能です。
// 通知
synchronized( obj )
{
obj.postFoo() ; // 何らかの処理
obj.notify() ; // 他スレッドの wait へ
}
// 待機
try
{
synchronized( obj )
{
obj.wait() ; // obj.notify が呼び出されるのを待つ
obj.receiveFoo() ; // 何らかの処理
}
}
catch ( Exception e )
{
}
§構文
書式記法
構文の書式は以下のように表記します。
abc
装飾されていない(あるいは読みやすさのため太字強調されている)文字や記号はそのまま記述します。
abc
斜体部分は任意の名前や式を記述します。
abc
abc:
def
任意書式 abc の詳細が、行をあけ : 記号以降に定義されています。
上の例は、任意の def を記述することを意味します。
[ abc ]opt
[ ]opt で囲まれた部分は省略可能であることを意味します。単に [ abc ] と表記した場合には、角括弧を含めそのままの表記を意味することに注意してください。
{ a | b | c }opt
{ }opt で囲まれた部分は書式が選択的であることを意味します。 | 記号で区切られた範囲が選択可能な一つの書式となります。つまり、上の例は a 又は b 又は c の何れかを記述することを意味します。
コメント
コメントは C++ と同様です。
// 行末までのコメント
/*
任意のコメント
*/
#if, #elif, #elseif, #else, #endif ディレクティブ
#if expr
#elif expr
#elseif expr
#else
#endif
Rosetta では C++ のような #if ディレクティブを使用できます。
#elseif は #elif と同じ意味です。
条件式には #define で定義した変数を使用することが出来ます。
C++ とは異なり、defined 演算子は使用できません。
条件が成立したブロックのみがパーサーの処理対象になります。
#define ディレクティブ
#define var-name expr
#if 文の条件式に使用できるマクロ変数を定義します。
Rosetta のマクロは、テキストマクロではなく、expr の評価値をマクロ変数として保持します。
マクロ変数は、#if, #elif, #elseif, #define 文でのみ参照できます。
複文
{ statement
| { statement-list } }opt
statement-list:
statement [ statement-list ]opt
文は単一の文の他、{ } 括弧に複数の文を囲んで記述出来ます。
但し Rosetta で { } 括弧は HashMap オブジェクトを構築するため、C++ のように任意の文として記述できません。
if 文や try 文などの中で、1文を記述する代わりに複数の文を記述するためにのみ用いることが出来ます。
式文
expr ;
式を評価します。
ほとんどの文は式文です。
変数定義文
[ modifier-list ]opt type-expr var-name [ [ array-size-expr ] ]opt [ = init-expr ]opt ;
modifier-list:
{ static
| const
| public
| protected
| private }opt [ modifier-list ]opt
その名前空間に type-expr 型の新しい変数 var-name を作成し、init-expr で初期化します。
また、構造体メンバの整数型や数値型に限り、array-size-expr に配列長を指定することが出来ます。
modifier-list に指定できる修飾子のうち、private, protected は記述できますが、期待通りの動作となるとは限りません。現在の仕様上、private と protected の挙動に明確な違いはありません。これは実行時に判定していることと関係しており、実行時にオブジェクトのメンバが、そのクラス自身のメンバか、親クラスのメンバであるかの情報を持っていないためです。
const 修飾子は C 言語と似ています。しかし、Rosetta では const 修飾されたオブジェクトを const 修飾されていない変数へ代入してもエラーとなりません。
関数定義文
[ modifier-list ]opt [ function ]opt [ type-expr ]opt func-name ( arg-list ) { statement-list }
modifier-list:
{ static
| abstract
| native
| const
| synchronized
| public
| protected
| private }opt [ modifier-list ]opt
arg-list:
{ [ void ]opt
| argument [ , argument ]opt }opt
argument:
[ arg-type-expr ]opt arg-name [ = default-expr ]opt
その名前空間に type-expr 型の返り値を返す関数 func-name を定義します。
JavaScript とは異なり、同名の関数は、引数が競合しなければ複数定義できます。
また、関数の引数は JavaScript 同様に型を省略できるほか、C++ のようにデフォルト値を指定できます。デフォルト値はその関数定義が記述された名前空間で評価されます。
関数の返り値は、function 予約語から始まっている場合、あるいはクラスのコンストラクタの場合省略できます。
modifier-list に指定できる修飾子のうち、private, protected は記述できますが、期待通りの動作となるとは限りません。現在の仕様上、private と protected の挙動に明確な違いはありません。これは実行時に判定していることと関係しており、実行時にオブジェクトのメンバが、そのクラス自身のメンバか、親クラスのメンバであるかの情報を持っていないためです。
const 指定は C++ 言語と似ていますが、関数の前に記述する点が異なります。(C++ の場合、返り値型の修飾となるためです)
import 文
import script-file-expr
import 文はスクリプトを読み込み追加します。
ファイル名は式で与えられます。従って通常はファイル名はダブルクォーテーションで囲んで記述します。バックスラッシュには注意が必要です。
この文はディレクティブではありませんので、パース時に処理されず、実行時に処理されます。従って例えばループ文の内側にあると、何度も実行されることになりますが推奨されません。
同じスクリプトを再び追加しようとしても無視されます。従って、ディレクティブを利用したメタコーディングには利用できません。
class 文
class class-name [ extends super-class ]opt [ implements super-class-list ]opt { statement-list }
super-class-list:
super-class [ , super-class-list ]opt
class class-expr ;
class-expr:
class-name [ :: class-expr ]opt
クラスを定義します。
Java と同じような書式ですが、Rosetta に interface は存在せず、implements は多重派生を実現します。但し、implements は static でないメンバ変数が存在しないクラスに限定されます(Java の interface 相当)。
また、Rosetta のクラスは、Java での static クラスに相当します。クラス内のローカルクラスを定義した場合には、暗黙に static クラスとなりますので注意してください。
尚、C++ と同じようにクラスの宣言のみを行うことも出来ます。
更に、クラスのローカルクラスの宣言を行うことも出来ます。
class Foo ;
class Foo::Bar ;
※ Rosetta は Java とは異なり記述された順にクラスが解釈されます。
struct 文
struct struct-name [ extends super-struct-list ]opt { statement-list }
super-struct-list:
super-struct [ , super-struct-list ]opt
struct struct-expr ;
struct-expr:
struct-name [ :: struct-expr ]opt
構造体を定義します。
Rosetta の構造体は C++ 言語と似ており、Java のクラスとは大きく異なります。
static でないメンバ変数は ArrayBuffer 上の構造を記述します。(static なメンバ変数はクラスと同じです)
メンバ関数はクラスとほぼ同様ですが、クラスのメンバ関数は暗黙に仮想関数であるのに対し、構造体のメンバ関数は仮想関数ではありません。
構造体は派生することは出来ますが、クラスから構造体を派生したり、構造体からクラスを派生したりすることは出来ません。
複数の構造体から派生することが出来、派生のメカニズムは C++ と同様です。
for 文
for ( [ init-expr ]opt ; [ cond-expr ]opt ; [ step-expr ]opt ) { statement-list }
for ( decl-var-statement [ cond-expr ]opt ; [ step-expr ]opt ) { statement-list }
for ( iter-var-name in expr ) { statement-list }
1つ目と2つ目の書式は C++ とよく似ています。
init-expr は初期化式で、decl-var-statement は変数定義文です。(変数定義文の終端はセミコロンとなることに注意してください)
cond-expr は反復条件で、省略すると永遠に繰り返します。
step-expr は1回の反復処理が終わったときに評価されます。
3つ目の書式は JavaScript と似ています。
ループ内のローカル変数として iter-var-name に指定された名前の String 変数が自動的に定義され、expr で評価されるオブジェクトのメンバ名が順次設定されます。
但し C++ とは異なり、何れの書式でもループ処理を { } 括弧で囲むことが必須となります。
while 文
while ( expr ) { statement-list }
expr が真の間、statement-list を繰り返し実行します。
C++ とは異なり、ループ処理を { } 括弧で囲むことが必須となります。
do~while 文
do { statement-list } while ( expr ) ;
expr が真の間、statement-list を繰り返し実行します。
但し statement-list が実行された後に expr が評価されます。
C++ とは異なり、ループ処理を { } 括弧で囲むことが必須となります。
if 文
if ( expr ) if-statement [ else else-statement ]opt
expr が真の時 if-statement、そうでない場合 else-statement を実行します。
switch 文
switch ( expr ) { statement-list }
case case-expr :
default:
expr が case-expr に一致する case ラベルにジャンプします。
一致するラベルが存在しない場合、default ラベルへジャンプします。
break 文
break ;
最も内側のループ、あるいは switch ブロックから脱出します。
continue 文
continue ;
最も内側のループの先頭に戻ります。
try 文
try try-statement [ catch-list ]opt [ finally finally-statement ]opt
catch-list:
catch ( [ type-expr ]opt var-name ) catch-statement [ catch-list ]opt
try-statement を実行します。
例外がスローされると、スローされたオブジェクトの型が type-expr に一致する catch 文の catch-statement が実行されます。
type-expr を省略した場合には常に例外を受け取ります。
finally 句を記述すると、try-statement、また catch した catch-statement を実行した後、必ず finally-statement が実行されます。
throw 文
throw expr ;
例外をスローします。
return 文
return [ expr ]opt ;
関数から復帰します。
with 文
with ( expr ) statement
expr で評価されるオブジェクトを名前空間チェーンに追加し statement を実行します。
synchronized 文
synchronized ( expr ) statement
expr で評価されるオブジェクトに対する同期状態で statement を実行します。
§ JIT コンパイルと最適化
コンパイルと実行
Rosetta はスクリプトが読み込まれると、まずグローバル空間に対してスクリプトが「実行」されます。
Rosetta では、import 文や class 文、struct 文は「実行」されることにより、スクリプトを追加したり、クラスや構造体の定義が行われます。
将来のバージョンでは、グローバル空間での実行の前に、import 文や class 文をコンパイルするように変更されるかもしれませんが、現在の Rosetta では、import 文や class 文は「実行」されるため、記述の順序は重要です。
JIT コンパイラが有効に設定されている場合、グローバル空間での実行の後に、コンパイルが実行されます。
コンパイルは関数単位で実行され、グローバル関数やクラスのメンバ関数がコンパイルされます。無名関数はコンパイルされません。
現在のバージョンのコンパイラでは、一旦 Sakura2 仮想マシン用のバイナリが出力されます。
更に、実行 CPU が Sakura2 の JIT コンパイラに対応した CPU の場合、ネイティブなバイナリに変換されます。
現在のバージョンの Sakura2 仮想マシンは、x86 系 CPU(32ビット)と、ARM 系(32ビット)へのネイティブコードへの変換をサポートしています。
直接ネイティブコードに変換する場合より実行速度は劣りますが(特にオブジェクト操作関係はほとんど高速化されません)、実装の簡略化のため現在のバージョンでは Sakura2 仮想マシンを利用しています。
コンパイルされる関数とされない関数
スクリプトをコンパイルする利点は大別して2つあり、1つは高速化です。
整数や数値型、またポインタ型変数操作が特に高速化されます。一方でオブジェクト操作はほとんど高速化しません。
関数内に、以下の構文が含まれているとその関数はコンパイルされません。
・import 文
・class 文
・struct 文
・function 文
・try 文
・throw 文
・with 文
・synchronized 文
・for ( ... in ... ) 文
・var 文や、型が不明なオブジェクトの操作
・配列リテラル
・自明でないオブジェクト参照
これらの構文はオブジェクト操作を中心としたものであり、コンパイルする利点が小さいためコンパイル対象から除外されます。
もう一つのコンパイルの利点は、実行されないコードの型チェックが実施される点です。
コンパイル時に自明でない型の変数は、オブジェクトとして処理されるか、関数自体がコンパイル対象外になる可能性があります。
高速化:配列ではなくポインタを使う
既述の通りコンパイルされると高速に動作しますが、オブジェクト操作は高速化されません。
以下のコードはコンパイルされたとしても、ほとんど高速化しません。
int[] a = new int[count] ;
for ( int i = 0; i < count; i ++ )
{
a[i] = i ;
}
変数 a は Array 型オブジェクト変数ですので、配列操作はすべてオブジェクト扱いとなります。
オブジェクト操作はポリモーフィズムに行われるため呼び出しジャンプの際の予測ミスペナルティが発生しやすいことや、オブジェクトの管理コストが発生するためそもそも高速ではありません。
付け加え、オブジェクト操作は Sakura2 仮想マシン上のシステムコールへ変換されるため、直接ネイティブコードへ変換した場合よりも多くのオーバーヘッドを伴います。
以下のようにバッファとポインタを利用するコードの場合、この部分に限定すれば、数十倍の高速化を期待できます。
Int32Pointer a = new Int32Pointer( count ) ;
for ( int i = 0; i < count; i ++ )
{
a[i] = i ;
}
高速化:クラスではなく構造体を使う
ユーザー定義 class の変数は常にオブジェクトです。
高速化の面では、可能であれば struct の使用が推奨されます。
また、class のメンバ変数も常にオブジェクト扱いなので注意が必要です。
以下のようなコードは見た目ほど高速化されません。
class Foo
{
protected Uint8Pointer m_buf ;
protected int m_length ;
public void foo( void )
{
for ( int i = 0; i < m_length; i ++ )
{
m_buf[i] = i ;
}
}
}
ループの内側でメンバ変数 m_buf や m_length を参照していますが、class の場合その度にオブジェクト操作が必要となります。
以下のようにすることでループの内側でオブジェクト操作がなくなるため、飛躍的に高速化されます。
class Foo
{
protected Uint8Pointer m_buf ;
protected int m_length ;
public void foo( void )
{
Uint8Pointer buf = m_buf ;
const int length = m_length ;
for ( int i = 0; i < length; i ++ )
{
buf[i] = i ;
}
}
}
一方、構造体のメンバには、整数や数値型変数しか定義できませんが、参照にはオブジェクト操作は必要ないため高速です。
但し、static なメンバ変数は class 同様オブジェクトですので注意が必要です。
struct Foo
{
protected static int m_fill ;
protected int m_length ;
public void foo( Uint8Pointer buf )
{
for ( int i = 0; i < m_length; i ++ )
{
buf[i] = Foo.m_fill ;
}
}
}
上記の m_fill はオブジェクト参照となるため、注意が必要です。
高速化:自明な定数とシステム関数
クラスや構造体の static なメンバ変数はオブジェクトですが、const な整数や数値変数は自明な定数として即値になります。
public void foo( Float32Pointer buf, int length )
{
for ( int i = 0; i < length; i ++ )
{
buf[i] = Math.sin( i * (2.0 * Math.PI) / length ) ;
}
}
上の Math.PI は Math クラスの static なメンバ変数ですが、const 修飾で定義されているため、即値となります。
この例では、(2.0 * Math.PI) が即値となります。
const 修飾されていない場合、実行時のオブジェクトのメンバ参照となるため非常に重くなります。
また、Math.sin 関数は直接 Sakura2 仮想マシンの sin 命令に変換されます。(Math クラスの static メンバ関数はすべて直接 Sakura2 仮想マシンの浮動小数点命令に変換されます)
従って、この例のループは非常に高速に実行されます。
もし関数呼び出しが一般的な関数の場合には、非常に大きなオーバーヘッドを伴います。
このループ処理をより高速化するには、ループ前で (2.0 * Math.PI) / length の計算結果をローカル変数に括り出します。
高速化:構造体ポインタ
ポインタによるメモリ参照の際のバウンダリチェックは Sakura2 仮想マシンのバウンダリチェックが利用されます。
構造体配列の要素参照や、構造体の配列メンバの参照の際、ポインタから参照可能な領域が変化します。この際、Sakura2 仮想マシン上に新たなメモリエイリアスを確保するため、処理上のコストとなります。(もしコンパイラが直接ネイティブバイナリを出力するよう改良されるとこのコストは省略されます)
public void foo( Vector3D v, int length )
{
for ( int i = 0; i < length; i ++ )
{
v[i].x = 0 ;
v[i].y = 0 ;
v[i].z = 0 ;
}
}
この v[i] の度にメモリエイリアスの確保が行われるため、パフォーマンスの低下を引き起こします。
これを解決するには、v[i] の代わりに (v + i) を使用するか、以下のようにします。
public void foo( Vector3D v, int length )
{
for ( int i = 0; i < length; i ++ )
{
v.x = 0 ;
v.y = 0 ;
v.z = 0 ;
v ++ ;
}
}
又は、Rosetta の実行オプションとして、フラットポインタを指定します。フラットポインタモードでコンパイルされると、ポインタの有効領域は変更されません。(その場合でもバウンダリチェックは行われますが、ArrayBuffer 内の構造は無視されます)
§ 主要クラス一覧
基本的なクラス
すべてのクラスは Object クラスから派生しています。
すべてのポインタ型クラスは Uint8Pointer クラスから派生しており、更にすべての構造体は Structure クラスから派生しています。
またネイティブなオブジェクトのコンテナは NativeObject クラスから派生しています。
システム関係
スレッドは Thread クラスにより提供されます。
System クラスは OS や EntisGLS4 が提供するシステム的な機能が提供されます。
算術
Math は基本的な算術関数が提供され、また JIT コンパイルの際には直接算術命令にコンパイルされます。
Randomizer は疑似乱数、他 CRC32、MD5 ダイジェストの計算機能が提供されています。
字句解析
StringParser は EntisGLS4 の低水準な字句解析クラスの機能を提供します。
UsageMatcher は EntisGLS4 の正規表現機能を提供します。
XMLDocument は XML の解釈、保存、データの取得・変更・追加が可能な簡易な XML 解析クラスです。このクラスはネイティブではなく xml_document.rs によって実装されています。
ファイルIO
ファイルシステム、及びファイルの入出力機能が提供されます。
ファイルシステムは EntisGLS4 による疑似的なファイルシステム層の上に構築されています。この疑似層を通過することによって Windows でも Android でも全く同じコード(ファイルパス)を走らせることが可能になります。
SmartBufferFile は実際のファイルシステムを介さずに、メモリ上だけでファイル入出力インターフェースをプログラムに供給します。
画面表示UI
画面表示と入出力インターフェースは Sprite によって提供されます。
これは EntisGLS4 の SGLSprite のコンテナとして Rosetta に提供されています。Rosetta スクリプトで入出力を直接記述する場合には SpriteKeyListener や SpriteMouseListener、また RenderableSprite を派生して記述します。
Window クラスはウィンドウの抽象的なクラスです。
画面表示を実際に行う場合には Dialog や WindowSprite を利用します(これらは Windows / Android 関係なく利用できます)。
Sprite 上でグラフィカルな UI を簡易に利用するには SkinManager や BasicFormParser を利用できます。これは PSD ファイルなどからスクリプトで変換したフォームデータを読み込んで Sprite に表示することができるものです。
VirtualInput はキーボードやゲームパッド、あるいはソフトウェア・ゲームパッドなどの入力を仮想化します。また二種類の入力キューを持っており、仮想キーや、コマンド(GUI(Sprite 等)上のボタン押下)の入力を取得することができます。
サウンド・メディア処理
SoundPlayer は生 PCM データを直接 Rosetta スクリプトから出力してサウンドを再生するクラスです。
AudioPlayer はオーディオファイルを再生する機能を提供します。
AudioInputStream, AudioOutputStream, VideoInputStream, VideoOutputStream はオーディオやビデオファイルを Rosetta スクリプトで処理するためのインターフェースクラスです。
画像描画処理
Image は画像を保持します。画像はそのままテクスチャとして描画することもできます。
PaintContext は 2D 描画を行うための機能を提供します。PaintContext を直接構築した場合には CPU による描画を行う PaintContext オブジェクトが生成されます。
RenderContext は 2D 描画に加え 3D 描画を行う機能を提供します。RenderContext はRenderBuffer からも派生しており、3D 描画関数インターフェースは RenderBuffer で定義されています。従って、RenderBuffer を引数(描画対象)にする描画処理は、直接描画と VertexBuffer の構築で共通化されます。
RenderContext を直接構築した場合にはどのような RenderContext が構築されるかわかりません。多くの場合、2D 描画を CPU で行い 3D 描画は GPU で行う RenderContext が生成されます。
2D 描画も 3D 描画も GPU で行いたい場合には RenderDevice の newRenderer 関数で生成します(但し RenderDevice が CPU によるソフトウェアレンダリングデバイスの場合には CPU レンダリングになります)。
RenderContext はいくつでも生成でき、またスレッドの制約は受けません(但し複数のスレッドから同一の RenderContext へ描画関数を呼び出す場合には同期的に呼び出す必要があります)。
RenderDevice は Window の getRenderDevice 関数で取得できます(通常は WindowSprite から取得します)。