= AScript の紹介

[[PageOutline(start=2, depth=9, type=unordered)]]

== AScript とは

=== 言語としての AScript

AScript は、リストやイテレータで表される複数データを一度に処理できる
「暗黙的マッピング処理」と「メンバマッピング処理」を特徴としたスクリプト言語です。
煩雑なリスト要素操作や制御構文を記述せずに、データを処理できます。

他に以下の特徴を持っています。

 * オブジェクト指向プログラミングに必要な、クラスとインスタンスの機構を持っています
 * ブロック形式で記述した手続きを関数に渡すことができます
 * モジュール管理機能を持っています。C++ でユーザ定義のモジュールを書くことができます
 * 評価前の式を引数にした関数を定義できます。この機能を使い、{{{if}}} や {{{while}}} などの制御構文もすべて関数で実装されます
 * クロージャに対応しています
 * 関数の引数にオプション引数・デフォルト値・可変長引数を指定できます。
   呼び出しの際は、キーワード引数指定ができます。

=== グルーとしての AScript

AScript に組み込まれたイメージデータ抽象化、データストリーム抽象化および
仮想パス名機能により、さまざまな処理を行うモジュールが、お互いに協調して動作できます。
以下のようなユースケースが、自然な形で実現できます。

 * イメージファイルを読み込んで他のフォーマットに変換
 * イメージファイルを読み込んで、二次元または三次元グラフィック描画を重ねあわせて
   ファイルに書き出し。または画面描画。
 * データベースの数値に基づいてグラフを描画し、PNG 形式にした後 HTTP プロトコルに出力
 * 複数の PNG ファイルをまとめて Icon ファイルを出力
 * zip ファイルの中のイメージファイルを取り出して、HTTP プロトコルに出力
 * HTTP 経由で CSV ファイルを読み込んでデータ処理

=== AScript が提供する機能

AScript はイメージデータを扱うクラスを標準で提供しています。
このクラスを中心に、イメージファイルの読み書き・イメージ描画・画面表示などを
行うモジュールが提供されます。

現在対応しているファイルフォーマットは BMP, JPEG, PNG, GIF, PPM, Icon です。
いずれも読み込みおよび書き込みが可能です。

イメージ描画のために、以下のモジュールが実装されています。

 * 二次元グラフィックライブラリ Cairo のインターフェースモジュール: cairo
 * 三次元グラフィックライブラリ OpenGL のインターフェースモジュール: opengl

画面表示のために、以下のモジュールが実装されています。

 * GUI を提供する Tk インターフェースモジュール: tk
 * 高速なグラフィック描画を行う SDL のインターフェースモジュール: sdl

HTTP サーバ・クライアントの機能を実現する http モジュールを提供します。
このモジュールをインポートすると、インターネット上の資源をローカルファイルと
同じように扱うこともできるようになります。

zip ファイルの読み書きを行うモジュール zipfile と、tar ファイルの読み込みを行う
モジュール tar を提供します。これらのモジュールをインポートすると、
アーカイブファイルの中のファイルも、通常ファイルと同じようにオープンすることが
できるようになります。

正規表現を処理するモジュール re を提供します。

標準的なテキストフォーマットの読み書きをするモジュールを提供します。

 * CSV フォーマット: csv
 * XML フォーマット: xml
 * YAML フォーマット: yaml

== インストール

Linux および Win32 に対応しています [http://sourceforge.jp/projects/ascript/releases/ (ダウンロード)]。
Ubuntu 10.10、Windows XP および Vista で動作確認をしています。

=== For Windows

ファイル
[http://sourceforge.jp/projects/ascript/downloads/49181/ascript-0.01-win32.zip/ ascript-0.01-win32.zip]
を適当なディレクトリに展開してください。

=== For Ubuntu Linux

ファイル
[http://sourceforge.jp/projects/ascript/downloads/49181/ascript-0.01-src.tar.gz/ ascript-0.01-src.tar.gz]
をダウンロードします。

以下の手順で AScript の本体をビルド・インストールします。

{{{
$ tar xvfz ascript-0.01-src.tar.gz
$ cd ascript-0.01/src
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig     # 必要に応じて
}}}

続けて、以下の手順でモジュールファイルをビルドします。

{{{
$ ascript build_modules.az
$ sudo ascript build_modules.az install
}}}

エラーが出る場合は、モジュールに必要なライブラリがシステムにインストールされていない
可能性があります。エラーメッセージにしたがって、パッケージをインストールしてください。

== 実行方法

{{{ascript[.exe]}}} はコマンドラインプログラムです。

 * 引数にスクリプトファイルを指定すると、その内容を実行します。スクリプトファイルの拡張子は {{{.az}}} です。
 * {{{ascript[.exe]}}} を引数なしで実行するとプロンプトが出て対話モードに入ります。終了するときは、キーボードから {{{Ctrl+C}}} を入力、または、コマンド {{{sys.exit()}}} を実行してください。

== ドキュメント

||AScript 言語リファレンス||[[LinkAttach(AScriptLanguageReference.pdf, caption=AScript Language Reference (英語版))]]||
||AScript ライブラリリファレンス||[[LinkAttach(AScriptLibraryReference.pdf, caption=AScript Library Reference (英語版))]]||

== 主な特徴

=== 暗黙的マッピング処理

「暗黙的マッピング処理」とは、数値や文字列をとる演算子や関数にリストやイテレータを与えた時、要素ごとに処理を施した結果を、リストまたはイテレータとして返す機能です。例を以下に示します。

{{{
>>> [1, 2, 3, 4] + [5, 6, 7, 8]
[6, 8, 10, 12]
>>> [1, 2, 3, 4] + 5
[6, 7, 8, 9]
>>> ([1, 2, 3, 4] + [5, 6, 7, 8]) / 2
[3, 4, 5, 6]
>>> [3, 8, 0, 4] < [4, 5, 3, 1]
[true, false, true, false]
>>> math.sqrt([1, 2, 3, 4])
[1, 1.41421, 1.73205, 2]
>>> string.upper(["aaa", "bbb", "ccc"])
["AAA", "BBB", "CCC"]
}}}

多くのプログラミング言語は上と同じ処理をするために、リストから要素をとりだして、処理をし、結果用のリストを生成するという手順をとります。AScript では、このプロセスが言語仕様の中に入っているので、明示的なループや高階関数を使うことなく、複数のデータを一括で処理できます。

「暗黙的マッピング処理」は入れ子になったリストに対しても有効です。

{{{
>>> string.len([["The", ["quick", "brown", "fox"]]], "jumps", "over", ["the", ["lazy", "dog"]])
[[[3, [5, 5, 3]]], 5, 4, [3, [4, 3]]]
}}}

AScript が提供する関数のほとんどは「暗黙的マッピング処理」がデフォルトで有効になります。ユーザ定義の関数も、アトリビュートを指定することで対応させることができます。

「暗黙的マッピング処理」をさまざまなデータ入出力関数や処理関数と組み合わせると、制御構文を記述することなく多くの課題を解決することができます。以下に例をあげます。

{{{
>>> x = [1, 2, 3, 4]
>>> printf("result = %2d, %2d, %2d, %f\n", x, x * x, x * x * x, math.sqrt(x))
result =  1,  1,  1, 1.000000
result =  2,  4,  8, 1.414214
result =  3,  9, 27, 1.732051
result =  4, 16, 64, 2.000000
}}}

{{{x * x}}} や {{{math.sqrt(x)}}} などの式で「暗黙的マッピング処理」が働いてリスト要素ごとの演算をしています。
さらに {{{printf}}} の実行でも、リストが引数として与えられたことによってやはりこの機能が作動し、要素ごとの表示処理をします。
{{{printf}}} は値を持たない関数なので、結果としてのリストは生成しません。

行番号をつけてファイルを表示するプログラムは以下のように書けます。

{{{
printf("%7d %s", (1..), open("hoge.txt").readlines())
}}}

{{{1..}}} と {{{file#readlines()}}} はリストではなくイテレータを返します。「暗黙的マッピング処理」はイテレータに対しても有効です。
{{{1..}}} は 1 から始まる無限数列を表しますが、長さの異なるリストやイテレータが与えられた場合は短い方にあわせられるので
表示する行数は {{{file#readlines()}}} が終了するまでになります。

以下は正規表現を使ってファイルから情報を抽出し、表示する例です。

{{{
import(re)
lines = open("hoge.h").readlines()
println(re.match(r"class (\w+)", lines).skipnil():*group(1))
}}}

{{{file#readlines()}}} で生成したイテレータ {{{lines}}} を受け取った {{{re.match()}}} は結果として match インスタンスを要素にするイテレータを返します。
{{{re.match()}}} は、パターンに合致しない場合は {{{nil}}} を返すので、イテレータのインスタンスメソッド {{{skipnil()}}} を使って {{{nil}}} 値をスキップするイテレータを生成します。
{{{:*}}} は後述するメンバマッピングオペレータで、上の例では、イテレータの各要素に対して {{{match#group()}}} メソッドを実行しています。

=== クラス宣言・インスタンス生成と、メソッド呼び出し・メンバマッピング

クラス宣言、インスタンス生成およびメソッド呼び出しの例を以下に示します。

{{{
Fruit = class {
    __init__(name:string, price:number) = {
        self.name = name
        self.price = price
    }
    Print() = printf("name:%s  price:%d\n", self.name, self.price)
}
fruits = [Fruit("apple", 100), Fruit("orange", 80), Fruit("grape", 120)]
for (fruit in fruits) {
    fruit.Print()
}
}}}

「構造体」「リスト初期化」「メンバマッピング」の機能を使うと、上の例はもっと簡潔に書けます。

{{{
Fruit = struct(name:string, price:number) {
    Print() = printf("name:%s  price:%d\n", self.name, self.price)
}
fruits = @(Fruit) {
    { "apple", 100 }, { "orange", 80 }, { "grape", 120 }
}
fruits::Print()
}}}

上の例に続けて、もう少し遊んでみます。以下はメンバをキーにしてソートをし、表示する例です。「シンプルラムダ」と呼ぶ簡易関数オブジェクト生成機能を使っています。

{{{
fruits.sort(&{$f1.price <=> $f2.price})::Print()
fruits.sort(&{$f1.name <=> $f2.name})::Print()
}}}

値段の合計と平均を求めます。

{{{
printf("sum = %.1f, average = %.1f\n", fruits::price.sum(), fruits::price.average())
}}}

一番長い名前の長さにそろえて一覧表示します。一見単純に見えるこの手の処理は、制御構文を使うと案外煩雑になってしまうのですが、「メンバマッピング」のおかげですっきりと記述できます。

{{{
printf("%-*s %d\n", fruits::name::len().max(), fruits::name, fruits::price)
}}}

メンバマッピングオペレータ {{{::}}} は即座に評価をしてリストを生成します。そのため、処理対象のデータが多くなると速度・メモリ効率が悪くなります。
イテレータを返すメンバマッピングオペレータ {{{:*}}} を使うと、結果が必要なときまで評価が先送りされるので、
性能が改善できます。上の例は以下のように記述することができます。

{{{
printf("%-*s %d\n", fruits:*name:*len().max(), fruits:*name, fruits:*price)
}}}

フィルタ関数を使って値段が 100 円未満のものを表示します。

{{{
fruits.filter(&{$f.price < 100})::Print()
}}}

フィルタ関数は条件項にイテレータをとることもできます。
暗黙的マッピング処理やメンバマッピング処理と組み合わせると、上記のプログラムは以下のように書けます。

{{{
fruits.filter(fruits:*price < 100)::Print()
}}}

=== 関数の引数と Quoted Value

関数の引数には、オプション引数・デフォルト値・可変長引数を指定できます。

{{{
f1(a, b?, c?) = printf("%s, %s, %s\n", a, b, c)
f1(2)                                # 2, nil, nil

f2(a, b = 10, c = "abc") = printf("%s, %s, %s\n", a, b, c)
f2(2)                                # 2, 10, abc

f3(a, b, c*) = printf("%s, %s, %s\n", a, b, c):nomap
f3(2, 3, 4, 5, 6, 7)                 # 2, 3, [4, 5, 6, 7]
f3(2, 3)                             # 2, 3, []

f4(a, b, c+) = printf("%s, %s, %s\n", a, b, c):nomap
f4(2, 3, 4, 5, 6, 7)                 # 2, 3, [4, 5, 6, 7].
f4(2, 3)                             # error. c has to get at least one value.
}}}

関数呼び出しの際は、キーワード引数指定ができます。キーワードと値は、辞書演算子 ({{{=>}}}) で対応づけます。

{{{
g1(a, b, c) = printf("%s, %s, %s\n", a, b, c)
g1(2, b => 3, c => 4)                # 2, 3, 4
}}}

引数リストの中に、{{{%}}} を後尾につけたシンボルを加えておくと、引数リストに合致しないキーワード引数指定の組を辞書にした値をそのシンボルに割り当てます。

{{{
g2(a, b, dict%) = printf("%s, %s, %s\n", a, b, dict)
g2(2, b => 3, c => 4, d => 5)        # 2, 3, %{c => 4, d => 5}
g2(2, 3, c => 4, d => 5)             # 2, 3, %{c => 4, d => 5}
}}}

引数宣言のシンボル名の先頭にバッククオートをつけると、未評価の式 (quoted value) を値として渡すことができます。

以下は、quoted value を使って、C の {{{for}}} ステートメントのような動作をする関数を作成している例です。

{{{
c_like_for(`init, `cond, `next):dynamic_scope {block:inside_scope} = {
    env = outers()
    env.eval(init)
    while (env.eval(cond)) {
        block()
        env.eval(next)
    }
}

n = 0
c_like_for (i = 1, i <= 10, i += 1) {
    n += i
}
printf("i = %d, sum = %d\n", i, n)
}}}

quoted value やブロックのような「未評価の式」が関数呼び出しの中に入ってくると、それらをどの変数スコープで評価すればよいかが問題になってきます。上のプログラムは AScript における解決方法を端的に表していますので、興味のある方は動作内容を追ってみてください。

=== ブロック

制御構文などの表記を可能にするため、Ruby の「ブロック付きメソッド」の概念を導入しています。 {{{if}}} や {{{while}}} などの構文や、クラス宣言、リスト・辞書要素の初期化表記も、すべてこの機構を使って実装されています。

{{{
# example of block declaration
f(x:number) {block} = {
    block(x)
    block(x + 1)
    block(x + 2)
}
}}}

呼び出し方は以下のようになります。

{{{
f(2) {|x|
    print(x)
} # 234
}}}

ブロック自体は特殊な変数スコープを持たせた関数オブジェクトで、ブロックパラメータは単に関数への引数になります。ブロックパラメータの操作が周囲に影響を与えることはありません。また、通常の関数オブジェクトで定義できる引数設定 (オプション引数・デフォルト値・可変長引数など) は、ブロックパラメータでも有効です。

{{{
x = 0
f(2) {|x|         # dummy arguments are completely isolated from outside
    print(x)
}
println(x)        # 0
}}}

ブロックを表すシンボルは何でもかまいません。

{{{
# any symbol can be used to specify a block
f(x:number) {yield} = {
    yield(x)
    yield(x + 1)
    yield(x + 2)
}
}}}

シンボルの後にクエスチョンマークをつけたブロックは、オプショナルとして扱われます。
この場合、ブロックをつけないで関数を実行すると {{{nil}}} に設定されます。

{{{
# a block specified by a symbol with a suffixed question is treated as optional
f_opt() {block?} = {
    if (block == nil) {
        println("not specified")
    } else {
        block()
    }
}

f_opt() # print "not specified"

f_opt() {
    println("message from block")
}       # print "message from block"
}}}

ブロックは通常、それを実行している関数の「外側」の環境にアクセスできる変数スコープで動作します
(「外側」とはレキシカルスコープのそれになりますが、ダイナミックスコープに切り替えることもできます)。
関数外部の変数の値を変更することができます。

{{{
g() {block} = {
    block()
}

n = 2
g() {
    n = 5
}
printf("n = %d\n", n) # n = 5
}}}

ブロックシンボルにアトリビュート {{{:inside_scope}}} をつけると、関数内部のスコープに切り替わり、
関数の内部処理で設定される変数などにアクセスできるようになります。
関数外部の変数は、参照できた値に対しての改変が可能です。

{{{
h() {block:inside_scope} = {
    m = "local in h()"
    block()
}

h() {
    printf("%s\n", m)    # h()'s local variable m is accessible
}

n = 2
h() {
    n = 5
}
printf("n = %d\n", n)    # n = 2

h() {
    n += 5
}
printf("n = %d\n", n)    # n = 7
}}}

quoted value にしたブロックを設定した変数を {{{{|..|}}}} で囲って関数に渡すと、それがブロック本体として扱われます。
ブロックパラメータも記述できます。例えば:

{{{
f() {block} = {
    block(1, 2, 3, 4)
}
}}}

という関数があった場合、以下のふたつの呼び出しは等価です。

{{{
block = `{|a, b, c, d|
    printf("%d %d %d %d\n", a, b, c, d)
}
f() {|block|}

f() {|a, b, c, d|
    printf("%d %d %d %d\n", a, b, c, d)
}
}}}

ブロックは通常、関数オブジェクトとして扱われますが、シンボルの先頭にバッククオートをつけると
quoted value として渡されます。これと、先に示した quoted value をブロック本体として渡す機能を使うと、
ブロックの delegation ができます。

{{{
# although a block is usually set as a function object, it would be passed as a
# quoted value if it's specified by a symbol with a prefixed back quote.
# with another special rule, which passes a quoted value as its block body,
# block information can be delegated. 
f(x:number) {`block} = {
    repeat(x) {|block|}
}
}}}

=== クロージャ

以下のプログラムが期待どおりに動きます。

{{{
new_counter(n:number) = {
    lambda() { n += 1}
}
cnt = new_counter(2)
printf("%d\n", cnt())
printf("%d\n", cnt())
printf("%d\n", cnt())
}}}

== 応用プログラム例

AScript を使った応用プログラム例が sample ディレクトリの下に格納されています。
ご参照ください。

||Cairo を使った二次元グラフィック描画||sample/cairo||
||OpenGL を使った三次元グラフィック描画||sample/opengl||
||HTTP のクライアントおよびサーバ機能||sample/http||
||SDL を使った画面描画||sample/sdl||
||Tk を使った GUI プログラミング||sample/tk||
||Active-X を使った Win32 操作||sample/win32||

== 開発状況

現在の開発状況はベータ版です。モジュールによっては、実装が不完全なものがあります。

ご意見、ご感想を [http://sourceforge.jp/forum/forum.php?forum_id=22931 フォーラム] にてお待ちしています。

''Please post your idea and opinions into [http://sourceforge.jp/forum/forum.php?forum_id=22931 Forum]. Messages written in English would also be welcome.''

== 使用した開発環境・ライブラリ・ツール

以下の開発環境・ライブラリ・ツールを使わせていただきました。ありがとうございます。

||開発環境||[http://www.embarcadero.com/jp/ Borland C++ Compiler] 5.5||
||正規表現||[http://www.geocities.jp/kosako3/oniguruma/ 鬼車] 5.9.2||
||乱数生成||[http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index-jp.html Mersenne Twister SFMT] 1.3.3||
||XML パーサ||[http://www.libexpat.org/ Expat] 2.0.1||
||YAML パーサ||[http://www.yaml.org/ YAML] 1.2||
||SDL||[http://www.libsdl.org/ SDL] 1.2||
||cairo||[http://cairographics.org/ cairo] 1.10.2||
||JPEG||[http://www.ijg.org/ Independent JPEG Group] 8c||
||PNG||[http://www.libpng.org/pub/png/libpng.html libpng]||
||TIFF||[http://www.libtiff.org/]||
||zlib||[http://www.zlib.net/ zlib]||
||bzip2||[http://bzip.org/ bzip2]||
||Tcl/Tk||[http://www.tcl.tk/ Tcl/Tk] 8.5||
||データベース||[http://www.sqlite.org/index.html SQLite] 3.6.23.1||
||combination||[http://photon.poly.edu/~hbr/boost/combinations.html Header <boost/algorithm/combination.hpp>]||
||CSV テストデータ生成||[http://kazina.com/dummy/ なんちゃって個人情報] ||

[[ReleaseList]]
[[RecentTickets(limit=5)]]
