C言語の伝統に則った言語には文 statement と式 expression の区別があり、+や,、&&といった演算子は式を作り、ifやforやreturn、;といったキーワードは文を作ります。一方、Haxeには構文上、文と式の区別がが存在せず、Rubyのように、C言語では文だったような構文もすべて式です。
多くの式は型と値を持ちますが、Void型の式や、単独で型や値といった意味を持たない式もあります。そのような意味を持たない式もマクロで使うことができます。
イカしたエクスプレッション紹介
enum haxe.macro.ExprDefのメンバーを紹介します。
定数式 EConst
リテラルや識別子。以下の種類があります。
- 整数
CInt - 浮動小数点数
CFloat - 文字列
CString - 識別子
CIdent - 正規表現
CRegexp
添字アクセス式 EArray
e1[e2]の形をした、添字アクセスの式。
単項演算子式 EUnop
op eまたはe opの形をした式。opには次の種類があります。
-
OpIncrement++ -
OpDecrement-- -
OpNot! -
OpNeg- -
OpNegBits~
二項演算子式 EBinop
e1 op e2の形をした式。opには以下の種類があります。
-
OpAdd+ -
OpMult* -
OpDiv/ -
OpSub- -
OpAssign= -
OpEq== -
OpNotEq!= -
OpGt> -
OpGte>= -
OpLt< -
OpLte<= -
OpAnd& -
OpOr| -
OpXor^ -
OpBoolAnd&& -
OpBoolOr|| -
OpShl<< -
OpShr>> -
OpUShr>>> -
OpMod% -
OpInterval... -
OpArrow=> -
OpAssignOp(op:Binop)+=-=/=*=<<=>>=>>>=|=&=^=%=
フィールドアクセス式 EField
e.fieldの形をした式。
括弧式 EParenthesis
(e) の形をした式。
オブジェクト宣言式 EObjectDecl
{field: expr, ...}の形をした、匿名構造体を宣言する式。
配列宣言式 EArrayDecl
[e, ...]の形をした、配列を宣言する式。連想配列や内包記法も内部表現はこの式になります。
呼出し式 ECall
e1(e2, ...)の形をした、関数呼出しの式。
new式 ENew
new T(e, ...)の形をした式。
var式 EVar
var x = e, ...の形をした、変数を宣言する式。値はVoid。
function式 EFunction
function f<T>(arg, ...) eの形をした、関数を宣言する式。
ブロック式 EBlock
{e; ...}の形をした式。ブロック式中一番最後の式の値が、ブロック式全体の値となります。
for式 EFor
for (e1) e2の形をした式。値はVoid。内包記法にも使われます。
in式 EIn
e1 in e2の形をした式。基本的にfor式の中で使われます。
if式 EIf
if (e1) e2 else e3の形をした式。else節がある場合、e1の値に応じて、e2またはe3の値がif式全体の値となります。
while式 EWhile
while (e1) e2またはdo e1 while(e2)の形をした式。値はVoid。
switch式 ESwitch
switch e1 {case e2, ... if (e3): e4; ...}の形をした式。e1の値に応じて、各ケースいずれかがswitch式全体の値となる。マクロ以外ではe1の部分を括弧で括られた(e)か配列の[e, ...]の形のどちらかにしなければなりません。
try式 ETry
try e1 catch (x: T) e2 ...の形をした式。e1または例外を捕捉したcatch節の値を返します。
return式 EReturn
return eの形をした式。Dynamic型として扱われます。
break式 EBreak, continue式 EContinue
breakとcontinueも式。return式同様Dynamic型として扱われます。
untyped式 EUntyped
untyped eの形をした式。eの型を無視した型が付きます。
throw式 EThrow
throw eの形をした式。例外を投げます。
cast式 ECast
cast eまたはcast(e, T)の形をした式。型キャストを行います
三項演算子式 ETernary
e1 ? e2 : e3の形をした式。これの代わりにif式があるので必要ありません。
型検査式 ECheckType
e : Tの形をした式。eの型がTであることをコンパイル時に検査する。マクロ以外では全体を括弧で括って(e : T)の形で使わなければなりません。
メタデータ式 EMeta
@m(p, ...) eや@:m(p, ...) e, $m{e}の形をした式。マクロなどで使います。
macro式
抽象構文木にmacro式は存在しません。これはシンタックスシュガーで、macroを使った表現は、内部で他の式を使った表現に置き換わります。
意味を持たない式
例えばin式は単独では意味を持たず、普通はfor式と組み合わせて使う必要があります。しかし、マクロに与える式の場合には、抽象構文木を書き換えてしまうわけで、式が意味を持たないことは問題となりません。意味を持たない式を使っている具体例として、hxparseのパーサーのswitch streamを使った記法が挙げられます。
曖昧な式
空のオブジェクト宣言式と空のブロック式はどちらも{}という表現で、区別が付きません。構文解析の段階では空のブロック式としてパースしておき、その後の処理で文脈を見て空のオブジェクト宣言か空のブロックかを判断しているようです。気をつけておかないと、マクロを書くときに引っかかりそうですね。
ASTを簡単に調べる方法
ihxを使うと簡単に抽象構文木を調べられます。
% haxelib run ihx
haxe interactive shell v0.3.4
type "help" for help
>> macro [for (x in a) if (p(x)) f(x)]
haxe.macro.Expr : { expr => EArrayDecl([{ expr => EFor({ expr => EIn({ expr => EConst(CIdent(x)), pos => { file => /tmp/IhxProgram_8249.hx, max => 235, min => 234 } },{ expr => EConst(CIdent(a)), pos => { file => /tmp/IhxProgram_8249.hx, max => 240, min => 239 } }), pos => { file => /tmp/IhxProgram_8249.hx, max => 240, min => 234 } },{ expr => EIf({ expr => ECall({ expr => EConst(CIdent(p)), pos => { file => /tmp/IhxProgram_8249.hx, max => 247, min => 246 } },[{ expr => EConst(CIdent(x)), pos => { file => /tmp/IhxProgram_8249.hx, max => 249, min => 248 } }]), pos => { file => /tmp/IhxProgram_8249.hx, max => 250, min => 246 } },{ expr => ECall({ expr => EConst(CIdent(f)), pos => { file => /tmp/IhxProgram_8249.hx, max => 253, min => 252 } },[{ expr => EConst(CIdent(x)), pos => { file => /tmp/IhxProgram_8249.hx, max => 255, min => 254 } }]), pos => { file => /tmp/IhxProgram_8249.hx, max => 256, min => 252 } },null), pos => { file => /tmp/IhxProgram_8249.hx, max => 256, min => 242 } }), pos => { file => /tmp/IhxProgram_8249.hx, max => 256, min => 229 } }]), pos => { file => /tmp/IhxProgram_8249.hx, max => 257, min => 228 } }
よきマクロ生活をお過ごしください。