Skip to content

Instantly share code, notes, and snippets.

@jasononeil
Last active March 22, 2018 21:17
Show Gist options
  • Save jasononeil/5429516 to your computer and use it in GitHub Desktop.
Save jasononeil/5429516 to your computer and use it in GitHub Desktop.
ClassicSwitch.hx - a macro which takes a switch statement and turns it into an if/elseif/else chain. This is useful if you want traditional ecmascript "switch" behaviour, not the kick-ass haxe pattern matching. See the README, the implementation, and some tests below.

ClassicSwitch.hx - a macro which takes a switch statement and turns it into an if/elseif/else chain. This is useful if you want traditional ecmascript "switch" behaviour, not the kick-ass haxe pattern matching. Times this is useful: if one of your cases is a variable:

option1 = "Something1";
option2 = "Something2";
switch (someVar)
{
    case option1: trace ("do something 1");
    case option2: trace ("do something 2");
}

This doesn't work with pattern matching, because option1, option2 are seen as capture variables. With this macro, you could do:

option1 = "Something1";
option2 = "Something2";
ClassicSwitch.from(switch (someVar)
{
    case option1: trace ("do something 1");
    case option2: trace ("do something 2");
});

... and things will behave as expected.

Some quick tests in the Main.hx below. Run with haxe -x Main.hx, in Haxe 3RC or above.

import haxe.macro.Expr;
import haxe.macro.Context;
class ClassicSwitch
{
macro static public function from(inExpr:Expr):Expr
{
var retExpr:Expr = null;
switch (inExpr.expr)
{
case ESwitch({ expr: EParenthesis(testExpr), pos:_ },cases,defaultExpr):
if (defaultExpr == null && cases.length == 0)
{
// Nothing to work with here...
Context.error("Your switch statement has no cases, and no default, you weirdo.", inExpr.pos);
}
else if (cases.length == 0)
{
// No cases... so just do the default.
// Default will be wrapped in a block so variables should be scoped correctly.
retExpr = defaultExpr;
}
else
{
// Build an "if"/"elseif" expression for each case...
var previousElseBlock:Array<Expr>;
for (c in cases)
{
if (c.guard != null)
Context.error("You can't use guards with ClassicSwitch", c.guard.pos);
if (c.values.length != 1)
Context.error("You must test exactly 1 value per case with ClassicSwitch", c.guard.pos);
// Set up the "if/else" construct for the current construct
var testValue = c.values[0];
var condition = macro $testExpr == $testValue;
var ifBlock = (c.expr != null) ? c.expr : macro {};
var elseBlock = [];
var elseBlockExpr = { expr: EBlock(elseBlock), pos: Context.currentPos() };
var ifExpr = { expr: EIf(condition, ifBlock, elseBlockExpr), pos: Context.currentPos() };
if (retExpr == null)
{
// If this is the original if(), set it as the return expression
retExpr = ifExpr;
}
else
{
// If not, append it to the else() block of the previous if().
// This recursion if {} else { if {} else {} } is instead of if {} elseif {} else {}
previousElseBlock.push(ifExpr);
}
// Keep a reference to the current elseBlock, so we can append to it on the next iteration, or after the loop
previousElseBlock = elseBlock;
}
// If there was a default: statement, add it as the final "else"
if (defaultExpr != null) { previousElseBlock.push(defaultExpr); }
}
default:
Context.error("ClassicSwitch.make() can only be passed a traditional switch statement as its value", inExpr.pos);
}
return retExpr;
}
}
class Main
{
static var name1 = "Clément";
static var name2 = "Greg";
static var name3 = "Simon";
static var name4 = "Juraj";
static var name5 = "Jason";
static function main()
{
testAllConstants();
testAllVariables();
testMix();
testNoDefault();
testOnlyDefault();
testPassValueBack();
}
static function testAllConstants()
{
var myName = "Jason";
var foundMatch = false;
ClassicSwitch.from(switch (myName) {
case "Clément": foundMatch = true;
case "Greg": foundMatch = true;
case "Simon": foundMatch = true;
case "Juraj": foundMatch = true;
case "Jason": foundMatch = true;
});
assertAreEqual(true, foundMatch);
}
static function testAllVariables()
{
var myName = "Jason";
var foundMatch = false;
ClassicSwitch.from(switch (myName) {
case name1: foundMatch = true;
case name2: foundMatch = true;
case name3: foundMatch = true;
case name4: foundMatch = true;
case name5: foundMatch = true;
});
assertAreEqual(true, foundMatch);
}
static function testMix()
{
var myName = "Simon";
var foundMatch = false;
ClassicSwitch.from(switch (myName) {
case "Jason":
foundMatch = true;
case name3:
foundMatch = true;
});
assertAreEqual(true, foundMatch);
}
static function testNoDefault()
{
var myName = "Mr. NoName";
var foundMatch = false;
ClassicSwitch.from(switch (myName) {
case "Jason":
foundMatch = true;
case name3:
foundMatch = true;
});
assertAreEqual(false, foundMatch);
}
static function testOnlyDefault()
{
var myName = "Jason";
var output:String = null;
ClassicSwitch.from(switch (myName) {
default:
output = "OK";
});
assertAreEqual("OK", output);
}
static function testPassValueBack()
{
var myName = "Jason";
var firstLetter = ClassicSwitch.from(switch (myName) {
case name1: "C";
case name2: "G";
case name3: "S";
case name4: "J";
case name5: "J";
});
assertAreEqual("J", "J");
}
static function assertAreEqual(a:Dynamic,b:Dynamic,?msg:String="",?p:haxe.PosInfos)
{
if (a != b)
{
trace ('$a was not equal to $b at ${p.fileName}:${p.lineNumber} in method ${p.methodName}');
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment