要約: | - マクロは、ソース コードを実行中に変換する言語拡張機能です。
- Curl® 言語はその強力なマクロ機能により高度な拡張性を提供します。
- マクロは、コンパイル時の処理が必要な場合に用いられます。
|
Curl® 言語を拡張するにはいくつかの方法があります。
新しいオブジェクトの型を作成するクラス、新しいコマンドを作成するためのプロシージャ、
新しいマークアップ形式を作成するためのプロシージャを宣言できます。
マクロを使用することにより、より基礎的な方法で Curl を拡張することができます。
Curl 言語のマクロは、Curl コンパイラのソースコード パターン認識機能サポートにより、
新しい構文を作成することができます。
マクロは他の言語拡張と違い、コンパイル前にコンパイラが適用する書換規則を指定します。
クラス、プロシージャ、テキスト プロシージャの定義は実行時に評価され、
引数およびプロパティは一番最初に評価されます。
次の例で、2つの引数の和を返すプロシージャ、
そしてそのプロシージャを呼ぶコード フラグメントを考察してみましょう。
{define-proc {sum-it a:int, b:int}:int
{return a + b}
}
let x:int = -3
let y:int = 7
{sum-it {abs x}, y}
引数として渡される式は、プロシージャが呼びだされる前に評価されます。
例えば、前のコードでは Curl ランタイムは {abs x} が 3 であると、
sum-it を呼びだす前に評価します。
このタイプの実行時評価は、次の
for 式のような基礎的言語構成では効率的な手段ではありません。
{for i = 1 to n do {output i}}
{output i} 式を、for 演算子の処理の前に評価するのは適切ではありません。
実行時に実行するコードは、{output i} を適切な時点で実行する、一連の操作を行います。
そのため、簡単なプロシージャ、クラス、およびテキスト プロシージャは機能しません。
Curl のマクロ メカニズムは、このようなケースで必要となるパワーそしてさらなる柔軟性を提供します。
Curl コンパイラがマクロの呼び出しに境遇すると、
マクロ定義に基づいて、マクロ コードを新しいソースコードに書き換え、置き換えられたコードをコンパイルします。
もし、ユーザーが解決しなければいけないプログラムの問題に、
このタイプのコンパイル時の処理が必要な場合、マクロの実装が最善の方法となります。
コンパイル時の処理が必要でない場合は、たいていの場合は、
クラス、プロシージャ、またはテキストプロシージャの定義がより良い方法です。
マクロはコンパイル時に評価されるため、コンパイル時に利用不可能な実行時のデータ構造や値にアクセスすることはできません。
ユーザーのプログラムの問題において、実行時のデータ構造や値にアクセスする必要がある場合、
マクロの定義は良い方法ではありません。
簡単な例で始めてみましょう。データを並べ替えるための目的で、2 つの変数または配列要素に代入する値を交換するコードを記述するとします。この操作は次のフォームのコードで実行できます。
{do
let tmp:{compile-time-type-of <x>} = <x>
set <x> = <y>
set <y> = tmp
}
ここで <x> と <y> は変数名または配列参照を表すプレースホルダーです。これらが変数名を表すという仮定で記述を開始し、配列参照やその他の種類の式を処理できるより一般的なソリューションについては後で考察することにします。
この操作を swap という名前で始まる単一の式で表わし、コードを短縮して 2 つの位置変数を持つプロシージャのように読めるようにします。{swap <x>, <y>} は <x> と <y> が同じデータ型の 2 つの変数名を表し、新しいマクロで認識する必要がある最も簡単な入力パターンになります。
変換の出力は上記で示したコード ブロックになり、コードのコンパイルや実行前に正しい変数名が記号 <x>と <y> に代入されます。この変換プロセスをマクロ展開と言います。
このマクロは次のように記述できます。
{define-macro public {swap ?x:identifier, ?y:identifier}
{return
{expand-template
{do
let tmp:{compile-time-type-of ?x} = ?x
set ?x = ?y
set ?y = tmp
}
}
}
}
この例では、すべてのマクロ定義に共通する基本要素を示しています。マクロ定義のフォームは、グローバル プロシージャ定義のフォームに似ています。グローバル プロシージャと同様に、他の定義やネストされたコードブロック内ではなくトップレベル (通常はパッケージ ファイル内) でマクロを定義する必要があります。
このマクロ定義とプロシージャ定義の相違点は次のとおりです。
- マクロ定義では、define-proc の代わりに define-macro または define-syntaxで始まります。
- メタ変数 <x> は ?x:identifier としてマクロの署名内で表わされます。ここで、ESC 文字 ? はパターンの非リテラル要素が後に続くことを示し、x はその要素にバインドされる変数名、:identifier はソース コードの一部として要素のデータ型を示します。
- このマクロは、カンマで区切られた 2 つの識別子 var1 と var2 に対して、{swap var1, var2} のフォームで呼び出されます。
- 定義本体には return ステートメントが含まれていますが、戻り値のデータ型はマクロで指定されていません。
- 各マクロの呼び出しを置き換えるソース コードは、expand-template 式で作成されます。この式のコンテンツ内で、?x と ?y の各インスタンスはマクロ呼び出しで使用される 1 番目と 2 番目の識別子に置き換えられます。
注意: 使用上の特別な制限として、使用される場所以外のパッケージでCurl マクロを定義する必要があります。詳細については、「
マクロの制限」のセクションを参照してください。
したがって最初の例は、テストするアプレット内ではなく Curl パッケージ内で定義する必要があります。次のヘッダーを持つ "swap-macros.scurl" という名前のファイルに保存します。
|| file "swap-macros.scurl"
{curl 8.0 package} || curl herald
{package SWAP-MACROS} || package declaration
次のインタラクティブな例では、使用前にユーザー定義パッケージから swap マクロをロードする必要があります。
例:
2 つの識別子の値の交換 |
|
{import * from SWAP-MACROS,
location = "../../default/support/swap-macros.scurl"
}
{let first:double = 1.0,
second:double = 2.0
}
{swap first, second}
{spaced-vbox
background = "silver",
{text first: {value first}},
{text second: {value second}}
}
| |
前述の例では、2 つの位置引数で呼び出されるプロシージャのフォームを模倣してマクロのフォームを作成しましたが、これをプロシージャと混同しないようにしてください。
まず、パターン テンプレートでは引数リストをカンマで区切る必要はありません。異なる入力パターンを簡単に指定できます。たとえば、次のようなマクロ呼び出しでは、
{swapping first <-> second}
次のフォームのマクロ テンプレートを作成します。
{swapping ?x:identifier <-> ?y:identifier}
マクロの他の部分には変更はありません。同様に、任意のテキストを含めたり要素間のカンマを省略することができます。
ただし、x <-> y などの新しい infix 演算子を定義してそれ自体で変換を起こすことはできません。入力パターンは中カッコで囲まれた式で、マクロ名となる識別子で始まるフォームで指定する必要があります。
他にプロシージャとの違いとして、上記で定義された
swap マクロは、指定されたパターンのとおりに使用する必要があります。つまり、2 つの識別子とその間のカンマというパターンを使用し、引数の 1 つとして配列要素を受け入れることはできません。たとえば、上記のインタラクティブな例を編集して変数
third を配列として次のように定義するとします。
{let third:{Array-of int} = {new {Array-of double} 3.0}}
次のようにマクロを呼び出してみます。
配列要素
third[0] の値をスケーラー変数の
second と交換できるように思えますが、例を変更して
実行 を押すと、
third[0] 式が
identifier 型のパターンと一致しないために構文エラー メッセージが表示されます。このパターンを一般化する方法を次に示します。
上記のように、
define-macro はソース コードの変換 (マクロ) に名前をバインドするのに使用されます。このコンストラクトの構文は次のとおりです。
構文: | {define-macro [access]
{macro-name pattern-template}
code-body
}
|
説明: |
access | 通常 public または libraryで、マクロを他のパッケージから呼び出すことができます。 |
macro-name | この変換処理に割り当てる名前です。 |
pattern-template | 各マクロの呼び出しで macro-name に続く一連の要素を表します。 |
code-body | 実行されるコードが入ります。古いソース コードを置き換える新しいソース コードを返す必要があります。 |
|
{macro-name pattern-template}
上記のマクロ定義部分をマクロの
署名と言い、このマクロを使用するために必要な入力フォームを表します。マクロ呼び出しと署名が一致しない場合、コンパイラは構文エラーを生成します。
code-body ではパターン マッチ処理で定義された変数を使用して計算を任意に実行できますが、成功した各マクロ呼び出しに対して新しい一連のソース コードを返して終了する必要があります。
- パラメータ リストの代わりに pattern-template を指定します。
- 戻り値のデータ型は指定されません。
- 戻り値は通常 expand-template 式で生成されます。
パターン テンプレートは一連の標準的なコードで、要素間の空白以外は正確に一致させる必要があり、ESC 文字 ? で示されるフォーム ?[variable]:pattern のパターン マッチ式が含まれます。コンパイラがマクロ呼び出しを解析するときに、これらのパターン マッチ式をオーバーラップしていないマクロ呼び出し部分と一致させる必要があります。記号 ? と :pattern の間に変数名がある場合、この名前はパターンと一致するソース コード部分にバインドされ、その値を code-body で使用できます。
パターン テンプレートで使用できる特殊なパターンには、次の単純なパターンがあります。
- identifier
- expression
- statement
- class-member
- text
- token
- verbatim (任意のソース コードに一致するもっとも一般的なパターン)
また、sequence、comma-sequence および one-of のような 1 つ以上のパターンの繰り返しに一致させる複合パターンを作成することもできます。expressions などゼロ個以上の一連の式とマッチさせる、事前定義の複合パターンもあります。
これらの単純パターンおよび複合パターンの詳細については、「
入力パターン」セクションを参照してください。
Curl マクロはプロシージャではなくプロシージャ タイプを持たないので、引数としてプロシージャに渡すことはできません。マクロの使用、つまり
マクロ呼び出しでは、値が返される場合と返されない場合があります。マクロの
code-body は、特別な種類の
CurlSource オブジェクトを返しますが、この戻り値を元のソース コードにある変数に代入できません。この戻り値は、単に元のコンテキストに組み戻される置換用のソース コードです。
名前空間の保護やコンパイルの順序などの理由から、マクロは呼び出されるパッケージとは異なるパッケージで定義される必要があります(この制限は、Curl RTE の今後のリリースでは多少緩和される可能性があります)。この制限により、マクロ呼び出し式が評価される前に、マクロ定義および呼び出される任意のヘルパー機能が確実にコンパイルされます。したがって、呼び出すマクロを
package に属するファイルに含める必要があります。また、マクロを呼び出すアプレットまたは他のパッケージにマクロ名がインポートできるように
access を
public として指定する必要があります。(マクロが定義されているライブラリと同じライブラリでマクロが使用される場合は、
library アクセス属性を使用することも出来ます。)
マクロ定義の名前の使用には、不測の名前空間の競合を防止し、いわゆる
Hygienic (潔癖な) マクロの作成に役立つ特別なスコープ ルールがあります。これらのルールでもっとも重要なものは次のとおりです。
- マクロ定義内で定義されていない識別子は、マクロが呼び出されるパッケージやアプレット内ではなくマクロが定義されているパッケージ内で常に検索されます。
- マクロ呼び出しで生成されるコード内で定義された新しい名前は、既定ではそのマクロ呼び出し固有の特別な名前空間内で作成されます。
最初のルールは、同一マクロの呼び出しは、呼び出される環境に応じてではなく、どこで呼び出されても同じ方法で展開されることを意味します。これは、マクロが使用する名前はマクロが定義されている環境に依存しているためです。このルールによりマクロの動作が安定し、また予期せずに個人情報を取り込むマクロ呼び出しを防止します。この挙動は、非 public、トップレベルでの定義を直接参照するためにマクロ展開を許可しています。例えば、マクロはマクロが定義されているパッケージ内で定義された package-protected 関数の呼び出しに展開されます。この挙動はクラスメンバーには適用されません。ですから、マクロ展開はマクロのパッケージ内で定義された package-protected のクラスのメンバーは参照されません。
2 番目のルールは、マクロ呼び出しが繰り返されても同じ識別子が競合を引き起こさずに安全に定義され、各インスタンスが新しいシンボルとして解釈されることを意味します。したがってマクロを簡単に作成および再利用できます。逆に、マクロ呼び出し内部で変数、定数またはプロシージャに名前を代入し、コード内の他の場所で再利用する場合があります。この場合は
expand-template 式の内部で特殊なエスケープ シーケンス
?= を使用して、ローカル環境で一意ではない名前が定義または参照されるようなコードを生成できます。
{expand-template x} などの
CurlSource 識別子式の前に
?= を置きます。
たとえば、次のコードをマクロ定義に含めて、マクロ外部でアクセスできる
foo という名前の変数を定義するコードを生成できます。
{define-macro public {create-foo}
{return
{expand-template
{let ?={expand-template foo}:int = 13}
}
}
}
呼び出し環境のローカル コードは、次のように他のマクロと同様に
foo にアクセスできるようになります。
{define-macro public {reset-foo}
{return
{expand-template
{set ?={expand-template foo} = 0}
}
}
}
その他の制限としては、マクロ定義により現在実行中のプロセスのグローバル状態を変更することはできません。特に、マクロ呼び出しの展開において、現在使用のパッケージ、またはその他のパッケージ内の任意のグローバル変数値にアクセスすることはできません。また、マクロ展開は外部の入出力処理を実行しません。ただし、マクロ呼び出しで変換される新しいコード自体はグローバル変数の読み書きが可能か、または I/O を実行することができます。
上記のとおり、Curl マクロの本質はソース コードを 1 つのパターンから別のパターンに変換することであり、この置換処理をマクロ展開と言います。単一のマクロ呼び出しの評価が複数の展開を伴うことがあります。変換されるソース コードに他のマクロ呼び出しが含まれる場合がそうです。評価されるコード内にマクロ呼び出しが存在しなくなるまで、マクロ展開が繰り返されます。
構文: | {expand-template code-template} |
マクロ expand-template には、特別な ? ESC 文字で始まる式の値を周りのテキストに "つなげる" ことによって、入力からソース コードを生成する機能があります。
識別子 x と y の使い方の違いを次の例で示します。
例:
expand-template の使用 |
|
{value
let x = {expand-template 5}
let exp = {expand-template {output ?x + y}}
{exp.get-text}
}
| |
置換された値は
CurlSource オブジェクトまたは
CurlSource の配列になります。配列の要素すべてが順番につなぎ合わされます。置換された値は通常パターン マッチングの結果として取得されますが、変数を
CurlSource 型として宣言し、
expand-template 式で返される値を代入することもできます。
マクロを記述してテストするときに、マクロ呼び出しの結果が表示されてマクロが予想通りに動作することをすぐに確認できると便利です。プロシージャ コールではないので、マクロ展開で生成されたコード変換には通常はアクセスすることができず、変数への代入も簡単にはできません。さらに、1 つのマクロ呼び出しの結果として複数段階のマクロ展開が発生することもあり、どの段階でどの変換が起こるかを確認する必要もあります。
Curl API にはマクロのデバッグに役立つように設計された
expand-to-string という特別なフォームがあります。必要に応じて外部マクロ呼び出しを 1 回以上繰り返し (式の内部でネストされているマクロは無視)、結果のコードを表示可能な文字列フォームに転換します。構文は次のとおりです。
構文: | {expand-to-string [ max-iterations = 1, ] {name ...} } |
パラメータ max-iterations は、展開の反復回数の最大値で、既定では 1 に設定されています。反復処理は、コードの一番外側のレベルにのみ適用します。値は整数リテラル max-int または infinity である必要があります。
この
expand-to-string ユーティリティを使用すると、マクロ呼び出しがソース コードを変換していく様子を段階的に確認することができます。たとえば、次のように最初のマクロ例の展開をチェックします。
例:
{swap x, y} における expand-to-string の使用 |
|
{import swap from SWAP-MACROS,
location = "../../default/support/swap-macros.scurl"
}
{pre
{expand-to-string {swap x, y}}
}
| |
複数の反復を必要とするマクロ呼び出しでこれを使用してマクロを完全に展開するために、まず hello という簡単なマクロを定義します。これは "Hello, world!" という文字列を持つ {hello} の呼び出しを置き換えるだけのマクロです。
|| file "hello-macros.scurl"
{curl 8.0 package}
{package HELLO-MACROS}
{define-macro public {hello}
{return
{expand-template "Hello, world!"}
}
}
次に 2 番目のマクロを作成して、最初のマクロを呼び出すコードを生成します。
|| file "hello-macros.scurl" (in part)
...
{define-macro public {indirect-hello}
{return {expand-template {hello}}}
}
これらの 2 つのマクロ定義を HELLO-MACROS というパッケージに追加し、hello-macros.scurl というファイルを作成して、次に示すようにアプレットからテストします。
例:
引数なしのマクロ呼び出し |
|
{import hello, indirect-hello from HELLO-MACROS,
location = "../../default/support/hello-macros.scurl"
}
The expression |"{hello}"| expands to:
{expand-to-string {hello}}
After one iteration |"{indirect-hello}"| expands to:
{expand-to-string {indirect-hello}}
After two iterations it expands to:
{expand-to-string max-iterations = 2, {indirect-hello}}
When a macro is used normally, it is expanded all the way:
{HBox
margin = 5pt,
background = "yellow",
{indirect-hello}
}
| |
マクロを別のパッケージからインポートして、../../default/support/hello-macros.scurl ファイルからロードする必要があることに注意してください。。
このマクロの署名では名前の後にパターン テンプレートが含まれていないため、マクロを呼び出すときは名前の後に他のコードはなく、
{hello} のみ指定する必要があります。上記の例の
{hello} を
{hello world} に変更して
Execute を押すと、
SyntaxError になります。
SyntaxError:
式の終わりでなければなりませんでしたが、'world' が見つかりました。
Curl 言語のパブリック マクロ機能の目的は、単純なマクロが簡単に記述でき、同時に必要に応じて複雑なマクロの作成にも十分なパワーを提供することです。
Curl マクロが簡単に記述できるのは、組み込みのパターン マッチング機能によります。これは、識別子、式、ステートメントおよびリテラル コードのようなソース コード要素や、これらの要素で構成されている任意の長さのシーケンスを認識できる機能です。
最初の例で署名が
{swap ?x:identifier, ?y:identifier}
というフォームのマクロがありました。このマクロは、
のように 2 つの識別子の名前で呼び出す必要があります。
この式を展開するときに Curl コンパイラはマクロ式を解析して、カンマで区切られた 2 つの識別子が含まれていることを確認する必要があります。
使用可能なパターンは他にも多数あります。たとえば、2 つの式の値を交換するために、より一般的な交換マクロ、つまり単純な識別子よりもっと一般的なフォーム (パッケージ名やオブジェクト名をプレフィックスとして持つ識別子のような) の交換マクロを記述する場合、代わりに expression パターンを使用できます。
|| file "swap-macros.scurl" (in part)
{curl 8.0 package}
{package SWAP-MACROS}
...
{define-macro public {swap-expressions ?a:expression, ?b:expression}
{return
{expand-template
{do
let tmp:{compile-time-type-of ?a} = ?a
set ?a = ?b
set ?b = tmp
}
}
}
}
この新しいマクロは、
{swap-expressions x, arr[i]}
または
{swap-expressions my-class.field-x, my-other-class.field-y}
などのカンマで区切られた 2 つの Curl 式を受け取ります。
このマクロが変数と配列要素の両方で使用できることを確認してみましょう。
例:
2 つの式の値の交換 |
|
{import swap-expressions from SWAP-MACROS,
location = "../../default/support/swap-macros.scurl"
}
{let arr:StringArray = {StringArray "one","two","three"},
var1:String = "foo",
var2:String = "bar"
}
{swap-expressions var1, var2}
{swap-expressions var2, arr[2]}
Now var1 has value {value var1},
var2 has value {value var2},
and arr[2] has value {value arr[2]}.
| |
同様に、オブジェクトの 2 つのフィールドの値を交換できます。
例:
2 つのフィールド値の交換 |
|
{import * from SWAP-MACROS,
location = "../../default/support/swap-macros.scurl"
}
{define-class public MyClass
field public-get public-set x:int = 13
field public-get public-set y:int = 42
}
{let my-object:MyClass = {MyClass}}
{swap-expressions
my-object.x,
my-object.y
}
Now field x has value {value my-object.x},
{br} and field y has value {value my-object.y}.
| |
上記の例は解説が目的で、一般的な使用法ではありません。実際には、
swap-expressions の各引数は評価可能な式であるだけでなく、新しい値に設定できる位置を表す必要があるため、このようなマクロを呼び出す場合には注意が必要です。たとえば
x + y は評価可能な式ですが、新しい値を代入することはできないので
swap-expressions の引数として使用できません。考慮すべきその他の要因には次が含まれます。
- 両方の式が同じデータ型を持つことをマクロがチェックする必要があるかどうか。コンパイル時の型 (compile-time-type-of) または実行時の型 (type-of) をテストする必要があるかどうか。
- 評価のたびに何らかの影響を及ぼす場合、x と y にマッチする表現の評価回数が問題となるかどうか。
- マクロ呼び出し時のエラーがどのように処理されるか。
次の表に、マクロのパターン テンプレートで使用できる単純な名前付きのパターンを示します。
パターン名 | 説明 | 例 |
identifier | 任意の正当な識別子とマッチします。 | MyObject, has-been-analyzed? |
expression | 識別子、リテラル値 (数字、文字列、true または false など)、単項または 2 項演算子式、または中カッコ内のプレフィックス式とマッチします。 | 3.14159, true, not y, x + (7 * 3), a[b], x.y, {text Foo} |
statement | ステートメントは expression とマッチするものに加え、let または set ステートメントとマッチします。これらを囲む中カッコはオプションで省略されています。 parse-statement を参照してください。 | let x = y, set a = b |
class-member | define-class 内で発生する任意のメンバにマッチする class-member。詳細は parse-class-member を参照してください。 | field public x:int = 0, {method public {x}:int {return 3}} |
token | 中カッコで囲まれた完全なプレフィックス式、識別子、リテラルまたは演算子のいずれかの認識可能な 1 つの Curl トークンとマッチします。トークン間の空白は無視されます。 | 7, +, foo, "stuff", {value x}, {hello 1.2.3} |
text | 後続の中カッコ式で区切られるまでの一連のテキスト文字列とマッチします。空白は保持されますが、バックスラッシュ エスケープ シーケンスは処理されます。 | H*Y*M*A*N K*A*P*L*A*N |
verbatim | 残りのすべてソース コードとマッチします。 | {verbatim arbitrary source code {...}} |
たとえば、次の pattern の場合、
{pattern alpha ?id:identifier beta}
識別子 alpha、その次は任意の識別子で最後に識別子 beta とマッチします。後続のコードがアクセスできるように、ここでは名前 id がパターンと一致する識別子に代入されます。
通常、上の表のパターンは greedy (欲張り) です。これは、選択可能な状態では短いものより長いソース コードのシーケンスへのマッチングが優先されるという意味です。
これらのパターンの最後の 2 つでは空白が保持されます。他のパターンでは、余分な内部の空白は無視して言語要素を返します。
さらに複雑なパターンを指定することもできます。たとえば、カンマで区切られた 1 つ以上の式で構成される複合パターンや、任意のパターン リストの 1 つと正確に一致するパターンなどを指定できます。
次の表に、複合パターンを構築するのに使用できるすべての演算子を示します。
パターン演算子 | 説明 | 例 |
{pattern pattern-template} | パターン テンプレートから 1 つの新しいパターンを作成する単純なグループ化演算子。 | {pattern
{while ?predicate:expression do
?body:statements
}
}
|
{literal typename} | このパターンは任意の型の単一 Curl リテラルとマッチします。 | {literal int}、{literal Distance}、 {literal String} |
{one-of pattern1 [, pattern2...]} | リスト内のパターンの 1 つとマッチするパターンで、最初にマッチするパターンで停止します。one-of は、パターンを区切るカンマがあいまいにならないように、パターン テンプレートではなくオペランドとしてパターンを取ることに注意してください。 | {one-of
{pattern above},
{pattern below},
{pattern to},
{pattern downto}
}
|
{optional pattern-template} | pattern-template のパターン マッチングまたはマッチングなし。前者が優先されます。 | {pattern
{while ?tag-expr:{optional tag = ?:identifier,}
?predicate:expression
do
?body:statements
}
}
|
{sequence pattern-template} | ゼロ個以上の pattern-template とマッチするパターン。 | このテンプレートはゼロ個以上の式とマッチします。{pattern
?code-body:{sequence ?:expression}
}
|
{comma-sequence pattern-template} | ゼロ個以上のカンマで区切られた pattern-template とマッチするパターン。 | このテンプレートは、カンマで区切られたゼロ個以上の引数 とマッチします。{pattern
?arguments:{comma-sequence ?:expression}
}
|
{bounded-sequence min, max, pattern-template} | min と max の間の数で pattern-template とマッチするパターン。上限を指定しない場合は infinity を使用します。 | 2 つ以上の識別子の列とのマッチング{bounded-sequence 2, infinity, ?:identifier}
|
{bounded-comma-sequence min, max, pattern-template} | min と max の間の数でカンマで区切られた pattern-template とマッチするパターン。 | カンマで区切られた 2 つ以上の識別子 の列とのマッチング {bounded-comma-sequence 2, infinity, ?:identifier}
|
たとえば、次のパターンは、識別子 alpha で始まり、ゼロ個以上の他の識別子を含み、識別子 beta の最初のインスタンスが後に続くシーケンスとマッチします。
{pattern alpha ?ids:{sequence ?:identifier} beta}
名前 ids の値がサイズ 0 のマッチ配列になる可能性があることに注意してください。
シーケンス コンストラクタはすべて non-greedy (欲張りではない) であり、つまりできるだけ少ない数のマッチングが優先されます。
シーケンスの個々の要素や、one-of や optional パターンの一部のようにマッチングがあるとは限らないパターンの部分要素に、パターン内の名前をアタッチすることはできないことに注意してください。
ただし、
sequence や
comma-sequence パターン全体に名前をアタッチする場合は、マッチングで生成されるオブジェクトは
for 式でループしてすべてのマッチングを表示するコンテナになります。
size メソッドにアクセスしてコンテナのサイズを取得できます。
comma-sequence の場合、反復処理ではカンマは表示されませんが、コンテナを
expand-template 呼び出しに代入するとカンマが表示されます。
ほとんどの単純なパターンには、ゼロ個以上のパターン インスタンスのシーケンスを意味する、省略形の複数を示すフォームがあります。たとえば
statements は
{sequence ?:statement} と同じです。省略形の複数を示すフォームには、次が含まれます。
- expressions
- statements
- identifiers
- texts
- tokens
これにより、前述のパターンは次のようにもっと簡単に表現できます。
{pattern alpha ?ids:identifiers beta}
またパターン マッチングを回避して、?raw:verbatim のように単純な逐語的パターン テンプレートを指定することにより、"raw (そのままの状態の)" 処理を行うことができます。ソース コードの一部が token や expression などのパターンに一致した後では、それより小さい要素に再解析できないことに注意してください。したがって、コードを変換するときには最初にマクロの入力パターンを十分詳細に指定する必要があります。
syntax-switch の目的の 1 つは、Curl ソース コードの特定クラス、
CurlSource とそのサブクラスに関する知識を不要にすることです。ただし上級ユーザーは、操作によっては直接
CurlSource 呼び出しに頼る必要がある場合があります。
syntax-switch は、ソース コード オブジェクトと複数の可能なパターンの 1 つがマッチングに成功した後で、コード ブロックに実行をスイッチします。この構文は
switch に非常に似ていますが、各
case ステートメントにパターンを伴います。
構文: | {syntax-switch source-object [, index:ParseIndex = BOS] [, must-match?:bool = false] [case pattern do statements] [case pattern do statements] ... [else statements] } |
説明: |
source-object | マッチングが見つかるまで各パターンに対して順にマッチングを 行います。 |
pattern | source-object と比較するパターンを表します。 |
statements | マッチした最初の pattern に対して実行されるコードです。 pattern 内でマッチした変数を参照できます。 マッチする case 式がない場合、else 句に続く任意の statements が実行されます。 |
must-match? = true | マッチする pattern がない場合、syntax-switch に SyntaxError を スローさせるオプションです。 must-match? が true の場合、else 句は実行できません。 |
index = value | 上級ユーザー用で、最初 (BOS) からではなく 途中から source-object の解析を続ける場合に使用します。
|
|
マクロ署名の場合と同じ種類のパターンを syntax-switch で使用できますが、各 case ステートメントには 1 つのパターンを指定し、パターン要素のシーケンスは指定できません。簡単な例を示します。
{syntax-switch source
case {pattern alpha ?id:identifier beta} do
{output "I matched identifier ", id.name}
else
{error "no match"}
}
同様に、次のようなゼロ個以上の識別子のシーケンスもマッチできます。
{syntax-switch source
case {pattern alpha ?ids:identifiers beta} do
{output "I matched ", ids.size, " identifiers"}
else
{error "no match"}
}
syntax-switch の主な用途は、複合パターン内の個々の要素に名前をつけたり、
one-of パターン内でどのパターンがマッチするか、または
optional パターン要素が存在するかどうかを検出することです。
この制御構造を使用して、異なるパターンのコンポーネントを 1 度に 1 ケースずつマッチして名前を付けてから、各ケースで
expand-template を使用して異なる出力を返すことができます。次のセクションの例でこの使用方法について説明します。
syntax-switch を利用したマクロ定義の拡張例を示します。このマクロの目的は、C 言語に似た単純なパブリック クラス
struct を作成するのに必要な作業を簡易化することです。このクラスは 1 つ以上の名前の付いた、すべてパブリック アクセスが可能なフィールドで構成され、コンストラクタを提供して各フィールドのこのタイプのオブジェクトを 1 つの値で初期化します。
このマクロは
define-struct と呼ばれ、次の入力コードを
{define-struct public X a:double, b:bool, c:HBox, d:int}
以下の新しいコードに変換します。
{define-class public X
field public a:double, b:bool, c:HBox, d:int
{constructor public
{default a:double, b:bool, c:HBox, d:int}
set self.a = a
set self.b = b
set self.c = c
set self.d = d
}
}
マクロを下のアプレットから呼び出せるように、別のパッケージ ファイルに定義します。
|| file "define-struct.scurl"
{curl 8.0 package}
{package DEFINE-STRUCT}
{import * from CURL.LANGUAGE.SOURCE}
{define-macro public
{define-struct
?access:{one-of
{pattern public},
{pattern package},
{pattern}
}
?name:identifier
?fields:{bounded-comma-sequence 1, infinity,
?:identifier
:
?:expression
}
}
let init-list:{Array-of CurlSource} = {new {Array-of CurlSource}}
{for entry key index in fields do
{syntax-switch entry, must-match? = true
case {pattern
?field-name:identifier
:
?field-type:expression
}
do
{init-list.append
{expand-template
set self.?field-name = ?field-name
}
}
}
}
{return
{expand-template
{define-class ?access ?name
field ?access ?fields
{constructor ?access {default ?fields}
?init-list
}
}
}
}
}
次の例では上に示したマクロを呼び出します。
例:
define-struct マクロの呼び出し |
|
{import define-struct from DEFINE-STRUCT,
location = "../../default/support/define-struct.scurl"
}
{value
{pre
{expand-to-string
max-iterations = 1,
{define-struct MyStruct a:int, b:double, c:HBox, d:int}
}
}
}
{define-struct MyStruct a:double, b:bool, c:HBox, d:int}
{let my-box:HBox = {HBox background = "yellow","some stuff"}}
{let x:MyStruct = {MyStruct 13.0, true, my-box, 43}}
x has fields:{br}{value x.a}, {value x.b}, {value x.c},
and {value x.d}
| |
上記の例では、すべての Curl ソース コードを表すクラス (
CurlSource クラス) が紹介されていました。マクロはソース コードを変換するため、マクロ呼び出しへの入力、マクロのコードで処理されるパターン マッチ部分、およびマクロ展開で生成された置換コードは、すべて
CurlSource 型のオブジェクトまたはそれらのオブジェクトの配列になります。明示的に
CurlSource を使用せずにマクロを作成して使用することはできますが、先の例で示したように、このクラスについての多少の知識が役に立つ場合もあります。
最も役に立つソース コード クラスのいくつかを次に示します。
これらのクラスについては『API リファレンス』で参照してください。これらのクラスで最も役に立つメソッドと属性の要約を次に一覧します。
Copyright © 1998-2019 SCSK Corporation.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of SCSK Corporation.
that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of SCSK Corporation.