I'm basically trying to create a macro that automatically generates an if/else if
chain by supplying one common outcome statement for all conditions.
This is what I've tried so far (modified the code just to show as an example):
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 :("); };
}
}
Then, in theory it could be used like this:
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;
}
}
Which should generate this condition tree:
if (!doSomething()) isOK = false;
else if (!doSomethingElse()) isOK = false;
else if (!doFinalThing()) isOK = false;
Alternatively, I suppose it could output this instead:
if(!doSomething() || !doSomethingElse() || !doFinalThing()) isOK = false;
Looking back at this now, true - it may not make much sense to write a whole macro to generate code that would be easier to type out in it's raw format.
But for the sake of learning about macros, does anyone know if multiple expressions can be passed in an Array<Expr>
like I tried in the above code sample?
You probably couldn't get the xConds
argument to behave like you expected because the final argument of an expression macro with the type Array<Expr>
is implicitly a rest argument. That means you ended up with an array that contained a single EArrayDecl
expression. This can be fixed by simply omitting the []
.
Regarding generating the if
-else
-chain - let's take a look at EIf
:
/**
An `if(econd) eif` or `if(econd) eif else eelse` expression.
**/
EIf( econd : Expr, eif : Expr, eelse : Null<Expr> );
The chain can be thought of as a singly linked list - the eelse
if the first EIf
should reference the next EIf
and so forth, until we stop with eelse = null
for the last EIf
. So we want to generate this for your example (pseudo-code):
EIf(doSomething(), isOk = false, /* else */
EIf(doSomethingElse, isOk = false, /* else */
EIf(doFinalThing(), isOk = false, null)
)
)
Recursion works well for this.
Typically it's more convenient to work with reification than raw expressions like I do here, but I'm not sure the former is really possible when dynamically generating expressions like this.
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()
};
}
}
And Main.hx
(mostly unchanged):
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;
}
}
To keep things simple I inverted the function call arguments with !
at the call site instead of handling that in the macro.
You can use -D dump=pretty
to generate AST dumps and check what code is being generated. Here's the result:
if ((! Main.doSomething()))isOK = false else if ((! Main.doSomethingElse()))isOK = false else if ((! Main.doFinalThing()))isOK = false;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With