nakomake.nako

第2節では、nakomakeのプログラムソースそのものの解説をします。なでしこの実行ファイル作成を支援するコマンドラインツールnakomakeの使い方や仕様などの詳しいことは、前節「1. 使い方 - nakomake」を参照してください。なお、プログラムのソースコードは、以下から入手することができます:

コマンドラインからの入力を受け取る

nakomakeはコマンドラインツールなので、コマンドラインから入力を受け取る必要があります。その内容は、変数「コマンドライン」で配列として取得することができます。

変数「コマンドライン」

コマンドライン[0] にはプログラム自分自身(実行ファイル)のパスが入っています。つまり、スクリプトとして実行(.nakoファイルをダブルクリックorなでしこエディタから実行)した時は、フロントエンドである vnako.execnako.exe のパスが入っています。しかし、実行ファイルにして実行した時は、その実行ファイル自身の名前になります。 nakomake はコマンドラインから呼び出して使うことを想定しているので、後者になります。

コマンドライン[1] 以降には、コマンドラインから実行する時に後ろにつけるオプションが入ります。例えば、次のように nakomake.exe を呼び出したとします:

nakomake.exe test.nako -import "data.nako,image.png" -e > make.nako

このとき、変数「コマンドライン」の内容は次のようになります:

コマンドライン
インデックス文字列データ
0path\to\nakomake.exe
1test.nako
2-import
3data.nako,image.png
4-e

基本的に、スペース区切りでデータが入っていることが分かります。文字列を指定するダブルクォーテーション「"」や、標準出力の内容をファイル出力する「> make.nako」など、コマンドプロンプト側がカバーする部分は変数「コマンドライン」の中に表れていないことに注意してください。

以上を踏まえた上で、コマンドラインのオプション設定を受け取るためのプログラムを考えてみましょう。まず、コマンドラインから受け取る必要のあるデータは次の通り:

  • 入力ファイル(オプション指定なし)
  • 引数付きオプション(オプションのあとにスペースを置いて引数を記述)
  • 引数なしオプション(オプションのあとに引数がない)

スタック

入力ファイルや引数付きオプションなどの細かい場合分けを上手くやるにはただの反復構文では難しそうです。そこで、コマンドライン配列をスタック[*1]にして扱います。

STACKとは配列=コマンドライン
POP() ※コマンドライン[0]はプログラム自身のパスなので不要

●POP()
 STACKの0を配列切り取り

●PUSH(E)
 STACKの0にEを配列挿入

●TOP()
 STACK[0]を戻す

そして、STACKが空でない間コマンドラインのデータを読み込み続けるループを作り、その中で条件分岐します。

TOP()が空でない 間
 対象=POP()
 もし対象が`file`オプションならば
  POP()にターゲット設定
 違えば、もし対象が`type`オプションならば
  POP()にタイプ設定
 違えば、もし対象が`plug-ins`オプションならば
  POP()にプラグイン設定
 違えば、もし対象が`import`オプションならば
  POP()にインポート設定
 違えば、もし対象が`runtime`オプションならば
  POP()にランタイム設定
 違えば、もし対象が`output`オプションならば
  POP()にアウトプット設定
 違えば、もし対象が`encrypt`オプションならば
  暗号化処理は必要
 違えば、もし対象が`noexe`オプションならば
  実行ファイル作成処理は不要
 違えば、もし対象が`deltemp`オプションならば
  一時ファイル削除処理は必要
 違えば、もし対象がヘルプオプションならば
  マニュアル表示
  終わる
 違えば
  対象にターゲット設定

読みこんだデータがどのオプションに一致するか判定し、引数付きオプションの場合はデータを POP() で更に一つ取りだして設定処理を行います。引数なしオプションなら何も POP() せずに必要な設定フラグを書き換えています。

ここで、文字列が「-option」「-o」「-Option」などのオプション形式になっているか判定するユーザ定義命令「オプション」は、次のようになっています:

●オプション({文字列}ARGが{文字列}OPT)
 ARG=ARGを小文字変換
 OPT=`-`&(OPTを小文字変換)
 もしARGがOPTならば
  はいを戻す
 違えば、もしARGが(OPTの2文字左部分)ならば
  はいを戻す
 違えば
  いいえを戻す

意外と知られていませんが、引数を助詞なしで定義すればこのようにリテラルに続けて命令を書くことができます。

実行ファイルの作成とパックファイル

なでしこの実行ファイルは、フロントエンドとソースコードの梱包方式を取っています。梱包されるファイルは以下の通り:

  • フロントエンド
    • vnako.exe
    • gnako.exe
    • cnako.exe
  • ソースコード
  • plug-in (DLL)
    • nakostr.dll
    • BREGEXP.dll
    • :
  • その他の梱包ファイル

但し、デラックス版でない普通のフリー版なでしこでは plug-ins を梱包することはできません。

「パックファイル作成」命令

なでしこの実行ファイルを作成するには、上記の plug-ins 以外のファイルをひとまとめにしたパックファイルを作ることになります。なでしこには、そのためのパックファイル関連の命令が用意されています:

  • ABにパックファイル作成
  • ABCへパックファイル抽出
  • Fのパックファイル存在
  • ABをパックファイル文字列抽出
  • ABCにパックファイル結合
  • AからBへパックファイル分離
  • ABをパックファイルソースロード
  • パックファイルソース実行

「パックファイル作成」命令は、"ABにパックファイル作成"と使います。Aは、「{梱包する元ファイル}={梱包名}={暗号化するなら1しないなら0}」という形式の文字列で指定します。複数梱包するなら改行して続けます(あるいは配列)。Bには作成先のパックファイルを指定します。

ソースコードの梱包

梱包で最も重要な決まりごとが、プログラム本体の

「{RUNTIME}lib\vnako.nako=vnako.nako=0」をパックリストに配列追加

処理内容(ログ)の出力

nakomake では、処理内容をそのまま「なでしこ」のプログラム形式で標準出力しています。これによって、出力結果を".nako"ファイルとして保存すれば、同じ実行ファイル化の作業を行いたい時、そのファイルを実行するだけで済みます。

命令の上書き

では、ログ(記録)を出力したりするにはどうすればいいでしょうか?命令を実行する時に逐一その内容を表示するようにプログラムを組む?いいえ、そんな必要はありません。命令を上書きすればいいのです[*2]。具体的には、次のようにします。

●フォルダ作成(Sに|Sへ|Sの)
 「『{S}』へフォルダ作成。」と表示
 Sへシステム:フォルダ作成

このように、「なでしこ」の命令は、柔軟にユーザ定義命令で上書きできるようになっています。「しかし上書きしたら元の命令が使えなくなるじゃないか!」と思うかもしれませんが、そんなことはありません。なでしこにはきちんと名前空間(ネームスペース)の仕組みがあるのです。

名前空間

上のプログラムを見てみると、新しい"フォルダ作成"命令の中で、"システム:フォルダ作成"という命令を実行しています。この"システム:"というのが名前空間の指定です。

なでしこで最初から使える命令は、全て"システム"という名前空間に属しています。一方ユーザ定義命令は、プログラムファイル名の名前空間に属するようになっています。そして名前空間は、前者よりも後者の方が優先順位が高くなっています。

従って、"上書き"と言ってもあくまで別の所に命令ができるだけであって、何も問題ない訳です。

上書きの利点

では、なぜ上書きすると良いのでしょうか?

まず1つ目は、同じ名前で命令を利用できるということです。もし"上書き"できなかったとすると、重複しないよう別の名前を作ることになります:

●俺フォルダ作成(Sに|Sへ|Sの)
 「『{S}』へフォルダ作成。」と表示
 Sへフォルダ作成

ほとんど同じことをしていてオマケ程度の"付加処理"が付くだけなのに、実際使う時にいちいちその別名を思い出したりするのは面倒です。

もう1つの利点はその"付加処理"にあります。"付加処理"が本来の処理に影響しないオマケであるということは、"上書き定義"を完全に取り除いてしまっても元々の命令が呼び出されるだけで、本来の処理に影響しないということです。

さらに、関数の上書き定義を別ファイル化して取り込むようにするなどの工夫をしておけば、プログラム本来の処理を見失わず、ログを取ったりデバッグしたりの切り替えが安全かつ簡単にできるでしょう。

また"付加処理"以外にも、命令の上書きは他にも様々な用途で使えます。例えばある処理を割りこませたり、ファイルの存在判定などエラーチェックを任せると言った使い方もできるでしょう。色々応用してみてください。

注釈

*1
スタックというのは、データの追加・取り出しを"頭"でのみ行う配列のようなものです。配列の"頭"に追加することを PUSH 、配列の"頭"を取り出す(配列から削除してその値を利用する)ことを POP と言います。今回は簡単のために"頭"を"0番目"にしていますが、通常、配列をスタックのように用いる場合は、高速に処理できるよう配列の末尾(配列要素数-1番目)を"頭"にします。まぁ、そもそも今回のプログラムでは PUSH は使っていませんが、ね。
*2
これを、 Java 言語などの慣習から「関数のオーバーライド」とも呼びます。なでしこでも、グループのメンバ関数(メソッド)を上書きして利用するなどの使い方ができます。