Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
package ;
/**
* ...
* @author Zachary Dremann
*/
#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.Type;
#end
class MacroCollection
{
private var storage:IntHash<Dynamic>;
public function new()
{
storage = new IntHash();
}
@:macro public function add(eThis:Expr, component:Expr)
{
// Use untyped to get around addComponentWithID being private, but being called from outside
var id = MyMacro.getComponentID(component);
return macro (untyped $eThis.addComponentWithID)($component, $id);
}
@:keep
function addComponentWithID(component:Dynamic, id:Int):Int
{
trace(id + " => " + component);
storage.set(id, component);
return id;
}
public function get<T>(id:Int):T
{
return this.storage.get(id);
}
}
class MyMacro
{
#if macro
static var nextId:Int;
static var components:Hash<Int>;
#end
#if macro
public static function getComponentID(component:Expr):Expr
{
if (components == null)
{
nextId = 0;
components = new Hash<Int>();
}
var type:Type = Context.follow(Context.typeof(component), false);
var typeName = getTypeName(type);
var index = 0;
if (components.exists(typeName))
index = components.get(typeName);
else
{
index = nextId++;
components.set(typeName, index);
}
return Context.makeExpr(index, component.pos);
}
public static function getTypeName(type:Type):String
{
var str = new StringBuf();
type = Context.follow(type, false);
switch(type)
{
case TInst(classRef, params):
var typeDef = classRef.get();
var nameArr = typeDef.pack.copy();
nameArr.push(typeDef.name);
str.add(nameArr.join("."));
if (params.length > 0)
{
str.addChar("<".code);
var pStrArr = Lambda.map(params, getTypeName);
str.add(pStrArr.join(","));
str.addChar(">".code);
}
case TEnum(enumRef, params):
var typeDef = enumRef.get();
var nameArr = typeDef.pack.copy();
nameArr.push(typeDef.name);
str.add(nameArr.join("."));
if (params.length > 0)
{
str.addChar("<".code);
var pStrArr = Lambda.map(params, getTypeName);
str.add(pStrArr.join(","));
str.addChar(">".code);
}
case TAnonymous(fieldsRef):
var fields:Array<ClassField> = fieldsRef.get().fields;
str.addChar("{".code);
var fieldStrArr = Lambda.map(fields, function(field) { return field.name + ":" + getTypeName(field.type); } );
str.add(fieldStrArr.join(","));
str.addChar("}".code);
case TFun(args, ret):
var argsList = Lambda.map(args, function(arg) { return arg.t; } );
argsList.add(ret);
var argsStrList = Lambda.map(argsList, getTypeName);
if (argsStrList.length == 1)
argsStrList.push("Void");
str.add(argsStrList.join("->"));
case TLazy(f):
str.add(getTypeName(f()));
case TMono(ref):
var val = ref.get();
if (val == null)
{
Context.error("Untyped value. Cannot use, try storing in a manually typed variable", Context.currentPos());
}
else
str.add(getTypeName(val));
case TDynamic(t):
Context.error("Cannot store dynamic values", Context.currentPos());
case TType(defRef, params):
Context.error("Something went wrong, typedefs should be followed", Context.currentPos());
}
Context.warning(str.toString(), Context.currentPos());
return str.toString();
}
#end
}
package;
import massive.munit.Assert;
#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.Type;
#end
typedef MyArray<T> = Array<T>;
typedef MyList = List<String>;
class MacroTest
{
private var collection:MacroCollection;
@Before
public function setUp()
{
collection = new MacroCollection();
}
@Test
public function twoInstancesOfTheSameTypeShareACommonID()
{
var first = collection.add(new MacroExampleA());
var second = collection.add(new MacroExampleA());
Assert.areEqual(first, second);
}
@Test
public function differentTypesHaveDifferentIDs()
{
var first = collection.add(new MacroExampleA());
var second = collection.add(new MacroExampleB());
Assert.areNotEqual(first, second);
}
@Test
public function stringsAlsoShareACommonID()
{
var first = collection.add("hello");
var second = collection.add("other");
Assert.areEqual(first, second);
}
@Test
public function boolsShareCommonId()
{
var first = collection.add(true);
var second = collection.add(false);
Assert.areEqual(first, second);
}
@Test
public function twoInstancesWithDifferentParamsOfSameTypeHaveDifferentIDs()
{
var first = collection.add(new Array<String>());
var second = collection.add(new Array<Int>());
Assert.areNotEqual(first, second);
}
@Test
public function testAnnonStructures()
{
var first = collection.add({name:"bob", age:31});
var second = collection.add({name:"sue", age:15});
Assert.areEqual(first, second);
second = collection.add( { name:"joe" } );
Assert.areNotEqual(first, second);
}
@Test
public function testFunctions()
{
var f1 = function(a:String) { return 1; };
var f2 = function(a:String) { return 2; };
var first = collection.add(f1);
var second = collection.add(f2);
Assert.areEqual(first, second);
second = collection.add(testAnnonStructures);
Assert.areNotEqual(first, second);
}
@Test
public function typedefsAreFollowed()
{
var first = collection.add(new MyArray<Float>());
var second = collection.add(new Array<Float>());
Assert.areEqual(first, second);
first = collection.add(new List<String>());
second = collection.add(new MyList());
Assert.areEqual(first, second);
second = collection.add(new MyArray<MyList>());
Assert.areNotEqual(first, second);
}
@Test
public function actuallyStoreValue()
{
var id1 = collection.add("Hello");
var id2 = collection.add([1,2,3,4]);
var str:String = collection.get(id1);
var arr:Array<Int> = collection.get(id2);
Assert.areEqual("Hello", str);
Assert.areEqual(4, arr.length);
collection.add("Other String");
str = collection.get(id1);
Assert.areEqual("Other String", str);
}
}
class MacroExampleA
{
public function new() {}
}
class MacroExampleB
{
public function new() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment