如何将 Expr(表达式)数组传递给 Haxe 宏?
How to pass an Array of Expr (expressions) to Haxe Macro?
我基本上是在尝试创建一个宏,通过为所有条件提供一个通用结果语句来自动生成 if/else if
链。
这是我迄今为止尝试过的(修改代码只是为了作为示例):
import haxe.macro.Expr;
class LazyUtils {
public macro static function tryUntilFalse( xBool:Expr, xConds:Array<Expr> ) {
var con1, con2, con3, con4, con5;
/*
* Here's a switch to handle specific # of conditions, because for-loops
* don't seem to be allowed here (at least in the ways I've tried so far).
*
* If you know how to use for-loop for this, PLEASE do tell!
*/
switch(xConds.length) {
case 1: {
con1 = conds[0];
return macro {
if (!$con1) $xBool;
}
}
case 2: {
con1 = conds[0];
con2 = conds[1];
return macro {
if (!$con1) $xBool;
else if (!$con2) $xBool;
}
}
case 3: {
con1 = conds[0];
con2 = conds[1];
con3 = conds[2];
return macro {
if (!$con1) $xBool;
else if (!$con2) $xBool;
else if (!$con3) $xBool;
}
}
// ... so on and so forth
}
return macro { trace("Unhandled length of conditions :("); };
}
}
那么理论上可以这样使用:
class Main {
static function main() {
var isOK = true;
LazyUtils.tryUntilFalse( isOK = false, [
doSomething(),
doSomethingElse(), //Returns false, so should stop here.
doFinalThing()
]);
}
static function doSomething():Bool {
// ???
return true;
}
static function doSomethingElse():Bool {
// ???
return false;
}
static function doFinalThing():Bool {
return true;
}
}
哪个应该生成这个条件树:
if (!doSomething()) isOK = false;
else if (!doSomethingElse()) isOK = false;
else if (!doFinalThing()) isOK = false;
或者,我想它可以输出这个:
if(!doSomething() || !doSomethingElse() || !doFinalThing()) isOK = false;
现在回想起来,确实如此 - 编写整个宏来生成更容易以原始格式输入的代码可能没有多大意义。
但是为了学习宏,有谁知道是否可以像我在上面的代码示例中尝试的那样在 Array<Expr>
中传递多个表达式?
您可能无法使 xConds
参数按您预期的方式运行,因为类型为 Array<Expr>
的表达式宏的最后一个参数隐式为 rest argument。这意味着您最终得到一个包含单个 EArrayDecl
表达式的数组。这可以通过简单地省略 []
.
来解决
关于生成 if
-else
-chain - 让我们看一下 EIf
:
/**
An `if(econd) eif` or `if(econd) eif else eelse` expression.
**/
EIf( econd : Expr, eif : Expr, eelse : Null<Expr> );
链可以被认为是一个单链表 - eelse
如果第一个 EIf
应该引用下一个 EIf
等等,直到我们用 eelse = null
最后一个 EIf
。所以我们想为你的例子生成这个(伪代码):
EIf(doSomething(), isOk = false, /* else */
EIf(doSomethingElse, isOk = false, /* else */
EIf(doFinalThing(), isOk = false, null)
)
)
递归对此很有效。
通常使用具体化比像我在这里做的原始表达式更方便,但我不确定在动态生成这样的表达式时前者是否真的可行。
import haxe.macro.Context;
import haxe.macro.Expr;
class LazyUtils {
public macro static function tryUntilFalse(setBool:Expr, conditions:Array<Expr>):Expr {
return generateIfChain(setBool, conditions);
}
private static function generateIfChain(eif:Expr, conditions:Array<Expr>):Expr {
// get the next condition
var condition = conditions.shift();
if (condition == null) {
return null; // no more conditions
}
// recurse deeper to generate the next if
var nextIf = generateIfChain(eif, conditions);
return {
expr: EIf(condition, eif, nextIf),
pos: Context.currentPos()
};
}
}
和Main.hx
(基本不变):
class Main {
static function main() {
var isOK = true;
LazyUtils.tryUntilFalse(isOK = false,
!doSomething(),
!doSomethingElse(), //Returns false, so should stop here.
!doFinalThing()
);
}
static function doSomething():Bool {
trace("doSomething");
return true;
}
static function doSomethingElse():Bool {
trace("doSomethingElse");
return false;
}
static function doFinalThing():Bool {
trace("doFinalThing");
return true;
}
}
为了简单起见,我在调用站点用 !
反转了函数调用参数,而不是在宏中处理它。
您可以使用 -D dump=pretty
生成 AST 转储并检查生成的代码。结果如下:
if ((! Main.doSomething()))isOK = false else if ((! Main.doSomethingElse()))isOK = false else if ((! Main.doFinalThing()))isOK = false;
我基本上是在尝试创建一个宏,通过为所有条件提供一个通用结果语句来自动生成 if/else if
链。
这是我迄今为止尝试过的(修改代码只是为了作为示例):
import haxe.macro.Expr;
class LazyUtils {
public macro static function tryUntilFalse( xBool:Expr, xConds:Array<Expr> ) {
var con1, con2, con3, con4, con5;
/*
* Here's a switch to handle specific # of conditions, because for-loops
* don't seem to be allowed here (at least in the ways I've tried so far).
*
* If you know how to use for-loop for this, PLEASE do tell!
*/
switch(xConds.length) {
case 1: {
con1 = conds[0];
return macro {
if (!$con1) $xBool;
}
}
case 2: {
con1 = conds[0];
con2 = conds[1];
return macro {
if (!$con1) $xBool;
else if (!$con2) $xBool;
}
}
case 3: {
con1 = conds[0];
con2 = conds[1];
con3 = conds[2];
return macro {
if (!$con1) $xBool;
else if (!$con2) $xBool;
else if (!$con3) $xBool;
}
}
// ... so on and so forth
}
return macro { trace("Unhandled length of conditions :("); };
}
}
那么理论上可以这样使用:
class Main {
static function main() {
var isOK = true;
LazyUtils.tryUntilFalse( isOK = false, [
doSomething(),
doSomethingElse(), //Returns false, so should stop here.
doFinalThing()
]);
}
static function doSomething():Bool {
// ???
return true;
}
static function doSomethingElse():Bool {
// ???
return false;
}
static function doFinalThing():Bool {
return true;
}
}
哪个应该生成这个条件树:
if (!doSomething()) isOK = false;
else if (!doSomethingElse()) isOK = false;
else if (!doFinalThing()) isOK = false;
或者,我想它可以输出这个:
if(!doSomething() || !doSomethingElse() || !doFinalThing()) isOK = false;
现在回想起来,确实如此 - 编写整个宏来生成更容易以原始格式输入的代码可能没有多大意义。
但是为了学习宏,有谁知道是否可以像我在上面的代码示例中尝试的那样在 Array<Expr>
中传递多个表达式?
您可能无法使 xConds
参数按您预期的方式运行,因为类型为 Array<Expr>
的表达式宏的最后一个参数隐式为 rest argument。这意味着您最终得到一个包含单个 EArrayDecl
表达式的数组。这可以通过简单地省略 []
.
关于生成 if
-else
-chain - 让我们看一下 EIf
:
/**
An `if(econd) eif` or `if(econd) eif else eelse` expression.
**/
EIf( econd : Expr, eif : Expr, eelse : Null<Expr> );
链可以被认为是一个单链表 - eelse
如果第一个 EIf
应该引用下一个 EIf
等等,直到我们用 eelse = null
最后一个 EIf
。所以我们想为你的例子生成这个(伪代码):
EIf(doSomething(), isOk = false, /* else */
EIf(doSomethingElse, isOk = false, /* else */
EIf(doFinalThing(), isOk = false, null)
)
)
递归对此很有效。
通常使用具体化比像我在这里做的原始表达式更方便,但我不确定在动态生成这样的表达式时前者是否真的可行。
import haxe.macro.Context;
import haxe.macro.Expr;
class LazyUtils {
public macro static function tryUntilFalse(setBool:Expr, conditions:Array<Expr>):Expr {
return generateIfChain(setBool, conditions);
}
private static function generateIfChain(eif:Expr, conditions:Array<Expr>):Expr {
// get the next condition
var condition = conditions.shift();
if (condition == null) {
return null; // no more conditions
}
// recurse deeper to generate the next if
var nextIf = generateIfChain(eif, conditions);
return {
expr: EIf(condition, eif, nextIf),
pos: Context.currentPos()
};
}
}
和Main.hx
(基本不变):
class Main {
static function main() {
var isOK = true;
LazyUtils.tryUntilFalse(isOK = false,
!doSomething(),
!doSomethingElse(), //Returns false, so should stop here.
!doFinalThing()
);
}
static function doSomething():Bool {
trace("doSomething");
return true;
}
static function doSomethingElse():Bool {
trace("doSomethingElse");
return false;
}
static function doFinalThing():Bool {
trace("doFinalThing");
return true;
}
}
为了简单起见,我在调用站点用 !
反转了函数调用参数,而不是在宏中处理它。
您可以使用 -D dump=pretty
生成 AST 转储并检查生成的代码。结果如下:
if ((! Main.doSomething()))isOK = false else if ((! Main.doSomethingElse()))isOK = false else if ((! Main.doFinalThing()))isOK = false;