MikoScript 言語仕様

 マクロ

 本言語インタプリタは、ソースプログラムをそのまま言語構文として扱う
のではなく、そのなかに「マクロ」部分があれば、まずそれを展開してから
言語構文として扱います。つまり、マクロは、通常の言語構文の要素ではなく、
それに置換される前のものです。

 プログラムは、マクロがなくても書けますが、マクロがあれば、より見易く、
簡便に、効率的に、書けます。例えば、プログラム内に直接数値を書いても、
それが何を意味しているのか分かりにくい場合がありますが、マクロを使えば、
その数値の代わりに、その意味を表わすシンボル名で表記できます。また、
よく使うルーチンは(比較的簡単なものなら)、関数にするよりも、マクロに
する方が、オーバーヘッドが遥に少なくて済みます。さらに、マクロを使うと、
別のソースファイルを取り込むことができるので、同じ内容を書く手間が省ける
だけでなく、メンテナンスが容易になります。

 このように、本言語のマクロは、ソースプログラムのコンパイル直前に
行なわれる文字列置換やファイル取り込みの機能を提供しますが、これらは
基本的に、C言語等のマクロの機能と同様です。しかし、本言語の表記は、
C言語の表記とは、多少異なります。

 マクロは、本言語に予め組み込まれているものと、ユーザーが任意に定義
して使うものがあります。次に、マクロの定義の仕方と、それがどのように
展開されるのかについて説明します。

●マクロの定義と展開(引数がない場合)
 引数がないマクロを定義する場合、以下のような書式になります。
    #set  マクロ名  マクロ定義本体
ここで、
・#set は、本言語に予め組み込まれているマクロで、これは、新たに
 マクロを定義する働きを持っています。
・#set は普通、行頭から書き始めますが、行頭から #set までの間に、
 半角空白や TAB があっても構いません。
・# と set の間に、空白は入れられません。

・「マクロ名」は、識別名になります。
・「マクロ名」と「マクロ定義本体」の間には、半角空白、または、TAB を
 1個以上入れます。

・「マクロ定義本体」は、改行の直前で終端します。但し、改行の直前に \ を
 書いておくと、その次の行も、「マクロ定義本体」の続きとして扱われます。
 その際、\ と改行コードは、「マクロ定義本体」の登録内容には含まれません。
 例えば、次のようなマクロ定義があるとします。
    #set  PI  3.1415926535897
 これは(行頭から書き始めているとして)、
    #set  PI  3.14\
    15926535897
 のように書いても同等になります。

・このように定義されたマクロを使う時は、そのマクロ名の前に # を付けます。
 例えば、上例のように定義された PI というマクロを使う時は、
      #PI
 と書きます。そうすると、コンパイル時に、この部分は、
      3.1415926535897
 に展開されます。
  このようなマクロ展開は、ソースプログラム内で、PI という識別名の
 前に # が付いている全箇所で行なわれます。しかし、次のような箇所では、
 PI という2字があっても、それはマクロとしての識別名ではないので、
 マクロ展開されません。
  ・# が付いていない、または、他の識別名内の一部(例: PI, #PIO, )
  ・通常文字列または純粋文字列内(例: "#PI = 3.14..." )
   (なお、直記文字列内では、マクロ展開が行なわれます)
  ・文字定数内(例: `#PI` )
  ・コメント内(例: /* #PI は円周率を表わすマクロです */ )

・「マクロ定義本体」の中で、別のマクロを使うこともできます。その場合も
 同様に、そのマクロ名の前に # を付けます。例えば、上例の PI という
 マクロを使って、
    #set  PI2  ( #PI * 2.0 )
 として、PI2 というマクロを定義できます。この場合、#PI2 は、
    ( 3.1415926535897 * 2.0 )
 に展開されます。
  この過程をもう少し詳しく説明すると、次のようになります。

 PI2 というマクロが登録される時、その定義本体は、
   ( #PI * 2.0 )
 になります。つまり、この時点で、PI というマクロが展開されて登録される
 わけではありません。従って、この時点で、PI というマクロが登録されて
 いなくても、エラーにはなりません。

 #PI2 のマクロが展開される時、まず、
   ( #PI * 2.0 )
 になります。次に、#PI が展開されて、
   ( 3.1415926535897 * 2.0 )
 になります。従って、この時点で、PI というマクロが登録されていなければ、
 エラーになります。

・「マクロ定義本体」内にコメントがあっても、その部分は登録されません。
 また、「マクロ定義本体」内の末尾部の半角空白や TAB も、登録されません。
 例えば、
    #set  PI  3.1415926535897    // 円周率
 というように定義されたマクロの定義本体には、// ... のコメント部と
 その直前の空白部は、登録されません。この例で、登録されるのは、
 数字列の部分だけです。もし、コメント部まで登録されるのなら、例えば、
    print #PI;
 とした場合、マクロ展開後は、
    print 3.1415926535897    // 円周率;
 となって、文末の ; がコメントになってしまいます。

・本言語では、上述のように、C言語等の表記とは違って、マクロ名の前には、
 必ず # を付けますが、これによって、ソースプログラム内の識別名が、
 マクロなのかそうでないのか一目で分かります。
 また、マクロ名のスコープ(名前空間)は、それ自身で独立しているので、
 マクロ名と同じ名前の変数や関数があっても構いません。

・既に定義されているマクロと同じ名前のマクロを再度定義した場合、
 古い方の定義は破棄されて、新しい方の定義が有効になります。
 その際、特に警告のメッセージはでません。

・マクロ定義は、現コンパイル中のソースプログラム内でのみ有効です。
 つまり、1つのソースプログラムのコンパイルが終れば、
 そこで定義されていた全てのマクロは破棄されます。そのため、
 共通に使うマクロがあれば、その定義は、各ソースプログラムごとに必要です。
 しかし、それを毎回書くのはさすがに面倒です。そこて、共通のマクロ定義は、
 別のファイルにまとめておき、各ソースプログラムでは、それを任意に
 取り込めるようになっています。これに関しては、#include のところで
 説明します。

●マクロの定義と展開(引数がある場合)
 引数があるマクロを定義する場合、以下のような書式になります。
    #set  マクロ名( 引数名の並び )  マクロ定義本体
ここで、
・#set と「マクロ名」に関しては、引数なしの場合と同様です。
・「マクロ名」と「( 引数名の並び )」の間に、空白は入れられません。
 もし入れると、引数なしのマクロ定義と解釈されます。その場合、
 「( 引数名の並び )」以降が、マクロ定義本体になってしまいます。

・「引数名の並び」は、空、または、1個の引数名、または、コンマで区切られた
 2個以上の引数名になります。つまり、次のような書式になります。
    #set  マクロ名()  マクロ定義本体
    #set  マクロ名( 引数名 )  マクロ定義本体
    #set  マクロ名( 引数名, 引数名 )  マクロ定義本体
    #set  マクロ名( 引数名, 引数名, 引数名 )  マクロ定義本体
    ・・・・・
 ここで、各「引数名」は、識別名になります。
 「引数名」の個数は 0〜20 個の範囲で任意です。

・「マクロ定義本体」は、基本的に、引数なしの場合と同様ですが、
 引数がある場合は、「マクロ定義本体」内で、「引数名」が使えます。
 その場合、「引数名」の前に # を付けます。例えば、
    #set  Max( a, b )  (((#a) >= (#b)) ? (#a) : (#b) )
 のようになります。

・「マクロ定義本体」内の #引数名 の部分(上例では、#a と #b )は、
 マクロ展開時に、その引数に渡された文字列と置換されます。例えば、
 上例の Max というマクロを使って、
    #Max( 120, 340 )
 とすると、これは、
    (((120) >= (340)) ? (120) : (340) )
 に展開されます。また、
    #Max( x + y, z )
 とすると、これは、
    (((x + y) >= (z)) ? (x + y) : (z) )
 に展開されます。また、
    #Max( name, "太郎" )
 とすると、これは、
    (((name) >= ("太郎")) ? (name) : ("太郎") )
 に展開されます。

・このように、引数があるマクロを使う時の書式は、# の有無を除いて、
 関数コールの書式と似ています。しかし、マクロは、あくまで、
 コンパイル時の文字列置換であって、関数コールとは根本的な違いが
 あります。

・マクロの引数を、文字列リテラル内に含めるには、直記文字列を使います。
 例えば、
    #set  Mate( a, b )   #'#a と #b#'
 と定義されたマクロを使って、
    #Mate( 太郎, 花子 )
 とすると、これは、
    #'太郎 と 花子#'
 に展開されます。
  ここで、注意すべきことがあります。引数に渡される文字列は、その先頭部と
 末尾部の空白や、コメント部が、除去されるということです。
 例えば、
    #Mate(  太郎 /*男*/,   花子 /*女*/   );
 というようにしても、同じ展開結果になります。けっして、
    #'  太郎 /*男*/ と    花子 /*女*/   #'
 のようには展開されません。
  なお、上記のマクロ定義で、
    #set  Mate( a, b )   #'#aと#b#'
 とすると、これを使った時に、エラーになります。これに関しては、
 直記文字列内でマクロを使うところで説明しています。

・マクロの各引数は、コンマで区切られ、丸括弧で囲われるので、
 各引数自身には、通常、コンマや丸括弧の文字は、使えません。
 しかし、本言語では、対応が取れた丸括弧の囲いなら、とりあえず、
 マクロの引数に、含めることができるようになっています。例えば、
    #set  List( name, items )   #name#items
 と定義されたマクロを使って、
    #List( Fruits, ( Apple, Orange, Peach ) )
 とすると、これは、
    Fruits( Apple, Orange, Peach )
 に展開されます。

・マクロは、再帰的には定義できません。
・マクロは、引数の個数が違っても、多重定義できません。

●コメント化マクロ
 既に述べましたが、「マクロ定義本体」内にコメントがあっても、その部分は
登録されません。例えば、次のようにして、マクロを定義しても、その定義本体は
空になります。
    #set  cmt   //
 #comment という組み込みマクロを使うと、このような定義が可能になります。
それには、次のようにします。
    #set  cmt   #comment
これで、cmt というマクロの定義本体には、// の文字列が登録されます。

このマクロを使って、例えば、
    #cmt  *** 処理開始 ***
とすると、これは、
    //  *** 処理開始 ***
に展開されます。ところが、これは、コメントなので、コンパイル時には、
読み飛ばされます。

 このようなマクロは、何に使うのかといえば、例えば、次のような時に使います。
デバッグ時には、内部の状態を確認するために、いろいろなところで、
「デバッグ用のプリント」を入れておくことがあります。しかし、これは、
プログラムが完成した時点では、不要になります。そのような場合、
デバッグ用のプリントには、通常の print を使わずに、
    #set  prt  print
のように定義したマクロを使います。つまり、例えば、
    print  "x = " : x ;
のようなデバッグ用のプリントは、
    #prt  "x = " : x ;
と書いておきます。デバッグが終った時点で、prt のマクロ定義を、
    #set  prt   #comment
に書き換えれば、デバッグプリントの部分はすべてコメントになるので、
実行時にプリントされなくなります。

●定義済み定数マクロ
 現状、本言語では、比較的使用頻度の高い、次のような定数のマクロ定義を、
コンパイル開始直前時に、自動的に行なっています。そのため、これらの定数
マクロに関しては、別途定義しておく必要はありません。
    #set  TRUE   (1)
    #set  FALSE  (0)
    #set  OK     (0)
    #set  NG     (-1)
    #set  NULL   (0)
 なお、言うまでもなく、予約語の null は、マクロの #NULL とは根本的に
違います。

●ファイルの取り込み
 本言語では、C言語等と同様に、現コンパイル対象のソースプログラム内に、
別のソースファイルを取り込む機構が備わっています。その書式は、C言語等と
同様で、次のようになります。
  #include "ファイル名"
あるいは
  #include <ファイル名>
 これは、1行に表記します。この行は、ソースプログラム内の任意の場所に
おくことができます。コンパイル時に、この行は、その「ファイル名」で指定
されたファイルの内容に置き換えられて、処理されます。

 #include で指定する「ファイル名」は、絶対パスでも、相対パスでも、
構いません。絶対パス(フルパス)の場合、そのパスだけが対象になります。
一方、相対パスの場合、その基準パスには、複数の候補があります。これらは、
その対象ファイルが見つかるまで、次の順に検索されます。
 (1)現対象の #include 行がある元のファイルと同じパス
 (2)起動コマンドの /i オプションで指定されたパス、または、
    SetIncludePath 関数で指定されたパス
 (3)本言語がインストールされたフォルダ内の Include というフォルダ

但し、#include <ファイル名> の場合、(1)のパスは検索されません。
これは、概ね次のような理由に依ります。
#include の対象になるファイルはいろいろありますが、大別すると、 
 (A)本言語システムが提供する全ユーザーに共通のファイル
 (B)各ユーザーが個別に作成したファイル
があります。
(A)のファイルは、通常、上記(3)のフォルダに入っていて、(1)のパス
にはありません。そのため、(A)用には、
  #include <ファイル名>
の書式を使います。
(B)のファイルは、通常、上記(1)のパスにあります。また、(2)のパス
の場合もあります。そのため、(B)用には、
  #include "ファイル名"
の書式を使います。
 なお、このような使い分けは、あくまで強制ではありません。

 #include 行と置き換えられたテキストの中に、また、#include 行があると、
それも同様に、そこで指定されたファイルの内容に置き換えられて処理されます。
本言語では、このネスティングの深さには、特に制約はありません。しかし、
ネスティングの無限深化を防ぐために、ネスティング内に、同一のファイルが
入ることを禁止しています。