diff --git a/src/reflaxe/BaseCompiler.hx b/src/reflaxe/BaseCompiler.hx index 214f0a1..9630e01 100644 --- a/src/reflaxe/BaseCompiler.hx +++ b/src/reflaxe/BaseCompiler.hx @@ -257,6 +257,23 @@ class BaseCompilerOptions { paramTypes: Null>, compileFunc: Null<(MetadataEntry, Array) -> Null> }> = []; + + /** + Converts all static variable declarations to have a self + calling lambda function where needed. This does not affect + variables with a deterministic/constant value. + + Useful if your target language does not support blocks as values + for variables, however the target language also needs to support + lambda function declarations and executing code directly on the top stack + (great examples are lua and python). + + You can use the ClassVarData.canBeInlined variable to check if + the variable does not need any kind of wrapper. You can also use + it with `convertStaticVarExpressionsToFunctions` turned off to + implement your own system. + **/ + public var convertStaticVarExpressionsToFunctions: Bool = true; } /** diff --git a/src/reflaxe/ReflectCompiler.hx b/src/reflaxe/ReflectCompiler.hx index 0e580a0..97161c1 100644 --- a/src/reflaxe/ReflectCompiler.hx +++ b/src/reflaxe/ReflectCompiler.hx @@ -35,6 +35,7 @@ using reflaxe.helpers.ModuleTypeHelper; using reflaxe.helpers.NameMetaHelper; using reflaxe.helpers.NullableMetaAccessHelper; using reflaxe.helpers.TypeHelper; +using reflaxe.helpers.TypedExprHelper; /** The heart of Reflaxe. @@ -532,7 +533,7 @@ class ReflectCompiler { if(shouldGenerateVar(field, compiler, isStatic, readVarAccess, writeVarAccess)) { final data = field.findVarData(cls, isStatic); if(data != null) { - varFields.push(data); + varFields.push(preprocessVar(compiler, field, data)); } else { throw "Variable information not found."; } @@ -584,6 +585,86 @@ class ReflectCompiler { return data; } + + static function preprocessVar(compiler: BaseCompiler, field: ClassField, data: ClassVarData): ClassVarData { + if(data.expr == null) { + return data; + } + + final fE = field.buildTField(data.classType); + var e = if (compiler.options.convertStaticVarExpressionsToFunctions) { + data.expr.transformLambaSelfCall(true); + } else { + data.expr.transformAssign(fE); + } + + data.setExpr(e); + + if(compiler.options.enforceNullTyping) { + NullTypeEnforcer.modifyExpression(data.expr); + } + for(preprocessor in compiler.expressionPreprocessors) { + preprocessor.process(data, compiler); + } + + e = { + if (compiler.options.convertStaticVarExpressionsToFunctions) { + switch(data.expr.expr) + { + case TCall(e, _): + switch(e.unwrapParenthesis().expr) + { + case TFunction(tfunc): + final block = tfunc.expr.unwrapBlock(); + if (block.length == 1) + { + final b = block[0]; + switch(b.expr) + { + case TReturn(e): + e; + case _: + b; + } + } + else + data.expr; + + case _: data.expr; + } + + case _: data.expr; + } + } else { + data.expr; + } + } + + var isInline = e.expr.match(TBinop(_, _, _)); + var finalExpr = switch(e.expr) + { + case TBinop(op, e1, e2): + if (op.match(OpAssign) || op.match(OpAssignOp(_))) + { + if (Std.string(e1) == Std.string(fE)) + e2; + else if (Std.string(e2) == Std.string(fE)) + e1; + else + e; + } + else + e; + case _: + e; + } + + data.setExpr(finalExpr); + data.setCanBeInlined(compiler.options.convertStaticVarExpressionsToFunctions ? true : isInline); + + return data; + } + // ======================================================= // * transpileEnum // ======================================================= diff --git a/src/reflaxe/data/ClassFieldData.hx b/src/reflaxe/data/ClassFieldData.hx new file mode 100644 index 0000000..b76493f --- /dev/null +++ b/src/reflaxe/data/ClassFieldData.hx @@ -0,0 +1,112 @@ +package reflaxe.data; + +#if (macro || reflaxe_runtime) + +import reflaxe.preprocessors.ExpressionPreprocessor; +import haxe.macro.Type; + +using reflaxe.helpers.ClassFieldHelper; +using reflaxe.helpers.NameMetaHelper; +using reflaxe.helpers.NullableMetaAccessHelper; +using reflaxe.helpers.NullHelper; +using reflaxe.helpers.PositionHelper; +using reflaxe.helpers.TypedExprHelper; + +class ClassFieldData { + public final id: String; + + public final classType: ClassType; + public final field: ClassField; + + public final isStatic: Bool; + + public var expr(default, null): Null; + + var variableUsageCount: Null>; // Not quite sure if its necessary here, but keep it :P + + public function new(id:String, classType: ClassType, field: ClassField, isStatic: Bool, expr:Null = null) { + this.id = id; + + this.classType = classType; + this.field = field; + + this.isStatic = isStatic; + + this.expr = expr; + } + + /** + Sets the typed expression. + Invalidates any cached information regarding the old expression. + **/ + public function setExpr(e: TypedExpr) { + expr = e; + + // The expression changed, so the stored usage count data is now invalid. + variableUsageCount = null; + } + + /** + Works the same as `setExpr`, but takes an array of expressions. + + If just one expression, it is used directly. + Multiple are converted into a block expression. + **/ + public function setExprList(expressions: Array) { + if(expressions.length == 1) { + setExpr(expressions[0].trustMe()); + } else if(expr != null) { + // Retain the previous expression's Position and Type. + setExpr(expr.copy(TBlock(expressions))); + } else { + throw "`expr` must not be `null` when using ClassFuncData.setExprList."; + } + } + + /** + A map of the number of times a variable is used can optionally + be provided for later reference. + + This is usually calculated using `EverythingIsExprSanitizer` prior + to other optimizations. + **/ + public function setVariableUsageCount(usageMap: Map) { + variableUsageCount = usageMap; + } + + /** + Returns the variable usage count. + If it has not been calculated yet, it is calculated here. + **/ + public function getOrFindVariableUsageCount(): Map { + if(expr == null) { + return []; + } + + final map: Map = []; + function count(e: TypedExpr) { + switch(e.expr) { + case TVar(tvar, _): { + map.set(tvar.id, 0); + } + case TLocal(tvar): { + map.set(tvar.id, (map.get(tvar.id) ?? 0) + 1); + } + case _: + } + return haxe.macro.TypedExprTools.map(e, count); + } + count(expr); + return variableUsageCount = map; + } + + /** + Applies preprocessors to the `expr`. + **/ + public function applyPreprocessors(compiler: BaseCompiler, preprocessors: Array) { + for(processor in preprocessors) { + processor.process(this, compiler); + } + } +} +#end diff --git a/src/reflaxe/data/ClassFuncData.hx b/src/reflaxe/data/ClassFuncData.hx index 6fdbb50..c361a62 100644 --- a/src/reflaxe/data/ClassFuncData.hx +++ b/src/reflaxe/data/ClassFuncData.hx @@ -15,13 +15,7 @@ using reflaxe.helpers.NullHelper; using reflaxe.helpers.PositionHelper; using reflaxe.helpers.TypedExprHelper; -class ClassFuncData { - public final id: String; - - public final classType: ClassType; - public final field: ClassField; - - public final isStatic: Bool; +class ClassFuncData extends ClassFieldData { public final kind: MethodKind; public final ret: Type; @@ -30,10 +24,6 @@ class ClassFuncData { public final property: Null; - public var expr(default, null): Null; - - var variableUsageCount: Null>; // Access using `getOrFindVariableUsageCount` - public function new( id: String, classType: ClassType, field: ClassField, isStatic: Bool, kind: MethodKind, ret: Type, @@ -41,18 +31,12 @@ class ClassFuncData { extractArgumentMetadata: Bool = true, property: Null = null ) { - this.id = id; - - this.classType = classType; - this.field = field; - - this.isStatic = isStatic; + super(id, classType, field, isStatic, expr); this.kind = kind; this.ret = ret; this.args = args; this.tfunc = tfunc; - this.expr = expr; if(extractArgumentMetadata) { extractArgumentMeta(); @@ -70,15 +54,6 @@ class ClassFuncData { return new ClassFuncData(id, classType, field, isStatic, kind, ret, args, tfunc, expr, false, property); } - /** - Applies preprocessors to the `expr`. - **/ - public function applyPreprocessors(compiler: BaseCompiler, preprocessors: Array) { - for(processor in preprocessors) { - processor.process(this, compiler); - } - } - /** TODO: Anyway I could make this... more condensed? **/ @@ -159,71 +134,6 @@ class ClassFuncData { return isSetterName() && property != null; } - /** - Sets the typed expression. - Invalidates any cached information regarding the old expression. - **/ - public function setExpr(e: TypedExpr) { - expr = e; - - // The expression changed, so the stored usage count data is now invalid. - variableUsageCount = null; - } - - /** - Works the same as `setExpr`, but takes an array of expressions. - - If just one expression, it is used directly. - Multiple are converted into a block expression. - **/ - public function setExprList(expressions: Array) { - if(expressions.length == 1) { - setExpr(expressions[0].trustMe()); - } else if(expr != null) { - // Retain the previous expression's Position and Type. - setExpr(expr.copy(TBlock(expressions))); - } else { - throw "`expr` must not be `null` when using ClassFuncData.setExprList."; - } - } - - /** - Returns the variable usage count. - If it has not been calculated yet, it is calculated here. - **/ - public function getOrFindVariableUsageCount(): Map { - if(expr == null) { - return []; - } - - final map: Map = []; - function count(e: TypedExpr) { - switch(e.expr) { - case TVar(tvar, _): { - map.set(tvar.id, 0); - } - case TLocal(tvar): { - map.set(tvar.id, (map.get(tvar.id) ?? 0) + 1); - } - case _: - } - return haxe.macro.TypedExprTools.map(e, count); - } - count(expr); - return variableUsageCount = map; - } - - /** - A map of the number of times a variable is used can optionally - be provided for later reference. - - This is usually calculated using `EverythingIsExprSanitizer` prior - to other optimizations. - **/ - public function setVariableUsageCount(usageMap: Map) { - variableUsageCount = usageMap; - } - /** Checks if the `args` of both `ClassFuncData` are identical. **/ diff --git a/src/reflaxe/data/ClassVarData.hx b/src/reflaxe/data/ClassVarData.hx index cb57c15..a74548e 100644 --- a/src/reflaxe/data/ClassVarData.hx +++ b/src/reflaxe/data/ClassVarData.hx @@ -12,22 +12,18 @@ using reflaxe.helpers.NullableMetaAccessHelper; using reflaxe.helpers.PositionHelper; using reflaxe.helpers.TypedExprHelper; -class ClassVarData { - public var classType(default, null): ClassType; - public var field(default, null): ClassField; +class ClassVarData extends ClassFieldData { + public final read: VarAccess; + public final write: VarAccess; - public var isStatic(default, null): Bool; - public var read(default, null): VarAccess; - public var write(default, null): VarAccess; + public var canBeInlined(default, null): Bool = false; public var getter(default, null): Null; public var setter(default, null): Null; - public function new(classType: ClassType, field: ClassField, isStatic: Bool, read: VarAccess, write: VarAccess) { - this.classType = classType; - this.field = field; + public function new(id:String, classType: ClassType, field: ClassField, isStatic: Bool, read: VarAccess, write: VarAccess, expr:Null = null) { + super(id, classType, field, isStatic, expr); - this.isStatic = isStatic; this.read = read; this.write = write; @@ -84,6 +80,10 @@ class ClassVarData { } return null; } + + public function setCanBeInlined(value: Bool) { + canBeInlined = value; + } } #end diff --git a/src/reflaxe/helpers/ClassFieldHelper.hx b/src/reflaxe/helpers/ClassFieldHelper.hx index a9427ec..b0aa8e0 100644 --- a/src/reflaxe/helpers/ClassFieldHelper.hx +++ b/src/reflaxe/helpers/ClassFieldHelper.hx @@ -11,9 +11,12 @@ import haxe.macro.Type; import reflaxe.data.ClassFuncArg; import reflaxe.data.ClassFuncData; import reflaxe.data.ClassVarData; +import reflaxe.data.ClassFieldData; using reflaxe.helpers.NameMetaHelper; using reflaxe.helpers.NullableMetaAccessHelper; +using reflaxe.helpers.RefHelper; +using reflaxe.helpers.ClassTypeHelper; /** Quick static extensions to help with `ClassField`. @@ -77,7 +80,8 @@ class ClassFieldHelper { return switch(field.kind) { case FVar(read, write): { - final result = new ClassVarData(clsType, field, isStatic, read, write); + var e = field.expr(); + final result = new ClassVarData(id, clsType, field, isStatic, read, write, e); findVarData_cache.set(id, result); result; } @@ -160,7 +164,7 @@ class ClassFieldHelper { } } - public static function getAllVariableNames(data: ClassFuncData, compiler: BaseCompiler) { + public static function getAllVariableNames(data: ClassFieldData, compiler: BaseCompiler) { final fields = data.classType.fields.get(); final fieldNames = []; for(f in fields) { @@ -183,6 +187,26 @@ class ClassFieldHelper { field.name; } } + + public static function buildTField(field: ClassField, clsType: ClassType, isStatic: Null = null):TypedExpr { + if(isStatic == null) { + isStatic = false; + for(s in clsType.statics.get()) { + if(equals(s, field)) { + isStatic = true; + break; + } + } + } + var fa = if (isStatic) { + FStatic(clsType.buildRef(), field.buildRef()); + } else { + FInstance(clsType.buildRef(), clsType.params.map(p -> p.t), field.buildRef()); + } + + final clsRef = clsType.buildRef(); + return TypedExprHelper.make(TField(clsType.generateDeclTExpr(TInst(clsRef, clsType.params.map(p -> p.t)), field.pos), fa), field.type, field.pos); + } } #end diff --git a/src/reflaxe/helpers/ClassTypeHelper.hx b/src/reflaxe/helpers/ClassTypeHelper.hx index 84fd5b7..d63029d 100644 --- a/src/reflaxe/helpers/ClassTypeHelper.hx +++ b/src/reflaxe/helpers/ClassTypeHelper.hx @@ -4,9 +4,11 @@ package reflaxe.helpers; import haxe.macro.Expr; import haxe.macro.Type; +import reflaxe.helpers.TypedExprHelper; using reflaxe.helpers.ClassFieldHelper; using reflaxe.helpers.NullHelper; +using reflaxe.helpers.RefHelper; /** Helper functions for `ClassType`. @@ -131,6 +133,10 @@ class ClassTypeHelper { } } } + + public static function generateDeclTExpr(cls: ClassType, t:Type, pos:Null): TypedExpr { + return TypedExprHelper.make(TTypeExpr(TClassDecl(cls.buildRef())), t, pos); + } } #end diff --git a/src/reflaxe/helpers/RefHelper.hx b/src/reflaxe/helpers/RefHelper.hx new file mode 100644 index 0000000..863ac8a --- /dev/null +++ b/src/reflaxe/helpers/RefHelper.hx @@ -0,0 +1,21 @@ +package reflaxe.helpers; + +#if (macro || reflaxe_runtime) + +import haxe.macro.Type.Ref; + +class RefHelper { + public static function buildRef(c:T):Ref { + return { + get: () -> c, + toString: () -> Std.string(c) + } + } + + public static function replaceRef(ref:Ref, newValue:T):Ref { + Reflect.setField(ref, "get", () -> newValue); + return ref; + } +} + +#end \ No newline at end of file diff --git a/src/reflaxe/helpers/TypedExprHelper.hx b/src/reflaxe/helpers/TypedExprHelper.hx index de8f0ea..b2eee98 100644 --- a/src/reflaxe/helpers/TypedExprHelper.hx +++ b/src/reflaxe/helpers/TypedExprHelper.hx @@ -110,6 +110,29 @@ class TypedExprHelper { } } + /** + Transforms the typed expression into an assignment expression given a `target` typedExpr. + This will return (in Haxe code) `target = self`. + **/ + public static function transformAssign(expr: TypedExpr, target: TypedExpr): TypedExpr { + return make(TBinop(OpAssign, target, expr), expr.t, expr.pos); + } + + /** + Transforms the typed expression into a lamba function that auto calls itself immediately after. + **/ + public static function transformLambaSelfCall(expr: TypedExpr, addReturn: Bool = false): TypedExpr { + final mk = (e:TypedExprDef) -> make(e, expr.t, expr.pos); + return mk(TCall(mk(TParenthesis(mk(TFunction({ + args: [], + t: expr.t, + expr: if (addReturn) + mk(TReturn(mk(expr.expr))) + else + expr + })))), [])); + } + public static function hasMeta(expr: TypedExpr, name: String): Bool { return switch(expr.expr) { case TParenthesis(e): { diff --git a/src/reflaxe/preprocessors/BasePreprocessor.hx b/src/reflaxe/preprocessors/BasePreprocessor.hx index b78b4ce..f26db84 100644 --- a/src/reflaxe/preprocessors/BasePreprocessor.hx +++ b/src/reflaxe/preprocessors/BasePreprocessor.hx @@ -1,7 +1,7 @@ package reflaxe.preprocessors; -import reflaxe.data.ClassFuncData; +import reflaxe.data.ClassFieldData; abstract class BasePreprocessor { - public abstract function process(data: ClassFuncData, compiler: BaseCompiler): Void; + public abstract function process(data: ClassFieldData, compiler: BaseCompiler): Void; } diff --git a/src/reflaxe/preprocessors/ExpressionPreprocessor.hx b/src/reflaxe/preprocessors/ExpressionPreprocessor.hx index 12ba3cf..2157bec 100644 --- a/src/reflaxe/preprocessors/ExpressionPreprocessor.hx +++ b/src/reflaxe/preprocessors/ExpressionPreprocessor.hx @@ -1,5 +1,7 @@ package reflaxe.preprocessors; +import reflaxe.data.ClassFuncArg; +import reflaxe.data.ClassFieldData; import reflaxe.compiler.NullTypeEnforcer; import reflaxe.data.ClassFuncData; import reflaxe.preprocessors.BasePreprocessor; @@ -162,7 +164,7 @@ class ExpressionPreprocessorHelper { /** This is where the implementations for the builtin `ExpressionPreprocessor` are. **/ - public static function process(self: ExpressionPreprocessor, data: ClassFuncData, compiler: BaseCompiler) { + public static function process(self: ExpressionPreprocessor, data: ClassFieldData, compiler: BaseCompiler) { if(data.expr == null) { return; } @@ -182,12 +184,13 @@ class ExpressionPreprocessorHelper { preventRepeatArguments: preventRepeatArguments, extraReservedNames: extraReservedNames }): { + var args:Array = (data is ClassFuncData) ? cast(data, ClassFuncData).args : []; final reservedNames = data.getAllVariableNames(compiler).concatIfNotNull(extraReservedNames); - final rvf = new PreventRepeatVariablesImpl(data.expr, null, data.args.map(a -> a.originalName).concat(reservedNames)); + final rvf = new PreventRepeatVariablesImpl(data.expr, null, args.map(a -> a.originalName).concat(reservedNames)); // Ensure the argument names don't match any class variables. - if(preventRepeatArguments) { - for(arg in data.args) { + if(preventRepeatArguments && data is ClassFuncData) { + for(arg in (cast(data, ClassFuncData)).args) { if(arg.ensureNameDoesntMatch(reservedNames) && arg.tvar != null) { rvf.registerVarReplacement(arg.getName(), arg.tvar); }