昨日の記事はcharsbarさんでした。
本日はyukikimotoが書きます。リレー形式でつなげていくのは、楽しいですね!
この記事はSPVMの日本語公式ドキュメントです。書き始めですが、数時間で、書けるところまで書きます。
Perl Advent Calender 2018をきっかけにに作成されました。
SPVMは、Perlの数値計算の遅さを改善するために開発されました。数値計算と配列の演算を高速化することができます。
仕様が完成した後にベンチマークテストを始める予定ですが、数値計算と配列の演算に対して、Perlの30倍、C言語の1/2の速度を、最初の目標としています。
二つ目の目的は、C/C++のバインディングを簡単にすることです。XSやInline::Cは、C/C++のバインディングという点で、非常に難しいと感じています。
簡単なAPIを使って、メモリ安全に、C/C++のバインディングができる機能を提供しています。
三つ目の目的は、単体で実行可能な実行ファイルを生成することです。
SPVMは、実行ファイルを生成する機能を持っているので、ランタイムなしに、プログラムの実行が可能で、配布することもできます。
共有ライブラリ(ダイナミックリンクライブラリ)の読み込みにも対応しています。
SPVMのソースコードには、MITライセンスが適用されるので、MITライセンスの元で、商用利用が可能です。
たとえば、ボタンなどのGUI部品を備えた、配布可能なWindowsアプリケーションが作成できる予定です。
どのような人をSPVMのユーザーとして想定していますか。
Perlで予想可能な数値演算を行いたい人
Perlは本当に型のない言語です。Perlは内部的には型を持っていますが、数値型と文字列型は、コンテキストに応じて、簡単に変換されてしまいます。
Perlの整数演算はオーバーフローが発生すると、浮動小数点型に変換されてしまいます。これは、便利ですが、数値計算における起こるであろうことの予測はしづらくなります。
Perlの整数演算は、コンパイラオプションによって、32bitあるいは64bitになります。浮動小数点演算は、double型しか持ちません。
Perlの数値演算は正しい結果を返しますが、内部の動作に依存し、予想がしづらいものとなっています。これはPerlがテキスト処理言語であるメリットの裏返しです。
SPVMは、静的型言語あり、32bit,64bitの整数演算、float,doubleの浮動小数点演算を区別して行うことができます。
数値計算におけるPerlのデメリットを緩和します。
Perlは、遺伝子解析などのビオテクノロジーの分野で利用されていますが、配列操作の遅さに不満を感じている研究者の方がいます。
配列操作を簡単に高速化でき、必要であればC/C++をバインディングし、openMPやSIMDなどの並列計算ができればと考えている研究者の方を想定しています。
SPVMを使うと、Perlの文法で、数値計算と配列演算を速くすることができます。
IoTの開発者は、ハードウェアからWebまでの幅広い知識が必要になります。大量の分野の知識が必要となるため、これを簡単に実現するライブラリがあれば便利です。
C/C++、アセンブラを使って、センサーのデバイスドライバを作成。HTTPクライアントライブラリを使って、Webにアクセス。開発環境で、実行ファイルを作成して、それぞれの機器にそのままコピー。このような一連の機能をSPVMは提供する予定です。
画像処理には、大量の配列演算が必要です。Perlは、配列のデータ構造を言語仕様として持たないため、不満を感じている方がいます。
SPVMは、連続した領域を持つ配列のデータ構造を提供しているので、DirectXやOpenGM、OpenCVなどと組み合わせて、画像処理をしたいユーザーに向いています。
AI・機械学習には、配列演算が必要です。Perlは、配列のデータ構造を言語仕様として持たないため、不満を感じている方がいます。
SPVMは、連続した領域を持つ配列のデータ構造を提供しているので、機械学習ライブラリとの相性が高いです。
SPVMは、JSONモジュール、非同期IOをサポートしたHTTPクライアント/サーバーをコアで提供する予定です。実行ファイルを生成可能なので、小さなLinuxサーバーを構築し、実行ファイルをコピーして、マイクロサービスを運用可能です。
SPVMは、リファレンスカウント方式のGCを採用しているので、FullGCが起こらず、非同期IOの性能に大きく関わるレイテンシを小さくできます。
SPVMはCPANモジュールです。cpanまたはcpanmを使ってインストールできます。
cpan SPVM cpanm SPVM
SPVMは連続したデータ領域を持つ配列型を提供しています。
Perlの数値計算の弱点の一つは、型を固定して計算することができないことです。ある操作によって浮動小数点に変換されたり、文字列型に変換されたりします。SPVMでは、仕様化された数値型と計算規則を持っているので、どの型によって計算されるかを正確把握することができます。
C99で追加された新しいC言語の数学関数がすべて利用できます。
SPVMの言語仕様は、メジャーバージョンごとに、仕様化されています。仕様を元に、独自にコンパイラを開発することが可能です。
SPVMのサブルーチンはコンパイル時に、機械語へコンパイルすることができます。
SPVMの文法は覚えやすく簡単になるように設計されています。
文法の95%以上はPerlの文法を採用しています。Perlをすでに学んでいるユーザーであれば、2時間あれば、すべての文法を覚えてしまうことができるでしょう。
型の種類、型の規変換規則は、明確です。型推論は、常に右から左へ行われます。
継承はなく「汎用オブジェクト型」と「インターフェース」を使って、ポリモーフィズムを実現します。
SPVMのチュートリアルです。
最初の簡単な例として配列の和を求めてみましょう。
「MyMath.spvm」というファイルファイルをlibディレクトリの中に作成してください。SPVMのソースファイルの拡張子は「.spvm」です。以下の内容を記述しましょう。
# lib/MyMath.spvm package MyMath { sub sum : int ($nums : int[]) { my $total = 0; for (my $i = 0; $i < @$nums; $i++) { $total += $nums->[$i]; } return $total; } }
SPVMではパッケージ構文を使用して、パッケージを作成します。Perlのパッケージブロック構文と同じ文法です。
# パッケージ構文 package MyMath { }
パッケージのブロックの中ではサブルーチンの定義を行うことができます。
package MyMath { sub sum : int ($nums : int[]) { } }
SPVMは静的型言語ですので、サブルーチンの宣言の中で、戻り値の型、引数名と引数の型を記述します。
戻り値は「int型」。引数は「int型の配列」です。int型は、32bit符号付整数を表現します。
配列の和を計算している部分をみてみましょう。
my $total = 0; for (my $i = 0; $i < @$nums; $i++) { $total += $nums->[$i]; } return $total;
和の計算方法の見た目は、Perlでfor文を使って書いたものとまったく同じです。
このようにSPVMではMPerlと同じ文法で記述できるのがひとつの特徴です。Perlユーザーが、新しい文法を覚える負担ができるだけ小さくなるように設計されています。
変数の宣言では型推論を使って、型を省略することができます。
my $total = 0;
SPVMの型推論は、右辺の型が確定しているときに、左辺の型宣言を省略することができます。
数値リテラルの「0」は「int型」ですので、「$total」の型も「int型」になります。以下の記述と同じ意味になります。
my $total : int = 0;
配列の長さは「@」を使うことで取得できます。
@$nums
SPVMには、コンテキストはなく「@」は常に配列の長さを返します。
次にSPVMで書かれたサブルーチンをPerlの側から呼び出してみましょう。
「sum.pl」というファイルを作成して、以下の内容を記述してください。
SPVMで書かれた「MyMath」パッケージの「sum」サブルーチンを呼び出して、配列の和を計算します。
use FindBin; use lib "$FindBin::Bin/lib"; use SPVM 'MyMath'; my $sp_nums = SPVM::new_iarray([3, 6, 8, 9]); my $total = MyMath->sum($sp_nums); print $total . "\n";
まず最初にlibディレクトリをモジュールの検索パスに追加します。
# libディレクトリをモジュールの検索パスに追加 use FindBin; use lib "$FindBin::Bin/lib";
次にSPVMモジュールを読み込みます。
# SPVMモジュールを読み込み use SPVM 'MyMath';
「use SPVM 'SPVMモジュール名'」という記述で、SPVMモジュールを読み込むことができます。
次にSPVMの配列を作成します。
# int型配列の作成 my $sp_nums = SPVM::new_iarray([3, 6, 8, 9]);
SPVM::new_iarray関数を使うと、配列のリファレンスを渡して、SPVMのint型の配列を作成することができます。
MyMathパッケージのsumサブルーチンを呼び出してみましょう。
# MyMathパッケージのsumサブルーチンを呼び出し my $total = MyMath->sum($sp_nums);
SPVMのサブルーチン呼び出しは、Perlのクラスメソッド呼び出しになることに注意してください。「絶対名 MyMath::sub」で呼び出すことはできません。
sum関数の戻り値は「int型」ですが、SPVMの整数型は、自動的にPerlのスカラ型に変換されます。
SPVMの言語仕様の詳細です。
SPVM自体のソースコードは、C言語で書かれ、C99に準拠します。
数値演算における仕様は、C99に準拠します。C99で、未定義・処理系依存のものは、SPVMにおいても未定義・処理系依存です。
加算演算子、減算演算子、乗算演算子、除算演算子、剰余演算子、シフト演算子、ビット演算子においては、C99における演算との対応が仕様化されます。
数値から文字列への型変換における仕様は、C99のsrpintfの「%g」フォーマットが仕様になります。
SPVMの型は、C99の次の型と完全に一致します。
SPVMの型 | C99の型 | 説明 |
---|---|---|
byte | int8_t | SPVMのbyte型はC99のint8_t型に一致します。 |
short | int16_t | SPVMのshort型はC99のint16_t型に一致します。 |
int | int32_t | SPVMのint型はC99のint32_t型に一致します。 |
long | int64_t | SPVMのlong型はC99のint64_t型に一致します。 |
float | float | SPVMのfloat型はC99のfloat型に一致します。 |
double | double | SPVMのdouble型はC99のdouble型に一致します。 |
オブジェクト型 | void* | SPVMのオブジェクト型はC99のvoid*型に一致します。 |
byte& | int8_t* | SPVMのbyte&型はC99のint8_t*型に一致します。 |
short& | int16_t* | SPVMのshort&型はC99のint16_t*型に一致します。 |
int& | int32_t* | SPVMのint&型はC99のint32_t*型に一致します。 |
long& | int64_t* | SPVMのlong&型はC99のint64_t*型に一致します。 |
float& | float* | SPVMのfloat&型はC99のfloat*型に一致します。 |
double& | double* | SPVMのdouble&型はC99のdouble*型に一致します。 |
値型 | 値型のフィールドの個数を要素数とし、対応した型を持つ配列型 | たとえば「package Point_2i : value_t { has x : int; has y : int; }」で定義されていた場合は、Point_2i型は、int32_t[2]型に一致します。 |
SPVMのソースコードの文字コードは、BOMなしのUTF-8で記述されます。
SPVMのソースコードは、LALR(1)法によって解析できます。yacc/bisonで生成されたパーサージェネレータによって解析することができます。
SPVMのソースコードは「空白文字」「コメント」「リテラル」「キーワード」「識別子」「区切り文字」「演算子」で構成されます。
SPVMのソースコードの行終端は、ASCIIコードの「LF」「CR」「CR LF」です。
行終端が現れたときは、適切に行番号がインクリメントされます。
SPVMにおける空白文字はASCIIコードの「SP」「HT」「FF」と「行終端」です。
空白文字はソースコード上では意味を持ちません。
SPVMにおけるキーワードは以下です。
byte BEGIN case croak default double elsif else enum eq eval for float gt ge has if interface_t isa int last length lt le long my native ne next new our object package private public precompile pointer_t return require rw ro self switch sub string short scalar undef unless use void value_t while weaken wo __END__ __PACKAGE__ __FILE__ __LINE__
SPVMにおける識別子は「パッケージ名」「サブルーチン名」「フィールド名」「パッケージ変数名」「レキシカル変数名」です。
SPVMにおける区切り文字は以下です。
( ) { } [ ] ; , ->
SPVMにおける演算子は以下です。
= > < ! ~ == <= >= != && || ++ -- + - * / & | ^ % << >> >>> += -= *= /= &= |= ^= %= <<= >>= >>>= \ $ @ . .=
コメントは「#」で始まり改行で終わります。
# コメント
POD(プレーンオールドドキュメント)を記述することができます。PODは行頭が「=」で始まる任意の文字列の行から始まり、行頭から行末まで「=cut」で終わる行までです。
PODの内側に書かれた文字列はソースコードとして解釈されず、コメントとして扱われます。
=pod 複数行 コメント =cut
=head1 複数行 コメント =cut
パッケージを定義するには以下の構文を使用します。
package パッケージ名 { }
パッケージ名は「大文字」で始まる必要があります。パッケージ名には「::」を使用することができます。
Foo Foo::Bar Foo::Bar::Baz
「パッケージ名」の後に「:」をつなげてその後ろに「デスクリプタ」を指定することができます。
package パッケージ名 : デスクリプタ { }
パッケージの定義のサンプルです。
# パッケージ名のみ package Point { }
# パッケージ名とデスクリプタ package Point : public { }
パッケージデスクリプタ
パッケージで指定できるデスクリプタの一覧です。
デスクリプタ名 | 役割 |
---|---|
public | このパッケージに対するnewキーワードが他のパッケージから利用できます。 |
private | このパッケージに対するnewキーワードが他のパッケージから利用できません。デフォルトの設定です。 |
interface_t | このパッケージは「インターフェイス型」になります。 |
value_t | このパッケージは「値型」になります。 |
pointer_t | このパッケージは「ポインタ型」になります。「ポインタ型」は「クラス型」の一種です。 |
「public」と「private」の両方のデスクリプタが指定された場合は、コンパイル時エラーが発生します。
「interface_t」「value_t」「pointer_t」のひとつより多くが同時に指定されている場合は、コンパイル時エラーが発生します。
パッケージ内部で定義できるもの
パッケージ内部では「use」「パッケージ変数」「フィールド」「列挙」「サブルーチン」が定義できます。
package Foo { # use use Point; # パッケージ変数 our $VAR int; # フィールド has var : int; # 列挙 enum { CONST_VAL } # サブルーチン sub foo : int ($num : int) { } }
モジュールとは、SPVMのソースコードとして読み込むことができるひとつのファイルのことをいいます。
# lib/path/Foo/Bar.spvm package Foo::Bar { }
モジュールには、複数のパッケージを含むことができます。
# lib/path/Foo/Bar.spvm package Foo::Bar { } package Foo::Bar::Baz { }
モジュールは、モジュールの読み込みパスに、以下のファイル名で配置される必要があります。
「::」を「/」に変更。末尾に「.spvm」をつける。
Foo.spvm Foo/Bar.spvm Foo/Bar/Baz.spvm
モジュールを読み込むには、useキーワードを使用します。
use Foo; use Foo::Bar;
モジュールはコンパイル時に読み込まれます。
モジュールが存在しなかった場合は、コンパイルエラーになります。
useキーワードは、パッケージの定義の直下で定義する必要があります。
package Foo { use Foo; }
SPVMでは、モジュールが、検索パスに存在する場合だけ読み込み、そうでない場合は、ブロックの内部が存在しないことにできるif require文があります。これはC言語の「#ifdef」の一部の機能を実現するために設計されました。
if (require Foo) { }
if require文では、elsifやelseを続けることができないので注意してください。
一つの例を見てみましょう。以下の例で、Fooが存在しない場合は、コンパイルエラーにはならず、ifブロックの中身が存在しないことになります。そのため「my $foo = new Foo;」は、存在しないことになっているので、コンパイルエラーになりません。
if (require Foo) { my $foo = new Foo; }
ひとつのテクニックとして、if require構文とインターフェース型と無名サブルーチンを使うと、モジュールを選択的に利用することができます。以下の例では、Foo->method1とBar->mehtod2を選択的に選んで実行しています。
package SomeInterface : interface_t { sub foo : int ($self : self, $arg : int); } # 別の関数内 my $interface : SomeInterface; unless ($inteface) { if (require Foo) { $interface = sub : int ($self : self, $arg : int) { Foo->method1($arg); }; } } unless ($inteface) { if (require Bar) { $interface = sub : int ($self : self, $arg : int) { Bar->method2($arg); } } } my $result = $interface->foo(3);
パッケージ変数とは、パッケージに属する、プログラムの開始から終了まで維持されるグローバル変数のことです。
「our」キーワードを使用してパッケージ変数を定義することができます。
our パッケージ変数名 : 型名;
パッケージ変数の定義は「パッケージ定義」の直下で行う必要があります。
パッケージ変数の定義には「型名」が必要です。型名には「数値型」と「オブジェクト型」を指定できます。
パッケージ変数名は「$」で始まり、次に「A-Z」、その後ろに、ひとつ以上の「a~z」「A~Z」「0~9」「_」を続けることができます。Perlと異なり、「$」の後ろには、必ず大文字の英数字が必要であることに注意してください。
パッケージ変数定義には、デスクリプタを合わせて指定することができます。複数のデスクリプタを空白を使って並べることができます。
our パッケージ変数名 : デスクリプタ 型名;
パッケージ変数デスクリプタ
パッケージ変数で指定できるデスクリプタの一覧です。
デスクリプタ名 | 役割 |
---|---|
public | このパッケージ変数は、外部のパッケージからアクセスできます。 |
private | このパッケージ変数は、外部のパッケージからアクセスできません。デフォルトの設定です。 |
ro | このパッケージ変数は、読み込み用のパッケージ変数アクセッサを持ちます。パッケージ変数アクセッサ名は、パッケージ変数名から「$」を除いたものです。パッケージ変数名が「$FOO」の場合は、パッケージ変数アクセッサ名は「FOO」になります。 |
wo | このパッケージ変数は、書き込み用のパッケージ変数アクセッサを持ちます。パッケージ変数アクセッサ名は、「SET_パッケージ変数名から$を除いたもの」になります。パッケージ変数名が「$FOO」の場合は、パッケージ変数アクセッサ名は「SET_FOO」になります。 |
rw | このパッケージ変数は、読み込み用のパッケージ変数アクセッサと書き込み用のパッケージ変数アクセッサを持ちます。読み込み用のパッケージ変数アクセッサ名は「ro」で説明したものと同じです。書き込み用のパッケージ変数アクセッサ名は「wo」で説明したものと同じです。 |
「public」と「private」の両方のデスクリプタが指定された場合は、コンパイル時エラーが発生します。
「ro」「wo」「rw」のひとつより多くが同時に指定されている場合は、コンパイル時エラーが発生します。
パッケージ変数アクセッサとは、パッケージ変数にアクセスするためのクラスメソッドのことです。
書き込み用のパッケージ変数アクセッサの戻り値は「void型」です。
SPVMのソースコードの中からパッケージ変数アクセッサが呼び出された場合は、パッケージ変数アクセッサはインライン展開されます。それ以外の場合は、インライン展開されません。
パッケージ変数定義のサンプル
パッケージ変数定義のサンプルです。
package Foo { our $NUM1 : byte; our $NUM2 : short; our $NUM3 : int; our $NUM4 : long; our $NUM5 : float; our $NUM6 : double; our $NUM_PUBLIC : public int; our $NUM_RO : ro int; our $NUM_WO : wo int; our $NUM_RW : rw int; }
パッケージ変数は、コンパイルが終了して、実行時に入る前に、型の初期値で初期化されます。
この初期値は、BEGINブロックを使うことで、変更することができます。
package Foo { our $VAR : int; BEGIN { $VAR = 3; } }
パッケージ変数の値を取得するには以下の構文を使用します。
$クラス名::パッケージ変数名
自分自身のパッケージに属するパッケージ変数の場合は「クラス名::」を省略できます。
$パッケージ変数名
パッケージ変数の値の取得は、式を返します。
定義されていないパッケージ変数の値を取得しようとした場合は、コンパイル時エラーが発生します。
パッケージの外側からプライベートなパッケージ変数にアクセスしようとした場合は、コンパイル時エラーが発生します。
パッケージ変数の値の取得のサンプル
package Foo { our $VAR : int; sub bar : int () { my $var1 = $Foo::VAR; my $var2 = $VAR; } }
パッケージ変数の値を設定するには以下の構文を使用します。
$クラス名::パッケージ変数名 = 右式自分自身のパッケージに属するパッケージ変数の場合は「クラス名::」を省略できます。
$パッケージ変数名 = 右式
パッケージ変数設定は、式を返します。返される結果は、代入後のパッケージ変数の値です。
定義されていないパッケージ変数の値を設定しようとした場合は、コンパイル時エラーが設定します。
パッケージの外側からプライベートなパッケージ変数にアクセスしようとした場合は、コンパイル時エラーが発生します。
パッケージ変数の値の取得のサンプル
package Foo { our $VAR : int; sub bar : int () { $Foo::VAR = 1; $VAR = 3; } }
フィールドとは「new」を使ってオブジェクト生成した場合に、オブジェクトからアクセスできるデータ領域のことです。
「has」キーワードを使用してフィールドを定義することができます。
has フィールド名 : 型名;
フィールド定義は「パッケージ定義」の直下で行う必要があります。
フィールド定義には「型名」が必要です。型名には「数値型」と「オブジェクト型」を指定できます。
フィールド名は、1文字以上の「a~z」「A~Z」「0~9」「_」で構成する必要があります。先頭は、数字から始めることはできません。連続した「_」を使用することはできません。
フィールド名には、キーワードと同じ名前を使用することができます。
フィールド定義には、デスクリプタを合わせて指定することができます。複数のデスクリプタを空白を使って並べることができます。
has フィールド名 : デスクリプタ 型名;
フィールドデスクリプタ
フィールドで指定できるデスクリプタの一覧です。
デスクリプタ名 | 役割 |
---|---|
public | このフィールドは、外部のパッケージからアクセスできます。 |
private | このフィールドは、外部のパッケージからアクセスできません。デフォルトの設定です。 |
ro | このフィールドは、読み込み用のフィールドアクセッサを持ちます。フィールドアクセッサ名は、フィールド名と同じです。フィールド名が「foo」の場合は、フィールドアクセッサ名は「foo」になります。 |
wo | このフィールドは、書き込み用のフィールドアクセッサを持ちます。フィールドアクセッサ名は、「set_フィールド名」になります。フィールド名が「foo」の場合は、フィールドアクセッサ名は「set_foo」になります。 |
rw | このフィールドは、読み込み用のフィールドアクセッサと書き込み用のフィールドアクセッサを持ちます。読み込み用のフィールドアクセッサ名は「ro」で説明したものと同じです。書き込み用のフィールドアクセッサ名は「wo」で説明したものと同じです。 |
「public」と「private」の両方のデスクリプタが指定された場合は、コンパイル時エラーが発生します。
「ro」「wo」「rw」のひとつより多くが同時に指定されている場合は、コンパイル時エラーが発生します。
フィールドアクセッサとは、フィールドにアクセスするためのメソッドのことです。
書き込み用のフィールドアクセッサの戻り値は「void型」です。
SPVMのソースコードの中からフィールドアクセッサが呼び出された場合は、フィールドアクセッサはインライン展開されます。それ以外の場合は、インライン展開されません。
フィールド定義のサンプル
フィールド定義のサンプルです。
package Foo { has num1 : byte; has num2 : short; has num3 : int; has num4 : long; has num5 : float; has num6 : double; has num_public : public int; has num_ro : ro int; has num_wo : wo int; has num_rw : rw int; }
不正なフィールドの定義は、コンパイル時エラーが発生します。
フィールドの取得の構文は以下です。
インボーカー式->{フィールド名}
フィールドの設定の構文は以下です。
インボーカー式->{フィールド名} = 右式;
インボーカー式は、クラス型あるいは値型あるいは値のリファレンス型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
フィールド名がインボーカー式のパッケージに存在しない場合は、
「sub」キーワードを使用してサブルーチンを定義することができます。
sub サブルーチン名 : 戻り値の型名 (引数名1 : 引数の型名1, 引数名2 : 引数の型名2, 以下続く) { }
サブルーチンの定義は「パッケージ定義」の直下で行う必要があります。
サブルーチン名は、1文字以上の「a~z」「A~Z」「0~9」「_」で構成する必要があります。先頭は、数字から始めることはできません。連続した「_」を使用することはできません。
サブルーチン名には、キーワードと同じ名前を使用することができます。
戻りの型名には、「void型」「数値型」「オブジェクト型」を指定することができます。
サブルーチンの定義には「戻り値の型名」と「0個以上の引数の定義」が必要です。「引数の定義」は「引数名」と「引数の型名」からなります。
引数名は、変数名である必要があります。
引数の型名には、「数値型」「オブジェクト型」「リファレンス型」を指定することができます。
サブルーチンのブロックの中には、0個以上のステートメントを記述できます。
サブルーチンの定義には、デスクリプタを合わせて指定することができます。複数のデスクリプタを空白を使って並べることができます。
デスクリプタ名 sub サブルーチン名 : 戻り値の型名 (引数の変数名1 : 引数の型名1, 引数の変数名2 : 引数の型名2, 以下続く) { }
サブルーチンデスクリプタ
サブルーチンで指定できるデスクリプタの一覧です。
デスクリプタ名 | 役割 |
---|---|
native | このサブルーチンは、ネイティブサブルーチンです。 |
precompile | このサブルーチンはプリコンパイルされます。 |
「native」と「precompile」の両方のデスクリプタが指定された場合は、コンパイル時エラーが発生します。
引数の型名の後ろに「...」を続けると、可変長引数となります。最後の引数のみ可変長引数にすることができます。
# 可変長引数の定義 sub サブルーチン名 : 戻り値の型名 (引数名1 : 引数の型名1, 引数名2 : 引数の型名2...) { }
数値型の戻り値を持つサブルーチンで、戻り値が定数であるサブルーチンを定数サブルーチンといいます。
sub foo : int () { return 5; } sub foo : long () { return 5L; } sub foo : float () { return 5.0f; } sub foo : double () { return 5.0; }
定数サブルーチンは、インライン展開されます。
SPVMは定数畳み込み最適化を行わないので、定数が演算されている場合は、定数サブルーチンにならず、インライン展開されないことに注意してください。
# 定数サブルーチンではなくインライン展開されない sub foo : int () { return 5 + 3; }
無名サブルーチンとは、名前を持たないサブルーチンのことです。無名サブルーチンは文です。
sub : 型名 ($self : self, 引数1, 引数2, ..., 引数n) { }
無名サブルーチンを定義すると、内部的に、パッケージの定義がおこなわれ、そのパッケージを元にしたオブジェクトが生成され、それが式として返されます。
無名サブルーチンは、メソッドである必要があります。
無名サブルーチンのサンプル
my $comparator = sub : int ($self : self, $x1 : object, $x2 : object) { }
メソッドとは、第一引数にself型を持つサブルーチンのことです。
sub サブルーチン名 : 型名 ($self : self, 引数1, 引数2, ..., 引数n) { }
インボカントとは、self型が指定された引数のことをいいます。
シグネチャとは、サブルーチンの戻り値と引数を次の規則で並べたものをいいます。引数は、存在しなくても構いません。間に空白を含むことはできません。
1. 戻り値の型名
2. (
3. 引数1,引数2, 引数3, 引数n
4. )
シグネチャのサンプルです。
# サブルーチン定義 sub foo : int ($num1 : double, $num2 : long[]) # シグネチャ int(double,long[]) # サブルーチン定義 sub foo : void () # シグネチャ void()
列挙はint型の定数を定義したい場合に利用します。連続したint型の定数を簡単に定義できます。「enum」キーワードを使って定義します。
enum { FLAG1, FLAG2, FLAG3 }
列挙の定義は「パッケージ定義」の直下で行う必要があります。
package Foo { enum { FLAG1, FLAG2, FLAG3 } }
最初の値は「0」から始まります。値は「1」づつインクリメントされます。この例の場合は「FLAG1」は「0」、「FALG2」は「1」、「FLAG3は「2」になります。
列挙の末尾の要素の後ろには「,」をつけることができます。
enum { FLAG1, FLAG2, FLAG3, }
列挙はint型を戻り値とする「定数サブルーチン」のエイリアスです。次のサブルーチンの定義と等価です。
sub FLAG1 : int () { return 0; } sub FLAG2 : int () { return 1; } sub FLAG3 : int () { return 2; }
enumの要素には、int型の値を設定することができます。
enum { FLAG1, FLAG2 = 4, FLAG3, }
上記の場合は「FLAG1」は「0」、「FALG2」は「4」、「FLAG3」は「5」になります。
enumの定義が不正な場合は、コンパイル時エラーが発生します。
列挙は、定数サブルーチンのエイリアスなので、サブルーチン呼び出しとまったく同じ方法で呼び出すことができます。
my $flag1 = Foo->FLAG1; my $flag2 = Foo->FLAG2; my $flag3 = Foo->FLAG3;
switch文のcase文において利用することもできます。
switch ($num) { case Foo->FLAG1: last; case Foo->FLAG2: last: case Foo->FLAG3: last: default: }
BEGINブロックとは、コンパイル時が終了し、実行時に入る前に、実行されるブロックのことです。
BEGINキーワードを使用してBEGINブロックを定義することができます。
BEGIN { }
BEGINブロックは、パッケージの定義の直下にある必要があります。
package Foo { BEGIN { } }
BEGINブロックの中には、0個以上の文を書くことができます。
BEGIN { my $foo = 1 + 1; my $bar; }
return文を書くことはできません。BEGINブロックは、引数がなく、戻り値がvoidのサブルーチンとして定義されます。
BEGINブロックは、いくつでも定義できます。
BEGINブロックの実行順序は、保証されません。他のパッケージにBEGINブロックが定義されている場合は、そのBEGINブロックが先に実行されることを想定しないでください。
BEGINブロックの一般的な用途は、パッケージ変数を初期化することです。
package Foo { our $NUM : int; our $POINT : Point; BEGIN { $NUM = 3; $POINT = Point->new; } }
レキシカル変数は「my」キーワードによって宣言します。「:」の後ろに「型」を指定する必要があります。
my $num : int;
レキシカル変数はスコープブロックの中で宣言され、スコープを持つ変数のことです。
{ my $num : int; }
レキシカル変数は、レキシカル変数の初期値によって初期化されます。
型推論によって、レキシカル変数を宣言するときに、型の指定を省略することができます。型推論は、常に代入演算子の右辺の型によって、行われます。
# int my $num = 1; # double my $num = 1.0;
レキシカル変数の宣言は、レキシカル変数自体を表現する式を返します。次のような記述が可能です。
my $ppp = my $bar = 4; if (my $bar = 1) { } while (my $bar = 1) { }
レキシカル変数の値の取得をするには、それ自体を記述します。
レキシカル変数
レキシカル変数の値の取得は、式を返します。
キシカル変数の値の取得のサンプル
$var; my $foo = $bar;
レキシカル変数の値の設定については、代入演算子を見てください。
$var = 式
「{」と「}」で囲まれた部分のことをブロックと呼びます。
# ブロック { }
ブロックの中には、スコープを作るスコープブロックがあります。
スコープとはスコープブロックに囲まれた範囲のことをいいます。
# スコープブロック { }
スコープの中で宣言されたレキシカル変数は、実行時に、宣言されている位置で、モータルなレキシカル変数として登録されます。
{ # レキシカル変数をモータルとして登録 my $num = new Foo; }
モータルなレキシカル変数に代入されたオブジェクトが、未定義値ではない場合は、リファレンスカウントが1増やされます。
スコープの終わりに到達すると、モータルなレキシカル変数に代入されているオブジェクトは、未定義値でない場合、リファレンスカウントが1減らされ、0になった場合は、解放されます。
スコープブロックとは、スコープを作るブロックのことです。スコープブロックには、0個以上の文を記述することができます。
# スコープブロック { my $num = 1; $num++; }
スコープブロックの一覧
# 単純なブロック { }
# サブルーチンのブロック sub : int () { }
# evalブロック eval { }
# ifブロック if (条件1) { }
#elsifブロック elsif (条件2) { }
# elseブロック else { }
# for ブロック for (my $i = 0; $i < 3; $i++) { }
# whileブロック while (条件) { }
switch (条件) { }
算術演算子の両辺、サブルーチンの引数、ifの条件部などになることができる文法の最小の単位を「項」と呼びます。
「項」には、「式」と、条件部のみに記述できる「条件」があります。
式と条件の違いは、条件は、式として利用できないということだけです。
項は常に、左から右の順番で評価されます。たとえば、以下の例では、3 * 3 が実行され結果は9になります。
my $i = 2; my $j = ($i = 3)* $i;
exceptionサブルーチンが例外を発生させた場合は、$jの値は、10のままです。
my $j = 10; eval { excpetion() / ($j = 3); }; }
式は次の項目で定義されます。
式の一覧
条件は次の項目で定義されます。
条件の一覧
条件の結果の型は、int型です。0以外の値の場合は真、0の場合は偽です。
条件部とは、条件判定が行われる部分のことです。条件部には「式」と「条件」が記述できます。
if文のかっこの中
if (条件部) { }
unless文のかっこの中
unless (条件部) { }
forのかっこの中の二つ目
for (初期化;条件部;次の値;) { }
whileのかっこの中
while (条件部) { }
論理AND演算子の左右
条件部 && 条件部
論理OR演算子の左右
条件部 || 条件部
論理否定演算子の右側
!条件部
条件部に式が書かれた場合は、結果の型はint型です。
式は、数値型あるいはオブジェクト型あるいは未定義型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
条件部に式が書かれた場合は、型に応じた結果を返します。
式が未定義値である場合は、0を返します。
型がint型の場合は、その値を返します。
型がlong型、float型、double型、オブジェクト型である場合は、C99における以下の演算と完全に一致する演算を行います。
!!x
10進数表現
整数リテラルの数値部は「0~9」の1つ以上の連続した文字で表現されます。
先頭に「+」あるいは「-」の符号をつけることができます。
整数リテラルの型は、デフォルトでは「int型」になります。
整数リテラルがint型で表現できる数値の範囲を超えている場合は、コンパイル時エラーが発生します。
末尾に「L」あるいは「l」のサフィックスをつけることで「long型」の整数リテラルを表現できます。
long型の整数リテラルの場合は、long型で表現できる数値の範囲を超えている場合は、コンパイル時エラーが発生します。
区切り文字として「_」を使用することができます。区切り文字は意味を持ちません。
不正な整数リテラルの表現はコンパイル時エラーが発生します。
整数リテラルがbyte型の変数に代入される場合、あるいはbyte型のサブルーチンの引数として渡される場合で、byte型で表現できる数値の範囲を超えていない場合は、数値縮小型変換によって、byte型に自動的に変換されます。範囲を超えている場合は、コンパイル時エラーとなります。
整数リテラルがshort型の変数に代入される場合、あるいはshort型のサブルーチンの引数として渡される場合で、short型で表現できる数値の範囲を超えていない場合は、数値縮小型変換によって、short型に自動的に変換されます。範囲を超えている場合は、コンパイル時エラーとなります。
整数リテラルのサンプルです。
123 +123 -123 123L 123l 123_456_789 -123_456_789L
16進数表現
整数リテラルの数値部は16進数を使って表現することができます。
数値部を16進数を使って表現するときは「0x」から始めます。
その後ろに「0~9」「a~f」「A~F」のひとつ以上の連続した文字が続きます。
不正な16進数表現は、コンパイル時エラーが発生します。
整数リテラルを16進数で表現したサンプルです。
0x3b4f -0x3F1A 0xDeL 0xFFFFFFFF_FFFFFFFF
8進数表現
整数リテラルの数値部は8進数を使って表現することができます。
数値部を8進数を使って表現するときは「0」から始めます。
その後ろに「0~7」のひとつ以上の連続した文字が続きます。
不正な8進数表現は、コンパイル時エラーが発生します。
整数リテラルを8進数で表現したサンプルです。
0755 -0644 0666L 0655_755
2進数表現
整数リテラルの数値部は2進数を使って表現することができます。
数値部を2進数を使って表現するときは「0b」から始めます。
その後ろに「0」か「1」のひとつ以上の連続した文字が続きます。
不正な2進数表現は、コンパイル時エラーが発生します。
整数リテラルを2進数で表現したサンプルです。
0b0101 -0b1010 0b110000L 0b10101010_10101010
浮動小数点リテラルは「符号部」「数値部」「指数部」「サフィックス」から構成されます。
浮動小数点リテラルには「10進数浮動小数点リテラル」と「16進数浮動小数点リテラル」があります。
「符号部」は「+」か「-」で表現されます。「符号部」の存在は、任意です。「符号部」が存在する場合は、先頭にある必要があります。
「10進数浮動小数点リテラル」は、数値部が「一桁以上の10進数字」で始まる必要があります。
「10進数字」とは「0~9」のことです。
「10進数浮動小数点リテラル」は、数値部に「小数点」が含まれているか、含まれていない場合は「指数部」あるいは「サフィックス」が必要です。
「小数点」とは「.」のことです。
「16進数浮動小数点リテラル」は、数値部が「0x」あるいは「0X」で始まり、その後ろに「一桁以上の16進数字」が続く必要があります。
16進数字とは「0~9」「a~f」「A~F」のことです。
「16進数浮動小数点リテラル」は、「数値部」に「小数点」を含むことができます。
「数値部」はアンダーライン「_」を含むことができます。これは単なる区切り文字で、無視されます。
「16進数浮動小数点リテラル」は、「指数部」が必要です。
「指数部」は「指数表現」と「符号付10進整数」で構成されます。
「指数表現」は「10進数浮動小数点リテラル」の場合は「e」あるいは「E」、「16進数浮動小数点リテラル」の場合は「p」あるいは「P」になります。
「指数部」の意味は「10進数浮動小数点リテラル」の場合は、10進数による桁移動、「16進数浮動小数点リテラル」の場合は、2進数による桁移動になります。
末尾に「f」あるいは「F」のサフィックスをつけると、浮動小数点リテラルの型は「float型」になります。
末尾に「d」あるいは「D」のサフィックスをつけると、浮動小数点リテラルの型は「double型」になります。
サフィックスが省略された場合は、浮動小数点リテラルの型は「double型」になります。
浮動小数点リテラルが「float型」の場合はC標準の「strtof関数」を使って、文字列からfloat型への変換が行われます。変換が失敗した場合は、コンパイル時エラーが発生します。
浮動小数点リテラルが「double型」の場合はC標準の「strtod関数」を使って、文字列からdouble型への変換が行われます。変換が失敗した場合は、コンパイル時エラーが発生します。
無限大を表現する浮動小数点リテラルはありません。標準関数である「INFINITY関数」「INFINITYF関数」を使用してください。
非値を表現する浮動小数点リテラルはありません。標準関数である「NAN関数」「NANF関数」を使用してください。
浮動小数点リテラルのサンプルです
1.32 -1.32 1.32f 1.32F 1.32e3 1.32e-3 1.32E+3 1.32E-3 0x3d3d.edp0 0x3d3d.edp3 0x3d3d.edP3 0x3d3d.edP-3f
文字リテラルは、シングルクォート「'」で囲まれます。
文字リテラルの内容は「ひとつのAsciiの印字可能文字」あるいは「ひとつのエスケープ文字」です。
文字リテラルの型は「byte型」になります。
不正な文字リテラルの場合は、コンパイル時エラーが発生します。
エスケープ文字
エスケープ文字 | 説明 |
---|---|
\0 | Asciiコードの0「NUL」 |
\a | Asciiコードの7「BEL」 |
\b | Asciiコードの8「BS」 |
\t | Asciiコードの9「HT」 |
\n | Asciiコードの10「LF」 |
\f | Asciiコードの12「FF」 |
\r | Asciiコードの13「CR」 |
\" | Asciiコードの34「"」 |
\' | Asciiコードの39「'」 |
\\ | Asciiコードの92「\」 |
\xの後ろに二桁の16進数 | 直接Asciiコードを指定します。16進数は「0~9」「a~f」「A~F」で表現します。 |
文字リテラルのサンプル
文字リテラルのサンプルです。
# 文字リテラル 'a' 'x' # エスケープ文字を使った文字リテラル '\a' '\b' '\t' '\n' '\f' '\r' '\"' '\'' '\\' '\x0D' '\x0A'
文字列リテラルは、ダブルクォート「"」で囲まれます。
文字列リテラルの内容は「0個以上のAsciiの印字可能文字あるいはエスケープ文字」です。
文字列リテラルの型は「string型」になります。
不正な文字列リテラルの場合は、コンパイル時エラーが発生します。
エスケープ文字
エスケープ文字 | 説明 |
---|---|
\0 | Asciiコードの0「NUL」 |
\a | Asciiコードの7「BEL」 |
\b | Asciiコードの8「BS」 |
\t | Asciiコードの9「HT」 |
\n | Asciiコードの10「LF」 |
\f | Asciiコードの12「FF」 |
\r | Asciiコードの13「CR」 |
\" | Asciiコードの34「"」 |
\' | Asciiコードの39「'」 |
\\ | Asciiコードの92「\」 |
\xの後ろに二桁の16進数 | 直接Asciiコードを指定します。16進数は「0~9」「a~f」「A~F」で表現します。 |
\N{}の中の複数桁の16進数 | Unicodeのコードポイントを16進数で指定します。UTF-8に変換されます。16進数は「0~9」「a~f」「A~F」で表現します。 |
Unicodeのコードポイントを指定するエスケープ文字以外は、文字リテラルのエスケープ文字と共通です。
文字列リテラルのサンプル
文字列リテラルのサンプルです。
# 文字列リテラル "abc" "あいう" # エスケープ文字を使った文字列リテラル "abc\tdef\n" "\x0D\x0A" "\N{U+3042}\N{U+3044}\N{U+3046}"
SPVMの文字列のデータ表現は、byte型の配列です。特別な内部表現を持たない、単なるバイト列です。
my $string = new byte[3]; $string->[0] = 'a'; $string->[1] = 'b'; $string->[2] = 'c';
byte型の配列は、文字列型に代入できます。文字列型は、コンパイル時には要素を変更できない型ですが、実行時はbyte型の配列になります。
my $string_const : string = $string;
文字列リテラルを代入することによって、文字列を作成できます。文字列リテラルを元にしたstring型の新しい文字列を返します。
my $string_const = "abc";
byte[]はC言語の「char*」、文字列型はC言語の「const char*」に該当するように設計されています。
未定義は「undef」で表現されます。
undef
未定義値は、任意のオブジェクト型の変数に代入することができます。
未定義値はオブジェクト型の値と「==」「!=」演算子を使用して、比較することができます。未定義値は、生成されたオブジェクトと等しくない事が保証されます。
未定義は条件部で使われた場合は、偽になります。
未定義値は、エクステンションにおいてC言語の値として利用された場合は、0と等しくなることが保証されます。
演算子は、単項演算子、二項演算子、インクリメント演算子、デクリメント演算子、比較演算子、論理演算子、代入演算子からなります。
単項演算子とは、項の前に置かれる演算子のことをいいます。
単項演算子 項
単項演算子には、単項プラス演算子、単項マイナス演算子、ビット否定演算子、論理否定演算子があります。
インクリメント演算子とデクリメント演算子は、単項演算子には含まれません。
二項演算子とは、左項と右項の間に置かれる演算子のことをいいます。項については、項を参考にしてください。
左項 二項演算子 右項
二項演算子には、加算演算子、減算演算子、乗算演算子、除算演算子、剰余演算子、ビットAND演算子、ビットOR演算子、論理AND演算子、論理OR演算子、シフト演算子、文字列連結演算子があります。
算術演算子は、算術を行う演算子のことで、加算演算子、減算演算子、乗算演算子、除算演算子、剰余演算子、単項プラス演算子、単項マイナス演算子、インクリメント演算子、デクリメント演算子からなります。
単項プラス演算子は「+」で表現される単項演算子です。
+式
式は数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
その後、単項プラス演算子は、与えられた値をコピーして返します。
単項プラス演算子は式を返します。
単項プラス演算子の結果の型は、単項数値拡大型変換された型です。
単項プラス演算子は例外を発生させません。
単項プラス演算子のサンプル
my $num = +10;
単項マイナス演算子は「-」で表現される単項演算子です。
-式
式は数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
その後、単項マイナス演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型における演算が定義されます。
-x
単項マイナス演算子は式を返します。
単項マイナス演算子の結果の型は、単項数値拡大型変換された型です。
単項マイナス演算子は例外を発生させません。
単項マイナス演算子のサンプル
my $num = -10;
加算演算子は「+」で表現される二項演算子です。
左式 + 右式
左式と右式は、数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、加算演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型における演算が定義されます。
x + y;
加算演算子は式を返します。
加算演算子の結果の型は、二項数値拡大型変換された型です。
加算演算子は、例外を発生させません。
減算演算子は「-」で表現される二項演算子です。
左式 - 右式
左式と右式は、数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、減算演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型における演算が定義されます。
x - y;
減算演算子は式を返します。
減算演算子の結果の型は、二項数値拡大型変換された型です。
減算演算子は、例外を発生させません。
乗算演算子は「*」で表現される二項演算子です。
左式 * 右式
左式と右式は、数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、乗算演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型における演算が定義されます。
x * y;
乗算演算子は式を返します。
乗算演算子の結果の型は、二項数値拡大型変換された型です。
乗算演算子は、例外を発生させません。
除算演算子は「/」で表現される二項演算子です。
左式 / 右式
左式と右式は、数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、除算演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型における演算が定義されます。
x / y;
除算演算子は式を返します。
除算演算子の結果の型は、二項数値拡大型変換された型です。
整数型に対する演算の場合には、除算演算子は、右辺が0であった場合に、例外が発生します。
浮動小数点型に対する演算の場合には、除算演算子は、例外を発生させません。
剰余演算子は「%」で表現される二項演算子です。
左式 % 右式
左式と右式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、剰余演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型における演算が定義されます。
x % y;
剰余演算子は式を返します。
剰余演算子の結果の型は、二項数値拡大型変換された型です。
剰余演算子は、右辺が0であった場合に、例外が発生します。
ビット演算子は、ビット演算を行う演算子のことで、>ビットAND演算子、ビットOR演算子、ビット否定演算子からなります。
ビットANDは「&」で表現される二項演算子です。
左式 & 右式
左式と右式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、ビットAND演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型における演算が定義されます。
x & y;
ビットAND演算子は式を返します。
ビットAND演算子の結果の型は、二項数値拡大型変換された型です。
ビットAND演算子は、例外を発生させません。
ビットORは「|」で表現される二項演算子です。
左式 | 右式
左式と右式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左式と右式に対して、二項数値拡大型変換が行われます。
その後、ビットOR演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型における演算が定義されます。
x | y;
ビットOR演算子は式を返します。
ビットOR演算子の結果の型は、二項数値拡大型変換された型です。
ビットOR演算子は、例外を発生させません。
ビット否定演算子は「~」で表現される単項演算子です。
~式
式は整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
その後、ビット否定演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型における演算が定義されます。
~x
ビット否定演算子は式を返します。
ビット否定演算子の結果の型は、単項数値拡大型変換された型です。
ビット否定演算子は例外を発生させません。
ビット否定演算子のサンプル
my $num = ~0xFF0A;
シフト演算子は、ビットシフトを行う演算子で、左シフト演算子、算術右シフト演算子、論理右シフト演算子からなります。
左シフトは「<<」で表現される二項演算子です。
左式 << 右式
左式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
右式は、int型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
左シフト演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型における演算が定義されます。
x << y;
左シフト演算子は式を返します。
左シフト演算子は、例外を発生させません。
算術右シフトは「<<」で表現される二項演算子です。
左式 >> 右式
左式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
右式は、int型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
算術右シフト演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。xに対して、C99との型の対応に応じた、int型、long型における演算が定義されます。
x >> y;
算術右シフト演算子は式を返します。
算術右シフト演算子は、例外を発生させません。
論理右シフトは「>>>」で表現される二項演算子です。
左式 >>> 右式
左式は、整数型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
右式は、int型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
論理右シフト演算子の演算結果は、C99における以下の演算と完全に一致する演算を行います。xに対して、C99との型の対応に応じた、int型、long型における演算が定義されます。
(符号付整数型キャスト)((符号なし整数型キャスト)x >> y);
論理右シフト演算子は式を返します。
論理右シフト演算子は、例外を発生させません。
比較演算子とは、左式と右式の間に置かれる演算子で、条件を返す演算子のことをいいます。
左式 比較演算子 右式
比較演算子には、数値比較演算子、文字列比較演算子、isa演算子があります。
数値比較演算子とは、数値あるいはオブジェクトのアドレスを比較するために、左式と右式の間に置かれる演算子で、条件を返す演算子のことをいいます。
左式 数値比較演算子 右式
数値比較演算子の一覧です。
演算子 | 比較可能な型 | 解説 |
---|---|---|
左式 == 右式 | 左式と右式が数値型、左式と右式がオブジェクト型(未定義値を含む) | 左式と右式が等しい |
左式 != 右式 | 左式と右式が数値型、左式と右式がオブジェクト型(未定義値を含む) | 左式と右式が等しくない |
左式 > 右式 | 左式と右式が数値型 | 左式は右式より大きい |
左式 >= 右式 | 左式と右式が数値型 | 左式は右式より大きいまたは等しい |
左式 < 右式 | 左式と右式が数値型 | 左式は右式より小さい |
左式 <= 右式 | 左式と右式が数値型 | 左式は右式より小さいまたは等しい |
左辺と右辺の型は、比較可能な型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
数値型の比較の場合は、左式と右式に対して、二項数値拡大型変換が行われます。
その後、数値比較演算子は、C99における以下の演算と完全に一致する演算を行います。C99との型の対応に応じた、int型、long型、float型、double型、オブジェクト型における演算が定義されます。
# 数値型の比較、オブジェクト型の比較 x == y; x != y; # 数値型の比較 x > y; x >= y; x < y; x <= y;
数値比較演算子の結果の型は、int型です。真である場合は0以外の値を、偽である場合は0を返します。
数値比較演算子は、例外を発生させません。
文字列比較演算子とは、文字列を比較するために、左式と右式の間に置かれる演算子で、条件を返す演算子のことをいいます。
左式 文字列比較演算子 右式
左式と右式は、文字列互換型である必要があります。
文字列比較演算子の一覧です。
演算子 | 解説 |
---|---|
左式 eq 右式 | 左式と右式が等しい |
左式 ne 右式 | 左式と右式が等しくない |
左式 gt 右式 | 左式は右式より辞書式順序で比較して大きい |
左式 ge 右式 | 左式は右式より辞書式順序で比較して大きいまたは等しい |
左式 lt 右式 | 左式は右式より辞書式順序で比較して小さい |
左式 le 右式 | 左式は右式より辞書式順序で比較して小さいまたは等しい |
文字列比較演算子の結果の型は、int型です。真である場合は0以外の値を、偽である場合は0を返します。
数値比較演算子とは、数値あるいはオブジェクトのアドレスを比較するために、左式と右式の間に置かれる演算子で、条件を返す演算子のことをいいます。
isa演算子とはは、型の適合性をチェックするための演算子で、条件を返します。
左式 isa 右型
isa演算子は、右型に応じて、3種類の動作をします。
1. 右型が、数値型、値型、汎用オブジェクト型、リファレンス型の場合は、コンパイル時に左式の型が右型と同一のものであるかをチェックします。同一であった場合は真を、そうでない場合は偽を返します。
2. 右型が、クラス型であった場合は、実行時に左式の型が、クラス型と一致するかをチェックします。一致した場合は真を、そうでない場合は偽を返します。左式の型は、オブジェクト型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
3. 右型が、インターフェース型であった場合は、実行時に左式の型がクラス型であり、そのクラスがインターフェースを満たすかどうかをチェックします。満たした場合は真を、そうでない場合は偽を返します。左式の型は、オブジェクト型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
論理演算子は、論理演算を行う演算子のことで、>論理AND演算子、論理OR演算子、論理否定演算子からなります。
論理演算子は、条件を返します。
論理AND演算子とは、論理AND演算を行うために、左項と右項の間に置かれる演算子で、「&&」で表現される条件を返す二項演算子です。項については、項を参考にしてください。
左項 && 右項
左項と右項は条件部です。
論理AND演算子は次のように動作します。
1. 左項の評価が真であれば、右項を実行し、右項の評価が、真であれば、全体として真を返し、右項の評価が偽であれば、全体として偽を返します。
2. 左項の評価が偽であれば、全体として偽を返します。右項は実行されません。
論理AND演算子の結果の型は、int型です。
論理AND演算子は、例外を発生させません。
論理OR演算子とは、論理OR演算を行うために、左項と右項の間に置かれる演算子で、「||」で表現される条件を返す二項演算子です。項については、項を参考にしてください。
左項 || 右項
左項と右項は条件部です。
論理OR演算子は次のように動作します。
1. 左項の評価が真であれば、全体として、真を返します。右項は実行されません。
2. 左項の評価が偽であれば、右項を実行し、右項の評価が、真であれば、全体として真を返し、右項の評価が偽であれば、全体として偽を返します。
論理OR演算子の結果の型は、int型です。
論理OR演算子は、例外を発生させません。
論理NOT演算子とは、論理NOT演算を行うために、項の左に置かれる演算子で、「!」で表現される条件を返す単項演算子です。項については、項を参考にしてください。
!項
項は条件部です。
論理NOT演算子は、項が真であれば、偽を返します。項が偽であれば、真を返します。
論理NOT演算子の結果の型は、int型です。
論理NOT演算子は、例外を発生させません。
文字列連結演算子は「.」で表現される二項演算子です。
左式 . 右式
左式あるいは右式が、数値型であった場合は、数値から文字列への型変換によって文字列に変換されます。
左式と右式はどちらも文字列互換型でなければなりません。そうでない場合は、コンパイルエラーになります。
文字列連結演算子は、左式と右式で表現される文字列を連結し、新しい文字列を返します。
左式と右式の両方が、文字列リテラルであった場合は、コンパイル時に連結された文字列リテラルが生成されます。パフォーマンスのコストを意識せずに、文字列連結演算子で、文字列リテラルを連結できます。
実行時に、左式あるいは右式が未定義値だった場合は、例外が発生します。
文字列連結演算子のサンプル
my $str = "abc" . "def"; my $str = "def" . 34; my $str = 123 . 456;
演算子の優先順位は、以下の通りです。下にいくほど、優先順位が高くなります。
結合方向 | 演算子 |
---|---|
右結合 |
加算代入演算子「+=」 減算代入演算子「-=」 乗算代入演算子「*=」 除算代入演算子「/=」 剰余代入演算子「%=」 ビットAND代入演算子「&=」 ビットOR代入演算子「|=」 ビット排他OR代入演算子「^=」 左シフト代入演算子「<<=」 符号付右シフト代入演算子「>>=」 符号なし右シフト演算子「>>>=」 文字列結合代入演算子「.=」 |
左結合 | 条件OR演算子「||」 |
左結合 | 条件AND演算子 「&&」 |
左結合 |
ビットOR演算子「|」 ビットXOR演算子 「^」 |
左結合 | ビットAND演算子「&」 |
非結合 |
数値等価演算子「==」 数値非等価演算子「!=」 文字列等価演算子「eq」 文字列非等価演算子「ne」 |
非結合 |
数値大なり演算子「>」 数値小なり演算子「<」 数値大なり等価演算子「>=」 数値小なり等価演算子「<=」 文字列大なり演算子「gt」 文字列大なり等価演算子「ge」 文字列小なり演算子「lt」 文字列小なり等価演算子「le」 isa演算子「isa」 |
非結合 |
スカラ演算子「scalar」 文字列長取得演算子「length」 require演算子「require」 |
左結合 |
左シフト演算子 「<<」 符号付き右シフト演算子「>>」 符号なし右シフト演算子「>>>」 |
左結合 |
加算演算子「+」 減算演算子「-」 文字列連結演算子「.」 |
左結合 |
乗算演算子「*」 除算演算子「/」 剰余演算子「%」 |
右結合 |
条件NOT演算子「!」 ビットNOT演算子「~」 リファレンス演算子「\」 プラス演算子「+」 マイナス演算子「-」 配列長取得演算子「@」 デリファレンス演算子「$」 型キャスト「(型名)」 |
非結合 |
前置インクリメント演算子「++」 後置インクリメント演算子「++」 前置デクリメント演算子「--」 後置デクリメント演算子「--」 |
右結合 |
new演算子「new」 |
左結合 |
アロー演算子「->」 |
左結合 |
左ブラケット「[」 左ブレース「{」 左括弧「(」 |
演算子の優先順位は「()」を使うことによって、最優先にすることができます。
# a * b が先 a * b + c # b + c が先 a * (b + c)
オブジェクトを生成するには、newキーワードと以下の構文をを使用します。
my $object = new パッケージ名;
指定されたパッケージは、クラス型でなければなりません。そうでない場合は、コンパイル時エラーが発生します。
フィールドは、すべて型の初期値で初期化されます。
オブジェクトの生成は、式を返します。
オブジェクトの生成のサンプル
my $object = new Foo;
配列を作成するには、newキーワードと以下の構文をを使用します。
my $object = new 型[要素数式];
型には、数値型、オブジェクト型、値型が指定できます。それ以外の型を指定した場合は、コンパイル時エラーが発生します。
要素数式は、int型以下の数値型である必要があります。そうでない場合は、コンパイル時エラーが発生します。
要素数式に対して、単項数値拡大型変換が行われます。
要素数式で指定された長さの配列が生成されます。
配列の要素は、すべて型の初期値で初期化されます。
配列において、要素は、メモリ上に連続していることが保証されます。
配列の生成は、式を返します。
配列の生成のサンプル
my $nums = new int[3]; my $objects = new Foo[3]; my $objects = new object[3]; my $values = new Complex_2d[3]
配列型自体も、オブジェクト型ですので、上記の構文を使用して、多次元配列を作成することができます。
my $nums = new int[][3]; my $nums = new int[][][3];
SPVMには、配列の生成を簡単にするための配列の初期化の構文があります。式はなくてもかまいません。
[] [式1, 式2, 式3]
配列の初期化は、式の要素数の長さを持った配列を返します。
配列の型は、式1の型を配列型にしたものです。要素が指定されない場合は、汎用オブジェクト型を配列型にしたものになります。
式2以降が、型の互換性を満たさない場合は、コンパイルエラーになります。
文とは、ひとつの処理のことで「スコープブロック」の中に記述することができます。
文の一覧です。
return文を使うと、サブルーチンから脱出します。モータル変数に代入されているオブジェクトは、自動的に解放されます。
return;
戻り値がある場合は、式を指定することができます。
return 式;
サブルーチンの定義において戻り値の型が「void型」である場合は、式が存在してはいけません。そうでない場合は、コンパイル時エラーが発生します。
サブルーチンの定義において戻り値の型が「void型」以外の場合は、式の型と一致していなければなりません。そうでない場合は、コンパイル時エラーが発生します。
「next文」によって「forブロック」または「whileブロック」を脱出して、次のループの先頭に移動することができます。
next;
next文のサンプル
# for for (my $i = 0; $i < 3; $i++) { if ($i == 1) { next; } } # while my $i = 0; while ($i < 3;) { if ($i == 1) { next; } $i++; }
「last文」によって「forブロック」または「whileブロック」または「switchブロック」を脱出することができます。
last;
last文のサンプル
# for for (my $i = 0; $i < 3; $i++) { if ($i == 1) { last; } } # while my $i = 0; while ($i < 3;) { if ($i == 1) { next; } $i++; } # switch my $num = 3; switch ($num) { case 1 : # 処理1 last; case 2 : # 処理2 last; case 3 : # 処理3 last; default: # デフォルト }
式文は「式」と「;」で構成される文のことです。
式;
式文のサンプルです。
1; $var; 1 + 2; foo(); my $num = 1 + 2;
空文は「;」だけで終わる文のことです。
;
SPVMは、静的型言語です。すべてのデータは静的な型を持ちます。
レキシカル変数の宣言、フィールドの定義、パッケージ変数の定義、サブルーチンの定義の引数と戻り値において、型が指定される必要があります。
レキシカル変数の宣言においては、型推論を利用して、暗黙的に型を指定することもできます。
レキシカル変数の初期値、パッケージ変数の初期値、オブジェクトの生成におけるフィールドの初期値は、型の初期値によって決まります。
型の初期化値の一覧です。データにおけるすべてのビット列は0に設定されます。
型名 | 初期値 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0 |
double | 0 |
オブジェクト型 | undef |
値型 | すべてのフィールドが0 |
SPVMの整数型は以下の4つです。
型名 | 説明 |
---|---|
byte | 8bit符号付整数型 |
short | 16bit符号付整数型 |
int | 32bit符号付整数型 |
long | 64bit符号付整数型 |
SPVMの整数型には、符号なし整数型は存在しません。
整数の計算規則については、算術演算子を参考にしてください。
SPVMの浮動小数点型は以下の2つです。
型名 | 説明 |
---|---|
float | 単精度浮動小数点型 - 32bitで浮動小数点を表現します |
double | 倍精度浮動小数点型 - 64bitで浮動小数点を表現します |
浮動小数点の計算規則については、算術演算子を参考にしてください。
パッケージ型とは「パッケージの定義」によって定義される型のことをいいます。
package Foo { }
パッケージ型は「クラス型」「インターフェース型」「値型」からなります。
# クラス型 package Foo { } # インターフェース型 package Foo : interface_t { } # 値型 package Foo : value_t { }
ポインタ型は、クラス型でもあるので、ポインタ型もパッケージ型になります。
# ポインタ型 package Foo : pointer_t { }
オブジェクト型とは「クラス型」「インターフェース型」「配列型」「文字列型」「汎用オブジェクト型」を合わせたものをいいます。「値型」「リファレンス型」は含みません。
オブジェクト型の値は「汎用オブジェクト型」に代入できます。
my $object : object = new Foo; my $object : object = new Foo[]; my $object : object = "abc";
未定義型とは、未定義値が持っている型のことです。明示的に利用することはできません。
未定義型の値は未定義値のみです。
未定義型の値は、オブジェクト型に代入できます。他の型に代入した場合は、コンパイル時エラーが発生します。
クラス型とは「パッケージの定義」によって定義される型で「値型」「インターフェース型」ではない型のことをいいます。
packag Foo { }
クラス型はnew演算子によって、オブジェクトを生成することができます。
my $foo = new Foo;
クラス型はオブジェクト型です。
クラス型はパッケージ型です。
「ポインタ型はクラス型です。
ポインタ型とは「パッケージの定義」において「pointer_t デスクリプタ」が指定されたものをいいます。
package Foo : pointer_t { }
ポインタ型は、クラス型の一種です。
ポインタ型のデータには、C言語のポインタを保存することができます。
ポインタ型には、フィールドを定義することはできません。定義されていた場合は、コンパイル時エラーが発生します。
インターフェース型とは「パッケージの定義」において「interface_t デスクリプタ」が指定されたものをいいます。
package SPVM::Comparator : interface_t { sub compare : int ($self : self, $x1 : object, $x2 : object); }
インターフェスは、C言語における関数ポインタに該当する機能を提供するために設計されました。
インターフェース型は、サブルーチンの定義を一つだけ持ちます。サブルーチンは、メソッドである必要があります。
インターフェース型は「フィールドの定義」「パッケージ変数の定義」を持つことはできません。
インターフェース型の値を、new演算子によって実体化することはできません。
インターフェース型は「パッケージ型」です。
インターフェース型は「オブジェクト型」です。
インターフェース型には、インターフェースを満たしたクラス型のオブジェクトを代入できます。この場合、クラスはインターフェースに適合するといいます。クラスがインターフェースを満たすのは次の二つの場合です。
1. インターフェース型として定義されたサブルーチンと同一の名前とシグネチャを持つクラス型のオブジェクトは、インターフェースに適合します。
# インターフェース型の定義 package SPVM::Comparator : interface_t { sub compare : int ($self : self, $x1 : object, $x2 : object); } # クラスの定義 package SomeComparator { sub compare : int ($self : self, $x1 : object, $x2 : object) { } sub foo : int () { } } # インターフェース型への代入 my $comparator : comparator = new SomeComparator;
2. インターフェース型として定義されたサブルーチンと同一のシグネチャを持つ無名サブルーチンは、インターフェースに適合します。
# インターフェース型の定義 package SPVM::Comparator : interface_t { sub compare : int ($self : self, $x1 : object, $x2 : object); } # インターフェース型への代入 my $comparator : comparator = sub : int ($self : self, $x1 : object, $x2 : object) { }
汎用オブジェクト型は「object」で表現します。C言語の「void*」型を表現するために設計されました。
my $object : object;
汎用オブジェクト型には「オブジェクト型」の値を代入できます。
my $object : object = new Foo; my $object : object = "abc"; my $object : object = new Foo[3];
self型とは、自身の属するパッケージ型を表現し、引数がインボカントであることを示します。
サブルーチンの定義において第一引数の型としてのみ利用できます。
void型とは、サブルーチンの定義において戻り値の型としてだけ利用できる、存在しないことを示す特別な型です。
次元を持たない型を基本型と呼びます。数値型、パッケージ型、汎用オブジェクト型、文字列型は、基本型です。
配列型は、連続した複数のデータ領域を表現します。基本型は、配列にすることができます。
int[] double[] Point[] object[] string[]
配列は次元を持ち最大255次元まで表現できます。
# 二次元 int[][] # 三次元 int[][][]
配列型は、オブジェクト型です。
配列を作成するには、new演算子を使用します。以下の例では、要素数が3のint型の配列を作成しています。
my $nums = new int[3];
多次元配列を作成するときも、new演算子を使用します。以下の例では、要素数が3のint[]型の配列を作成しています。
my $nums = new int[][3];
SPVMにおいては、byte配列型は文字列互換型であるという点において特別な型です。
byte[]
文字列型は、文字列を表現する型です。stringによって表現します。C言語の「const char*」を表現するために設計されました。
my $str : string;
文字列リテラルによって、生成された文字列オブジェクトを代入できます。
my $str : string = "abc";
SPVMの文字列は、要素を変更できないバイト型の配列です。配列アクセスを行って、文字を取得することができます。
# 文字の取得 my $ch = $str->[1];
要素を変更しようとした場合は、コンパイル時エラーが発生します。
# 要素の変更はコンパイルエラー $str->[1] = 'd';
文字列型は、コンパイルが終わった後は、バイト型の配列とまったく同じものになります。たとえば、一つ目の表現は、二つ目の表現として扱われます。
# isa 文字列型 if ($str isa string) { } # isa バイト型の配列 if ($str isa byte[]) { }
SPVMの文字列は、変更不可ですが、これは、コンパイル時チェックであることに注意してください。
文字列型は、byte[]型に、キャストすることができ、実行時に文字列を変更することができます。
my $bytes = (byte[])$str; $bytes->[1] = 'd';
文字列は、常に変更が可能であるものとして、扱ってください。
文字列互換型とは、文字列型とbyte配列型のことを言います。
値型とは、同一の複数の数値型を定義できる型です。連続した数値型のデータ構造を、メモリ節約的で高速に処理するために設計されました。
パッケージの定義において「value_t」デスクリプタを指定することで、値型となります。
package Point_3i : value_t { has x : int; has y : int; has z : int; }
このサンプルでは、3つの連続したint型を「Point_3i」として定義しています。
フィールドに指定できるのは、整数型と浮動小数点型だけです。
値型のフィールドの個数の最大値は16個に制限されています。
すべてのフィールドのデータ型は、同一の型である必要があります。
利用する場合は、変数の宣言を行ってください。すべての領域が、0で初期化されます。
my $point : Point_3i;
値型のデータは、サブルーチンのスタック領域に確保されます。
値型のデータは、連続していることが保証されます。
値型の名前は、パッケージ名の規則を満たしている必要があります。
それに加えて、最後が「_」「フィールドの個数」「サフィックス」で終わる必要があります。
フィールドの個数は、実際に定義されているフィールドの個数と一致している必要があります。
サフィックスは、実際に定義されている数値型と対応している必要があります。
不正な値型の定義の場合は、コンパイル時エラーが発生します。
値型のサフィクスの一覧
数値型 | サフィックス |
---|---|
byte | b |
short | s |
int | i |
long | l |
float | f |
double | d |
値型のフィールドの設定と取得は、アロー演算子で行うことができます。
# 値型のフィールドの取得 my $x = $point->{x}; # 値型のフィールドの設定 $point->{x} = 1;
サブルーチンの引数として値型を渡す場合は、内部的にはフィールドで定義された複数のデータ型に展開されて渡されます。
# SPVM上 foo($point); # 内部 foo($x, $y, $z);
このことはSPVMから値型を利用している場合は、意識する必要がありませんが、エクステンションにおいて、サーブルーチンの引数を受け取るときに、意識する必要があります。
値型は配列にすることができます。この場合、配列の領域は、値型として定義されたデータがヒープ領域上で、連続していることが保証されます。
my $points : Point_3i[] = new Point_3i[];
リファレンス型とは、変数への参照を表現する型のことです。数値型か値型の名前の後ろに「&」を付けることで定義できます。
my $num : int; my $num_ref : int& = \$num; my $point : Point_3i;; my $point_ref : Point_3i& = \$point;
リファレンス型は、単独では存在できず、リファレンスが代入される必要があります。
リファレンスにできるのは、数値型と値型のみです。オブジェクト型をリファレンスにすることはできません。
リファレンス型は、サブルーチンの引数にすることができます。
foo($num_ref); foo($point_ref);
デリファレンスすることで、参照先の値を取得したり、変更することができます。
# の値を my $num2 = $$num_ref; # 数値型の値を変更 $$num_ref = 3; # 値型の値を取得 my $point2 = $$point_ref; # 値型の値を設定 $$point_ref = $point2;
リファレンス型の対象が値型であった場合に、値型のフィールドの設定と取得は、アロー演算子で行うことができます。
# リファレンス型の対象が値型であった場合に、値型のフィールドを取得 my $x = $point_ref->{x}; # リファレンス型の対象が値型であった場合に、値型のフィールドを設定 $point_ref->{x} = 1;
数値のリファレンス型とは、数値型の変数に対するリファレンス型のことをいいます。
値のリファレンス型とは、値型の変数に対するリファレンス型のことをいいます。
型推論によって、レキシカル変数の宣言するときに、型の指定を省略することができます。型推論は、常に代入演算子の右辺の型によって、行われます。
# int my $num = 1; # double my $num = 1.0; # Foo my $foo = new Foo;
型に互換性があるというのは、型変換なしで、値の移動ができる型のことです。
型に互換性があるのは次の場合です。
移動元と移動先の型が同名の場合
my $num1 : int; my $num2 : int; $num1 = $num2;
移動元の型がbyte[]型で、移動先の型が文字列型の場合
my $bytes = new byte[3]; my $str : string; $str = $bytes;
移動元の型がオブジェクト型で、移動先の型が汎用オブジェクト型の場合
my $foo : Foo = new Foo; my $object : object; $object = $foo;
移動元の型と移動先の型が、汎用オブジェクト型あるいは、汎用オブジェクト型の配列で、移動元の型の次元数が、移動先の型の次元数以上の場合
my $objects_dim2_src : object[]; my $objects_dim1_dist : object; $objects_dim1_dist = $objects_dim2_src;
注意点として、汎用オブジェクトの配列と基本型の配列には互換性はありません。
# コンパイルエラー my $objets : object[] = new int[3];
型に互換性がない場合は、暗黙的な型変換が試みられます。暗黙の型変換に失敗した場合は、コンパイル時エラーが発生します。
暗黙的な型変換とは、SPVMによって行われる自動的な型変換のことです。次の箇所が、暗黙的な型変換が行われる可能性のある個所です。
次の場合に暗黙的な型変換が行われます。
移動元と移動先の型がどちらも数値型で、移動元の型よりも移動先の型が大きい場合は、数値拡大型変換が行われます。
# 暗黙の拡大型変換 my $num : long = 123; my $num : double = 12.5f;
移動元と移動先の型がどちらも数値型で、移動元の型よりも移動先の型が小さい場合で、移動元の値が、整数リテラルかつ移動先の型の値の範囲で表現できる場合は、数値縮小型変換が行われます。
# 暗黙の縮小型変換 my $num : byte = 123; my $num : short = 134;
移動元の型が数値型で、移動先の型が汎用オブジェクト型の場合は、対応する数値を表現するオブジェクト型へのボクシング変換が行われます。以下の場合の例では、SPVM::Int型のオブジェクトに変換されたものが、汎用オブジェクトに代入されます。
# object型への暗黙のボクシング変換 my $num = 123; my $object : object = $num;
移動元の型が数値型で、移動先の型が対応する数値を表現するオブジェクト型の場合は、対応する数値を表現するオブジェクト型へのボクシング変換が行われます。
# object型への暗黙のボクシング変換 my $num = 123; my $object : SPVM::Int = $num;
移動元の型が汎用オブジェクト型で、移動先の型が数値型の場合は、対応する数値型におけるアンボクシング変換が行われます。以下の場合の例では、SPVM::Int型のオブジェクトからint型への変換が試みられます。
# object型からの暗黙のアンボクシング変換 - my $object : object; my $num : int = $object;
移動元の型が数値を表現するオブジェクト型で、移動先の型が対応する数値型の場合は、対応する数値型におけるアンボクシング変換が行われます。
# 数値を表現するオブジェクト型からの暗黙のアンボクシング変換 my $num_obj = SPVM::Int->new(3); my $num : int = $num_obj;移動元の型が数値型で、移動先の型が、文字列型の場合は、数値から文字列への型変換が行われます。以下の場合の例では、数値の「123」が文字列「"123"」に変換されたものが代入されます。
# 文字列型への暗黙のボクシング変換 my $num = 123; my $str : string = $num;
明示的な型変換とは、型変換を明示的に記述して行う型変換のことを言います。「(」「型名」「)」と記述します。
(long)int型の値をlong型に変換するサンプルは以下のようになります。
my $num = (long)3;
移動元の型と指定した型が同一の場合は、単に値のコピーになります。
my $num : int = (int)4;
明示的な型変換の一覧です。
移動元の型と指定した型における変換の内容の一覧です。移動元の型と指定した型において型の互換性がなく、この一覧にない場合は、コンパイルエラーになります。
指定した型 | 移動元の型 | 変換の内容 |
---|---|---|
byte[] | string | オブジェクトがコピーされます。 |
大きな数値型 | 小さな数値型 | 数値拡大型変換が行われます。 |
小さな数値型 | 大きな数値型 | 数値縮小型変換が行われます。 |
対応する数値を表現するオブジェクト型 | 数値型 | ボクシング変換が行われます。 |
汎用オブジェクト型 | 数値型 | ボクシング変換が行われます。 |
数値型 | 対応する数値を表現するオブジェクト型 | アンボクシング変換が行われます。 |
数値型 | 汎用オブジェクト型 | アンボクシング変換が行われます。 |
文字列型 | 数値型 | 数値が、C標準のsprintf関数の「%g」フォーマットを使って、文字列化されます。 |
数値型は、型の順序を持ちます。型の順序は小さい方から「byte」「short」「int」「long」「float」「double」です。
単項数値拡大型変換とは、式がbyte型あるいはshort型であった場合に、int型へ数値拡大型変換を行うことをいいます。
単項数値拡大型変換が行われるのは以下の場合です。
二項数値拡大型変換とは、左辺と右辺に数値型をとる二項演算子において、左式と右式に適用される数値拡大型変換のことをいいます。
次のルールが適用されます。
1. 一方の式が、double型の場合は、他方の型はdouble型に変換されます。
2. 一方の式が、float型の場合は、他方の型はfloat型に変換されます。
3. 一方の式が、long型の場合は、他方の型はlong型に変換されます。
4. それ以外の場合は、int型に変換されます。
二項数値拡大型変換が行われるのは以下の場合です。
数値縮小型変換とは、数値型において大きい型から小さい型への変換が行われる場合に適用される変換の規則のことです。
数値拡大型変換とは、数値型において小さい型から大きい型への変換が行われる場合に適用される変換の規則のことです。
ボクシング変換とは、数値型の値を、数値を表現するオブジェクト型に変換する操作のことをいいます。
アンボクシング変換とは、数値を表現するオブジェクト型の値を、対応する数値型の値に変換する操作のことをいいます。
SPVMには、数値を表現するオブジェクト型として以下の型があります。
数値型 | 対応するオブジェクト型 |
---|---|
byte | SPVM::Byte |
short | SPVM::Short |
int | SPVM::Int |
long | SPVM::Long |
float | SPVM::Float |
double | SPVM::Double |
数値から文字列への型変換とは、数値型が文字列型に変換される場合に適用される変換規則のことです。
数値型の値をC99のsprintfの「%g」フォーマットを使って、SPVMの文字列へ変換します。
自動的に読み込まれるモジュールは以下です。useキーワードによって読み込まなくても利用することができます。
SPVMの標準関数の一覧です。
sub INFINITY : double ()
無限大を「double型」で返します。C標準の「INFINITYマクロ」の単純なラッパーです。
sub INFINITYF : float ()
無限大を「float型」で返します。C標準の「INFINITYマクロ」の単純なラッパーです。
sub NAN : double ()
非値を「double型」で返します。C標準の「NANマクロ」の単純なラッパーです。
sub NANF : float ()
非値を「float型」で返します。C標準の「NANマクロ」の単純なラッパーです。
加算演算子・減算演算子・乗算演算子の整数演算でオーバーフローした場合の動作はどうなりますか。
C99は、加算演算子・減算演算子・乗算演算子における整数演算のオーバーフロー時の動作について定義していないので、処理系依存になります。
ほとんどの環境においては、2の補数表現を使って、整数を表現し加算と減算を行いますので、2の補数表現で演算を行った場合の結果になることが期待されます。
はい。ライブラリがC89、C11、C++の各仕様で書かれていても利用できます。C99準拠というのは、SPVM自体のソースコード自体において適用されるだけです。
通常は、定数畳み込みの最適化は、行われませんが、プリコンパイルされたサブルーチンでは、定数畳み込みがCコンパイラによって試みらるでしょう。
演算子の優先順位はPerlをベースにして作成されており、ほぼPerlの優先順位だと考えて大丈夫です。
ひとつの違いは、SPVMには型キャストがあることです。型キャストは「単項演算子」の優先順位よりもひとつ低く、「乗算演算子」の優先順位よりも一つ高くなっています。
以下の例ではキャストが先に行われます。
(long)3 + 1;
これを、戻り値にすることは可能なはずですが、現在は実装されていません。
実装されていない理由は、一時変数の増加とオペレーションコードの増加が見込まれ、最適な形で実装することが、現在の作者の実力では困難であることです。
現在の内部実装では、条件判定した結果は、内部的な条件フラグに保存されます。
戻り値が欲しい場合は、次のように記述してください。
my $flag : int; if ($num > 3) { $flag = 1; } else { $flag = 0; }
SPVMは柔軟で、十分な後方互換性を保つことを目標に現在、設計しています。
いくつかの分野で、プログラムが正しく記述できること、パフォーマンスの要件を満たすことが必要です。1.0のリリースは、この要件が満たされた後になります。
デバイスドライバや、Open CV、Open GL、SIMD、 Open MP、GPUなどのC/C++ライブラリとの連携の確認。
HTTPSのリクエストを処理できるHTTPクライアント/サーバーライブラリ。
Windows APIを利用したネイティブWindowsアプリケーションの作成。
少なくともデバイスドライバやC/C++ライブラリと連携でき、WebにHTTPSで接続できる、ネイティブアプリケーションが作成できることの確認が必要です。
言語仕様と文法についてはPerlを主に参考にしています。必要な部分でPerl 6の文法・キーワードも採用しています。「has」「native」「ro」「rw」「wo」など。
言語仕様とバーチャルマシンについては、Java言語とJavaVMの言語仕様を参考にしました。
数値型と数値計算においては、Javaの計算規則とほぼ同じです。数値型の種類、演算子の種類、拡張型変換、縮小型変換などです。
言語仕様においては、ボクシング、アンボクシング、可変長引数については、Javaを参考に作りました。
SPVMのバーチャルマシンの初期実装は、JavaVMを参考にして、可変長バイト命令を解釈するスタック型VMとして作成されました。現在のSPVMは、64bitの固定長命令を解釈するレジスター型VMとなっています。
文字列がUTF-8であることと、インターフェースの実装については、go言語の言語仕様を参考にしています。
レジスタ型VMを採用している最も大きな理由は、SPVMのオペレーションコードを、C言語のソースコードに変換するときに、1対1で対応させることができるためです。gccの最適化が適用できます。
残念なことですが、列挙に利用できるるのはint型だけです。
他の型の定数を利用したい場合は、定数を返すサブルーチンを定義してください。
sub FOO : double () { return 3.14; }
一つの定数を返すサブルーチンは、定数としてインライン展開されることが仕様上で保証されているので、パフォーマンスを気にせず利用できます。
SPVMにおいては、メソッド呼び出し、クラスメソッド呼び出しについては、かっこの省略が可能です。サブルーチン名だけを指定した場合においては、かっこの省略ができません。
これは、サブルーチン名だけでは、識別子名がサブルーチンであることを、ソースコードの中で、決定できないためです。パッケージ名やフィールド名との区別ができません。
他のソースファイルを解析することによって、かっこを省略する構文は、理論的には可能ですが、SPVMでは、単一ファイルの中で、静的な構文解析を完了できるということを重要視しました。
gcc 4.3で確認しており、保証される最低のバージョンはgcc 4.3です。C99がサポートされている必要があります。
残念ながら、SPVMには、符号なし整数型はありません。
Unix、Linux、macOS、Windowsに対応しています。
コンテキストは存在しません。関数呼び出しには括弧が必要です。三項演算子はありません。シングルクォートは、文字定数です。
標準関数や標準モジュールは、Perlとは完全に異なっています。
サブルーチンは、必ずメソッドか、クラスメソッドになります。サブルーチンの絶対名での呼び出しはできません。
モジュールの拡張子は「spvm」です。
型はすべて静的型です。サブルーチン呼び出しは、コンパイル時に解決されます。配列は静的です。動的配列とハッシュは、モジュールとして提供されます。
スレッドは、コアではサポートされていませんが、エクステンションを使ったユーザーモジュールを作成することで、間接的に利用できます。
SPVMはシングルスレッドで動くように設計されています。シングルスレッドは、利用者にとって簡単で安全な設計です。
スレッドの機能はSPVMのコアにはありませんが、CやC++のスレッドライブラリを利用して、エクステンションから利用することはできます。
エクステンションでは、スレッド用のSPVMの実行環境を生成して、スレッド上で、利用できます。
SPVMのモジュールとして作成すれば、SPVMからサブルーチンを通して、間接的にスレッドを利用できます。
エクステンションとは、SPVMからC/C++の関数を呼び出すための仕組みのことです。
プリコンパイルとは、SPVMのサブルーチンを、コンパイル時に機械語に変換する仕組みのことです。機械語に変換されたサブルーチンは、高速に実行できます。
precompileが指定されたサブルーチンを含むモジュールファイルは、コンパイル時に、Cのソースコードに変換されます。
precompile sub sum : int ($num1 : int, $num2 : int) { return $num1 + $num2; }
Cのソースコードは、ビルドディレクトリの中に作成されます。
生成されたCのソースコードは、Perlをコンパイルしたコンパイラ(通常はgccかclang)によって、機械語(.oの拡張子を持つオブジェクトファイル)にコンパイルされます。
機械語に変換された後、各OSで呼び出すことのできる共有ライブラリ(.soや.dll)にリンクされます。
プリコンパイルする場合に、必要となるディレクトリのことです。
ビルドディレクトリを利用することをSPVMに教えるにはSPVM::BuildDirモジュールを使用する必要があります。
use SPVM::BuildDir;
スクリプトがあるディレクトリの「spvm_build」というディレクトリがデフォルトのビルドディレクトリ名になります。
ビルドディレクトリ名を自分で指定したい場合は、次のようにします。
use FindBin; use SPVM::BuildDir "$FindBin::Bin/mydir;
SPVMでは、インターフェスとは、実装を持たないメソッドが一つだけ定義されているパッケージのことをいいます。
インターフェースのひとつの例は、標準モジュールである「SPVM::Comparable」です。
package SPVM::Comparable : interface_t { sub compare : int ($self : self, $object1 : object, $object2 : object); }
機能としては、C言語の関数ポインタに似ています。
残念なことですが、SPVMにはジェネリクスはありません。SPVMは、コンパイル時の型決定性よりも、型の簡単さを選択しました。
コンテナの要素は、汎用オブジェクト型で定義してください。汎用オブジェクトから実際のオブジェクトを取得するためには、型キャストが必要です。
sub add : void ($self : self, $object : ojbect) { ... } sub get : object ($self : self, $index : int) { ... }
my $list = SPVM::List->new; $list->add("hello!"); my $str = (string)$list->get(0);
残念ですが、継承はありません。SPVMでは「汎用オブジェクト型」と「インターフェース」を使って、ポリモーフィズムを実現します。
サブルーチンのオーバーロードはありません。サブルーチンは、サブルーチン名で一意的に識別されます。
採用されていない最も大きな理由は、Perl自体が型を持たないために、PerlからSPVMのサブルーチンへ渡す値の型が決定できないためです。
このためSPVMでは、サブルーチン名によって、戻り値と引数の型がわかるように設計されています。
はい、浮動小数点の表現方法、および演算は、処理系に依存します。
二つの連続したアンダーラインは、エクステンションにおいて、パッケージ名とサブルーチン名の区切りとして利用されるためです。
エクステンションはC言語で書かれます。
# SPVMのサブルーチン package Foo::Bar { sub baz : void () { } }
// エクステンションにおける関数名 SPVM_NATIVE_Foo__Bar__baz(SPVM_ENV* env, SPVM_VALUE* stack) { }
SPVMの「Foo::Barパッケージのbazサブルーチン」はエクステンションの「SPVM_NATIVE_Foo__Bar__baz」に対応します。これは1対1に対応し、相互に名前の変換が可能です。
残念ながら、SPVMからPerlのサブルーチンを呼び出すことはできません。
一度代入した値を変更できなくする機能はありませんが、他の機能の組み合わせで実現することができます。
コンパイル時定数で数値型の場合
定数サブルーチンを使用します。
package Foo { sub VAL : double () { return 5.1234; } }
コンパイル時定数でオブジェクト型の場合
パッケージ変数を定義します。パッケージ変数に読み込み用のアクセッサを定義します。BEGINブロックを使って、パッケージ変数を初期化します。
package Foo { our $POINT : ro int; BEGIN { $POINT = Point->new; } }
はいできます。
パッケージ変数を定義します。BEGINブロックを使って、パッケージ変数を初期化します。singletonメソッドで、オブジェクトを返します。
package Foo { our $SINGLETON : Foo; BEGIN { $SINGLETON = new Foo; } sub singleton : Foo () { return $SINGLETON; } }
コア機能、標準関数、標準モジュールについては、作者が決定を行っています。その範囲の中であれば、開発への参加が可能です。
バグ報告、ベンチマーク、言語評価、ブログなどでの紹介は歓迎です。
「README」の中に開発手順が記載されています。
木本裕紀(kimoto.yuki@gmail.com)
moti(motohiko.ave@gmail.com)
バグ報告はGitHubのIssueで行うことができます。
2019年1月7日
明日はaeroastroさんです。