Easy way to extract expressions of the possible returning values of a function

Does exist an easy way to extract expressions of the possible returning values of a function with a macro?
For example,

var someFunc = (s: String) ->  if ( a > b ) Math.random() else 2;

I need extract Math.random() and 2 expressions for its transformation. Of course, I’m interested in a common solution:)

You can start with this

var someFunc = Macro.transform( (s: String) -> if ( a > b ) Math.random() else 2 );

Where the transform function is a public static macro function inside a Macro.hx class

public static macro function transform(expr:haxe.macro.Expr) {
  trace(expr);
  return macro $expr;
}

This macro logs the expression that comes in, and just returns the expression too.
At this point its a matter of extracting the variable. This can be done with pattern matching.


Lets start the pattern matching with an easier example:

var someFunc = Macro.transform( () -> { trace(1); } );

Then we can use this to match

class Macro {
  public static macro function transform(expr:haxe.macro.Expr) {
    switch (expr) {
      case macro  () -> $b{functionBody}: 
          trace("it matches:", functionBody);
        case _: 
        	trace("it doesn't match");
    }
    return macro $expr;
  }
}

Notice that $b { } is used to match a block expression (see Expression Reification in manual).

Now, that already seems to match :slight_smile:


Let’s take it bit more forward:

var a = 3;
var b = 5;
var someFunc = Macro.transform( () -> { if (a>b) 5 else 3; } );

Then add a switch on the functionBody

class Macro {
  public static macro function transform(expr:haxe.macro.Expr) {
    switch (expr) {
      case macro  () -> $b{ functionBody}: 
          trace("it matches:", functionBody);
        
          switch (functionBody[0]) {
            case macro if ($e{cond}) $e{first} else $e{second}: 
          		trace("condition:", cond);
          		trace("if:", first );
          		trace("else:", second );
              
            case _:
              trace("it doens't match here too");
          }
        
        case _: 
        	trace("it doesn't match");
    }
    return macro $expr;
  }
}

Some interesting notes:

  • $b{exprs} is EBlock(exprs), so for convenience I just use functionBody[0].
  • $e { } is used to match a expression (see Expression Reification in manual)

Now I hope this should give you enough to keep going :fire: Enjoy!


Bonus level: Instead of a static function, you could use meta data and use build macro to detect that (or you could make a build macro that tests on all expressions but that can be quite heavy.)

var someFunc = @:magicalTransform () -> { if (a>b) 5 else 3; };
2 Likes

Hmm I don’t understand the question this way. Maybe just loop in a function’s expressions and return “returns” expressions ?

Mark, thank you very much for the detail answer:) I’ve known more about macro expression matching. But as @filt3rek said I need detect all EReturn cases for the universal solution. Then inside them inspect all haxe expressions like EIf, ESwitch and so on. I thought it must be a ready solution in the macro tools.

Then inside them inspect all haxe expressions like EIf, ESwitch and so on

It’s still a problem:

switch (funcBodyExpr.expr)
{
	case EReturn(e):
	{
		trasformOnlyExpressionsSupposedToBeReturned(e);
   	}
	case _:
}

How trasformOnlyExpressionsSupposedToBeReturned should be implemented in the common case? Return expression might be any possible return expression allowed in haxe.
For example return expression if (a>b) 5 else 3; should be transformed into
if (a>b) js.Promise.resolve(5) else js.Promise.resolve(3); and so on.

You could use tink.macro.Exprs.yield, tink_macro/Exprs.hx at e1e487079bdbb808ea70b9393ff7c335dac8ccf6 · haxetink/tink_macro · GitHub

1 Like

Thank you. This is exactly what I am looking for.