Top / Alan言語仕様

Alan v0.31
Reference

Last updated: 2007-08-08
Created: 2004-12-11


by Fumisky Wells

はじめに

この書は Alan の言語仕様を網羅したものです。 そのため、初心者にとっては難しい個所があるかもしれませんが、 気にしないでください。難しい部分は後から必要に応じて参照すれば良く、 それで十分です。

逆に、茶帯以上の方(意味不明)は、言語仕様に関してはある程度把握している 必要はあります。君は黒帯を目指せるか、なんてね。

CONTENTS

  1. 文法
    1. 変数定義文
    2. 演算子式
    3. 代入
    4. 代入(2)
    5. 単位数の増減
    6. if
    7. for
    8. loop
    9. break
    10. 関数定義
    11. enum定義
    12. return
    13. 例外処理
    14. import文
    15. クラス定義
  2. データ型
    1. 基本型
      1. 整数型
      2. 文字列型
      3. byte型
      4. 論理値型
      5. 正規表現型
      6. ファイル型
      7. 実数型
      8. void型
      9. 部分文字列型
      10. files型
      11. type型
      12. err型
    2. コンテナ型
      1. 配列型
      2. リスト型
      3. map(連想配列)型
    3. クラス定義
  3. 組込み関数
  4. 組込み変数
  5. パッケージ
    1. Math --数学関係
    2. Net --ネットワーク関係
    3. Time --時間・日付関係
    4. Filesys--ファイルシステム関係
  6. 制約事項

文法

簡単なものから難しい(と思われる)ものへ、という順番で Alan の 文法を説明します。
  1. Alan のプログラムとは、式の集まりです。
  2. 式とは下記を指します:
    1. 変数定義
    2. 演算子式
    3. 代入
    4. 代入(2)
    5. 単位数の増減
    6. if
    7. for
    8. loop
    9. break
    10. 関数定義
    11. enum定義
    12. return
    13. import
    14. クラス定義
    これが示すように、
    1. 定義式と実行式の順は任意です。 Perl と同様、いきなりメインプログラムを書いたりできます。 C/C++ のように必ず関数で囲う必要はありません。 Java や Eiffel のように必ず Class で囲う必要もありません。

      これは、学習の容易さを目指したためです。小規模プログラム (数10行程度) には、メイン関数定義や トップクラス定義はあまり重要ではありません。 小規模プログラムにはそれなりの記述で済ませる手軽さがあってよい と思います。

    2. また、文と式の区別は無く、全て「式」です。つまり、 ifも loop も全て式です。つまり、値を結果値を持ちます。 Ruby と同様です。

  3. 変数定義は下記となります:
    文法: 型:変数名 [=初期値 ] {, 変数名 [=初期値 ]}
    例: int:x=3, y=10

    (NOTE: 上の文法の この色の部分は、
    [... ] -- 省略可能。
    {... } -- 0回以上の繰り返し
    を表すメタ文字です。そのほか、
    {...}+ -- 1回以上の繰り返し
    [0-9] -- 数値(0..9)一文字
    [a-z] -- 小文字アルファベット(a..z)一文字
    という表現もあります。以下、同様です。)

    Fortran/Algol68/C/C++ と同じ似た構文を採用しました。というのは、 英語、日本語で読んでも 左から右に意味が通りやすいと思ったからです。 例えば、上の int:x = 3 は下記のように左から右に読めます。

    "Integer x is 3."
    「整数 x は 3」

    コロンは、構文解析を簡単にするためにつけました。

    これに対し Pascal 系の変数宣言文は下記のようになります:

    x : integer
    これは数学の
    x ∈ integer
    に似ているのでこれだけを見ると好まれるかも知れないのですが、 初期値をセットするところで
    x : integer = 3;
    などとなって 個人的には違和感を感じてます(まあ慣れの問題かもしれませんが)。

    Alan とちょうど逆です。Pascal系言語を使用されていると混乱する かもしれませんが、Alanは俺言語なので他言語との一貫性は あまり考慮しません。

    なお、C を設計した Denis Ritchie はその後 Inferno という分散 OS の記述言語 Limbo で、変数宣言を Pascal 式にしたそうです(!)。

    NOTE: 定義と宣言
    Alan では、変数宣言ではなく変数定義と呼ぶことにします。 定義とは、ある文字 x が出てきた場合、それが何かを定義するもの。 宣言とは、ある文字 x をここで使用すると宣言するもの、という違いがあります。 関数の場合は違いがはっきりしています。 関数の定義は C では int f(){...} で、関数の宣言は int f(); です。 後者は、f を使用する場所で、「文字 f をこういう形で使用するよ」 と宣言しているわけです。変数の場合、定義とも宣言ともとれる わけなので、話を簡単にするために定義という表現に統一しました。

  4. 演算子式は下記となります(優先順位順。低->高):
    式の種類 説明
    A or B 論理和
    A and B 論理積
    not A 論理否定
    A == B,
    (以下同様に)
    <>, <, <=, >, >=, in
    比較演算子。順に、等しい、等しくない、小さい、 小さいか等しい、大きい、大きいか等しい、 集合に含まれる(文字列がパターンにマッチする)
    A | B パイプ
    A + B, A - B 和、差。+ は文字列の結合にも使用。
    A * B, A / B, A mod B 積、商、剰余
    -A 単項マイナス
    A(引数)
    A.メンバ
    関数呼び出し
    メンバ選択
    1234 整数リテラル
    "..." 文字列リテラル
    /regular expression/ 正規表現リテラル
    変数名, 関数名 変数名, 引数をとらない関数の呼出
    (式) 括弧で囲った式

    C の演算子を基本としていますが、下記の点が異なっています:

    メンバ選択演算子 '.' の左辺に、任意の式が使用できるようになりました。

  5. 代入:
    文法: 変数 = 式
    例:
    a = a + 10
    s = s +  " (^_^)/"
    

    頻度の多い代入文は、やはり Pascal系の := よりは C系の = が読みやすいと思い、こちらを採用しました。 なお、C とは異なり、代入は式ではなく文です。 これにより、if 文などの条件式に比較式 a == b のつもりで a = b と書いてしまう落とし穴はコンパイル時にチェックされます。 (もっとも、bool型を導入したので、bool型以外のデータ型であれば a = b を代入式としても if 文でチェックできるようにはなるのですが、 そこまではしていません)。

  6. 代入(2):
    文法: 変数 += 式;
    変数 -= 式;
    変数 *= 式;
    変数 /= 式;
    例:
    a += 10
    s +=  " (^_^)/"
    

    C と同様に、a = a + b の省略形として a += b と 記述することができます。

    加算と同様、a -= b (a から b を引く), a *= b (a に b を掛けた 結果を a に代入), a /= b (a/b の結果を a に代入) も 記述できます。

  7. 単位数の増減:
    文法: 変数++;
    変数--;
    例: a++;
    a--;

    変数を単位数(現在は全てのデータ型共に 1)だけ 増加(++) または減少(--) させます。

  8. if:
    ifは、Modula-2 や Ruby と同じ構造です。
    文法: if 条件式 then 式 {elsif 条件式 then ... }[ else 式 ] end
    例:
    int:x = 3
    if x==0 then
       put("x is 0\n")
    elsif x==1 then
       put("x is 1\n")
    else
       put("x is other\n")
    end
    

    なお、ifは式なので、上の例はこのようにも書けます:

    int:x = 3
    put(if x==0 then
          "x is 0\n"
        elsif x==1 then
          "x is 1\n"
        else
          "x is other\n"
        end
    )
    

  9. for文:
    文法: for 型 変数名 in 式 { ,式 } do 文 end
    例:
    # for文中の 1,10 は、1 から 10 までカウントする反復子
    for int:i in 1, 10 do
       put(i)
    end
    
    反復子を用いた繰り返しは上のようなスタイルとなります。

    Ver0.09 では反復子は下記の種類をサポートしています:

    途中で for式を脱出する場合は break文を使用します。

    反復子を使った方が簡単に繰り返しが記述できるので、まず for文を使うことを考えて下さい。 それが無理な場合は次の loop 文で対処します。

    for式と等価な loop式は次のようになります。反復子がどのように生成され 補助変数 i と作用しているかが分かるかと思います:
    for int:i in 1, 10 do
       :
    end
    int.iter: I = int.iter.init(1, 10)
    int: i
    loop
       if not I.get(i) then break end
       :
    end
    

  10. loop:
    loopは、繰り返し処理を行う場合に使います。
    文法: loop 文 end
    例:
    # smile storm
    loop
       put("(^_^)/ ")
    end
    

    loopは、forでは対処できない一般的な繰り返し処理の場合 に使うことになります。
    forと同様、loopを脱出するには、後で述べる breakを使用します。

    Alan では、繰り返しは forと loopの2つだけです (*6)。 他の言語にあった、while、do...while、repeat...untilは、 すべて loopで対応できるので、文法的には数を絞りました。 もちろん、whileを用いるよりも loopを使用した方が 若干冗長になりますが、これは

    ちょっとした便利さのために文法を追加するよりも、覚えること を少なくする方を優先する
    というトレードオフで決めています。

  11. break:
    文法: break
    例: break

    for式、loop式の脱出に使います。 Ver0.09 では、1重の for か loop の脱出のみサポートしています。

  12. 関数定義は下記となります:
    文法: 戻値型 : 関数名(パラメータ定義) 式 end
    例:
    # 1からnまでの和を求める
    int: sum(int: n)
       if n==1 then
          1
       else
          n + sum(n-1)
       end
    end
    
    put(sum(100))
    

    戻り値は 最後の式の値となります。Perl, Ruby, shell などと同じです。

    関数の入れ子(関数の中で更に関数を定義すること。Nest)は できません(*)。

    (*) この制約により、スタックフレーム(あるいはアクティベーションレコード) が単純になります。C/C++ と Pascal の違いです。関数の入れ子は直交性の点では 美しいのですが、それに伴う実行時コストと利益を考え、Alan でも C/C++ と同様 としました。

    引数の性質

  13. enum定義
    文法: enum定義 ::= enum { ID列 | enum定義 }+ end
    例
    enum Family                     # type definition
      FATHER, MOTHER
      enum children
        BROTHER, ME, SISTER
      end
    end
    
    Family: f = Family.children.ME  # var definition
    
    put(f, "\n")                    # print
    
    if f == Family.FATHER then      # comparison
      put("father!\n")
    else
      put("not father!\n")
    end
    
    if f in Family.children then    # 'in' comparison
      put("child\n")
    else
      put("not child\n")
    end
    

    実体は32bit整数ですが、整数値に文字を与えることで、 ソースの可読性を増すのが enumの目的です。 下記の特徴があります:

    1. put() すると、整数値ではなく対応する文字列が印字されます。
    2. ネストした enum 定義で ID をグループ化できます。
    3. グループに属するか否かを 'in' 演算子で実行できます(例の f in Family.children 参照)。実体は bitmask 演算です。
    4. 名前空間が C/C++ の enum と異なり、1段狭くなっています。 たとえば enum Family FATHER, ... end とあると、必ず Family.FATHER と指定しなくてはいけません(C/C++ では FATHER が Family と同じ 名前空間内にある)。

    演算子

    比較 ==, <>, in
    ==, < は ID同士の比較、in は IDと enumグループとの間の比較に 用います。

  14. return
    文法: return
    例: return
    関数から呼び出し側に戻るときは、関数の終りに至った時と return文で戻る方法の2種類あります。

    return時の戻値は、return 直前の式の値となります。

    つまり、C/C++/Perl などのように "return 式" とは書けません。 文法上矛盾が発生してしまうため、"return 式" は却下になりました。

  15. 例外処理
    文法(単一式): 通常の単一式 式
    rescue拡張した式 式 || ... [ else ... ] end
    例
    # file open に失敗した場合、メッセージを出し、すぐ終了
    # (ただし、以下はあくまで例として挙げたのであって、この程度の
    # エラー処理であれば rescue無しで済ませた方がシンプルでしょう)
    
    void: f()
       file: f = "/etc/password" ||
          put("cannot open /etc/password\n")
          return
       end
       for str:s in f do
          put(s)
       end
    end
    
       f
    
    文法(複合式): 通常の複合式 ... end
    rescue拡張した複合式 ... { rescue { err型要素 } then ... } end
    例
    # loop中に発生した場合への対応
    loop
        #式
    rescue then
        put("exitting loop...\n")
    end
    

    Alan では例外処理をサポートします。

    単一式(endで終わらない式。1+1など)での例外処理が Alan の意図する例外処理だと考えているので、まずこちらから 説明します。

    Alan では、エラー処理 = 例外処理、です。従来の言語 (Cなど)の if 文によるエラー処理はそのまま Alan の ||演算子や rescue句で書ける、と考えてください。

    「それでは従来の言語の if文でのステータスチェックと変わらないのでは?」 という疑問があるかもしれませんね。 rescue句を書かない場合は、エラーが発生したときに自動的に それをユーザに通知し、自動的に上位(ブロック・関数呼出し元)に 戻っていく点が異なります。つまり、rescue句がない場合は、 例外処理をサポートしている言語(Ruby, Eiffel, Ada, C++, Java など) と同様の動きをします。 この点、if文によるステータスチェックですと、ステータスチェックを 怠ると、エラーが発生しても無視して次を処理しようとします。 この点が大きな違いです。

    私の知る限り、Alan 以外で単文レベルで例外ハンドラが書けるのは Ruby だけです(Lispはどうなんでしょうか?どなたか教えてください)。

    単一文に例外処理を付加する場合、例外処理演算子 '||' に続いて ... end まで記述してください。 shell や Perl をご存じの方は、'||' に親しみがあるかと思います。

    複合式( = endで終わる式)の場合、式の終りを表す end を、 rescue ... end に 拡張し、エラー処理を記述することで、そのブロック内で発生した エラーに対処することができるようになります。

    また、エラーが発生した場合にすぐに終了させたい場合は rescue句も記述する必要はありません。自動的に最上位まで エラーが伝搬し、exit(1) します。

    制約事項

    1. retry はありません。
    2. v0.08 で採用していた "Organised Panic" は廃止しました。 DbC に基づく Organised Panic が正しいのか、Ada/C++/Ruby 等、メジャーな言語の例外処理フローのほうが正しいのか、 未だに判断が付きかねますが、実装としては rescue 句の 最後に raise0() を発行するかどうかの違いだけなので、 今は両者を試している、というところです。 俺言語の利点かな?

    サンプルは、 R/src/tst/v0.03/4_exception/ をご覧下さい。

    エラーコード

    例外(エラー)発生時、エラーの識別のために enum err型のエラーコードを 用いています。

  16. import
    文法: import パッケージ名 [ 別名 ] { , パッケージ名 [ 別名 ] }* end
    例1:
    import
       Math
    end
    put(cos(0.0))
    
    例2:
    import
       Math M
    end
    put(M.cos(0.0))
    

    importは、指定されたパッケージを取り込みます。 別名を指定しなかった場合は、デフォルトでその パッケージの中のシンボルをグローバル領域に取り込みます。 例えば、上の例1のように、real型を引数にとりreal型の結果を返す 三角関数 cos は定義としては:

    real: Math.cos(real);
    ですが、"import Math end" 文で取り込んだ場合、 Math という名前空間はなくなって cos(...) で使用できるようになる、 ということです。これがデフォルトの動作です。

    名前の衝突を避けるために別名をつけることもできます。
    別名の使い方の一例としては、 インポートするパッケージの数が多くなり名前が衝突するような場合、 大文字のアルファベット1文字で衝突を避けるようにします。 イメージとしては

    パッケージを import文で手元にたぐり寄せ、 良く使うものにアルファベット1文字を つけることで識別しやすくする
    といった感じです (というか、「よく使うパッケージに1文字の識別子を与えておく」 という使い方をしたいためにこの構文を採用してみた、というのが 真相です。俺言語だからこういうことも試せるわけですね)。
    ここでは例として1文字としましたが、もちろん、別名の長さは任意です。

    importはグローバル域で使用します。関数内や適当なブロックの中で 宣言した場合に何が起るかは不定です (すいません。エラーとかもまだ出ないと思います)。

    同じパッケージの import を複数回行った場合何が起るかも不定です (まだエラーチェックしていません)。

    importは宣言文で、それ自体何かを実行する、というものではありません。

    現在提供しているパッケージは下記です。 「名前空間を導入してみたかった」かつ 「wwwstat.al を記述するに必要十分なもの」という動機がまずありきなので、 全くもって不十分なものに過ぎません m(__)m:

    Math 数学関係
    Net ネットワーク関係
    Time 時間・日付関係

    パッケージをユーザが定義する機能はまだ提供できていません。

    パッケージの実装は今のところ C++ で記述して静的リンクする、という原始的 な方法のみです。Python / Ruby / Perl のようなことを提供できるかは まだ未定です。

    戻値

    import も一応式ですが戻値はありません。つまり void 型の戻値、 ということです。

  17. → クラス定義

組込み関数

void: put (...) # 印字関数
void: putf (str: fmt, ...) # 書式付き印字関数
void: putf (Fmt: fmt, ...) # 書式付き印字関数
str: get (int: len) # データ取得
substr: matched (int: i) # 正規表現のパターンマッチ文字列取得
void: exit # 強制終了
void:
void:
void:
raise
raise
raise
(err, str)
(err)
(str)
# 例外発生

void: put(...)

説明
任意の型の変数を任意個数 stdout に印字します。
戻り値
-
例
str: name = "Wells"
put("Hello, ", name, "!\n")
SEE ALSO
putf()
BUG

str: get(int:len)

説明
標準入力から指定の長さ len [byte] を文字列として取得します。
戻り値
取得した文字列
例外
VE_STR_TOO_LONG -処理できる長さ(v0.15時点: 254byte)を 超えた文字列を取得しようとしました。
VE_EOF -ファイルの終りに達していた場合。 EOFに達する直前の場合、通常取得した文字列の長さは len より 短くなりがちですが、この場合はエラーではありません。 下記の例をご覧下さい。100byteづつ取得していますが、 標準入力のデータ長が 100で割りきれない場合でも正しく 標準出力にコピーしています。
例
# get を使った stdin から stdout へのコピー。
# あくまで例です。普通は stdin イタレータを使います。

loop
   put(get(100)) ||
      break
   end
end
SEE ALSO
BUG

substr: matched(int:i)

説明
直前のパターンマッチ式でマッチした部分文字列のうち、 i番目のものを返します。
i = 0 は、マッチした部分全体の文字列を返します。
i = 1 は、1番目の正規表現中のカッコ(...)で囲った部分文字列を返します。
以下同様で、i = matchednum - 1 までが部分文字列の添字となります。 i がそれ以外の場合、VE_REX_EXCEED_MATCHED_NUM 例外が発生します。
戻り値
部分文字列
例外
VE_REX_EXCEED_MATCH_NUM マッチ部分文字列の数がバッファを超えました。 src/type/rex.h の V_REX_MATCH_NUM を増やして再コンパイル して下さい。
VE_REX_EXCEED_MATCHED_NUM matched(i) 呼出し時に、添字 i が範囲を超えました。
例
"Hello, World!" in /[Hh](ello)/
put(matched(0), "\n")
put(matched(1), "\n")
SEE ALSO
rex.matched()
BUG

void: exit

説明
強制的に処理を中断します。
戻り値
-
例
put("Hi")
exit        # Hiだけ印字されてここで終了
put("Ho")

void: raise(err, str)
void: raise(err)
void: raise(str)

説明
例外を発生させます。 errを省略した場合(3番目のAPI)、エラーコードとして err.RUNTIME が 使用されます。
例
void: f()
  put(3)
  raise("error!")
  put(4)
end

  f

組込み変数

ENV # 環境変数
file: stdin # 標準入力
file: stdins # 標準入力あるいはコマンドラインで指定したファイル群
file: stderr # 標準エラー出力

ENV

SYNOPSIS
str: ENV(str:var) #read
ENV(str:var) = val #write
説明
環境変数をあたかも map[str] のようにアクセスする 組み込み変数です。
例1
put(ENV("LANG"))
ENV("LANG") = "C"
例2
ENV を走査するイタレータも提供しています。が、 environ(5) の wrapper でしかないので、"key=val" の値しか取得できません。 Perl の keys(ENV) に相当するようなことはできません:
for str: env in ENV do
   put(env, "\n")
end
SEE ALSO
BUG

stdin

SYNOPSIS
file: stdin
説明
標準入力を表す組込みの大域変数です。 file 型です。
戻り値
i番目の要素(文字列)
例
# 標準入力を標準出力にコピーします

for str:s in stdin do
    put(s)
end
SEE ALSO
BUG

stdins

SYNOPSIS
files: stdins
説明
Perl のダイヤモンド演算子 <> に相当する、 Alan の組込みグローバル変数です。 反復子として使用します。

コマンドライン引数が与えられた場合、そのファイルを順次 読み込みモードでオープンし、1行1行走査します。

コマンドライン引数がない場合は、stdin から読み込もうとします。

例
# Alan 版 cat

for str:s in stdins do
    put(s)
end
SEE ALSO
TBD
BUG
TBD

stderr

SYNOPSIS
file: stderr
説明
標準エラー出力です。

組込み関数 put() と同様のメンバ関数 stderr.put() が 使用可能です。

例
int: i = 100
stderr.put("NOTE: this is stderr output", "with any type of argument"
            , " i = ", i, "\n")
      
SEE ALSO
TBD
BUG
TBD

パッケージ

限定的ですがパッケージ(というより現状では名前空間)を導入しました。 import文も参照下さい。
  1. Math --数学関係
  2. Net --ネットワーク関係
  3. Time --時間・日付関係
  4. Filesys--ファイルシステム関係

制約事項

Alan v の制約事項を挙げます。
  1. 正規表現型(rex)
    1. 正規表現は POSIX regex functions (regcomp(3)など)をそのまま使用して 実現していますので、regex 以上のことはできません。
    2. Alan 文字列は '\0' を許していますが、正規表現が regex ライブラリによって実現されている関係上、 パターンマッチに関しては文字列は '\0' はサポートされていません。
  2. パッケージはユーザレベルではまだ定義できません。
  3. closure はありません。従って、Perl の s///eg, Rubyの gsub(//){...} 相当ができません。
  4. GC(Garbage Collection)はありません。 が、C++相当のコンストラクタ/デストラクタモデルに沿っているので、 GC相当とまではいかなくとも、ある程度ユーザは資源管理を 意識しなくて済むようにはなっています。
  5. 他にも山ほど・・・。





//以上

Top / Alan言語仕様





Alan ver0.31