はじめに
Curlのマクロの特徴を要約すると、次の3点があげられます。
- ソースコード実行中にマクロを展開
- コンパイル時に評価されるデータ構造や値を使った展開
- 構文解析機能により言語拡張が可能
基礎編では、Curlのマクロの特徴に挙げた1点目と2点目について、サンプルプログラムを用いて説明します。
マクロとは
Curlコンパイラは、ソースコード実行中にマクロを見つけるとマクロをCurlSourceに展開しその後ネイティブコードを生成します。そのため、展開箇所に最も適したコードが生成されるため、処理速度も期待できます。
下図が、マクロのイメージです。
{define-macro public {macro-rei1} {return {expand-template {String "自分でつくれる!Curl言語~マクロを用いた言語カスタマイズ~" } } } }
{value {macro-rei1} }
上記の{macro-rei1}の部分で、{String "自分でつくれる!Curl言語~マクロを用いた言語カスタマイズ~" }が展開されます。
マクロを作成してみよう
それでは、実際にマクロを作成して特徴を見ていきます。今回使用する「Curl v8.0」は、以下からダウンロードが可能です。 インストールに関しては、こちらを参照ください。
まずは、下記のプログラムを見てください。
{define-proc {xfloor dividend:int, divisor:int}:(int, int) def (quotient, remainder ) = {floor dividend, divisor} {return (quotient, remainder)} }
このプロシージャは、引数divisorにゼロが渡ってくると「整数値において、ゼロ値での除法が行われました。」という例外が発生します。例は簡単なものなので通常トレースは入れませんが、複雑な業務APでは、プロシージャの入口・出口・主要箇所にトレースを入れ、デバッグを行います。機能によっては、サービス開始以降もトレースのレベルを下げて必要なトレースを出し続けることもあるでしょう。そんな場合には、マクロが便利です。
それでは次ページより、マクロを使って、xfloorプロシージャの入口で引数の情報、出口で演算結果の情報を出力するプログラムに書き換えます。
構成
マクロの制約として、同じパッケージで定義したマクロはそのパッケージでは使えないという制約があります。実際の業務で使用する構成に、プログラム例1の構成を変更します。構成は(1)マニフェスト(2)マクロ定義(3)xfloorプロシージャ(4)スタートプログラムにします。
{curl 8.0 manifest} {curl-file-attributes character-encoding = "shift-jis"} {manifest sample} {component package MACRO, location = "macro.curl" } {component package MATH, location = "math.curl" } {component file start.curl, location = "start.curl" }
{curl 8.0 package} {curl-file-attributes character-encoding = "shift-jis"} {package MACRO} {define-macro public {logging ?tag:token, ?arg1:identifier, ?arg2:identifier } {return {if {{tag.get-text}.equal? "\"start\""} then || 入口の情報(ここから) {expand-template {output "【" & ?tag & "】" & {format "%s関数 [%s行目](dividend:%d, divisor:%d)", {this-function}, {this-line}, ?arg1, ?arg2} } || 入口の情報(ここまで) } else {expand-template || 出口の情報(ここから) {output "【" & ?tag & "】" & {format "%s関数 [%s行目](quotient:%d, remainder:%d)", {this-function}, {this-line}, ?arg1, ?arg2} } } || 出口の情報(ここまで) } } }
{curl 8.0 package} {curl-file-attributes character-encoding = "shift-jis"} {package MATH} {import * from MACRO} {define-proc public {xfloor dividend:int, divisor:int}:(int, int) {logging "start", dividend, divisor} || マクロ呼出 def (quotient, remainder ) = {floor dividend, divisor} {logging "end", quotient, remainder} || マクロ呼出 {return (quotient, remainder)} }
{curl 8.0 applet} {curl-file-attributes character-encoding = "shift-jis"} {applet manifest = "manifest.mcurl"} {import * from MATH} {value {try {xfloor 5, 1} catch th:Exception do th.message } }
実行
start.curlを実行すると「5」が表示されます。マクロの実行結果は、Curl RTEコンソールに下記が表示されます。
【start】xfloor関数[9行目](dividend:5,divisor:1) 【end】xfloor関数[11行目](quotient:5,remainder:0)
解説
-
ポイント1
マクロの実行結果では、展開した箇所の関数名や行番号が取得できます。
-
ポイント2
マクロ定義は、macro.curlのロジックを記述します。今回の例ではxfloorプロシージャでマクロを入口と出口に2か所記述しましたが、実際に展開されるロジックは、下記になります。
{output "【" & "start" & "】" & {format "%s関数 [%s行目](dividend:%d, divisor:%d)", {this-function}, {this-line}, dividend, divisor} }
{output "【" & "end" & "】" & {format "%s関数 [%s行目](quotient:%d, remainder:%d)", {this-function}, {this-line}, quotient, remainder} }
マクロ定義は、展開出力パターンを示します。コンパイラはそれを解析し、最適なCurlSourceクラスのオブジェクトに展開してネイティブコードを生成します。そのため、実行時に無駄な判定が無くなり、高速に処理できます。
マクロの便利機能を使ってみよう
前ページで説明したマクロ定義(macro.curl)を、Curlマクロの便利な機能で改善します。具体的には、下記の2点を修正します。
- マクロの引数を1つ以上持つ、可変な引数
- 引数の識別子の文字列をハードコーディングしない
マクロ定義(macro.curl)は下記になります。
{curl 8.0 package} {curl-file-attributes character-encoding = "shift-jis"} {package MACRO} {import * from CURL.IDE.CPA.SOURCE} {import * from CURL.LANGUAGE.SOURCE} {define-macro public {logging ?head:token, ?args:{bounded-comma-sequence 1, infinity , ?:identifier } ||(1) } def common = {expand-template "【" & ?tag & "】" & {this-function} & "関数" & "[" & {this-line} & "行目]" } def arg-info = {{Array-of CurlSource}} {for arg key i in args do ||(2) {if i > 0 then {arg-info.append {Operator OperatorKind.Ampersand}} {arg-info.append {CurlSource.from-string "\",\""}} {arg-info.append {Operator OperatorKind.Ampersand}} } {arg-info.append {CurlSource.from-string "\"" & {arg.get-text} & ":\""}} ||(3) {arg-info.append {Operator OperatorKind.Ampersand}} {arg-info.append arg} ||(4) } {return {expand-template {output ?common & "(" & ?arg-info & ")" } } } }
- (1)可変引数定義
- (2)可変引数を取り出す
- (3)識別子を文字列で取り出す
- (4)識別子
上記のマクロ定義(macro.curl)を前ページで紹介したプログラムと置き換えて実行すると、同じ結果になります。また、展開されるロジックは、下記になります。
{output "【" & "start" & "】" & {this-function} & "関数" & "[" & {this-line} & "行目]" & "(" & "dividend:"& dividend &","&"divisor:"& divisor & ")" }
{output "【" & "end" & "】" & {this-function} & "関数" & "[" & {this-line} & "行目]" & "(" & "quotient:"& quotient &","&"remainder:"& remainder & ")" }
上記の改善プログラムは文字列を編集するために雑多なコードになりましたが、(1)可変引数定義、(2)可変引数を取り出す、(3)識別子を文字列で取り出す、(4)識別子という4点が改善ポイントになります。
マニフェストと連携してみよう
マクロ定義時、macro-envが暗黙引数として渡ってきます。そのため、マニフェスト情報を簡単に参照することが可能です。今回は、マニフェスト情報を使用して、マクロを無効にする方法を1つ紹介します。
マニフェストに任意の識別子logging-levelを追加してマクロ定義側で参照し、マクロを無効にします。修正箇所は、下記の2か所です。
{curl 8.0 manifest} {curl-file-attributes character-encoding = "shift-jis"} {manifest sample , logging-level = 0 } || 識別子の定義 以下、省略
{curl 8.0 package} {curl-file-attributes character-encoding = "shift-jis"} {package MACRO} {import * from CURL.IDE.CPA.SOURCE} {import * from CURL.LANGUAGE.SOURCE} {define-macro public {debuginfo ?head:token, ?arg1:identifier, ?arg2:identifier } {if {macro-env.manifest.meta-data.get "logging-level"} == 0 then || 識別子の参照 {return {expand-template } || 空を返却 } } 以下、省略
業務APにおいて、業務共通のような情報により動作を変える局面が多々あると思います。レベルにより、出力情報を変更することも可能です。
使用したマクロの機能
今回紹介したマクロの機能を簡単に説明します。詳しくはヘルプを参照してください。
- define-macro:マクロを定義します。returnでCurlSourceオブジェクトを返却しマクロ呼び出しの箇所に置換されます。
- expand-template:Curlソースコードを表すテキストをCurlSourceオブジェクトに変換します。
- identifier:マクロの入力パターンの1つです。任意の識別子に使用します。
- token:マクロの入力パターンの1つです。リテラル、式、演算子に使用します。
- macro-env:manifestやpackageのメタデータを取得することが可能です。
- bounded-comma-sequence:カンマ区切りの繰り返しを宣言します。
- OperatorKind:Curl ソース パーサーでサポートされている演算子です。文字列からCurlSourceを作成するより処理速度が速い
まとめ
基礎編では、マクロを利用してデバッグトレースを出力する方法を紹介しました。マニフェストと関連づけることで業務APに修正をかけずにデバッグトレースの内容を変えることも可能なため、かなり有効ではないでしょうか。応用編は、マクロの構文解析機能について紹介します。