VCSSLの主な文法・仕様

前回は、VCSSLプログラムの実行と、基本的な入出力について扱いました。 今回は、VCSSLの主な文法と仕様について、まとめて見てみましょう。 即席ガイドという趣旨から、読者がすでにC系の言語の知識があるとの前提に立って、簡潔に駆け足でまとめていきます。

なお、解説はVCSSLのコードで行いますが、特に断りのない限り、サブセット言語であるVnanoでも同じ解説が成り立ちます。

- 目次 -

変数と型、演算

変数の宣言

変数の宣言は以下のようにします:

VariableDeclear.vcssl

変数はグローバル領域でもローカル領域でも宣言できます。 ただしグローバル変数は同じ型なら重複宣言が許されますが、ローカル変数では許されません。 また、グローバル変数は宣言行の前であっても参照できますが、ローカル変数は宣言行の後でしか参照できません。 なお、constの付いたグローバル定数は、全ての行の実行よりも先に初期化されます。

なお、Vnanoでは、上述のようにグローバル変数に対して特別扱いする機能は削られており、全てローカル変数と同じ扱いになります。

プリミティブ型

基本的なプリミティブ型は int / float / complex (Vnanoでは未対応) / string / bool の5種です。この他にも多倍長拡張型などがありますが、この5種だけ覚えておけば普通に使うには十分です:

VariableType.vcssl

実行すると「 1 2.3 (4.0,5.0) Hello true 」と表示されます。 float と int の精度は処理系定義で、普通はfloatが倍精度(double)です。 intも符合付き64bitの精度です。一応double / longも使えますが、float / intと同一視されます。 complex の実部と虚部(re関数とim関数で取得可能)も float と同じものです。 stringは値型ですがNULL許容型で、NULLとの比較や代入ができます(Vnanoでは不可)。

プリミティブ型の演算

int、float、complex型については、各種の四則演算や比較演算が行えます。intとfloatを混ぜた四則演算結果はfloat型に、intとcomplexまたはfloatとcomplexを混ぜた四則演算結果はcomplex型になります。なお、complex型では大小比較は行えません。

string型は文字列を扱う型で、参照型ではなく値型です。四則演算では加算のみが可能で、文字列の結合となります。string型と他の型との加算結果はstring型になります。

bool型は論理型(真偽型)で、論理演算が行えます。VCSSLでは比較演算の結果はbool型で、if文などの条件式は、必ずbool型でなければいけません。

配列

続いて配列ですが、最初に一つ注意が必要な点があります。VCSSLの配列は、多くのC系言語と異なり、参照型ではなく値型として振る舞います (VCSSLでは、GCを不要にするため、参照型が存在しません)。つまり、配列同士の代入演算は、参照の代入ではなく、全要素の値のコピーとなります。同じデータ領域を参照するようにはなりません。

使い方は以下のような形式になっています:

Array.vcssl

実行結果は「0 1 2」です。多次元配列では以下のように宣言します:

Array2D.vcssl

配列は宣言と同時に初期化も可能です(Vnanoでは不可):

ArrayInit.vcssl

上のように宣言時に要素数を省略した場合、初期化しなければ要素数0の配列となります。

配列の要素数を取得するにはlength関数を使用します:

ArrayLength.vcssl

実行結果は「3」です。length関数の第2引数は次元インデックスで、例えば2次元なら:

ArrayLength2D.vcssl

のように、要素数を知りたい次元(左から0, 1, 2, …)を指定します(実行結果は「2 3」)。

配列の要素数は、alloc関数で動的に変更できます:

ArrayAllocFunction.vcssl

実行結果は「0 1 2」です。要素数変更後も、もともとあった要素の値は保たれます。

構造体(Vnanoでは使用不可)

型の紹介の最後として、構造体です:

StructBox.vcssl

実行すると「100 200」と出力されます。

構造体「変数」の宣言時に、C言語のようにstructキーワードを付けてはいけない点に要注意です。 なお、構造体も値型で、参照型にすることはできません。構造体変数の代入は、全メンバの値のコピーとなります。 また、構造型はNULL許容型で、NULLとの比較と代入ができます。禁止する機能は今のところありません。

ところで、現在のVCSSLの実装では、構造体はメモリ・処理速度共にハイコストです。 要素数の多い配列や、高速に回る箇所では、あまり積極的に使うことはできません。

制御構文

if 文 ( if-else 文 )

VCSSLでは、制御構文は if( if-else )/ for / while の3種のみです。まずはif文の例です:

実行すると入力値で分岐してメッセージが出力されます。条件式はbool型限定です。

If.vcssl

なお、VCSSLでは、if / else / for / whileのぶら下がり構文を禁止しているため、ブロックスコープの { } は省略できません。以下のように省略するとエラーとなります:

IfError.vcssl

ところで、「ぶら下がり構文が禁止ならば、else if というのはおかしいのでは? elifのような別の構文が必要では…」という指摘があるかもしれません。 これはその通りで、elseの後にifがぶら下がっているのではなく、特例的に「else if」で一つの構文キーワードです。

for 文

続いてfor文の例です:

For.vcssl

実行すると、入力値以下の正の整数を出力します。 ただしコンソールへの出力処理は、演算処理よりも時間がかかるため、ループ回数を増やすと、出力完了までに時間がかかります。

while 文

続いてwhile文の例です。for文の例と同じ処理を記述すると以下のようになります:

While.vcssl

関数

VCSSLでは、関数の宣言は基本的にC系言語の標準的な形式を踏襲しています:

Function.vcssl

実行すると「 3 」と出力されます。

配列を引数や戻り値とするには、次のように記述します:

FunctionArray.vcssl

なお、関数はオーバーロードが可能です。 つまり、引数の型や個数が異なる関数は、別のものとして扱われるため、識別子(関数名)が競合しても問題ありません。 なお、シグネチャが完全に競合してしまっている場合は、後に宣言されたほうの関数が有効になります。

動的解釈 (Vnanoでは使用不可)

インタプリタ形式の処理系では、eval関数により、実行時に動的な式の解釈が行えます:

EvalExpr.vcssl

実行すると「3」と出力されます。レキシカルスコープで、代入を行う事も可能です:

EvalStat.vcssl

evalの戻り値はstring配列で、intやfloatの非配列に代入するには変換コストがかかるため、ループ時などでは後者のほうが高速です。 なお、ループ時のevalの構文解析コストについては、処理系側で色々とキャッシュされるため、ほとんどありません。 処理系の実装にも依存しますが、概ね静的解釈と大差ないパフォーマンスが得られます。

ファイル入出力

数値計算などでは必須となるファイル入出力についても、簡潔にまとめておきます。

簡易ファイル入出力

まずは最も単純な、簡易ファイル入出力です。あまり複雑な事はできませんが、簡単です:

SimpleIO.vcssl

実行すると、「test.txt」というファイルが生成されて「Hello World」と書き込まれ、さらにそれが読み込まれてコンソールに表示されます。

なお、適当な箇所で改行したければ、書き込み内容に定数EOLを挟んでください:

SaveWithEOL.vcssl

実行すると、改行を挟んで「Hello」「World」と書き出されます。 EOLは環境依存の改行コードを表す文字列定数です。もし、いわゆるLF(0x0A)を書き出したければ定数LFを、CR(0x0D)を書き出したければ定数CRを使用してください。

基本的なファイル入出力

もう少し高度なファイル出力の基本形は、以下のようになります:

Writeln.vcssl

実行すると、改行を挟み「Hello」「World」書かれたファイル「test.txt」が生成されます。

open関数はファイルを開き、複数のファイルを識別するための番号を割り振って返します。第2引数の "w" は書き込みモードを意味します。追記モードなら "a" とします。

writeln関数は、ファイルに書き込んで改行する関数で、改行を行わないwrite関数も存在します。使い方は同じで、第1引数が対象ファイルの番号、第2引数以降(可変長)が書き込み内容です。

続いてファイル入力です。基本形は以下の通りです:

Readln.vcssl

「test.txt」と同じ場所で実行すると、その内容がコンソールに出力されます。

open 関数の第2引数の "r" は読み込みモードを意味します。readln 関数は、ファイルから1行の内容を読み込む関数です。 実は readln 関数が返すのは配列なのですが、"r" モードの時は行内容をそのまま要素数 1 で返し、このように非配列変数( line )で受け取れます。

数値CSV形式や数値TSV形式のファイル入出力

open 関数は、数値CSV/TSV形式のファイルを扱えます。まずは書き込みです:

WritelnCSV.vcssl

実行すると、ファイル「test.csv」に、改行を挟んで「0,1,2」「3,4,5」と書き出されます。

続いて読み込みです:

ReadlnCSV.vcssl

実行すると先ほどの「test.txt」を読み込み、改行を挟んで「0 1 2」「3 4 5」と出力します。このように、rcsvモードでのreadln関数は、1行をコンマ区切りで読み込み、配列で返します。

ここで扱ったwcsv / rcsv モードでは数値CSV形式ですが、同様にwtsv / rtsv モードで数値TSV形式も扱えます。 ただし、これらの機能は、あくまで「数値」を対象とする、単純なものであるという点に注意が必要です。 文字列をCSV形式やTSV形式で正しく扱うには、file.TextFileライブラリ( https://www.vcssl.org/ja-jp/lib/file/TextFile )を使用してください。

ライブラリの読み込み (Vnanoでは搭載ソフト側の個別の方法参照)

文法面は大体まとめ終えたので、この節の最後に、ライブラリの扱いについて説明しておきます。

最初にライブラリを用意

まずは、以下の内容のファイルを「 TestLib.vcssl 」という名前で、「 testdir 」というディレクトリ(フォルダ)の中に作ってください:

- ./testdir/TestLib.vcssl -

TestLib.vcssl

include (Vnanoでは使用不可)

上で作ったライブラリを読み込みます。C/C++などではincludeがありますが、これはVCSSLでも使えます。 先ほどのtestdirディレクトリ(フォルダ)と同じ場所のプログラムから読み込むには:

IncludeLib.vcssl

実行すると「Hello」と出力されます。このように、includeの後には、「そのファイルから見た相対パス」を " " で囲って記述します。 includeはC/C++と同じく、ライブラリのコードがその箇所にそのまま展開されます。 しかしよく知られる通り、識別子の競合や多重includeなどの面倒もあります。

import (Vnanoでは使用不可)

includeで生じる面倒を回避した、より高度なライブラリ読み込み方法として、importも使用できます。 VCSSLでは基本的にincludeよりもimportが推奨です:

ImportLib.vcssl

実行すると「Hello」と出力されます。 このように、importの後には、「実行するファイル(ライブラリではない)から見た相対パス」を、スラッシュ区切り( / )の代わりにドット区切り( . )で指定します。

モジュールと名前空間 (Vnanoでは使用不可)

importで読み込んだライブラリは、includeと違ってその場に展開はされず、独立な形で読み込まれます。 この独立なプログラム単位をモジュールと呼び、多重importしても唯一性が確保されます。

全てのモジュールは互いにグローバル領域を参照できますが、暗黙的な名前空間も持ちます。 複数モジュールで識別子(変数名や関数名)が競合している場合、自身のモジュールのものが優先的に参照されますが、 所属名前空間を指定する事により、別モジュールのものも参照できます:

ModuleNamespace.vcssl

実行すると改行区切りで「Hello」「World」と出力されます。

標準ライブラリ

VCSSLの標準ライブラリは、以下のWebページで参照できます:

― VCSSL ライブラリ ―
  https://www.vcssl.org/ja-jp/lib/

なお、実はこれまでの解説でも使用していた標準ライブラリがあります。 それは「 System 」ライブラリで、例えばprint / println関数などもSystemライブラリが提供しています。 Systemライブラリは、特例的に全モジュールよりも先に自動で読み込まれるため、import不要で使用できます。

今回で、このガイド前半の基本編は終わりです。次回からは後半の応用編です。次回は、標準ライブラリの中から、実際に GUI と 2D / 3D グラフィックスを扱ってみます。