Static Extension support + small fixes
Static extension for real/custom classes. aka. the `using` keyword Warning: UNTESTED
This commit is contained in:
@@ -42,6 +42,7 @@ class CustomClass implements IHScriptCustomClassBehaviour {
|
||||
__interp.importFailedCallback = __class.__interp.importFailedCallback;
|
||||
|
||||
// __interp.variables = __class.staticInterp.variables;
|
||||
@:privateAccess __interp.usingHandler.usingEntries = __class.ogInterp.usingHandler.usingEntries;
|
||||
__interp.publicVariables = __class.ogInterp.publicVariables;
|
||||
__interp.staticVariables = __class.ogInterp.staticVariables;
|
||||
__interp.customClasses = __class.ogInterp.customClasses;
|
||||
|
||||
@@ -45,6 +45,7 @@ class CustomClassHandler implements IHScriptCustomConstructor implements IHScrip
|
||||
__interp.importFailedCallback = ogInterp.importFailedCallback;
|
||||
|
||||
//__interp.variables = ogInterp.variables;
|
||||
__interp.usingHandler.usingEntries = ogInterp.usingHandler.usingEntries;
|
||||
__interp.publicVariables = ogInterp.publicVariables;
|
||||
__interp.staticVariables = ogInterp.staticVariables;
|
||||
__interp.customClasses = ogInterp.customClasses;
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ enum Expr {
|
||||
EMeta( name : String, args : Array<Expr>, e : Expr );
|
||||
ECheckType( e : Expr, t : CType );
|
||||
|
||||
EImport( c : String, ?asname:String );
|
||||
EImport( c : String, ?asname:String, ?isUsing:Bool );
|
||||
EClass( name:String, fields:Array<Expr>, ?extend:String, interfaces:Array<String>, ?isFinal:Bool, ?isPrivate:Bool );
|
||||
}
|
||||
|
||||
|
||||
+151
-5
@@ -29,6 +29,7 @@
|
||||
package hscript;
|
||||
|
||||
import haxe.CallStack;
|
||||
import hscript.utils.UsingHandler;
|
||||
import hscript.utils.UnsafeReflect;
|
||||
import haxe.PosInfos;
|
||||
import hscript.Expr;
|
||||
@@ -144,6 +145,8 @@ class Interp {
|
||||
// "flixel.FlxG"
|
||||
];
|
||||
|
||||
var usingHandler:UsingHandler;
|
||||
|
||||
#if hscriptPos
|
||||
var curExpr:Expr;
|
||||
#end
|
||||
@@ -160,6 +163,8 @@ class Interp {
|
||||
variables = new Map<String, Dynamic>();
|
||||
publicVariables = new Map<String, Dynamic>();
|
||||
staticVariables = new Map<String, Dynamic>();
|
||||
|
||||
usingHandler = new UsingHandler();
|
||||
|
||||
variables.set("null", null);
|
||||
variables.set("true", true);
|
||||
@@ -582,11 +587,11 @@ class Interp {
|
||||
public inline function error(e:#if hscriptPos ErrorDef #else Error #end, rethrow = false):Dynamic {
|
||||
#if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end
|
||||
|
||||
if (rethrow) {
|
||||
if (rethrow)
|
||||
this.rethrow(e);
|
||||
} else {
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -702,7 +707,7 @@ class Interp {
|
||||
return variable == null ? thing : Type.getClassName(variable);
|
||||
}
|
||||
customClasses.set(name, new CustomClassHandler(this, name, fields, importVar(extend), [for (i in interfaces) importVar(i)]));
|
||||
case EImport(c, n):
|
||||
case EImport(c, n, isUsing):
|
||||
if (!importEnabled)
|
||||
return null;
|
||||
var splitClassName = [for (e in c.split(".")) e.trim()];
|
||||
@@ -712,8 +717,20 @@ class Interp {
|
||||
var oldClassName = realClassName;
|
||||
var oldSplitName = splitClassName.copy();
|
||||
|
||||
if (variables.exists(toSetName)) // class is already imported
|
||||
if (variables.exists(toSetName)) { // class is already imported
|
||||
if(isUsing && !usingHandler.entryExists(toSetName))
|
||||
setUsing(toSetName, variables.get(toSetName));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if(isUsing && customClasses.exists(toSetName)) {
|
||||
// NOTE: you will need to create the class first before
|
||||
// setting the extension
|
||||
if(!usingHandler.entryExists(toSetName))
|
||||
setCustomClassUsing(toSetName, customClasses.get(toSetName));
|
||||
return null;
|
||||
}
|
||||
|
||||
var realClassName = getLocalImportRedirect(realClassName);
|
||||
|
||||
@@ -753,6 +770,7 @@ class Interp {
|
||||
error(EInvalidClass(oldClassName));
|
||||
} else {
|
||||
if (en != null) {
|
||||
if(isUsing) error(EInvalidClass(oldClassName));
|
||||
// ENUM!!!!
|
||||
var enumThingy = {};
|
||||
for (c in en.getConstructors()) {
|
||||
@@ -768,6 +786,7 @@ class Interp {
|
||||
}
|
||||
variables.set(toSetName, enumThingy);
|
||||
} else {
|
||||
if(isUsing) setUsing(toSetName, cl);
|
||||
variables.set(toSetName, cl);
|
||||
}
|
||||
}
|
||||
@@ -1315,8 +1334,20 @@ class Interp {
|
||||
}
|
||||
|
||||
if(v == null) {
|
||||
#if php
|
||||
// https://github.com/HaxeFoundation/haxe/issues/4915
|
||||
try {
|
||||
if ((v = UnsafeReflect.getProperty(o, f)) == null)
|
||||
v = Reflect.getProperty(cls, f);
|
||||
}
|
||||
catch(e:Dynamic) {
|
||||
if ((v = UnsafeReflect.field(o, f)) == null)
|
||||
v = Reflect.field(cls, f);
|
||||
}
|
||||
#else
|
||||
if ((v = UnsafeReflect.getProperty(o, f)) == null)
|
||||
v = Reflect.getProperty(cls, f);
|
||||
#end
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -1355,6 +1386,110 @@ class Interp {
|
||||
return v;
|
||||
}
|
||||
|
||||
// STATIC EXTENSION ("USING")
|
||||
|
||||
// Real class static extension
|
||||
function setUsing(name:String, obj:Dynamic) {
|
||||
if (usingHandler.entryExists(name)) return;
|
||||
|
||||
var fn:Dynamic->String->Array<Dynamic> -> Dynamic = null;
|
||||
var fields:Array<String> = [];
|
||||
|
||||
// Predefined static extension classes
|
||||
switch (name) {
|
||||
case "StringTools": // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/iris/Iris.hx#L45
|
||||
fields = Type.getClassFields(StringTools);
|
||||
fn = function(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
|
||||
if (f == "isEof") // has @:noUsing
|
||||
return null;
|
||||
return switch (Type.typeof(o)) {
|
||||
case TInt if (f == 'hex'):
|
||||
StringTools.hex(o, args[0]);
|
||||
case TClass(String):
|
||||
if (UnsafeReflect.hasField(StringTools, f)) {
|
||||
var field = UnsafeReflect.field(StringTools, f);
|
||||
if (UnsafeReflect.isFunction(field))
|
||||
UnsafeReflect.callMethodUnsafe(StringTools, field, [o].concat(args));
|
||||
else
|
||||
null;
|
||||
} else null;
|
||||
default:
|
||||
null;
|
||||
}
|
||||
}
|
||||
case "Lambda": // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/iris/Iris.hx#L62
|
||||
fields = Type.getClassFields(Lambda);
|
||||
fn = function(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
|
||||
if (o != null && o.iterator != null) {
|
||||
if (UnsafeReflect.hasField(Lambda, f)) {
|
||||
var field = UnsafeReflect.field(Lambda, f);
|
||||
if (UnsafeReflect.isFunction(field)) {
|
||||
return UnsafeReflect.callMethodUnsafe(Lambda, field, [o].concat(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
if(obj == null)
|
||||
error(ECustom("Unknown using class " + name));
|
||||
|
||||
var cls = obj;
|
||||
switch (Type.typeof(cls)) {
|
||||
case TClass(c):
|
||||
fields = Type.getClassFields(c);
|
||||
case TObject:
|
||||
fields = Reflect.fields(cls);
|
||||
default:
|
||||
error(ECustom('$name is not a class'));
|
||||
}
|
||||
fn = function(o:Dynamic, f:String, args:Array<Dynamic>) {
|
||||
if (!Reflect.hasField(cls, f))
|
||||
return null;
|
||||
|
||||
var field = Reflect.field(cls, f);
|
||||
if (!Reflect.isFunction(field))
|
||||
return null;
|
||||
|
||||
// invalid if the function has no arguments
|
||||
var totalArgs = Tools.argCount(field);
|
||||
if (totalArgs == 0)
|
||||
return null;
|
||||
|
||||
return UnsafeReflect.callMethodUnsafe(cls, field, [o].concat(args));
|
||||
}
|
||||
}
|
||||
|
||||
if(fn != null) usingHandler.registerEntry(name, fn, fields);
|
||||
}
|
||||
|
||||
// Custom Class Static Extension
|
||||
@:access(hscript.CustomClassHandler)
|
||||
function setCustomClassUsing(name:String, cls:CustomClassHandler) {
|
||||
if (usingHandler.entryExists(name)) return;
|
||||
|
||||
var fn:Dynamic->String->Array<Dynamic> -> Dynamic;
|
||||
var customClass:CustomClassHandler = cls;
|
||||
var fields:Array<String> = customClass.__staticFields.copy();
|
||||
|
||||
fn = function(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
|
||||
if (!customClass.hasField(f))
|
||||
return null;
|
||||
|
||||
var field:Dynamic = customClass.getField(f);
|
||||
if (!Reflect.isFunction(field))
|
||||
return null;
|
||||
/*
|
||||
var totalArgs:Int = Tools.argCount(field);
|
||||
if (totalArgs == 0)
|
||||
return null;
|
||||
*/
|
||||
return UnsafeReflect.callMethodUnsafe(null, field, args);
|
||||
}
|
||||
|
||||
usingHandler.registerEntry(name, fn, fields);
|
||||
}
|
||||
|
||||
function fcall(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
|
||||
// Custom logic to handle super calls to prevent infinite recursion
|
||||
if(inCustomClass) {
|
||||
@@ -1366,6 +1501,17 @@ class Interp {
|
||||
}
|
||||
}
|
||||
|
||||
if (usingHandler.usingEntries.iterator().hasNext()) { // If is not empty
|
||||
var v:Dynamic = null;
|
||||
for (n => us in usingHandler.usingEntries) {
|
||||
if(us.hasField(f)) {
|
||||
v = us.call(o, f, args);
|
||||
if (v != null)
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var func = get(o, f);
|
||||
// Workaround for an HTML5-specific issue.
|
||||
// https://github.com/HaxeFoundation/haxe/issues/11298
|
||||
|
||||
+5
-2
@@ -956,11 +956,13 @@ class Parser {
|
||||
var tk = token();
|
||||
push(tk);
|
||||
mk(EFunction(inf.args, inf.body, name, inf.ret, nextIsPublic, nextIsStatic, nextIsOverride, nextIsPrivate, nextIsFinal, nextIsInline),p1,pmax(inf.body));
|
||||
case "import":
|
||||
case "import" | "using":
|
||||
var isUsing = id == "using";
|
||||
var oldReadPos = readPos;
|
||||
var tk = token();
|
||||
switch( tk ) {
|
||||
case TPOpen:
|
||||
if(isUsing) error(ECustom('Expected identifier'),tokenMin,tokenMax);
|
||||
var tok = token();
|
||||
switch(tok) {
|
||||
case TConst(c):
|
||||
@@ -986,6 +988,7 @@ class Parser {
|
||||
t = token();
|
||||
if( t != TDot ) {
|
||||
if(t.match(TId("as"))) {
|
||||
if(isUsing) error(ECustom('Expected . or ;'),tokenMin,tokenMax);
|
||||
t = token();
|
||||
switch( t ) {
|
||||
case TId(id):
|
||||
@@ -1010,7 +1013,7 @@ class Parser {
|
||||
ensure(TSemicolon);
|
||||
push(TSemicolon);
|
||||
var p = path.join(".");
|
||||
mk(EImport(p, asname),p1);
|
||||
mk(EImport(p, asname, isUsing),p1);
|
||||
default:
|
||||
unexpected(tk);
|
||||
null;
|
||||
|
||||
+10
-5
@@ -115,9 +115,9 @@ class Printer {
|
||||
add("??NULL??");
|
||||
return;
|
||||
}
|
||||
switch( #if hscriptPos e.e #else e #end ) {
|
||||
case EImport(c, n):
|
||||
add("import " + c);
|
||||
switch(Tools.expr(e)) {
|
||||
case EImport(c, n, u):
|
||||
add('${u ? 'using' : 'import'} $c');
|
||||
if(n != null)
|
||||
add(' as $n');
|
||||
case EClass(name, fields, extend, interfaces):
|
||||
@@ -127,8 +127,13 @@ class Printer {
|
||||
for(_interface in interfaces) {
|
||||
add(' implements $_interface');
|
||||
}
|
||||
add(' {\n');
|
||||
tabs += "\t";
|
||||
add(" {\n");
|
||||
for( e in fields ) {
|
||||
add(tabs);
|
||||
expr(e);
|
||||
//add(";\n");
|
||||
}
|
||||
//for(field in fields) {
|
||||
// expr(field);
|
||||
//}
|
||||
@@ -211,7 +216,7 @@ class Printer {
|
||||
case ECall(e, args):
|
||||
if( e == null )
|
||||
expr(e);
|
||||
else switch( #if hscriptPos e.e #else e #end ) {
|
||||
else switch( Tools.expr(e)) {
|
||||
case EField(_), EIdent(_), EConst(_):
|
||||
expr(e);
|
||||
default:
|
||||
|
||||
@@ -111,4 +111,23 @@ class Tools {
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT USE INLINE ON THIS FUNCTION
|
||||
**/
|
||||
public static function argCount(func: haxe.Constraints.Function): Int {
|
||||
// https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Tools.hx#L206
|
||||
#if cpp
|
||||
return untyped __cpp__("{0}->__ArgCount()", func);
|
||||
#elseif js
|
||||
return untyped js.Syntax.code("{0}.length", func);
|
||||
#elseif hl
|
||||
var ft = hl.Type.getDynamic(func);
|
||||
if (ft.kind != HFun)
|
||||
return -1;
|
||||
return ft.getArgsCount();
|
||||
#else
|
||||
return -1;
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package hscript.utils;
|
||||
|
||||
@:structInit
|
||||
class UsingEntry {
|
||||
public var call:Dynamic->String->Array<Dynamic>->Dynamic;
|
||||
public var fields:Array<String>;
|
||||
|
||||
public function hasField(name:String) {
|
||||
return fields.contains(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special class that handles static extension function calls.
|
||||
*
|
||||
* A static extension allows pseudo-extending
|
||||
* existing types without modifying their source.
|
||||
* In Haxe this is achieved by declaring a static method with a first argument
|
||||
* of the extending type and then bringing the defining class into context through `using`.
|
||||
*
|
||||
* Example:
|
||||
* ```haxe
|
||||
* class IntExtender {
|
||||
* static public function triple(i:Int) {
|
||||
* return i * 3;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* using IntExtender;
|
||||
*
|
||||
* trace(12.triple()); // 36
|
||||
* ```
|
||||
*
|
||||
* @see https://haxe.org/manual/lf-static-extension.html
|
||||
*/
|
||||
class UsingHandler { // The ACTUAL UsingHandler.hx >:3
|
||||
@:allow(hscript.CustomClass)
|
||||
@:allow(hscript.CustomClassHandler)
|
||||
public var usingEntries(default, null):Map<String, UsingEntry> = [];
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function registerEntry(name:String, entry:Dynamic->String->Array<Dynamic>->Dynamic, fields:Array<String>) {
|
||||
usingEntries.set(name, {call: entry, fields: fields});
|
||||
}
|
||||
|
||||
public function entryExists(name:String):Bool {
|
||||
return usingEntries.exists(name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user