みなさん、こんにちは。

前回に続いて routineX で routine のお作法を勉強しましょう。

#argument の戻り値でどの alternative が入力されたかが分かるという話をしました。そうすると次は alternative ごとに処理を分けたくなりますね。つまり条件分岐が必要になります。そこで #if の登場です。

|then| と |else| で処理を分けていますね。#if の前に [ が付いていることに注目してください。#output No parameters. の次の行も ] があり2つで組みになっています。TACL には #endif がないので条件分岐の処理ブロックを定義するために、一対の [ ] で処理ブロックを宣言する形になっています。ここは大事なので試験に出ますよ。 また、比較の部分にも注目してください。val = 1 となっていますね。[ ] をつけて [val] = 1 としなくていいのだろうか、と思った人は手を挙げて。 そう、それでも全く問題ありません。あくまでもここの場合は、ですが。

少し説明しましょう。

[#if [val] = 1 |then|

は val を展開すると

[#if 1 = 1 |then|

となります。こうなる限りは問題ないのですが、val の中身が空白になるようなケースはどうなるでしょうか。展開後は次のようになります。

[#if = 1 |then|

うーん、何か変ですね。これは文法違反です。空白になる可能性があるなら次のように対策しなければなりません。

[#if “[val]” ‘=’ “1” |then|

“[val]” とすることで文字列扱いになります。これなら val が空白でも

[#if “” ‘=’ “1” |then|

と展開されるので文法は守られます。なお、’=’ というのは文字列を比較する場合の等号です。数値の場合とは記法が変わることに注意してください。あっ。そうそう。第二講で、「variable の type に number 型はない」という話をしましたね。variable は展開してなんぼ。展開前は number でも text でも関係ないのです。展開後はもちろん関係ありますよ。上記の例でも

[#if “[val]” = “1” |then|

は型の不一致のためエラーになります。お分かりですね?

なお、val が空白であるかどうかを検査する場合は

[#if “[val]” ‘=’ “” |then|

とやればいいのですが、次のように #emptyv built-in function を使う方法もあります。

[#if [#emptyv val] |then|

こちらの方が洗練されているかな。うん、スタイリッシュにいきましょう。

話を戻してさらにマクロを拡張します。#argument に TEMPLATE alternative を追加して、ファイル名の直指定とテンプレート指定の両方に対応してみましょう。 TEMPLATE はファイル名を要求する alternative ですが、ファイルが実在しなくてもかまいませんし、ワイルドカードも使えるというものです。これで alternative が FILENAME TEMPLATE END の3つになりますので、条件分岐も3分岐が必要です。TACL には #elseif もないので、3分岐だと次のようになります。

ネストが深くなるとインデントも深くなって見づらいな~。よく見れば分かるのだけどね~
というわけで、3分岐以上の場合はスマートに #case を使いましょう。

#case の場合は val ではなくて [val] です。ややこしいですね。さて、|1| の部分を label と呼びます。ここでは 1/2/otherwise という label を使っています。label は文字列として扱われます。|1| も数字の1ではなく文字列の1です。したがって、こんなコードもエラーにはなりません。

ここで [name] の [ ] を付け忘れて [#case name などしてしまうと、name の中身が何であっても bonus の値は 99999 固定になってしまいます。分かりますよね?label には複数の値を指定することもできます。こんな感じです。

「1か4ならxxxの処理」という意味ですね。(今回、4はあり得ませんけどね、あくまでも例です)

ここまででパラメータで指定するファイルが1つのケースを説明してきましたが、ファイルが複数になる場合はどうしたらいいのでしょうか。routineX の仕様を変更して、ファイル名を指定したときはさらにもうひとつだけテンプレートパラメータを受け付ける、という仕様にしてみます。つまり、

はOKで、パラメータを前後入れ替えた

はエラーになるような処理です。

#argument を複数記述していますね。 |1| の label に #set val [#argument/value prm2/TEMPLATE] を追加して、最初のパラメータが実在するファイルなら、2つめの TEMPLATE 型パラメータを prm2 に取り込むようにしています。ここで val の値を検査していないのは、prm2 が TEMPLATE 以外なら routine のパラメータチェック機能で弾かれてしまうので、検査するロジックを書いてもどうせ実行されないからです。

つまり次の #output next parameter ……. が実行されるのであれば、val の値は 1 以外あり得ないわけです。 となるとで、「使わないのに val に代入するのは無駄ではないか」という疑問が湧きます。本当に戻り値を使わないのであれば、sink というマクロが使えます。

#set val [#argument/value prm2/TEMPLATE]

の代わりに

sink [#argument/value prm2/TEMPLATE]

とします。これで #argument の結果を読み捨てることができます。 なお、#set val [#argument END] が2か所に出てきます。パラメータがここで終端されている(=余分なパラメータがない)ことを確認しています。これも実務的には大事なテクニックです。

さてさて、この routineX で次のように入力してみましょう。

administrator は不正なファイル名なので当然エラーになります。それはいいのですが、実は少し問題があります。この routine で宣言した variableが #pop されないのです。試しに。

と入力してみましょう。

しっかり残っていることが分かります。 これはつまり、#argument のエラー処理で処理が打ち切られてしまうので、#pop の処理まで廻らないということです。対策として #EXCEPTION という built-in function を使います。

さきほどのソースに8行追加しました。routine の中で何がしかのエラーが発生すると、#EXCEPTION に処理が戻って _ERROR label の処理を実行するようにできます。上記の例では、#argument でエラーが起きると、

#OUTPUT ROUTINE TRAP:_ERROR
#pop val prm1
#RETURN

の3行を処理して終了します。#OUTPUT …… は処理中断を示すメッセージですね。ここでヘルプを出すのもいいと思います。その次に #pop を記述していますので、宣言した variable を解放できます。#RETURN は routine の処理を終了する built-in variable です。これがないと次の行に処理が進みますので、また処理の本体を実行してしまいます。

#FILTER _ERROR

は #EXCEPTION でエラー検出を有効にするためのおまじないです。これを忘れないようにしてください。 ただ、こうするとどんなエラーが出たか分かりにくいのも事実です。そこで、発生したエラーメッセージを拾えるよう細工してみます。

エラーメッセージは #ERRORTEXT built-in function で拾えます。上記では拾ったエラーを単純に表示しているだけです。エラーメッセージを解析して何かしようとするのは、まあ労力の無駄だと思いますので、表示しておくだけでいいでしょう。

なお、#EXCEPTION で検出できるイベントは、標準では3つです。
_CALL routine 呼び出し
_ERROR エラー発生
_BREAK Ctl+C 押下
上記のマクロでは _CALL は何も処理をしていません。しかし省略はできません。routineX 呼び出し時に _CALL が発生するので、これを省略すると

[#CASE [#EXCEPTION]

で対応する label がないということでエラーになってしまいます。なお、_BREAK も _ERROR と同じく#FILTER がないとトラップが発生しないので注意してください。

長くなりましたが、もうひとつだけお付き合いください。

バラメータの個数が固定できない場合はどうしたらいいでしょうか。この場合はループ制御でパラメータを次々に処理することになります。ループ処理は #loop |while| または #loop |until| を使います。|while| ならば

また |until| ならば

というような具合です。|do| label からが処理の記述になることに注意してください。上記の例では flag variable でループからの脱出を制御していますが、routine の場合は #more という built-in function を使う手もあります。

#more は、routine の未検査パラメータがまだあるかを検査する関数です。未検査がなくなれは #more は false を返してループから抜けますので、END にヒットすることはなくなります。 混乱するといけないので念のために整理しておきますが、#if、#case、#loop は routine であるか否かに関わらず(macro や obey でも)使用できます。しかし #argument や #more、#return は routine 専用です。

ついでなのでもう一つ。routine 専用の要素として #result というものがあります。#result はC言語で言うところの「関数の戻り値」と同じような役割を果たします。C言語と違うのは、#result で指定した分だけ戻り値が増えるという点です。

これを実行してみると

#result で指定したものがすべて返っていることが分かります。これも覚えておくと便利です。

これで routine で使う要素の説明が終わりました。次回は fl マクロに戻って機能拡張を続けます。Au revoir!