【问题标题】:How to pass an Array of Expr (expressions) to Haxe Macro?如何将 Expr(表达式)数组传递给 Haxe 宏?
【发布时间】:2016-04-01 17:42:44
【问题描述】:

我基本上是在尝试创建一个宏,通过为所有条件提供一个通用结果语句来自动生成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&lt;Expr&gt; 中传递多个表达式?

【问题讨论】:

    标签: arrays syntax macros expression haxe


    【解决方案1】:

    您可能无法让xConds 参数的行为与您预期的一样,因为Array&lt;Expr&gt; 类型的表达式宏的最后一个参数隐含地是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;
    

    【讨论】:

    • +1 非常感谢。很高兴您可以贡献您关于 Array&lt;Expr&gt; 本质上是 rest 论点的知识。我无法想象我会从哪里开始寻找这些信息(或者我会如何想到它是如何工作的)。哦,如果您有任何好的阅读/链接要分享,我想看看它! :)
    • (通过链接 - 不一定只指 rest 参数的事情。任何有助于更好地使用 Haxe 宏的资源或教程都可以)。
    • 这篇文章最后有一个不错的链接集合:notes.underscorediscovery.com/haxe-compile-time-macros
    • 顺便说一句,code.haxe.org 刚刚推出,已经有大量的宏示例了。
    猜你喜欢
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 2019-12-13
    • 2010-11-14
    • 1970-01-01
    相关资源
    最近更新 更多