みなさん、こんにちは。
前回に続いて 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|
と展開されるので文法は守られます。なお、’=’
というのは文字列を比較する場合の等号です。数値の場合とは記法が変わることに注意してください。あっ。そうそう。第2回で、「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
専用です。
ついでなのでもう1つ。routine
専用の要素として #result
というものがあります。#result
はC言語で言うところの「関数の戻り値」と同じような役割を果たします。C言語と違うのは、#result
で指定した分だけ戻り値が増えるという点です。
これを実行してみると
#result
で指定したものがすべて返っていることが分かります。これも覚えておくと便利です。
これで routine
で使う要素の説明が終わりました。次回は fl
マクロに戻って機能拡張を続けます。Au revoir!