ICI言語を使おう

2012/7/1 aito

Cによく似たスクリプト言語

ICIは、1992年にTim Longが作ったスクリプト言語です。あまり注目されていませんが、もっと使われても良い言語だと思うので、紹介文を書いてみました。

ICIはスクリプト言語なので、perl, ruby, pythonあたりと同じような用途に使うことができます。すなわち、
といったことに使えます。

「じゃあ別に ruby とかでいいんじゃね?」と思わないでもないですが、ICIの最大の特徴は、C言語(C++ではない)によく似 ていることです。プログラミング言語を一つだけ知っている大学生などは「Cしか知らない」ことが圧倒的に多い ので、そういう人たちが使うのに適した言語だと思います。

ICIの文法はCに似ていますが、スクリプト言語に必要な機能を一通り持っています。たとえば、
などです。

準備とプログラム実行

ICIインタプリタはSourceForge のサイトからダウンロードできます。WindowsとLinux用のバイナリが用意されていますが、ソースプログラムからビルド するとMacOSでも動くようです。

ICIインタプリタのコマンド名は ici です。適当なスクリプト prog.ici について、

ici prog.ici

を実行することでスクリプトを実行することができます。

インストールするとPDFのマニュアルがついてきます。オンラインマニュアルもありま すが、どうやら不完全なようです。ちなみにマニュアルは英語版しかありません。

プログラム例:Cとの違い

入力の各行に整数が書いてあって、その総和と平均を出力するプログラムはこんな感じです。
/* 総和と平均 */
auto sum = 0;
auto n = 0;
while ((line = getline()) != NULL) {
  sum += int(line);
  n++;
}
auto avg = float(sum)/n;  // 途中で変数宣言しても良い
printf("N=%d\tSum=%d\tAverage=%f\n",n,sum,avg);


Cみたいにプログラムを書きたい場合に、気をつけることは次のようなところです。
コメントは(最近の)Cと同じで、/* コメント */ か、 // コメント(行末まで) のように書きます。

プログラム例:配列

次に、入力の各行に実数が書いてある時、その平均と分散を(ナイーブな方法で)計算するプログラムを見てみましょう。
/* 統計分析 */
data = [array];
while ((line = getline()) != NULL) {
  push(data,float(line));
}
n = nels(data);
sum = 0.0;
for (i = 0; i < n; i++) {
  sum += data[i];
}
mean = sum/n;
sum2 = 0.0;
for (i = 0; i < n; i++) {
  sum2 = pow(data[i]-mean,2);
}
var = sum2/n;
printf("N=%d\tMean=%g\tVariance=%g\n",n,mean,var);

ICIには「配列変数」というものはなく、通常の変数に「配列」を代入します。

data = [array];

最初から値の入っている配列を代入することもできます。

data = [array 1, 2, 3];

配列には異なる型のデータを混在させることができます。

data = [array 1, "hello", 3.1415];

配列は関数っぽく書くこともできます。

data = array(1, "hello", 3.1415);

配列を [array 式] と書いた時には式はプログラム読み込み時に評価され、array(式)と書いた時には実行時に評価されます。簡単な覚え方としては、[array ...] は定数で初期化するときに使い、プログラム内で動的に配列を作るときには array(...) を使うといいでしょう。

配列へのアクセスはCと同じですが、配列に上限はなく(下限は0)、配列サイズは必要に応じて大きくなります。また、次のような便利な関数/手続 きが使えます。

プログラム例:構造体と連想配列(ハッシュ)

ICIでは、Cの「構造体」のようなものも使えます。ICIの構造体は、実はperlなどにある「連想配列」(ハッシュ)と同じものです。例とし て、英語の文章を入力すると単語の出現頻度を数えるプログラムを見てみましょう。
/* 単語出現頻度カウント */
count = [struct];
words = gettokens();
forall (w in words) {
  if (count[w] == NULL)
    count[w] = 1;
  else
    count[w]++;
}
forall (n,w in count) {
  printf("%s %d\n",w,n);
}

[struct]は連想配列(構造体)です。配列のときと同じように「構造体変数」というものはなく、普通の変数に連想配列を代入することで連想 配列として使えるようになります。連想配列では、配列のキーとして文字列を使うことができます。

gettokens()関数は、入力をスペースで区切られたトークン列に分解して、それをまとめて1つの配列として返します。便利です。

この中に出てくる forall という文は、配列と連想配列の各要素に順番にアクセスするための制御文です。配列 a の全要素にアクセスする場合は
    forall (x in a) { ... }
同じく連想配列にアクセスする場合は
    forall (value, key in a) { ... }
と書けます。
※forall で連想配列にアクセスする場合、key と value の順番が perl や ruby と逆になっているので、これらの言語になれている場合には注意が必要です。

ICIのstructは、Cの構造体のように書くこともできます。次の3通りの書き方は、全く同じものを指します。
初期値を持った構造体は、
    x = [struct name = "Mike Smith", age = 24];
のように書くことができます。配列の時と同じように
    x = struct("name", "Mike Smith", "age", 24);
のようにも書けます。前者は定数による初期化、後者は実行時に構造体を作る書き方です。

プログラム例:関数定義

/* 関数の例 */

static factorial(n)
{
  if (n == 0)
    return 1;
  return n*factorial(n-1);
}

printf("%d\n",factorial(10));

関数定義はCとよく似ています。ただし、違う点は次のとおりです。

プログラム例:関数と変数

/* 関数と変数の例 */

static foo()
{
   x = 10;
   auto y = 20;
   printf("x=%d, y=%d\n",x,y);
}

static x = 100;
static y = 200;

foo();
printf("x=%d, y=%d\n",x,y);
このプログラムを実行すると、
x=10, y=20
x=10, y=200
と表示されます。

グローバル変数とローカル変数の話です。関数の外側で「auto 変数」または宣言なしに使う変数は auto 変数で、これを関数の中から参照することはできません。いわゆるグローバル変数を使う場合は「static 変数」という宣言が必要です。

static 宣言された変数は関数の中からアクセスできます。関数の中でローカル変数を使いたい場合には、auto 宣言をする必要があります。または、
x := 10;
と記述すると、xをローカル変数として値を代入することができます。

プログラム例:正規表現

ICIでも正規表現が使えます。正規表現は #...# と書くので、perlなどとはちょっと雰囲気が違いますが、中身の記述は同じです。

入力に "Name: 名前 Age: 年齢" という文字列が来た時に、名前と年齢の一覧を作るプログラムです。
/* 正規表現 */

namelist = [array];
auto a;
while ((line = getline()) != NULL) {
  if ((a = line ~~~ #^Name:\s*(.+)\s*Age:\s*(\d+)#) != NULL) {
     push(namelist,struct("name",a[0], "age",a[1]));
  }
}

forall (x in namelist) {
  printf("name=%s age=%s\n",x.name, x.age);
}

#^Name:\s*(.+)\s*Age:\s*(\d+)# の部分が正規表現です。正規表現の書き方自体は他の言語とほとんど同じです。正規表現を使った演算子や関数として、次のようなものが使えます (全部ではありません)。

プログラム例:ファイル入出力

ファイルの入出力はCと似ています。以下のプログラムは、コマンドライン引数に2つのファイル名を書くと、1番目のファイルを2番目のファイルに コピーします。
if (nels(argv) != 3) {
  printf(stderr,"Usage: %s from-file to-file\n");
  exit(1);
}
f1 = fopen(argv[1],"r");
f2 = fopen(argv[2],"w");
while ((line = getchar(f1)) != NULL) {
  put(line,f2);
}
close(f1);
close(f2);

まず本題のファイル入出力から言うと、fopen() がファイルを開く関数で close() が閉じる関数です。閉じるほうが fclose() でないところがいまいちです。使い方はほとんどCと同じですので、説明の必要はないでしょう。

この例題ではコマンドライン引数を使っています。コマンドライン引数は配列 argv に格納されています(配列自体から長さを nels() で取り出せるので、argc はありません)。argv[0] には常にスクリプト名が入ります。引数は argv[1] 以降です。

ファイル入出力には、例えば次のような関数があります。
通常のファイル入出力だけでなく、ソケット通信もできます。

その他

他にもだいたい普通のスクリプト言語でやりたいと思う機能は一通り揃っていて(配列のソート、文字列操作、外部コマンドの呼び出し、等々)それな りに便利ではあります。関数がオブジェクトだったりして、普通のスクリプト言語とちょっと違う雰囲気の部分もあります。

また、ICI4 からオブジェクト指向っぽいプログラムが書けるようになったのですが、C++ともJavaともObjective-Cとも違う雰囲気です。概念的には JavaScriptに近いですが、記述法はだいぶ違います。