Palanとは
Palanとは、より簡単で安全なC言語の代替となりうる言語を目指して、少しづつ開発しているプログラム言語(コンパイラ)です。ver0.2は最小限の機能であったver0.1に、浮動小数点数、配列リテラル、デフォルト引数、簡易な型推論等を追加と細かな改良を加えたものとなります。構造体は扱えずC言語ライブラリ関数の呼び出しも半端なため、まだ簡単な数値計算ぐらいにしか使えません。アセンブリにコンパイルして実行ファイルを生成するため、スクリプト言語に比べてかなり高速に動作すると思われますが、本格的なパフォーマンス最適化は実装されていません。
※このリファレンスは古くなりました。最新は[v0.3リファレンス]
(https://qiita.com/tosyama/items/44146bb978a31679e177)をご覧ください。
Qiitaに開発日記を公開しています。
プログラミング言語Palan開発日記
プログラミング言語Palan開発日記 2年目
設計思想・方針
明確さと簡易さを両立させた言語を目指しています。また、感覚的なことになりますが開発者本人にとっての書き味のよさを重要視しています。
サンプルプログラムと解説
ver0.2では言語機能としては不十分ですが、下記のようなクイックソートのプログラム程度であれば作成できます。
ccall int32 printf();
// init data.
const N = 10;
int16[N] data = [0,4,8,3,7,2,6,1,5,0];
printf("before:");
show(data);
func show(int16[N] data)
{
i=0;
while i<N {
printf(" %d", data[i]);
i+1 -> i;
}
printf("\n");
}
printf("after:");
data ->> quicksort(0, N-1)
->> data
-> show();
func quicksort(int16[N] >>data, int32 left, right)
-> int16[N] data
{
if left >= right { return }
var mid, i = left, left +1;
while i <= right {
if data[i] < data[left] {
mid+1 -> mid;
data[mid], data[i] -> data[i], data[mid];
}
i+1 -> i;
}
data[left], data[mid] -> data[mid], data[left];
data ->> quicksort(left, mid-1)
->> quicksort(mid+1, right)
->> data;
}
コンソールから下記のように、pacコマンドでコンパイルして実行ファイルを作成できます。実行ファイルを実行すると、ソート前とソート後の数字の並びが表示されます。
$ ./pac quicksort.pa -o a.out
linking: a.out
$ ./a.out
before: 0 4 8 3 7 2 6 1 5 0
after: 0 0 1 2 3 4 5 6 7 8
オプションを指定しない場合は、コンパイル後、すぐに実行されます。
$ ./pac quicksort.pa
before: 0 4 8 3 7 2 6 1 5 0
after: 0 0 1 2 3 4 5 6 7 8
プログラムの解説
Palanのファイルの拡張子は".pa"となります。
mainのようなスタート関数はなく、基本的に上から順に実行されます。
ccall int32 printf();
ver0.2のPalanではデフォルトで標準Cライブラリとリンクします。ccallで宣言することでライブラリの任意の関数を使えるようになります。今のところ引数の宣言は不要です。
// init data.
const N = 10;
ソートする配列の要素数の定数宣言です。定数はスコープ内であればどこでも使えます。値としてはリテラルおよびリテラル同士の計算が指定できます。
*//*はコメントです、行末まで無視されます。
int16[N] data = [0,4,8,3,7,2,6,1,5,0];
ソート対象となる配列変数の宣言と初期化です。int16は16bit整数、*[]は配列であることを示しています。配列は[]*の中にカンマ区切りで数値を書くことで初期化できます。ここでは配列をソート対象の数字列で初期化します。
printf("before:");
show(data);
C標準ライブラリのprintfをコールして、コンソールに文字を表示します。*show()*は配列の中の数字をコンソールに表示するPalanの関数コールです。show関数の定義は後にあります。スコープ内であれば、関数の定義と呼び出しを記述する順番は前後しても構いません。
func show(int16[N] data)
{
配列の中を表示する関数を定義しています。
int16[N] dataと定義された引数は、コピーされた配列が引数に渡りますので、関数の中で配列を変更しても呼び出し元の配列には影響がありません。
i=0;
while i<N {
printf(" %d", data[i]);
i+1 -> i;
}
printf("\n");
whileは繰り返し文で、i<Nの条件をみたす限り *{}内の文を繰り返します。->は代入を表し、左の値を右の変数に代入します。他の言語では代入に=を使うことが多いのですが、Palanでは=は初期化時のみで、代入は->*を使うところが一つの特徴になります。
printf("after:");
data ->> quicksort(0, N-1)
->> data
-> show();
data配列をquicksort関数に入れてソートを実行し、結果を取得、表示します。代入と同じ*->で関数の引数に、値を指定して関数コールできます。また、代入と関数コールを->で繋げることができ、Palanではチェーンコールと呼んでいます。->>*は、配列の所有権を渡します。所有権を渡した変数は所有権を得るまで使えません。
func quicksort(int16[N] >>data, int32 left, right)
-> int16[N] data
{
ソートを行う関数を定義しています。
int16[N]>> data と定義された引数は、所有権を渡す必要があります。リファレンスが渡るためコピーは発生しません。-> int16[N] dataが戻り値の定義となり、関数から戻る際、変数dataのリファレンスを返しますので、呼び出し元は所有権を取り戻すことができます。
if left >= right { return }
ifは、直後に書かれた条件を満たした時にのみ{}の中の処理を実行します。ここではleftがright以上の場合、return文で関数を終了し呼び出し元にもどります。
{}の中の文が1つだけの場合は行末の*;*は省略できます。
data[mid], data[i] -> data[i], data[mid];
*,*で区切ることで複数の変数の代入ができます。ここではこの複数の代入を使って配列の要素を入れ替えています。
ここまででクイックソートのプログラムの解説を終わります。Palanの雰囲気が感じられたでしょうか?
動作環境
Palanコンパイラpacは下記の環境で動作します。
- CPU: X86-64 Intel系 CPU
- Memory: 2GB
- OS: Ubuntu 14.04.5 LTS (64bit)以降 (18.04.1 LTS)
- Library: g++ (gcc 5.5.0/as/ld)以降(gcc 7.3.0), libboost-program-options
インストール
ver0.2ではインストール用のバイナリファイル/パッケージには用意していません。githubよりソースをクローンしてビルドする必要があります。ビルド方法はREADME.mdをお読みください。
https://github.com/tosyama/palan
コマンドラインリファレンス
Palanコンパイラ(pac)はコンソールからコマンドラインで使用します。
- ヘルプの表示
$ pac -h
コマンドラインのオプションが確認できます。
- バージョン情報の表示
$ pac -v
- コンパイルしてアセンブリを表示
$ pac -S foo.pa
標準出力にアセンブリが出力されます。
- コンパイルしてオブジェクトファイルを作成
$ pac -c foo.pa
foo.oがカレントディレクトリに作成されます。オブジェクトファイル名はソースコードの拡張子を除いたファイル名+".o"となります。
※ オブジェクトファイルはldでリンクすることで実行ファイルにできますが、複数のオブジェクトファイルの結合は今のPalanではサポートできてません。
- コンパイルして実行ファイルを作成
$ pac -o foo foo.pa
上記の例では、foo.oというオブジェクトファイルと、fooという実行ファイルが生成されます。
- コンパイルして、すぐに実行。
$ pac foo.pa
a.outという名称の実行ファイルを生成して実行します。実行後、オブジェクトファイルと実行ファイルは削除されます。
コメント
"//"の記述以降はコメントとなります。記述は行末まで無視され、プログラムとはみなされません。
// コメントです。
基本型
ver0.2では整数型と浮動小数点型が使用できます。
型名 | 説明 | 最小値 | 最大値 |
---|---|---|---|
sbyte | 符号付き8bit整数 | -128 | 127 |
byte | 符号なし8bit整数 | 0 | 255 |
int16 | 符号付き16bit整数 | -32,768 | -32,767 |
uint16 | 符号なし16bit整数 | 0 | 65535 |
int32 | 符号付き32bit整数 | -2147483648 | 2147483647 |
uint32 | 符号なし32bit整数 | 0 | 4294967295 |
int64 | 符号付き64bit整数 | -9223372036854775808 | 9223372036854775807 |
uint64 | 符号なし64bit整数 | 0 | 18446744073709551615 |
flo32 | 32bit浮動小数点 | (+-)1.175494e-38 | (+-)3.402823E+38 |
flo64 | 64bit浮動小数点 | (+-)2.225074e-308 | (+-) 1.797693e+308 |
変数
変数宣言
変数を使用するには事前に宣言する必要があります。
型名の後に変数名を書くことで変数を宣言します。変数はスコープ内("{"と"}"で囲まれた領域)で使用できます。
int32 i; // 32bit整数の変数iの宣言
int16 x, y; // 16bit整数の変数 xとyの宣言
byte b, sbyte sb; // 符号なし8bit整数bと符号あり8bit整数sbの宣言
=を使って宣言時に変数を初期化できます。複数の変数宣言の場合は、=の右側に","で区切って初期値となる式を記述します。
int32 i = 0;
int16 x, y = 3, 4;
byte b, sbyte sb = 1u, -1;
x + y -> i; // iに3+4の結果が入る
型推論
変数宣言の初期化時に、型をvarと指定すると右辺の型と同じにすることができます。宣言する変数が1つの場合はvarも省略することができます。
int32 i = 10;
var i2 = i; // i2はint32
i3 = 10; // i3はint64
f = 1.23; // fはflo64
var x, y = 1.0, i; // xとyはflo64
var xx, var yy = 1.0, i; // xxはflo64、yyはint32
配列変数
固定長配列を使用できます。配列変数を使用するには事前に宣言する必要があります。
[]内に要素数を記述します。","で区切ることで多次元配列の宣言もできます。指定する要素数は固定値である必要があり、整数リテラル、定数およびそれ同士の計算に限られます。
使用するときは*[]*に0から要素数-1までのインデックスを指定します。インデックスには任意の式を指定できますが、範囲を超えないようにしてください。範囲を超えて指定した場合はクラッシュの原因になります。
int32[10] a1, a2; //要素数10の32bit整数の配列変数1 a1, a2の宣言
int32[3,4] m; //3x4の2次元配列の宣言。メモリの並びは要素4の配列が3つ。
// 要素へのアクセス
1 -> a1[0]; 10 -> a2[9];
12 -> m[2,3];
配列の配列
*[]*を繋げることで配列の配列も使用できます。多次元配列と異なり、要素となる配列とその参照の配列が生成されます。
int32[10][3,4] arrayOfMat; // int32[3,4]を要素とする要素数10の配列
123 -> arrayOfMat[9][2,3];
int32[3,4] m = arrayOfMat[9]; // 2次元配列として扱われる
配列表現
配列表現を使うと、配列を初期化したり無名の配列を作成したりすることができます。配列表現は一連の値を*,で区切り[]*で囲んで表します。配列表現の配列に値を代入したり、配列表現から所有権を取得することはできません。
多次元配列は配列表現を並べて書くことで表現できます。入れ子形式でも書くこともできます。
int32[3] a = [1,2,3]; // aを初期化
i = 10;
[i,i+1,i+2]->a; // aに代入
int32[2,3] a2 = [1,2,3][4,5,6]; //多次元配列
int32[2,3] a3 = [[1,2,3],[4,5,6]]; // 入れ子形式の記述も可能
int32[2][3] aa = [1,2,3][4,5,6]; //配列の配列
基本的に配列表現の型は代入先の型になりますが、型推論の際は多次元配列とみなされます。(配列の配列にはなりません)
a = [1,2,3][4,5,6]; // aの型はint64[2,3]
要素数の省略
配列変数の初期化時に要素数を省略すると、右辺の値の要素数と同じになります。ただし、次元数は一致している必要があります。
int32[] a = [1,2,3]; // aの型はint32[3]
flo32[,] f = [1,2][3,4]; // fの型はflo32[2,2]
int32[,3] a2 = [1,2,3][4,5,6]; // 一部省略も可
int32[][] aa = [1,2,3][4,5,6]; // 配列の配列も可
式
値を返す構文を式と呼びます。式は";"で区切られるまで繋げて一つの文(ステートメント)を形成できます。
リテラル
ソースコード中に固定値を直接記述できます。
名称 | 説明 | 例 |
---|---|---|
整数リテラル | 整数の固定値です。符号付64bit整数として扱われます。10進数で記述します。 | -12345 12345 |
符号なし整数リテラル | 正の整数の固定値です。符号なし整数として扱われます。10進数の後ろにuを付けます。 | 12345u |
小数リテラル | 小数の固定値です。64bit浮動小数点数として扱われます。整数部と小数部を*.で区切り記述します。その後ろにe*で始まる指数部も記述できます。 | 123.4 1.234e2 |
文字列リテラル | 文字列の固定値です。ver0.2ではC言語関数の引数にしか使えません。"で囲みます。エスケープシーケンスが使えます。 | "Hello World!\n" |
配列リテラル | 全ての要素がリテラルか定数で構成された固定の配列表現です。通常の配列とは違い、定数の右辺や仮引数のデフォルト値としても使用できます。 | [1,2,3] |
変数
変数宣言した変数名を記述するとその変数の値が返ります。代入、算術演算、関数引数などで使用できます。
代入
代入演算子*->*を使うと計算結果などを変数に格納できます。左辺から右辺に値がコピーされます。
複数の変数に代入することもできます。
121+2 -> i; // 123をiに代入
i + 3 -> i; // 126をiに代入
123, 456 -> i, j; // 複数の代入。123をiに、456をjに代入
f() -> i, j; // 関数fが複数の値を返し、それぞれ代入できる
複数の代入を使って値を入れ替えることもできます。
int32 i, j = 1, 2;
i,j -> j,i; // 値の入れ替え。iは2、jは1となる。
注意: ver0.2から仕様が変わりました。
配列変数や、配列の配列に代入演算子を使用すると全ての要素がコピーされます。(Deep Copy)
int32[10] a, b;
...
a -> b; // 深いコピー
所有権の移動(Move)
配列などのオブジェクトに関して所有権が移動できます。所有権の移動にはMove演算子*->>を使います。2番目以降の変数にも移動したい場合は移動先の変数に>>*を付加します。配列の中身をコピーせずに参照のみを移動先の変数に渡すため、代入に比べて高速に値を渡せます。
所有権移動元の変数は、移動後は所有権を獲得するまで使用できません。もし使用した場合、クラッシュの原因となります。
int32[10] a, b, c, d;
...
a ->> b; // 配列の中身の所有権をaからbへ移動
// 2->a[2]; // 使用不可
b, c ->> a, >>d; // aは所有権を取り戻すと同時にcからdに所有権を移動
int32[10] e <<= a; // 所有権を移動して初期化
Moveは主に関数との大きなオブジェクトの受け渡しで使用します。配列変数同士の直接のMoveでは、受け渡し先が保持していたメモリは解放されます。
算術演算
整数や小数に対して下記の算術演算ができます。
演算子 | 名称 | 説明 | 例 |
---|---|---|---|
+ | 加算 | 足し算を行います。 | 1+2 -> i; // 3 |
- | 減算 | 引き算を行います。 | 4-3 -> i; // 1 |
* | 乗算 | 掛け算を行います。 | 3*2 -> i; // 6 |
/ | 除算 | 割り算の商を求めます。整数同士の場合、少数点は切り捨てられます。 | 10/3 -> i; // 3 |
% | 余算 | 割り算の余りを求めます。少数には使用できません | 10%3 -> i; // 1 |
- | 負 | 負数を返します。 | -i -> i; |
演算子には優先順位があります。*, / , % は +, -よりも優先順位が高くなります。
基本的にリテラル同士の計算はコンパイル時に行われます。
比較演算
比較演算子を使って整数および小数の比較ができます。比較の結果が真の場合、1となります。偽の場合、0となります。式が使用できるところであればどこでも使えますが、if文やwhile文の条件として使用する機会が多いでしょう。
浮動小数点の比較では常に丸め誤差に気をつける必要があります。
演算子 | 名称 | 説明 | 例 |
---|---|---|---|
== | 等しい | 左値と右値が等しい場合1、それ以外の場合0になります。 | if a==10 {...} |
!= | 等しくない | 左値と右値が等しくない場合1、等しい場合0になります。 | if a!=10 {...} |
> | 大なり | 左値が右値より大きい場合1、等しいか小さい場合0になります。 | if a>10 {...} |
< | 小なり | 左値が右値より小さい場合1、等しいか大きい場合0になります。 | if a<10 {...} |
>= | 大なりイコール | 左値が右値より大きいか等しい場合1、小さい場合0になります。 | if a>=10 {...} |
<= | 小なりイコール | 左値が右値より小さいか等しい場合1、大きい場合0になります。 | if a<=10 {...} |
整数と小数の演算
整数と小数が混在する演算を行う場合は、整数を64bit浮動小数点に昇格した上で計算や比較が行われます。
整数の昇格
整数型の変数は64bit整数に昇格して計算や比較が行われます。代入時に結果の上位ビットが切り捨てられます。
符号付き整数と符号なし整数の計算の場合は、符号なし整数は符号付き整数として扱われて計算されます。
注意: C言語と仕様が異なります。
条件演算
複雑な条件を判断するために下記の条件演算が使用できます。
演算子 | 名称 | 説明 | 例 |
---|---|---|---|
&& | かつ | 左値が0でない、かつ右値が0でない場合1、それ以外は0になります。左値が0であった場合、右値の式は計算されません。 | if a>0 && a <= 10 {...} |
¦¦ | または | 左値が0でない、または右値が0でない場合1、それ以外は0になります。左値が1であった場合、右値の式は計算されません。 | if a<0 ¦¦ a >= 10 {...} |
! | 否定 | 式が0の場合1、0でない場合1になります | if !(a<0) {...} |
注意: 他の言語では論理演算と呼ぶことが多いですが、実質的な動作は条件分岐であるため、Palanでは条件演算と定義しています。
関数の呼び出し
関数名と()の中に引数を指定することで宣言、定義されたスコープ内の関数を呼び出すことができます。スコープ内であれば呼び出し後に関数定義があっても構いません。
引数には所有権を要求するものがあります。その場合には所有権を手放すことを明確にするため">>"を引数の後ろに付加する必要があります。
戻り値は代入演算子、またはMove演算子を使って受け取ることができます。
戻り値を受け取ることは必須ではありません。受け取らなかった戻り値が使用していたメモリは自動解放されます。
int32 x, y;
int32[10] a;
int32 result;
// 関数呼び出しの例
foo(); // 引数なし、戻り値を受け取らない。
foo(x,y) -> result; // 引数あり、戻り値あり。
foo(a>>, x, y)->>a, result; // 所有権移動の引数、複数の戻り値
foo(a) -> a; // 内容のコピーによる受け渡し
func foo() { ... } // 関数定義は後でもよい
func foo(int32 x, y) { ... }
...
オーバーロード
Palanでは関数名が同じで引数が異なる関数を定義できます(オーバーロード)。呼び出し時に引数の型と数が完全に一致するものがあればその関数が呼び出されます。
型が完全に一致しない場合、整数など暗黙に型変換した上で一致する関数が1つである場合、その関数が呼び出されます。2つ以上の関数に一致した場合はコンパイルエラーとなります。
引数の省略
関数の引数にデフォルト値が設定されている場合は、引数を省略することができます。引数を省略した場合はデフォルト値が使われます。
// xと、yの引数が省略可能な関数
func foo(int32 x=1, y=2) { }
foo(3,4); // 通常の呼び出し
foo(); // foo(1,2)
foo(3); // foo(3,2)
foo(,3); // foo(1,3);
チェーンコール
代入やMove演算子を使って引数を指定して関数呼び出しを行ったり、さらに関数の戻り値を次の関数呼び出しの引数に適用できます。
通常の関数呼び出しと比較し、関数を左から右に順番に記述できるので、データや処理の流れやを表現しやすくなります。
代入演算子の左値、または関数の戻り値は、引数の先頭から順に割り当てられ、関数が呼び出されます。
int32 a,b;
add(a,3)->b; // 通常の関数呼び出し
a->add(3)->b; // 代入(1つ)で引数指定
a,3 -> add() -> b; // 代入(2つ)で引数指定
add(add(a,3),9)->b; // 通常の関数呼び出し(ネスト)
a->add(3)->add(9)->b; // 関数の戻り値を次の関数へ渡す
func add(int32 a, b)->int32 c
{...}
Move演算子は最初の引数にのみ適用されます。2番目以降の引数に適用する方法は提供していませんので、その場合は通常の関数呼び出しを使用する必要があります。
byte[10] s1, s2;
s1 ->> mix(>>s2) ->> s1, >>s2;
// s1, s2 ->> mix(); // NG: 2番目の引数は代入と見なされる
func mix(byte[10] >>a, >>b)
-> byte[10] a, b
{...}
定数
constキーワードを使用するとプログラム上で共通に使用する数値や配列を定数として定義できます。リテラルおよびリテラル同士の計算を指定できます。式の中で使う以外に、配列の型の宣言の要素数やデフォルト引数でも使用できます。
const M,N = 3,4;
const FORMAT = "bar: %d";
const LEN = M*N; // リテラル同士の計算
int32 l,z = LEN, Z; // 式。Zは定義前だが使用可能
func foo(int32[M,N] m) // 固定配列の宣言
{
const FORMAT = "foo:%d"; // 定数の上書き
printf(FORMAT, LEN); // 親のブロックで定義された定数LENは使用可能
}
const Z = LEN;
定数はスコープ内であれば定義前でも使用できます。ただし定数の定義中で別の定数使用する場合は、その定数は前に定義しておく必要があります。
子のブロックでは親ブロックの同じ名前の定数を定義すると上書きされます。
制御文
ifやwhile等の制御文を使うことで、処理を分岐させたり繰り返したりして流れを制御できます。
トップレベル
関数定義の外に記述されたコードをトップレベルと呼びます。プログラムはトップレベルの上から順次実行され、トップレベルの最後に来ると終了します。
トップレベルではreturn文は使用できません。任意の場所で終了する場合はC標準ライブラリのexit関数等を使用してください。
ブロック
*{}*で囲まれた領域がブロックです。ブロックを使用する事で、変数、関数、定数やその名前の有効な領域を制限できます。
通常、ブロック内で宣言した変数はそのブロックと子のブロック内で使用でき、ブロックの外側では使用できません。
処理がブロックを抜けると、ブロック内で宣言した変数が使用しているメモリは全て解放されますので、ブロックを適切に使用すると使用メモリを節約できます。
if文
条件によって処理を分岐させたい場合はif文を使用します。
if文 は条件式を評価し、条件式が真(0以外)の場合は、直後のブロックを実行します。
if a==2 { // ifの後に、任意の条件式を記述
// 条件式が真(aが2)の場合に実行
}
条件式が偽(0)の場合に別の処理を行わせたい場合は、if文のあとに else文を記載します。
if a==2 {
// 条件式が真(aが2)の場合に実行
} else {
// 条件式が偽(aが2以外)の場合に実行
}
else if文をつなげることで、複数の条件分岐ができます。
if a==2 {
// 条件式が真(aが2)の場合に実行
} else if a==3 {
// 条件式が真(aが3)の場合に実行
} else {
// 全ての条件式が偽(aが2と3以外)の場合に実行
}
while文
条件によって処理を繰り返す場合はwhile文を使用します。while文は条件式を評価し、条件式が真(0以外)である限り直後のブロックを実行し続けます。
無限ループによるCPUビジーの原因になりやすいため条件式のコーディングは特に気をつける必要があります。
int32 i=0;
while i<10 { // 条件式が真(iが10より小さい)の限り繰り返す
printf("%d",i);
i+1 -> i;
}
繰り返しの中断とスキップ
break文を使用して、whlle文による繰り返しを中断してブロックから抜ける事ができます。
また、continueを使用すると、残りの処理をスキップして次の繰り返しを続けることができます。
通常、この2つの文はif文と組み合わせて使うことになります。
i=0;
while i<10 {
if i==5 { break }
if i==3 { 4->i; continue }
printf("%d",i);
i+1 -> i;
}
// 出力は"0124"になる
関数
関数とは一連の処理をまとめたものです。入力を引数として受け取り、処理を行ったあと出力を戻り値として返します。Palanの関数定義では入力、出力を明確に表現できます。
関数定義
Palanの関数を定義するにはfuncキーワード、関数名を記述し、その後に*()内に呼び出し元から受け取る仮引数を宣言します。
関数が戻り値を返す場合は、->*の後に戻り値を宣言します。
上記宣言の直後に関数で行う処理をブロックの中に記述します。
// 引数なし、戻り値なしの関数定義
func foo()
{
// ここに処理を記述
}
// 引数あり、戻り値なしの関数定義
func foo(int32 a, b, int16 c)
{
printf("a:%d, b:%d c:%d\n", a, b, c); // 仮引数は通常の変数として扱える
}
// 引数なし、戻り値ありの関数定義
func bar() -> int32 a, b, int16 c
{
// 戻り値も変数として扱える。
// 関数終了時の値が呼び出し元に返る。
1 -> a; 2-> b; 3->c;
}
// 引数あり、戻り値ありの関数定義
// 引数と、戻り値で同じ変数名aを指定可能。
func bar(int32 a, b) -> int32 a, c
{
1 -> c;
}
仮引数
関数呼び出し時に指定された引数の値は、仮引数に渡ります。
仮引数には型名と、変数名を記述します。複数の仮引数を宣言するには","で繋げます。前の引数と型が同じ場合は型名を省略できます。
通常、仮引数には呼び出し元の変数の値がコピーされます。仮引数の値を変更しても、呼び出し元の変数の値は変わりません。
call int32 printf();
func foo(int32 i, j, sbyte b, int32[10] a) // ()で囲まれた部分が仮引数の宣言
{
printf("before: i=%d, a[3]=%d¥n", i, a[3]);
8,9 -> i, j;
33 -> b;
88 -> a[3];
printf("after: i=%d, a[3]=%d¥n", i, a[3]);
}
// 引数に指定する変数
int32 ii, jj = 3, 4;
sbyte bb = 1;
int32[10] arr;
99 -> arr[3];
foo(ii, jj, bb, arr); // 関数を呼び出し
printf("original: ii=%d¥n, arr[3]=%d", ii, arr[3]);
出力は下記のようになります。元の変数は変更されていません。
before: i=3, a[3]=99
after: i=8, a[3]=88
original: ii=3, arr[3]=99
仮引数の前に*>>*を指定した場合、コピーではなく、呼び出し元の変数の所有権を要求します。参照が渡されるのでディープコピーは発生せず高速に受け渡しが可能です。一方、渡したままでは呼び出し元の変数は使用できなくなりますので、通常、呼び出し元に変更後の値を返す戻り値も宣言します。
仮引数の順番は、他の関数でも使うようなメインとなるデータを第一引数にして、オプション等を後ろにするとチェーンコールで使いやすくなります。
デフォルト引数
仮引数には*=*でデフォルト値を指定することができます。デフォルト値を使用すると呼び出し元の引数指定の負担を減らすことができます。ただし、オーバーロードど併用する場合は、曖昧さを発生させてコンパイルエラーの原因になるので慎重に設計する必要があります。
デフォルト値としては、リテラルや定数などの固定値のみ使用できます。
// j, aに対してデフォルト値が指定されている
func foo(int32 i, j = 3, int32[2] a = [1,2])
{
}
戻り値
処理の結果を呼び出し元に返すには戻り値を使用します。
戻り値は*->の後に型名と変数名を宣言します。複数の戻り値を宣言するには",*"で繋げます。前の引数と型が同じ場合は型名を省略できます。
func foo() -> int32 i, j, int16 l
{
1,2,3 -> i, j, l;
}
戻り値に指定した変数の関数終了時の値が、呼び出し元に返されます。
"%d, %d, %d\n", foo() -> printf(); // => 1, 2, 3
仮引数と同じ変数を戻り値として宣言することができます。その場合は呼び出し元の引数の値が変数代入されて呼び出され、そのまま戻り値にも使用されます。
func foo(int32 j) -> int32 i, j, int16 l // jは引数にも戻り値にも使用
{
j+1 -> j;
1,2 -> i, l;
}
"%d, %d, %d\n", foo(10) -> printf(); // => 1, 11, 2
型だけ指定し、変数名を指定しない書き方もできます。その場合はすべての戻り値を型のみの宣言にする必要があります。また、関数を終了する際は、return文で戻り値をすべて記載する必要があります。
func foo() -> int32, int32, int16
{
return 1,2,3; // 必須
}
"%d, %d, %d\n", foo() -> printf(); // => 1, 2, 3
Return文
関数の任意の場所で処理を終了する場合はreturn文を使用します。
return文には呼び出し元に返す値を式で指定できますが、戻り値の宣言で変数名を指定している場合はその変数の値となるため返す値は不要です。
戻り値の宣言が型名だけの場合は必ず戻り値が必要です。
トップレベルの処理においてはreturn文は使用できません。処理を終了する場合は標準Cライブラリのexit関数等を使用してください。
C言語関数
ver0.2のPalanのプログラムは標準Cライブラリとリンクされます。ccall宣言を記述するとprintfなどの標準Cライブラリの関数が使用できるようになります。C言語の戻り値の型、int / long long等は Palanの型 int32, int64等に置き換える必要があります。voidは記載不要となります。
仮引数の宣言はありません。呼び出し時の引数がそのまま関数に渡されます。
残念ながら、今はドラフトの実装であるため、C言語の構造体、マクロ、グローバル変数等は使用できません。
ccall int32 printf(); // 戻り値がある場合
ccall exit(); // 戻り値がない場合
システムコール
syscall宣言をするとLinux(64bit)システムコールが使用できます。システムコールは任意の名前を指定でき、関数と同じように使用できます。宣言時にシステムコール番号と戻り値の型、対応する関数名を宣言します。
仮引数の宣言はありません。呼び出し時の引数がそのまま関数に渡されます。
syscall 1: int64 write();
syscall 60: exit();
注意: プログラム終了のシステムコールを宣言して使用できますが、printfなどを使用している場合はバッファに出力が残ったまま終了してしまいます。printfを使用する際は、標準Cライブラリのexit()関数を使用した方がよいでしょう。
メモリ管理
Palanにはメモリ確保のnewはありません。配列は宣言時に自動的にヒープ領域に確保されます。
GCやdeleteもありません。ヒープ領域に確保された領域はその所有権を持つ変数がスコープをはずれたり、別の所有権が移動された際に解放されます。
メモリ解放はGCとは違い即座に行われます。GCと比較して解放処理の分その処理速度が落ちることも考えられますが、常に安定したパフォーマンスと最小限のメモリを維持でき、突然のGCで急に処理が止まるようなことはありません。
トップレベルで確保された配列はプログラム終了時にOSより一度に解放されますので、個別の解放処理は行われません。
終わりに
まだまだ使える言語には程遠いですが、地道に開発を続けていきたいです。もし、興味をもっていただけて、一度でも使っていただければ幸いです。