Created
June 6, 2014 21:54
-
-
Save anonymous/fa078c67f788eef4f8a8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import haxe.macro.Context; | |
import haxe.macro.Expr; | |
import haxe.macro.Printer; | |
import haxe.macro.Type; | |
using haxe.macro.Tools; | |
// The original code used interfaces to define properties, and some interfaces | |
// had the same property name with different types. These interfaces are then | |
// implemented by classes that provide the implementation of those properties. | |
// | |
// The problem is that using interfaces is costly, and we have some techniques | |
// (not described here) that allow us to, if interfaces are not used, reduce | |
// the size of our binary drastically. | |
// | |
// So we have a technique whereby we declare a single "Interfaces" base class | |
// that "implements" all of the interface properties, and that base class is | |
// inherited by all other classes. Now all of those classes have access to | |
// a union of all of the interfaces's properties. | |
// | |
// The problem is that when the Interfaces base class is defined, there are | |
// some properties that have the same name but different type, and thus they | |
// collide. | |
// | |
// The goal is to use macros to allow those properties to be renamed using a | |
// type-specific name in the Interfaces base class, but have references to the | |
// properties using their 'original' names to replaced with a reference to the | |
// new name. | |
class FixPropertyReferences | |
{ | |
macro static function process() : Array<Field> | |
{ | |
var fields = Context.getBuildFields(); | |
for (field in fields) { | |
processField(field); | |
} | |
return fields; | |
} | |
#if macro | |
static function processField(field : Field) | |
{ | |
switch (field.kind) { | |
case FFun(f) if (f.expr != null): | |
f.expr = processExpr(f.expr); | |
case _: | |
} | |
} | |
static function processExpr(e : Expr) | |
{ | |
return switch (e) { | |
case macro $e.$name: | |
macro FixPropertyReferences.replace($e, $v{name}); | |
case _: | |
e.map(processExpr); | |
} | |
} | |
#end | |
macro static public function replace(e : Expr, name : String) | |
{ | |
if (name != "property") { | |
return macro @pos(e.pos) $e.$name; | |
} | |
var typeName : String = null; | |
switch (e.expr) { | |
case EConst(c): | |
switch (c) { | |
case CIdent(s): | |
typeName = s; | |
default: | |
} | |
default: | |
} | |
if (typeName == null) { | |
return macro @pos(e.pos) $e.$name; | |
} | |
if (typeName == "classA") { | |
return macro @pos(e.pos) $e.intProperty; | |
} | |
else if (typeName == "classB") { | |
return macro @pos(e.pos) $e.stringProperty; | |
} | |
return macro @pos(e.pos) $e.$name; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if USE_INTERFACES | |
// Define USE_INTERFACES when building to use the interfaces-based version. | |
// Everything works normally and sanely. | |
interface InterfaceA | |
{ | |
public var property(get, set) : Int; | |
} | |
interface InterfaceB | |
{ | |
public var property(get, set) : String; | |
} | |
#else | |
// If USE_INTERFACES is *not* refined, then the "interfaces" become a | |
// base class with a union of all properties declared by the interfaces, | |
// and the original interface names just become typedefs of this base class. | |
// To avoid collisions with the property name, the properties are re-named to | |
// include some type info to keep them separate. | |
// The goal is to use macros to allow anyone to refer to the 'property' | |
// property of the sub-classes of Interfaces, and to get either intProperty or | |
// stringProperty, depending upon which subclass is involved. | |
class Interfaces | |
{ | |
public function new() | |
{ | |
mIntProperty = 10; | |
mStringProperty = "ten"; | |
} | |
public var intProperty(get, set) : Int; | |
public var stringProperty(get, set) : String; | |
private function get_intProperty() : Int | |
{ | |
return mIntProperty; | |
} | |
private function set_intProperty(v : Int) : Int | |
{ | |
mIntProperty = v; | |
return v; | |
} | |
private function get_stringProperty() : String | |
{ | |
return mStringProperty; | |
} | |
private function set_stringProperty(v : String) : String | |
{ | |
mStringProperty = v; | |
return v; | |
} | |
private var mIntProperty : Int; | |
private var mStringProperty : String; | |
} | |
typedef InterfaceA = Interfaces; | |
typedef InterfaceB = Interfaces; | |
#end | |
#if USE_INTERFACES | |
// If USE_INTERFACES is set, then interfaces are used and there are no | |
// name collisions. Simple. | |
class ClassA implements InterfaceA | |
{ | |
public function new() | |
{ | |
mProperty = 10; | |
} | |
public var property(get, set) : Int; | |
private function get_property() : Int | |
{ | |
return mProperty; | |
} | |
private function set_property(v : Int) : Int | |
{ | |
mProperty = v; | |
return v; | |
} | |
private var mProperty : Int; | |
} | |
class ClassB implements InterfaceB | |
{ | |
public function new() | |
{ | |
mProperty = "ten"; | |
} | |
public var property(get, set) : String; | |
private function get_property() : String | |
{ | |
return mProperty; | |
} | |
private function set_property(v : String) : String | |
{ | |
mProperty = v; | |
return v; | |
} | |
private var mProperty : String; | |
} | |
#else | |
// If NO_INTERFACES is not set, then the implementation classes don't need to | |
// have anything, it's all in the base class | |
class ClassA extends Interfaces | |
{ | |
public function new() | |
{ | |
super(); | |
} | |
} | |
class ClassB extends Interfaces | |
{ | |
public function new() | |
{ | |
super(); | |
} | |
} | |
#end | |
#if !USE_INTERFACES | |
@:build(FixPropertyReferences.process()) | |
#end | |
class MacroTest | |
{ | |
public static function main() | |
{ | |
var classA = new ClassA(); | |
var classB = new ClassB(); | |
classA.intProperty += 10; | |
//classB.property = "twenty"; | |
// The following will succeed if USE_INTERFACES is set. But if | |
// USE_INTERFACES is not set, then the compiler will give an error | |
// because there is no such property 'property' in either classA or | |
// classB. | |
// The goal is to use macros so that: | |
// classA.property turns into classA.intProperty | |
// classB.property turns into classB.stringProperty | |
Sys.println("classA.property is " + classA.property); | |
Sys.println("classB.property is " + classB.property); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment