Compare commits

...
Author SHA1 Message Date
Jaime Humberto Macias Bustamante 9db1cb9ca1 Just found out i have to rewrite the entire thing from scratch for the sake of optimizations...
:'3
2025-05-29 15:25:26 -07:00
Jaime Humberto Macias Bustamante 1bcbce2d5b Property combination check
Also, it parses 'dynamic'.

Still working on getter/setter for variables
2025-05-16 14:10:20 -07:00
Jaime Humberto Macias Bustamante 22d6404799 getter/setter for variables 2025-05-15 01:17:11 -07:00
Jaime Humberto Macias Bustamante 932e643dc9 Better access context (IN PROGRESS)
Also, throws an error if a variable with "(get, set)" doesn't have a type or a default value.
2025-05-15 01:07:29 -07:00
Jaime Humberto Macias Bustamante 1cc678fbef Reverting getter/setter to remake
"clean this up, it sucks, i hate it"
- ne_eo
2025-05-14 11:16:02 -07:00
Jaime Humberto Macias Bustamante 122fd03a29 solved possible issues + field check improved 2025-05-13 20:47:14 -07:00
Jaime Humberto Macias Bustamante a3b153eb99 Update Interp.hx 2025-05-13 15:35:03 -07:00
Jaime Humberto Macias Bustamante b36d8b167f sorry, I forgot :'3 2025-05-13 15:24:25 -07:00
Jaime Humberto Macias Bustamante cae6d5072c Improved "null" handling 2025-05-13 12:04:49 -07:00
Jaime Humberto Macias Bustamante 8dce40354a "null" access handling + get/set for variables
- It handles "null" allowing access only inside of the class.

- "final" variables can be initialized in the constructor if they doesn't have a default value.

- "var a(get, set):Int" is parsed outside of Custom Class, but for now it doesn't call their accessors.

almost finished :3
2025-05-13 00:38:11 -07:00
Jaime Humberto Macias Bustamante 19b5b2b049 getter, setter and final for Custom Classes
full support for "var a(get, set):Int".
Also, it takes into account final vars

Soon for inline custom classes

[UNTESTED]
2025-05-12 11:27:43 -07:00
Jaime Humberto Macias Bustamante 64ee722124 Allow trailing array commas (https://github.com/HaxeFoundation/hscript/pull/139) 2025-05-11 12:36:55 -07:00
Jaime Humberto Macias Bustamante 77282e3b7b Update Parser.hx 2025-05-11 12:15:45 -07:00
Jaime Humberto Macias Bustamante 927b72c91a nvm, backward compatibility 2025-05-11 11:42:39 -07:00
Jaime Humberto Macias Bustamante 10b67def74 Moved "resolveField" to "hget" + fixes 2025-05-11 00:27:40 -07:00
Jaime Humberto Macias Bustamante 3f1f0e7234 Simplified Custom Class assignment 2025-05-08 16:25:16 -07:00
Jaime Humberto Macias Bustamante c7160d05fd super Custom Class check + null check optimization 2025-05-07 11:03:22 -07:00
Jaime Humberto Macias Bustamante 05577f7559 Cleanup 2025-05-06 11:58:38 -07:00
Jaime Humberto Macias Bustamante e26385f591 Fixed superClass calling + special operations
Fixed not being able to do "+=" on Custom Classes and getters and setters on constructor.

Also, fixed not being able to call functions from a CustomClass super class.
2025-05-05 21:42:02 -07:00
Jaime Humberto Macias Bustamante 33583e751f Small optimizations 2025-05-05 14:28:44 -07:00
Jaime Humberto Macias Bustamante 742b40c86c The Beginning of the End (Old Custom Class) 2025-05-04 23:20:56 -07:00
Jaime Humberto Macias Bustamante bf602f758b Typedefs for Custom Classes
Only works as an import method, for example: `typedef OtherShader = funkin.backend.shaders.CustomShader;`.
2025-05-04 20:13:01 -07:00
Jaime Humberto Macias Bustamante 34b07378fd Fix 1e parsing
Fix 1e5, 1e-5 evaluated as Infinity (https://github.com/HaxeFoundation/hscript/pull/132)
2025-04-10 14:51:36 -07:00
Jaime Humberto Macias Bustamante 6622220415 Array parse fix
Do not allow arrays without comma between values (https://github.com/HaxeFoundation/hscript/pull/138)
2025-04-09 11:57:04 -07:00
Jaime Humberto Macias Bustamante ca0599b180 capture variables for inline Custom Classes 2025-04-09 11:43:06 -07:00
Jaime Humberto Macias Bustamante d055203559 Public vars for Custom Classes in scripts
Uses public variables from the same scope as where the class was defined.
2025-04-08 11:48:31 -07:00
Jaime Humberto Macias Bustamante 65b43ebe78 Custom Classes inside of scripts 2025-04-07 11:52:49 -07:00
Jaime Humberto Macias Bustamante 9aa3481f62 superClass thing improvement
STILL IN PROGRESS
2025-04-05 19:48:59 -07:00
Jaime Humberto Macias Bustamante e087521a36 Update CustomClassDecl.hx
oops
2025-04-03 16:08:25 -07:00
Jaime Humberto Macias Bustamante f43221c3bb "Type" for Custom Classes - Part II
- Move the superClass instantiation to CustomClassDecl
- Imported classes available for Static Functions

Warning: untested code.
2025-03-27 16:02:06 -07:00
Jaime Humberto Macias Bustamante e71f5a0471 "Type" for Custom Classes 2025-03-24 20:28:29 -07:00
Jaime Humberto Macias Bustamante 73c17e51dc Enums, new import blocklist and redirects
warning: untested
2025-03-24 11:58:56 -07:00
Jaime Humberto Macias Bustamante e6b5d6af49 ".bind" support + "package;" 2025-03-04 20:26:32 -07:00
Jaime Humberto Macias Bustamante 9833c61f78 Using and Enums
Based on Enums and Using system from hscript-iris, mostly made by ne_eo.
2025-03-03 21:53:02 -07:00
Jaime Humberto Macias Bustamante a1ee770794 oops :3 2025-02-04 00:57:53 -07:00
Jaime Humberto Macias Bustamante 270087fc62 Update Parser.hx
forgot to put the "EIgnore" work
2025-02-03 20:35:10 -07:00
Jaime Humberto Macias Bustamante bd0ae3fbe1 Typedef support
Original Implementation by ne_eo for hscript-iris.

For now only works for normal scripts and making import redirects. Soon for custom classes.
2025-02-03 00:00:15 -07:00
Jaime Humberto Macias Bustamante 241d97aed0 Import aliases for Custom Classes
Import aliases support for Custom Classes. This allows you to import classes and other custom classes with an alias inside of a Custom Class.

This includes some improvements on "parseModuleDecl()"
2025-02-01 21:00:41 -07:00
Jaime Humberto Macias Bustamante b25ecb08bc Function Call reimplemented
The previous function call system has a serious issue. It can not differentiate between class fields and function argument fields.
2025-01-21 22:07:41 -07:00
Jaime Humberto Macias Bustamante c45373aa55 Custom Classes Rework 2025-01-21 00:27:11 -07:00
25 changed files with 2986 additions and 212 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
/hscript.swf /hscript.swf
/release.zip /release.zip
dump/*
tests/bin/* tests/bin/*
+1 -1
View File
@@ -3,7 +3,7 @@ hscript-improved
How to install How to install
``` ```
haxelib git hscript-improved https://github.com/FNF-CNE-Devs/hscript-improved.git haxelib git hscript-improved https://github.com/CodenameCrew/hscript-improved.git
``` ```
To enable custom classes support you have to do this in project.xml To enable custom classes support you have to do this in project.xml
+5
View File
@@ -33,6 +33,10 @@ class TestHScript extends TestCase {
assertScript("- 123",-123); assertScript("- 123",-123);
assertScript("1.546",1.546); assertScript("1.546",1.546);
assertScript(".545",.545); assertScript(".545",.545);
assertScript("1e5",100000);
assertScript("1.2e2",120);
assertScript("100e-2",1);
assertScript("1.2e-1",0.12);
assertScript("'bla'","bla"); assertScript("'bla'","bla");
assertScript("null",null); assertScript("null",null);
assertScript("true",true); assertScript("true",true);
@@ -53,6 +57,7 @@ class TestHScript extends TestCase {
assertScript("3 * 2 // + 5 \n + 6",12); assertScript("3 * 2 // + 5 \n + 6",12);
assertScript("3 /* 2\n */ + 5",8); assertScript("3 /* 2\n */ + 5",8);
assertScript("[55,66,77][1]",66); assertScript("[55,66,77][1]",66);
assertScript("[11,22,33,][2]", 33);
assertScript("var a = [55]; a[0] *= 2; a[0]",110); assertScript("var a = [55]; a[0] *= 2; a[0]",110);
assertScript("x",55,{ x : 55 }); assertScript("x",55,{ x : 55 });
assertScript("var y = 33; y",33); assertScript("var y = 33; y",33);
+2 -1
View File
@@ -1,3 +1,4 @@
--macro keep('IntIterator') --macro keep('IntIterator')
--macro hscript.macros.UsingHandler.init() --macro hscript.macros.AbstractHandler.init()
--macro hscript.macros.UsingMacro.init()
--macro hscript.macros.ClassExtendMacro.init() --macro hscript.macros.ClassExtendMacro.init()
+7
View File
@@ -124,10 +124,17 @@ class Bytes {
#end #end
bout.addByte(Type.enumIndex(e)); bout.addByte(Type.enumIndex(e));
switch( e ) { switch( e ) {
case EIgnore(_):
case EImport(c): case EImport(c):
// TODO // TODO
case EClass(_, _, _, _): case EClass(_, _, _, _):
// TODO // TODO
case ERedirect(_, _):
// TODO
case EUsing(name):
// TODO
case EEnum(name, fields):
// TODO
case EConst(c): case EConst(c):
doEncodeConst(c); doEncodeConst(c);
case EIdent(v): case EIdent(v):
+9
View File
@@ -12,6 +12,11 @@ class Config {
"openfl.display.BlendMode", "openfl.display.BlendMode",
]; ];
// Runs support for using in specific classes.
public static final ALLOWED_USING = [
];
// Incase any of your files fail // Incase any of your files fail
// These are the module names // These are the module names
public static final DISALLOW_CUSTOM_CLASSES = [ public static final DISALLOW_CUSTOM_CLASSES = [
@@ -21,4 +26,8 @@ class Config {
public static final DISALLOW_ABSTRACT_AND_ENUM = [ public static final DISALLOW_ABSTRACT_AND_ENUM = [
]; ];
public static final DISALLOW_USING = [
];
} }
+18 -10
View File
@@ -4,6 +4,7 @@ import hscript.Interp.DeclaredVar;
import hscript.utils.UnsafeReflect; import hscript.utils.UnsafeReflect;
using StringTools; using StringTools;
@:deprecated('Switch to new Custom Class instead')
class CustomClassHandler implements IHScriptCustomConstructor { class CustomClassHandler implements IHScriptCustomConstructor {
public static var staticHandler = new StaticHandler(); public static var staticHandler = new StaticHandler();
@@ -22,7 +23,7 @@ class CustomClassHandler implements IHScriptCustomConstructor {
this.extend = extend; this.extend = extend;
this.interfaces = interfaces; this.interfaces = interfaces;
this.cl = extend == null ? CustomTemplateClass : Type.resolveClass('${extend}_HSX'); //this.cl = extend == null ? CustomTemplateClass : Type.resolveClass('${extend}_HSX');
if(cl == null) if(cl == null)
ogInterp.error(EInvalidClass(extend)); ogInterp.error(EInvalidClass(extend));
} }
@@ -75,7 +76,7 @@ class CustomClassHandler implements IHScriptCustomConstructor {
comparisonMap.set(key, value); comparisonMap.set(key, value);
} }
_class.__custom__variables = interp.variables; //_class.__custom__variables = interp.variables;
//trace(fields); //trace(fields);
for(expr in fields) { for(expr in fields) {
@@ -99,21 +100,21 @@ class CustomClassHandler implements IHScriptCustomConstructor {
// } // }
//} //}
_class.__class__fields = [for(key => value in classVariables) key]; //_class.__class__fields = [for(key => value in classVariables) key];
//trace(_class.__class__fields); //trace(_class.__class__fields);
//@:privateAccess //@:privateAccess
//trace(interp.__instanceFields); //trace(interp.__instanceFields);
_class.__interp = interp; //_class.__interp = interp;
_class.__allowSetGet = false; //_class.__allowSetGet = false;
interp.scriptObject = _class; interp.scriptObject = _class;
for(variable => value in interp.variables) { for(variable => value in interp.variables) {
if(variable == "this" || variable == "super" || variable == "new") continue; if(variable == "this" || variable == "super" || variable == "new") continue;
if(variable.startsWith("set_") || variable.startsWith("get_")) { if(variable.startsWith("set_") || variable.startsWith("get_")) {
_class.__allowSetGet = true; //_class.__allowSetGet = true;
} }
} }
@@ -138,9 +139,11 @@ class CustomClassHandler implements IHScriptCustomConstructor {
if(!interp.__instanceFields.contains(variable)) { if(!interp.__instanceFields.contains(variable)) {
interp.__instanceFields.push(variable); interp.__instanceFields.push(variable);
} }
/*
if(!_class.__class__fields.contains(variable)) { if(!_class.__class__fields.contains(variable)) {
_class.__class__fields.push(variable); _class.__class__fields.push(variable);
} }
*/
} }
} }
@@ -152,9 +155,10 @@ class CustomClassHandler implements IHScriptCustomConstructor {
return name; return name;
} }
} }
/*
class CustomTemplateClass implements IHScriptCustomClassBehaviour implements IHScriptCustomAccessBehaviour { class CustomTemplateClass implements IHScriptCustomClassBehaviour implements IHScriptCustomAccessBehaviour {
public var __interp:Interp; public var __interp:Interp;
public var __customClass:hscript.customclass.CustomClass;
public var __allowSetGet:Bool = true; public var __allowSetGet:Bool = true;
public var __custom__variables:Map<String, Dynamic>; public var __custom__variables:Map<String, Dynamic>;
public var __real_fields:Array<String>; public var __real_fields:Array<String>;
@@ -198,15 +202,18 @@ class CustomTemplateClass implements IHScriptCustomClassBehaviour implements IHS
} }
} }
*/
/** /**
* This is for backwards compatibility with old hscript-improved, since some scripts use it * This is for backwards compatibility with old hscript-improved, since some scripts use it
**/ */
@:dox(hide) @:dox(hide)
@:keep @:keep
class TemplateClass implements IHScriptCustomBehaviour implements IHScriptCustomAccessBehaviour { class TemplateClass implements IHScriptCustomBehaviour implements IHScriptCustomAccessBehaviour {
public var __interp:Interp; public var __interp:Interp;
public var __allowSetGet:Bool = true; public var __allowSetGet:Bool = true;
public var __allowPrivateAccess:Bool = true;
public function hset(name:String, val:Dynamic):Dynamic { public function hset(name:String, val:Dynamic):Dynamic {
var variables = __interp.variables; var variables = __interp.variables;
@@ -236,7 +243,8 @@ class TemplateClass implements IHScriptCustomBehaviour implements IHScriptCustom
return v; return v;
} }
} }
/*
final class StaticHandler { final class StaticHandler {
public function new() {} public function new() {}
} }
*/
+32 -5
View File
@@ -51,9 +51,10 @@ enum ExprDef {
typedef ExprDef = Expr; typedef ExprDef = Expr;
enum Expr { enum Expr {
#end #end
EIgnore(skip:Bool);
EConst( c : Const ); EConst( c : Const );
EIdent( v : String ); EIdent( v : String );
EVar( n : String, ?t : CType, ?e : Expr, ?isPublic : Bool, ?isStatic : Bool, ?isPrivate : Bool, ?isFinal : Bool, ?isInline : Bool ); EVar( n : String, ?t : CType, ?e : Expr, ?isPublic : Bool, ?isStatic : Bool, ?isPrivate : Bool, ?isFinal : Bool, ?isInline : Bool, ?get : FieldPropertyAccess, ?set : FieldPropertyAccess );
EParent( e : Expr ); EParent( e : Expr );
EBlock( e : Array<Expr> ); EBlock( e : Array<Expr> );
EField( e : Expr, f : String , ?safe : Bool ); EField( e : Expr, f : String , ?safe : Bool );
@@ -81,6 +82,9 @@ enum Expr {
EImport( c : String, ?asname:String ); EImport( c : String, ?asname:String );
EClass( name:String, fields:Array<Expr>, ?extend:String, interfaces:Array<String>, ?isFinal:Bool, ?isPrivate:Bool ); EClass( name:String, fields:Array<Expr>, ?extend:String, interfaces:Array<String>, ?isFinal:Bool, ?isPrivate:Bool );
ERedirect( name:String, className:String, ?cl:Class<Dynamic> );
EUsing(name:String);
EEnum(name:String, fields:Array<EnumType>);
} }
@:structInit @:structInit
@@ -159,9 +163,10 @@ enum Error {
enum ModuleDecl { enum ModuleDecl {
DPackage( path : Array<String> ); DPackage( path : Array<String> );
DImport( path : Array<String>, ?everything : Bool ); DImport( path : Array<String>, ?everything : Bool, ?asname:String );
DClass( c : ClassDecl ); DUsing( path : Array<String>);
DTypedef( c : TypeDecl ); DTypedef( c : TypeDecl );
DClass( c : ClassDecl );
} }
typedef ModuleType = { typedef ModuleType = {
@@ -211,8 +216,30 @@ final class FunctionDecl {
} }
typedef VarDecl = { typedef VarDecl = {
var get : Null<String>; var get : FieldPropertyAccess;
var set : Null<String>; var set : FieldPropertyAccess;
var isFinal : Bool;
var expr : Null<Expr>; var expr : Null<Expr>;
var type : Null<CType>; var type : Null<CType>;
} }
typedef TypeParamDecl = {};
enum EnumType {
ESimple(name: String);
EConstructor(name: String, args: Array<Argument>);
}
enum abstract FieldPropertyAccess(UInt8) {
var ADefault;
var ANull;
var AGet;
var ASet;
var ADynamic;
var ANever;
}
enum AccessContext {
CInner(cl:String);
COuter;
}
+1
View File
@@ -2,6 +2,7 @@ package hscript;
interface IHScriptCustomAccessBehaviour { interface IHScriptCustomAccessBehaviour {
var __allowSetGet:Bool; var __allowSetGet:Bool;
var __allowPrivateAccess:Bool;
public function hset(name:String, val:Dynamic):Dynamic; public function hset(name:String, val:Dynamic):Dynamic;
public function hget(name:String):Dynamic; public function hget(name:String):Dynamic;
+1 -11
View File
@@ -1,16 +1,6 @@
package hscript; package hscript;
interface IHScriptCustomClassBehaviour { interface IHScriptCustomClassBehaviour {
public var __interp:Interp; public var __customClass:hscript.customclass.CustomClass;
public var __custom__variables:Map<String, Dynamic>;
public var __allowSetGet:Bool;
public var __real_fields:Array<String>; public var __real_fields:Array<String>;
public var __class__fields:Array<String>;
public function hset(name:String, val:Dynamic):Dynamic;
public function hget(name:String):Dynamic;
public function __callGetter(name:String):Dynamic;
public function __callSetter(name:String, val:Dynamic):Dynamic;
} }
+892 -127
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
package hscript;
class InterpConfig {
// Automatic import redirect for certain classes
public static final IMPORT_REDIRECTS = [
"Type" => "hscript.proxy.ProxyType"
];
// Incase an import fails
// These are the module names
@:unreflective
public static final DISALLOW_IMPORT = [
"Type"
];
}
+271 -21
View File
@@ -22,6 +22,7 @@
package hscript; package hscript;
import haxe.rtti.Meta; import haxe.rtti.Meta;
import hscript.Expr; import hscript.Expr;
import hscript.Expr.FieldPropertyAccess;
enum Token { enum Token {
TEof; TEof;
@@ -86,6 +87,11 @@ class Parser {
**/ **/
public var resumeErrors : Bool; public var resumeErrors : Bool;
/*
package name, set when using "package;" in your script.
*/
public var packageName:String = null;
// implementation // implementation
var input : String; var input : String;
var readPos : Int; var readPos : Int;
@@ -97,6 +103,8 @@ class Parser {
var disableOrOp : Bool = false; var disableOrOp : Bool = false;
var isVar : Bool = false;
#if hscriptPos #if hscriptPos
var origin : String; var origin : String;
var tokenMin : Int; var tokenMin : Int;
@@ -234,6 +242,16 @@ class Parser {
} }
} }
function extractIdent(tk: Token): String {
switch (tk) {
case TId(id):
return id;
default:
unexpected(tk);
return null;
}
}
inline function expr(e:Expr):#if hscriptPos ExprDef #else Expr #end { inline function expr(e:Expr):#if hscriptPos ExprDef #else Expr #end {
#if hscriptPos #if hscriptPos
return e.e; return e.e;
@@ -272,7 +290,7 @@ class Parser {
function isBlock(e:Expr):Bool { function isBlock(e:Expr):Bool {
if( e == null ) return false; if( e == null ) return false;
return switch( expr(e) ) { return switch( expr(e) ) {
case EBlock(_), EObject(_), ESwitch(_): true; case EBlock(_), EObject(_), ESwitch(_), EEnum(_, _): true;
case EFunction(_,e,_,_,_,_): isBlock(e); case EFunction(_,e,_,_,_,_): isBlock(e);
case EClass(_,e,_,_): true; case EClass(_,e,_,_): true;
case EVar(_, t, e, _,_): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false; case EVar(_, t, e, _,_): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false;
@@ -285,19 +303,22 @@ class Parser {
case EReturn(e): e != null && isBlock(e); case EReturn(e): e != null && isBlock(e);
case ETry(_, _, _, e): isBlock(e); case ETry(_, _, _, e): isBlock(e);
case EMeta(_, _, e): isBlock(e); case EMeta(_, _, e): isBlock(e);
case EIgnore(skip): skip;
default: false; default: false;
} }
} }
function parseFullExpr( exprs : Array<Expr> ):Void { function parseFullExpr( exprs : Array<Expr> ):Void {
var e = parseExpr(); var e = parseExpr();
exprs.push(e); if(!expr(e).match(EIgnore(_)))
exprs.push(e);
var tk = token(); var tk = token();
// this is a hack to support var a,b,c; with a single EVar // this is a hack to support var a,b,c; with a single EVar
while( tk == TComma && e != null && expr(e).match(EVar(_)) ) { while( tk == TComma && e != null && expr(e).match(EVar(_)) ) {
e = parseStructure("var"); // next variable e = parseStructure("var"); // next variable
exprs.push(e); if(!expr(e).match(EIgnore(_)))
exprs.push(e);
tk = token(); tk = token();
} }
@@ -463,15 +484,24 @@ class Parser {
case TBkOpen: case TBkOpen:
var a = []; var a = [];
tk = token(); tk = token();
var first:Bool = true;
while( tk != TBkClose && (!resumeErrors || tk != TEof) ) { while( tk != TBkClose && (!resumeErrors || tk != TEof) ) {
if(!first) {
if(tk != TComma)
unexpected(tk);
else {
tk = token();
if (tk == TBkClose)
break;
}
}
first = false;
push(tk); push(tk);
var oldoo = disableOrOp; var oldoo = disableOrOp;
disableOrOp = false; disableOrOp = false;
a.push(parseExpr()); a.push(parseExpr());
disableOrOp = oldoo; disableOrOp = oldoo;
tk = token(); tk = token();
if( tk == TComma )
tk = token();
} }
if( a.length == 1 && a[0] != null ) // What is this for??? if( a.length == 1 && a[0] != null ) // What is this for???
switch( expr(a[0]) ) { switch( expr(a[0]) ) {
@@ -827,7 +857,40 @@ class Parser {
} }
} }
var ident = getIdent(); var ident = getIdent();
var get = ADefault, set = ADefault;
var hasGetSet:Bool = false;
var tk = token(); var tk = token();
if( tk == TPOpen) {
if( id == "final" )
unexpected(tk);
var getId = getIdent();
switch (getId) {
case 'default': // Do nothing
case 'get': get = AGet;
case 'null': get = ANull;
case 'dynamic': get = ADynamic;
case 'never': get = ANever;
default: error(ECustom("Custom property accessor is not supported"), p1, tokenMax);
}
ensure(TComma);
var setId = getIdent();
switch (setId) {
case 'default': // Do nothing
case 'set': set = ASet;
case 'null': set = ANull;
case 'dynamic': set = ADynamic;
case 'never': set = ANever;
default: error(ECustom("Custom property accessor is not supported"), p1, tokenMax);
}
ensure(TPClose);
hasGetSet = true;
tk = token();
}
var t = null; var t = null;
nextType = null; nextType = null;
if( tk == TDoubleDot && allowTypes ) { if( tk == TDoubleDot && allowTypes ) {
@@ -841,8 +904,12 @@ class Parser {
e = parseExpr(); e = parseExpr();
else else
push(tk); push(tk);
if(hasGetSet)
checkAccess(get, set, e, t);
nextType = null; nextType = null;
mk(EVar(ident, t, e, nextIsPublic, nextIsStatic, nextIsPrivate, id == "final", nextIsInline), p1, (e == null) ? tokenMax : pmax(e)); mk(EVar(ident, t, e, nextIsPublic, nextIsStatic, nextIsPrivate, id == "final", nextIsInline, get, set), p1, (e == null) ? tokenMax : pmax(e));
case "while": case "while":
var econd = parseExpr(); var econd = parseExpr();
var e = parseExpr(); var e = parseExpr();
@@ -892,6 +959,18 @@ class Parser {
var tk = token(); var tk = token();
push(tk); push(tk);
mk(EFunction(inf.args, inf.body, name, inf.ret, nextIsPublic, nextIsStatic, nextIsOverride, nextIsPrivate, nextIsFinal, nextIsInline),p1,pmax(inf.body)); mk(EFunction(inf.args, inf.body, name, inf.ret, nextIsPublic, nextIsStatic, nextIsOverride, nextIsPrivate, nextIsFinal, nextIsInline),p1,pmax(inf.body));
case "package":
// ignore package
var tk = token();
push(tk);
packageName = "";
if (tk == TSemicolon)
return mk(EIgnore(false));
var path = parsePath();
// mk(EPackage(path.join(".")));
packageName = path.join(".");
mk(EIgnore(false));
case "import": case "import":
var oldReadPos = readPos; var oldReadPos = readPos;
var tk = token(); var tk = token();
@@ -1033,7 +1112,91 @@ class Parser {
var tk = token(); var tk = token();
push(tk); push(tk);
mk(EClass(name, fields, extend, interfaces, nextIsFinal, nextIsPrivate), p1); mk(EClass(name, fields, extend, interfaces, nextIsFinal, nextIsPrivate), p1);
case "typedef":
var name = getIdent();
ensureToken(TOp("="));
var t = parseType();
switch(t) {
case CTAnon(_) | CTFun(_):
/*
Ignore parsing if its,
typedef Name = {
> Person
var name:String;
var age:Int;
}
*/
mk(EIgnore(true));
case CTPath(path, params):
if (params != null && params.length > 1)
error(ECustom("Typedefs can't have parameters"), p1, tokenMax);
if (path.length == 0)
error(ECustom("Typedefs can't be empty"), p1, tokenMax);
var className = path.join(".");
var cl = Tools.getClass(className);
if(cl != null)
return mk(ERedirect(name, className, cl));
var expr = mk(EIdent(path.shift()));
while (path.length > 0) {
expr = mk(EField(expr, path.shift(), false));
}
// todo? add import to the beginning of the file?
mk(EVar(name, null, expr));
default:
error(ECustom("Typedef, unknown type " + t), p1, tokenMax);
null;
}
case "using":
var path = parsePath();
mk(EUsing(path.join(".")));
case "enum":
var name = getIdent();
ensure(TBrOpen);
var fields = [];
var currentName = "";
var currentArgs: Array<Argument> = null;
while (true) {
var tk = token();
switch (tk) {
case TBrClose:
break;
case TSemicolon | TComma:
if (currentName == "")
continue;
if (currentArgs != null && currentArgs.length > 0) {
fields.push(EnumType.EConstructor(currentName, currentArgs));
currentArgs = null;
} else {
fields.push(EnumType.ESimple(currentName));
}
currentName = "";
case TPOpen:
if (currentArgs != null) {
error(ECustom("Cannot have multiple argument lists in one enum constructor"), tokenMin, tokenMax);
break;
}
currentArgs = parseFunctionArgs();
default:
if (currentName != "") {
error(ECustom("Expected comma or semicolon"), tokenMin, tokenMax);
break;
}
var name = extractIdent(tk);
currentName = name;
}
}
mk(EEnum(name, fields));
case "return": case "return":
var tk = token(); var tk = token();
push(tk); push(tk);
@@ -1448,6 +1611,24 @@ class Parser {
return args; return args;
} }
function checkAccess(get:FieldPropertyAccess, set:FieldPropertyAccess, ?expr:Expr, ?type:CType) {
#if hscriptPos
var p1 = tokenMin;
#end
switch([get, set]) {
case [AGet, ASet] | [AGet, ANever] | [ANever, ASet]:
if(expr != null && !isVar)
error(ECustom("Attempt to assign on field that is not a real variable"), p1, pmax(expr));
else if(type == null)
error(ECustom("Property requires type-hint"), p1, tokenMax);
case [ANever, ANever]:
error(ECustom("Unsupported property combination"), p1, (expr == null) ? tokenMax : pmax(expr));
default:
if(expr == null && type == null)
error(ECustom("Property requires type-hint or initialization"), p1, tokenMax);
}
}
// ------------------------ module ------------------------------- // ------------------------ module -------------------------------
public function parseModule( content : String, ?origin : String = "hscript" ):Array<ModuleDecl> { public function parseModule( content : String, ?origin : String = "hscript" ):Array<ModuleDecl> {
@@ -1463,6 +1644,13 @@ class Parser {
push(tk); push(tk);
decls.push(parseModuleDecl()); decls.push(parseModuleDecl());
} }
haxe.ds.ArraySort.sort(decls, (m1, m2) -> {
var i1 = Type.enumIndex(m1);
var i2 = Type.enumIndex(m2);
if(i1 > i2) return 0;
return -1;
});
return decls; return decls;
} }
@@ -1509,10 +1697,22 @@ class Parser {
return DPackage(path); return DPackage(path);
case "import": case "import":
var path = [getIdent()]; var path = [getIdent()];
var star = false; var asname:String = null;
var star = false; // For now, unused
while( true ) { while( true ) {
var t = token(); var t = token();
if( t != TDot ) { if( t != TDot ) {
if (t.match(TId("as"))) {
t = token();
switch (t) {
case TId(id):
asname = id;
default:
unexpected(t);
}
break;
}
push(t); push(t);
break; break;
} }
@@ -1520,34 +1720,56 @@ class Parser {
switch( t ) { switch( t ) {
case TId(id): case TId(id):
path.push(id); path.push(id);
/* idk if put this work
case TOp("*"): case TOp("*"):
star = true; star = true;
*/
default: default:
unexpected(t); unexpected(t);
} }
} }
ensure(TSemicolon); ensure(TSemicolon);
return DImport(path, star); return DImport(path, star, asname);
case "using":
var path = parsePath();
ensure(TSemicolon);
return DUsing(path);
case "class": case "class":
var name = getIdent(); var name = getIdent();
var params = parseParams(); var params = parseParams();
var extend = null; var extend:Null<CType> = null;
var implement = []; var implement:Array<CType> = [];
while( true ) { while( true ) {
var t = token(); var t = token();
switch( t ) { switch( t ) {
case TId("extends"): case TId("extends"):
extend = parseType(); var e = parseType();
switch(e) {
case CTPath(path, params):
if(extend != null) {
error(ECustom('Cannot extend a class twice.'), 0, 0);
}
extend = e;
default:
error(ECustom('${Std.string(e)} is not a valid path.'), 0, 0);
}
case TId("implements"): case TId("implements"):
implement.push(parseType()); var i = parseType();
switch(i) {
case CTPath(path, params):
implement.push(i);
default:
error(ECustom('${Std.string(i)} is not a valid interface path.'), 0, 0);
}
//implement.push(parseType());
default: default:
push(t); push(t);
break; break;
} }
} }
var fields = []; var fields:Array<FieldDecl> = [];
ensure(TBrOpen); ensure(TBrOpen);
while( !maybe(TBrClose) ) while( !maybe(TBrClose) )
fields.push(parseField()); fields.push(parseField());
@@ -1611,17 +1833,42 @@ class Parser {
ret : inf.ret, ret : inf.ret,
}), }),
}; };
case "var": case "var" | "final":
var name = getIdent(); var name = getIdent();
var get = null, set = null; var get = ADefault, set = ADefault;
if( maybe(TPOpen) ) { var hasGetSet:Bool = false;
get = getIdent();
if(maybe(TPOpen)) {
if(id == "final")
unexpected(TPOpen);
var getId = getIdent();
switch(getId) {
case 'default': // Do nothing
case 'get': get = AGet;
case 'null': get = ANull;
case 'dynamic': get = ADynamic;
case 'never': get = ANever;
default: error(ECustom("Custom property accessor is not supported"), tokenMin, tokenMax);
}
ensure(TComma); ensure(TComma);
set = getIdent(); var setId = getIdent();
switch(setId) {
case 'default': // Do nothing
case 'set': set = ASet;
case 'null': set = ANull;
case 'dynamic': set = ADynamic;
case 'never': set = ANever;
default: error(ECustom("Custom property accessor is not supported"), tokenMin, tokenMax);
}
ensure(TPClose); ensure(TPClose);
hasGetSet = true;
} }
var type = maybe(TDoubleDot) ? parseType() : null; var type:Null<CType> = maybe(TDoubleDot) ? parseType() : null;
var expr = maybe(TOp("=")) ? parseExpr() : null; var expr:Null<Expr> = maybe(TOp("=")) ? parseExpr() : null;
if(hasGetSet)
checkAccess(get, set, expr, type);
if( expr != null ) { if( expr != null ) {
if( isBlock(expr) ) if( isBlock(expr) )
@@ -1640,8 +1887,9 @@ class Parser {
kind : KVar({ kind : KVar({
get : get, get : get,
set : set, set : set,
isFinal: id == "final",
type : type, type : type,
expr : expr, expr : expr
}), }),
}; };
default: default:
@@ -1797,6 +2045,8 @@ class Parser {
} }
if( pow == null ) if( pow == null )
invalidChar(char); invalidChar(char);
if( exp == 0 )
exp = 10;
return TConst(CFloat((Math.pow(10, pow) / exp) * n * 10)); return TConst(CFloat((Math.pow(10, pow) / exp) * n * 10));
case ".".code: case ".".code:
if( exp > 0 ) { if( exp > 0 ) {
+38 -1
View File
@@ -113,7 +113,8 @@ class Printer {
add("??NULL??"); add("??NULL??");
return; return;
} }
switch( #if hscriptPos e.e #else e #end ) { switch( Tools.expr(e) ) {
case EIgnore(_):
case EImport(c, n): case EImport(c, n):
add("import " + c); add("import " + c);
if(n != null) if(n != null)
@@ -133,6 +134,8 @@ class Printer {
tabs = tabs.substr(1); tabs = tabs.substr(1);
add("}"); add("}");
case ERedirect(n, cl):
add('typedef $n = $cl');
case EConst(c): case EConst(c):
switch( c ) { switch( c ) {
case CInt(i): add(i); case CInt(i): add(i);
@@ -344,9 +347,43 @@ class Printer {
add(" : "); add(" : ");
addType(t); addType(t);
add(")"); add(")");
case EUsing(name):
add("using ");
add(name);
case EEnum(name, params):
if (params.length == 0) {
add("enum " + name + " {}");
return;
}
add("enum " + name + " {\n");
tabs += "\t";
for (p in params) {
add(tabs);
switch p {
case EConstructor(name, args):
add(name);
add("(");
for (a in args)
addArgument(a);
add(")");
case ESimple(name):
add(name);
}
add(";\n");
}
tabs = tabs.substr(1);
add(tabs);
add("}");
} }
} }
function addArgument(a: Argument) {
if (a.opt)
add("?");
add(a.name);
addType(a.t);
}
public static function toString( e : Expr ):String { public static function toString( e : Expr ):String {
return new Printer().exprToString(e); return new Printer().exprToString(e);
} }
+147 -1
View File
@@ -22,6 +22,8 @@
package hscript; package hscript;
import hscript.Expr; import hscript.Expr;
using StringTools;
class Tools { class Tools {
public static function iter( e : Expr, f : Expr -> Void ):Void { public static function iter( e : Expr, f : Expr -> Void ):Void {
@@ -59,7 +61,7 @@ class Tools {
if( def != null ) f(def); if( def != null ) f(def);
case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e); case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e);
case ECheckType(e,_): f(e); case ECheckType(e,_): f(e);
default:
} }
} }
@@ -91,6 +93,7 @@ class Tools {
case ECheckType(e,t): ECheckType(f(e), t); case ECheckType(e,t): ECheckType(f(e), t);
case EImport(c): EImport(c); case EImport(c): EImport(c);
case EClass(name, el, extend, interfaces): EClass(name, [for( e in el ) f(e)], extend, interfaces); case EClass(name, el, extend, interfaces): EClass(name, [for( e in el ) f(e)], extend, interfaces);
default: #if hscriptPos e.e #else e #end;
} }
return mk(edef, e); return mk(edef, e);
} }
@@ -111,4 +114,147 @@ class Tools {
#end #end
} }
@:access(hscript.Interp)
public static function clearClasses() {
#if CUSTOM_CLASSES
Interp._customClasses.clear();
Interp._customClassAliases.clear();
#end
}
public static function removeInnerClass(name: String): String {
var ll = name.lastIndexOf(".");
if (name.indexOf(".") != ll) { // checks if there are 2 or more dots
// wtf did i make -neo
// this is awesome -crow
return name.substr(0, name.lastIndexOf(".", ll - 1)) + "." + name.substr(ll + 1);
}
// only one dot or no dots (will work since -1 + 1 = 0)
return name.substr(ll + 1);
}
// TODO: maybe use this function for import since also check for innerclasses
public static function getClass(name: String): Dynamic {
var splitClassName = [for (e in name.split(".")) e.trim()];
var realClassName = splitClassName.join(".");
var c: Dynamic = Type.resolveClass(realClassName);
if (c == null) // try importing as enum
try
c = Type.resolveEnum(name);
if (c == null) {
// lastly try removing any inner class from it
// this allows you to import stuff like
// flixel.text.FlxText.FlxTextBorderStyle
// without the script crashing immediately
var className = removeInnerClass(realClassName);
if (className != name) {
c = Type.resolveClass(className);
if (c == null)
c = Type.resolveEnum(className);
}
}
return c;
}
public static function isIterable(v: Dynamic): Bool {
// TODO: test for php and lua, they might have issues with this check
return v != null && v.iterator != null;
}
/**
* DO NOT USE INLINE ON THIS FUNCTION
**/
public static function argCount(func: haxe.Constraints.Function): Int {
#if cpp
return untyped __cpp__("{0}->__ArgCount()", func);
#elseif js
return untyped js.Syntax.code("{0}.length", func);
#else
return -1;
#end
}
public static function getClassDeclFields(classDecl:hscript.Expr.ClassDecl, onlyStatic:Bool = false):Array<FieldDecl> {
var fields = classDecl.fields.filter(function(field:FieldDecl) {
var isStatic = field.access.contains(AStatic);
return onlyStatic ? isStatic : !isStatic;
});
return fields;
}
}
/**
* @see https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Tools.hx#L217
*/
class EnumValue {
public var enumName: String;
public var name: String;
public var index: Int;
public var args: Array<Dynamic>;
public var enumParent:Dynamic;
public function new(enumName: String, name: String, index: Int, enumParent:Dynamic, ?args: Array<Dynamic>) {
this.enumName = enumName;
this.name = name;
this.index = index;
this.enumParent = enumParent;
this.args = args;
}
public function toString(): String {
if (args == null)
return enumName + "." + name;
return enumName + "." + name + "(" + [for (arg in args) arg].join(", ") + ")";
}
public inline function getEnumName(): String
return this.enumName;
public inline function getConstructorArgs(): Array<Dynamic>
return this.args != null ? this.args : [];
public function compare(other: EnumValue): Bool {
if (enumName != other.enumName || name != other.name)
return false;
if (args == null && other.args == null)
return true;
if (args == null || other.args == null)
return false;
if (args.length != other.args.length)
return false;
for (i in 0...args.length) {
// TODO: allow deep comparison, like arrays
if (args[i] != other.args[i])
return false;
}
return true;
}
}
class HScriptEnum implements IHScriptCustomBehaviour {
public var enumValues(default, null) = {};
public function new() {}
public function setEnum(name:String, enumValue:Dynamic) {
Reflect.setField(enumValues, name, enumValue);
}
public function getEnumValue(name:String):Null<Dynamic> {
if(Reflect.hasField(enumValues, name)) return Reflect.field(enumValues, name);
return null;
}
public function hget(name:String):Dynamic {
return getEnumValue(name);
}
public function hset(name:String, val:Dynamic):Dynamic {
return null;
}
} }
+512
View File
@@ -0,0 +1,512 @@
package hscript.customclass;
import hscript.utils.UnsafeReflect;
import haxe.Constraints.Function;
import hscript.Expr;
import hscript.Expr.FieldPropertyAccess;
import hscript.Expr.VarDecl;
import hscript.Expr.FunctionDecl;
import hscript.Expr.FieldDecl;
using Lambda;
using StringTools;
/**
* The Custom Class core.
*
* Provides handlers for custom classes.
*
* Rest in peace, CustomClassHandler.hx
*
* Based on Polymod Hscript class system
* @see https://github.com/larsiusprime/polymod/tree/master/polymod/hscript/_internal
*/
@:access(hscript.customclass.CustomClassDecl)
class CustomClass implements IHScriptCustomAccessBehaviour {
public var interp:Interp;
public var superClass:Dynamic;
public var superConstructor(default, null):Dynamic;
public var superIsCustomClass(get, never):Bool;
private function get_superIsCustomClass():Bool
return (superClass != null && superClass is CustomClass);
public var className(get, never):String;
private function get_className():String {
return __class.toString();
}
private var __class:CustomClassDecl;
private var __cachedSuperFields:Null<Map<String, Dynamic>> = null;
private var __cachedFieldDecls:Map<String, FieldDecl> = [];
private var __cachedFunctionDecls:Map<String, FunctionDecl> = [];
private var __cachedVarDecls:Map<String, VarDecl> = [];
public var accessContext:AccessContext = COuter;
public var __allowSetGet:Bool = false;
public var __allowPrivateAccess:Bool = false;
private var isInline(default, null):Bool = false;
private var ogVariables(default, null):Map<String, Dynamic>;
private var initializing(default, null):Bool = false; // Allows final variables to be initialized
public function new(__class:CustomClassDecl, args:Array<Dynamic>, ?extendFieldDecl:Map<String, Dynamic>, ?ogInterp:Interp, ?callNew:Bool = true) {
this.__class = __class;
this.interp = new Interp(this);
if (ogInterp != null) {
interp.importFailedCallback = ogInterp.importFailedCallback;
interp.errorHandler = ogInterp.errorHandler;
interp.allowStaticVariables = ogInterp.allowStaticVariables;
interp.staticVariables = ogInterp.staticVariables;
// todo: make it so you can use variables from the same scope as where the class was defined
if(__class.isInline != null && __class.isInline) {
isInline = __class.isInline;
ogVariables = ogInterp.variables;
interp.allowPublicVariables = __class.staticInterp.allowPublicVariables;
interp.publicVariables = __class.staticInterp.publicVariables;
}
}
buildImports();
buildUsings();
if (extendFieldDecl != null)
__cachedSuperFields = extendFieldDecl;
buildClass();
if (hasFunction('new') && callNew) {
buildSuperConstructor();
initializing = true;
callFunction('new', args);
initializing = false;
if (this.superClass == null && this.__class.classDecl.extend != null)
this.interp.error(ECustom("super() not called"));
} else if (__class.classDecl.extend != null) {
createSuperClass(args);
}
}
function buildClass() {
if (__cachedSuperFields == null)
__cachedSuperFields = [];
for (f in __class.classDecl.fields) {
if (f.access.contains(AStatic))
continue; // Skip static field. It's handled by CustomClassDecl.hx
__cachedFieldDecls.set(f.name, f);
switch (f.kind) {
case KFunction(fn):
__cachedFunctionDecls.set(f.name, fn);
#if hscriptPos
var fexpr:Expr = {
e: ExprDef.EFunction(fn.args, fn.body, f.name, fn.ret, false, false),
pmin: fn.body.pmin,
pmax: fn.body.pmax,
line: fn.body.line,
origin: fn.body.origin
};
#else
var fexpr = Expr.EFunction(fn.args, fn.body, f.name, fn.ret, false, false);
#end
var func:Function = this.interp.expr(fexpr);
this.interp.variables.set(f.name, func);
case KVar(v):
if(v.get != ADefault || v.set != ADefault)
__allowSetGet = true;
__cachedVarDecls.set(f.name, v);
if (v.expr != null) {
var varValue = this.interp.expr(v.expr);
this.interp.variables.set(f.name, varValue);
}
}
}
if (!__cachedSuperFields.empty()) {
for (f => v in __cachedSuperFields) {
this.hset(f, v);
}
__cachedSuperFields.clear();
}
}
function buildSuperConstructor() {
superConstructor = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
createSuperClass(args);
});
}
private function createSuperClass(?args:Array<Dynamic>) {
if (args == null)
args = [];
if(__class.superClassDecl is CustomClassDecl)
superClass = new CustomClass(__class.superClassDecl, args, __cachedSuperFields, this.interp);
else {
if (__cachedSuperFields != null) {
Reflect.setField(__class.superClassDecl, "__cachedFields", __cachedSuperFields); // Static field
}
var disallowCopy = Type.getInstanceFields(__class.superClassDecl);
superClass = Type.createInstance(__class.superClassDecl, args);
superClass.__customClass = this;
superClass.__real_fields = disallowCopy;
if(isInline) {
for(s => v in ogVariables)
if(!disallowCopy.contains(s))
interp.variables.set(s, v);
}
}
}
function buildImports() {
var i:Int = 0;
for (_import in __class.imports) {
var importedClass = _import.fullPath;
var importAlias = _import.as;
if (Interp.customClassExist(importedClass) && this.interp.importFailedCallback != null) {
this.interp.importFailedCallback(importedClass.split("."), importAlias);
continue;
}
#if hscriptPos
var e:Expr = {
e: ExprDef.EImport(importedClass, importAlias),
pmin: 0,
pmax: 0,
origin: this.className,
line: i
};
#else
var e = Expr.EImport(importedClass, importAlias);
#end
this.interp.expr(e);
i++;
}
}
inline function buildUsings() {
for (us in __class.usings) {
@:privateAccess this.interp.useUsing(us);
}
}
public function callFunction(name:String, ?args:Array<Dynamic>):Dynamic {
var r:Dynamic = null;
if (hasField(name)) {
var fn = getFunction(name);
try {
if (fn == null)
interp.error(ECustom('${name} is not a function'));
r = UnsafeReflect.callMethodUnsafe(null, fn, args == null ? [] : args);
} catch (e:hscript.Expr.Error) {
// A script error occurred while executing the custom class function.
// Purge the function from the cache so it is not called again.
purgeFunction(name);
}
}
else {
var fixedArgs = [];
for (a in args) {
if ((a is CustomClass)) {
var customClass:CustomClass = cast(a, CustomClass);
fixedArgs.push(customClass.superClass != null ? customClass.getSuperclass() : customClass);
} else {
fixedArgs.push(a);
}
}
var superFn:Function = null;
if(superClass is CustomClass) {
superFn = cast(superClass, CustomClass).hget(name);
}
else {
var fixedName = '_HX_SUPER__${name}';
superFn = Reflect.field(superClass, fixedName);
}
if (superFn == null || !Reflect.isFunction(superFn)) {
this.interp.error(ECustom('Error while calling function super.${name}(): EInvalidAccess'
+ '\n'
+ 'InvalidAccess error: Super function "${name}" does not exist! Define it or call the correct superclass function.'));
}
r = Reflect.callMethod((superClass is CustomClass) ? null : superClass, superFn, fixedArgs);
}
return r;
}
// Field check
private function hasField(name:String):Bool {
return __cachedFieldDecls.exists(name);
}
private function getField(name:String):FieldDecl {
return __cachedFieldDecls != null ? __cachedFieldDecls.get(name) : null;
}
private function hasVar(name:String):Bool {
return __cachedVarDecls.exists(name);
}
private function getVar(name:String):VarDecl {
return __cachedVarDecls.get(name);
}
private function hasFunction(name:String):Bool {
return __cachedFunctionDecls.exists(name);
}
private function getFunction(name:String):Function {
var fn = this.interp.variables.get(name);
return Reflect.isFunction(fn) ? fn : null;
}
// SuperClass field check
private function cacheSuperField(name:String, value:Dynamic) {
if (__cachedSuperFields != null) {
__cachedSuperFields.set(name, value);
}
}
var __superClassFieldList:Array<String> = null;
public function superHasField(name:String):Bool {
if (superClass == null)
return false;
// Reflect.hasField(this, name) is REALLY expensive so we use a cache.
if (__superClassFieldList == null) {
__superClassFieldList = [];
if(superClass is CustomClass) {
var cls:Null<Dynamic> = superClass;
while(cls != null && cls is CustomClass) {
var currentClass = cast(cls, CustomClass);
var fields = [for(f in currentClass.__cachedFieldDecls.keys()) f];
__superClassFieldList.concat(fields);
var next = currentClass.superClass;
if(next == null)
break;
cls = next;
}
// The last fetched class is a real class
if(!(cls is CustomClass))
__superClassFieldList.concat(Reflect.fields(cls).concat(Type.getInstanceFields(Type.getClass(cls))));
}
else {
var realFields = Reflect.fields(superClass).concat(Type.getInstanceFields(Type.getClass(superClass)));
__superClassFieldList.concat(realFields);
}
}
return __superClassFieldList.indexOf(name) != -1;
}
/**
* Remove a function from the cache.
* This is useful when a function is broken and needs to be skipped.
* @param name The name of the function to remove from the cache.
*/
private function purgeFunction(name:String):Void {
if (__cachedFunctionDecls != null) {
__cachedFunctionDecls.remove(name);
}
}
// Access fields
public function hget(name:String):Dynamic {
switch (name) {
case "superClass": return this.superClass;
case "createSuperClass": return this.createSuperClass;
case "hasFunction": return this.hasFunction;
case "callFunction": return this.callFunction;
default:
if (hasFunction(name)) {
var fn:Function = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
return this.callFunction(name, args);
});
return fn;
}
if (hasVar(name)) {
var v = getVar(name);
var getter = v.get;
var isInner = switch (accessContext) {
case CInner(cl): return cl == this.className;
default: false;
}
var value:Dynamic = null;
if(getter == ANever || getter == ANull && (!isInner || !__allowPrivateAccess))
throw 'field $name cannot be accessed for reading';
if (__allowSetGet && getter == AGet) {
if (hasFunction('get_$name'))
value = __callGetter(name);
else
throw 'Method get_$name required by property $name is missing';
}
else if (this.interp.variables.exists(name))
value = this.interp.variables.get(name);
else {
if (v.expr != null) {
value = this.interp.expr(v.expr);
this.interp.variables.set(name, value);
}
}
return value;
}
if (this.superClass != null) {
if (superHasField(name)) {
// Anonymous structure as a super class???
if (Type.getClass(this.superClass) == null) {
// Anonymous structure
if (Reflect.hasField(this.superClass, name))
return Reflect.field(this.superClass, name);
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '[ANONYMOUS STRUCTURE]'";
}
if (this.superClass is CustomClass) {
var superCustomClass:CustomClass = cast(this.superClass, CustomClass);
superCustomClass.__allowSetGet = this.__allowSetGet;
superCustomClass.__allowPrivateAccess = this.__allowPrivateAccess;
superCustomClass.accessContext = switch (accessContext) {
case CInner(_): CInner(superCustomClass.className);
default: COuter;
}
return superCustomClass.hget(name);
}
// Real Class
if (__allowSetGet)
Reflect.getProperty(this.superClass, name);
else
Reflect.field(this.superClass, name);
}
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'";
}
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "'";
}
return null;
}
public function hset(name:String, val:Dynamic):Dynamic {
switch (name) {
default:
if (hasVar(name)) {
var v = getVar(name);
var setter = v.set;
var isInner = switch (accessContext) {
case CInner(cl): return cl == this.className;
default: false;
}
if (setter == ANever || setter == ANull && (!isInner || !__allowPrivateAccess) || (v.isFinal && !initializing))
throw 'field $name cannot be accessed for writing';
if (__allowSetGet && setter == ASet) {
if (hasFunction('set_$name'))
return __callSetter(name, val);
else
throw 'Method set_$name required by property $name is missing';
}
this.interp.variables.set(name, val);
}
else if (this.superClass != null) {
if (superHasField(name)) {
if (Type.getClass(this.superClass) == null) {
// Anonymous structure
if (Reflect.hasField(this.superClass, name)) {
Reflect.setField(this.superClass, name, val);
return val;
}
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '[ANONYMOUS STRUCTURE]'";
}
if (this.superClass is CustomClass) {
var superCustomClass:CustomClass = cast(this.superClass, CustomClass);
superCustomClass.__allowSetGet = this.__allowSetGet;
superCustomClass.__allowPrivateAccess = this.__allowPrivateAccess;
superCustomClass.accessContext = switch (accessContext) {
case CInner(_): CInner(superCustomClass.className);
default: COuter;
}
return superCustomClass.hset(name, val);
}
// Real Class
if (__allowSetGet)
Reflect.setProperty(this.superClass, name, val);
else
Reflect.setField(this.superClass, name, val);
}
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'";
}
else
throw "field '" + name + "' does not exist in custom class '" + this.className + "'";
}
return val;
}
public function __callGetter(name:String):Dynamic {
__allowSetGet = false;
var r = callFunction('get_$name');
__allowSetGet = true;
return r;
}
public function __callSetter(name:String, val:Dynamic):Dynamic {
__allowSetGet = false;
var r = callFunction('set_$name', [val]);
__allowSetGet = true;
return r;
}
/**
* Returns the real superClass if the Custom Class
* extends another Custom Class, and so on until
* it reaches a real class, otherwise it will
* return the last fetched Custom Class
* @return Null<Dynamic>
*/
public function getSuperclass():Null<Dynamic> {
var cls:Null<Dynamic> = this.superClass;
// Check if the superClass is another custom class,
// so it will find for a real class, otherwise
// returns the last super CustomClass parent.
while(cls != null && cls is CustomClass) {
var next = cast(cls, CustomClass).superClass;
if(next == null)
break; // Return the Custom Class itself
cls = next;
}
return cls;
}
}
+290
View File
@@ -0,0 +1,290 @@
package hscript.customclass;
import hscript.Expr.AccessContext;
import hscript.proxy.ProxyType;
import hscript.customclass.utils.FunctionUtils;
import haxe.Constraints.Function;
import hscript.Expr.FieldDecl;
import hscript.Expr.VarDecl;
import hscript.Expr.FunctionDecl;
/**
* The ACTUAL "StaticHandler"
* @author Jamextreme140
*/
@:access(hscript.Interp)
@:structInit
class CustomClassDecl implements IHScriptCustomAccessBehaviour {
public var classDecl:Expr.ClassDecl; //This holds the class instantiation info
public var imports:Map<String, CustomClassImport>;
public var usings:Array<String>;
public var pkg:Null<Array<String>> = null;
public var ogInterp:Null<Interp> = null;
public var isInline:Null<Bool> = null;
public var staticInterp:Interp = new Interp();
public var superClassDecl(default, null):Dynamic = null; //This holds the super class reference.
var _cachedStaticFields:Map<String, FieldDecl> = [];
var _cachedStaticFunctions:Map<String, FunctionDecl> = [];
var _cachedStaticVariables:Map<String, VarDecl> = [];
public var __allowSetGet:Bool = true;
public var __allowPrivateAccess:Bool = false;
public var accessContext:AccessContext = COuter;
public final name:String;
public function new(classDecl:Expr.ClassDecl, imports:Map<String, CustomClassImport>, usings:Array<String>, ?pkg:Array<String>, ?ogInterp:Interp, ?isInline:Bool) {
this.classDecl = classDecl;
this.imports = imports;
this.usings = usings;
this.pkg = pkg;
this.ogInterp = ogInterp;
this.isInline = isInline;
// Cache the classname
var pkg = pkg != null ? '${pkg.join(".")}.' : "";
var className = classDecl.name;
this.name = '$pkg$className';
if(ogInterp != null) {
staticInterp.importFailedCallback = ogInterp.importFailedCallback;
staticInterp.errorHandler = ogInterp.errorHandler;
staticInterp.allowStaticVariables = ogInterp.allowStaticVariables;
staticInterp.staticVariables = ogInterp.staticVariables;
if(isInline != null && isInline) {
// uses public variables from the same scope as where the class was defined
staticInterp.variables = ogInterp.variables;
staticInterp.allowPublicVariables = ogInterp.allowPublicVariables;
staticInterp.publicVariables = ogInterp.publicVariables;
}
}
cacheImports();
processUsings();
cacheFields();
if(classDecl.extend != null)
buildSuperClass();
}
function cacheImports() {
// This will make imported classes available for Static Functions
var i:Int = 0;
for(s => imp in imports) {
var importedClass = imp.fullPath;
var importAlias = imp.as;
if(this.staticInterp.variables.exists(imp.name)) continue; // class is already imported
if (Interp.customClassExist(importedClass) && this.staticInterp.importFailedCallback != null) {
this.staticInterp.importFailedCallback(importedClass.split("."), importAlias);
continue;
}
#if hscriptPos
var e:Expr = {
e: ExprDef.EImport(importedClass, importAlias),
pmin: 0,
pmax: 0,
origin: this.classDecl.name,
line: i
};
i++;
#else
var e = Expr.EImport(importedClass, importAlias);
#end
this.staticInterp.expr(e);
}
}
function cacheFields() {
for (f in classDecl.fields) {
if (f.access.contains(AStatic)) {
_cachedStaticFields.set(f.name, f);
switch (f.kind) {
case KFunction(fn):
_cachedStaticFunctions.set(f.name, fn);
#if hscriptPos
var fexpr:Expr = {
e: ExprDef.EFunction(fn.args, fn.body, f.name, fn.ret, false, false),
pmin: fn.body.pmin,
pmax: fn.body.pmax,
line: fn.body.line,
origin: fn.body.origin
};
#else
var fexpr = Expr.EFunction(fn.args, fn.body, f.name, fn.ret, false, false);
#end
var func:Function = this.staticInterp.expr(fexpr);
this.staticInterp.variables.set(f.name, func);
case KVar(v):
if(v.get != ADefault || v.set != ADefault)
__allowSetGet = true;
_cachedStaticVariables.set(f.name, v);
if (v.expr != null) {
var varValue = this.staticInterp.expr(v.expr);
this.staticInterp.variables.set(f.name, varValue);
}
}
}
}
}
function processUsings() {
for(us in usings) {
this.staticInterp.useUsing(us);
}
}
function buildSuperClass() {
var extendString = new Printer().typeToString(classDecl.extend);
if (this.pkg != null && extendString.indexOf(".") == -1) {
extendString = this.pkg.join(".") + "." + extendString;
}
var cls:Dynamic = Type.resolveClass('${extendString}_HSX');
if(cls == null)
cls = ProxyType.resolveClass(extendString);
superClassDecl = cls;
if(superClassDecl == null)
staticInterp.error(ECustom("could not resolve super class: " + extendString));
}
public function callFunction(name:String, ?args:Array<Dynamic>):Dynamic {
var func:Function = getFunction(name);
return FunctionUtils.callStaticFunction(name, this, staticInterp, func, args != null ? args : []);
}
public function hasField(name:String):Bool {
return _cachedStaticFields.exists(name);
}
private function hasFunction(name:String) {
return _cachedStaticFunctions.exists(name);
}
private function getFunction(name:String):Function {
var fn = this.staticInterp.variables.get(name);
return Reflect.isFunction(fn) ? fn : null;
}
private function hasVar(name:String):Bool {
return _cachedStaticVariables.exists(name);
}
private function getVar(name:String):VarDecl {
return _cachedStaticVariables.get(name);
}
/**
* Remove a function from the cache.
* This is useful when a function is broken and needs to be skipped.
* @param name The name of the function to remove from the cache.
*/
private function purgeFunction(name:String):Void {
if (_cachedStaticFunctions != null) {
_cachedStaticFunctions.remove(name);
}
}
public function hget(name:String):Dynamic {
if(hasVar(name)) {
var v = getVar(name);
var getter = v.get;
var isInner = switch (accessContext) {
case CInner(cl): return cl == this.toString();
default: false;
}
var r:Dynamic = null;
if (getter == ANever || getter == ANull && (!isInner || !__allowPrivateAccess))
throw 'field $name cannot be accessed for reading';
if(__allowSetGet && getter == AGet){
if(hasFunction('get_$name'))
r = __callGetter(name);
else
throw 'Method get_$name required by property $name is missing';
}
else if (this.staticInterp.variables.exists(name))
r = this.staticInterp.variables.get(name);
else {
if(v.expr != null) {
r = this.staticInterp.expr(v.expr);
this.staticInterp.variables.set(name, r);
}
}
return r;
}
if(hasFunction(name)) {
// TODO: optimize this
var fn:Function = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
return this.callFunction(name, args);
});
return fn;
}
throw "static field '" + name + "' does not exist in custom class '" + this.classDecl.name + "'";
}
public function hset(name:String, val:Dynamic):Dynamic {
if (hasVar(name)) {
var v = getVar(name);
var setter = v.set;
var isInner = switch (accessContext) {
case CInner(cl): return cl == this.toString();
default: false;
}
if (setter == ANever || setter == ANull && (!isInner || !__allowPrivateAccess) || v.isFinal)
throw 'field $name cannot be accessed for writing';
if (__allowSetGet && setter == ASet) {
if (hasFunction('set_$name'))
return __callSetter(name, val);
else
throw 'Method set_$name required by property $name is missing';
}
else {
this.staticInterp.variables.set(name, val);
return val;
}
}
throw "static field '" + name + "' does not exist in custom class '" + this.classDecl.name + "'";
}
public function __callGetter(name:String):Dynamic {
__allowSetGet = false;
var r = callFunction('get_${name}');
__allowSetGet = true;
return r;
}
public function __callSetter(name:String, val:Dynamic):Dynamic {
__allowSetGet = false;
var r = callFunction('set_${name}', [val]);
__allowSetGet = true;
return r;
}
public function toString():String {
return name;
}
}
typedef CustomClassImport = {
var ?name:String;
var ?pkg:Array<String>;
var ?fullPath:String; // pkg.pkg.pkg.name
var ?as:Null<String>; // import pkg.Name as OtherName
}
@@ -0,0 +1,25 @@
package hscript.customclass.utils;
import hscript.utils.UnsafeReflect;
import haxe.Constraints.Function;
@:access(hscript.customclass.CustomClassDecl)
class FunctionUtils {
public static inline function callStaticFunction(name:String, classDecl:CustomClassDecl, interp:Interp, fn:Function, args:Array<Dynamic> = null) {
var r:Dynamic = null;
try {
if (fn == null)
interp.error(ECustom('${name} is not a function'));
r = UnsafeReflect.callMethodUnsafe(null, fn, args);
} catch (e:hscript.Expr.Error) {
// A script error occurred while executing the custom class function.
// Purge the function from the cache so it is not called again.
classDecl.purgeFunction(name);
interp.error(#if hscriptPos e.e #else e #end);
}
return r;
}
}
@@ -10,12 +10,12 @@ import haxe.macro.Compiler;
using StringTools; using StringTools;
class UsingHandler { class AbstractHandler {
public static function init() { public static function init() {
#if !display #if !display
if(Context.defined("display")) return; if(Context.defined("display")) return;
for(apply in Config.ALLOWED_ABSTRACT_AND_ENUM) { for(apply in Config.ALLOWED_ABSTRACT_AND_ENUM) {
Compiler.addGlobalMetadata(apply, '@:build(hscript.macros.UsingHandler.build())'); Compiler.addGlobalMetadata(apply, '@:build(hscript.macros.AbstractHandler.build())');
} }
#end #end
} }
+109 -31
View File
@@ -211,6 +211,15 @@ class ClassExtendMacro {
continue; continue;
if (f.name == "new") { if (f.name == "new") {
hasNew = true; hasNew = true;
switch (f.kind) {
case FFun(fn):
var constructor:Field = buildConstructor(fn.args);
shadowClass.fields.push(constructor);
definedFields.push(f.name);
default:
continue;
}
continue; continue;
} }
if (f.name.startsWith(FUNC_PREFIX)) if (f.name.startsWith(FUNC_PREFIX))
@@ -245,7 +254,7 @@ class ClassExtendMacro {
if (returns) { if (returns) {
overrideExpr = macro { overrideExpr = macro {
var name:String = $v{name}; var name:String = $v{name};
/*
if (__custom__variables != null) { if (__custom__variables != null) {
if(__custom__variables.exists(name)) { if(__custom__variables.exists(name)) {
var v:Dynamic = null; var v:Dynamic = null;
@@ -254,12 +263,17 @@ class ClassExtendMacro {
} }
} }
} }
*/
if (__customClass != null && @:privateAccess __customClass.hasFunction(name)) {
return __customClass.callFunction(name, [$a{arguments}]);
}
return super.$name($a{arguments}); return super.$name($a{arguments});
}; };
} else { } else {
overrideExpr = macro { overrideExpr = macro {
var name:String = $v{name}; var name:String = $v{name};
/*
if (__custom__variables != null) { if (__custom__variables != null) {
if(__custom__variables.exists(name)) { if(__custom__variables.exists(name)) {
var v:Dynamic = null; var v:Dynamic = null;
@@ -269,6 +283,12 @@ class ClassExtendMacro {
} }
} }
} }
*/
if (__customClass != null && @:privateAccess __customClass.hasFunction(name)) {
__customClass.callFunction(name, [$a{arguments}]);
return;
}
super.$name($a{arguments}); super.$name($a{arguments});
}; };
} }
@@ -331,7 +351,7 @@ class ClassExtendMacro {
pack: cl.pack.copy(), pack: cl.pack.copy(),
name: cl.name name: cl.name
}, [ }, [
{name: "IHScriptCustomAccessBehaviour", pack: ["hscript"]}, {name: "IHScriptCustomBehaviour", pack: ["hscript"]},
{name: "IHScriptCustomClassBehaviour", pack: ["hscript"]} {name: "IHScriptCustomClassBehaviour", pack: ["hscript"]}
], false, true, false); ], false, true, false);
shadowClass.name = '${cl.name}$CLASS_SUFFIX'; shadowClass.name = '${cl.name}$CLASS_SUFFIX';
@@ -339,15 +359,29 @@ class ClassExtendMacro {
Utils.setupMetas(shadowClass, imports); Utils.setupMetas(shadowClass, imports);
Utils.processImport(imports, "hscript.utils.UnsafeReflect", "UnsafeReflect"); Utils.processImport(imports, "hscript.utils.UnsafeReflect", "UnsafeReflect");
// Adding hscript getters and setters shadowClass.fields.push({
name: "__cachedFields",
pos: Context.currentPos(),
kind: FVar(macro: Map<String, Dynamic>),
access: [APublic, AStatic]
});
// Adding hscript getters and setters
/*
shadowClass.fields.push({ shadowClass.fields.push({
name: "__interp", name: "__interp",
pos: Context.currentPos(), pos: Context.currentPos(),
kind: FVar(macro: hscript.Interp), kind: FVar(macro: hscript.Interp),
access: [APublic] access: [APublic]
}); });
*/
shadowClass.fields.push({
name: "__customClass",
pos: Context.currentPos(),
kind: FVar(macro: hscript.customclass.CustomClass),
access: [APublic]
});
/*
shadowClass.fields.push({ shadowClass.fields.push({
name: "__custom__variables", name: "__custom__variables",
pos: Context.currentPos(), pos: Context.currentPos(),
@@ -361,21 +395,23 @@ class ClassExtendMacro {
kind: FVar(macro: Bool, macro true), kind: FVar(macro: Bool, macro true),
access: [APublic] access: [APublic]
}); });
*/
shadowClass.fields.push({ shadowClass.fields.push({
name: "__real_fields", name: "__real_fields",
pos: Context.currentPos(), pos: Context.currentPos(),
kind: FVar(macro: Array<String>), kind: FVar(macro: Array<String>),
access: [APublic] access: [APublic]
}); });
/*
shadowClass.fields.push({ shadowClass.fields.push({
name: "__class__fields", name: "__class__fields",
pos: Context.currentPos(), pos: Context.currentPos(),
kind: FVar(macro: Array<String>), kind: FVar(macro: Array<String>),
access: [APublic] access: [APublic]
}); });
*/
// Unneccessary since the get/set call is already handled.
/*
shadowClass.fields.push({ shadowClass.fields.push({
name: "__callGetter", name: "__callGetter",
pos: Context.currentPos(), pos: Context.currentPos(),
@@ -429,7 +465,7 @@ class ClassExtendMacro {
}), }),
access: [APublic] access: [APublic]
}); });
*/
// Todo: make it possible to override // Todo: make it possible to override
if(cl.name == "FunkinShader" || cl.name == "CustomShader" || cl.name == "MultiThreadedScript") { if(cl.name == "FunkinShader" || cl.name == "CustomShader" || cl.name == "MultiThreadedScript") {
Context.defineModule(cl.module, [shadowClass], imports); Context.defineModule(cl.module, [shadowClass], imports);
@@ -472,31 +508,43 @@ class ClassExtendMacro {
var hgetField = if(hasHgetInSuper) { var hgetField = if(hasHgetInSuper) {
macro { macro {
if(__allowSetGet && __custom__variables.exists("get_" + name)) /*
return __callGetter(name); if(__custom__variables != null) {
if (__custom__variables.exists(name)) if(__allowSetGet && __custom__variables.exists("get_" + name))
return __custom__variables.get(name); return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
}
*/
return super.hget(name); return super.hget(name);
} }
} else { } else {
macro { macro {
if(__allowSetGet && __custom__variables.exists("get_" + name)) /*
return __callGetter(name); if(__custom__variables != null) {
if (__custom__variables.exists(name)) if(__allowSetGet && __custom__variables.exists("get_" + name))
return __custom__variables.get(name); return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
}
*/
return UnsafeReflect.getProperty(this, name); return UnsafeReflect.getProperty(this, name);
} }
} }
var hsetField = if(hasHsetInSuper) { var hsetField = if(hasHsetInSuper) {
macro { macro {
if(__allowSetGet && __custom__variables.exists("set_" + name)) /*
return __callSetter(name, val); if(__custom__variables != null) {
if (__custom__variables.exists(name)) { if(__allowSetGet && __custom__variables.exists("set_" + name))
__custom__variables.set(name, val); return __callSetter(name, val);
return val; if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
}
} }
if(__real_fields.contains(name)) { */
if(__real_fields != null && __real_fields.contains(name)) {
UnsafeReflect.setProperty(this, name, val); UnsafeReflect.setProperty(this, name, val);
return UnsafeReflect.field(this, name); return UnsafeReflect.field(this, name);
} }
@@ -504,17 +552,21 @@ class ClassExtendMacro {
} }
} else { } else {
macro { macro {
if(__allowSetGet && __custom__variables.exists("set_" + name)) /*
return __callSetter(name, val); if(__custom__variables != null) {
if (__custom__variables.exists(name)) { if(__allowSetGet && __custom__variables.exists("set_" + name))
__custom__variables.set(name, val); return __callSetter(name, val);
return val; if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
}
} }
if(__real_fields.contains(name)) { */
if(__real_fields != null && __real_fields.contains(name)) {
UnsafeReflect.setProperty(this, name, val); UnsafeReflect.setProperty(this, name, val);
return UnsafeReflect.field(this, name); return UnsafeReflect.field(this, name);
} }
__custom__variables.set(name, val); //if(__custom__variables != null) __custom__variables.set(name, val);
return val; return val;
} }
} }
@@ -566,7 +618,7 @@ class ClassExtendMacro {
] ]
}) })
}); });
/*var p = new Printer(); /*var p = new Printer();
var aa = p.printTypeDefinition(shadowClass); var aa = p.printTypeDefinition(shadowClass);
if(aa.length < 5024) if(aa.length < 5024)
@@ -579,6 +631,32 @@ class ClassExtendMacro {
return fields; return fields;
} }
static function buildConstructor(constArgs:Array<FunctionArg>):Field {
var superCallArgs:Array<Expr> = [for (arg in constArgs) macro $i{arg.name}];
return {
name: 'new',
access: [APublic],
pos: Context.currentPos(),
kind: FFun({
args: constArgs,
expr: macro
{
// Call the super constructor with appropriate args
super($a{superCallArgs});
if(__cachedFields != null) {
for(k => v in __cachedFields) {
Reflect.setProperty(this, k, v);
trace(k);
}
__cachedFields = null;
}
},
}),
};
}
} }
#else #else
class ClassExtendMacro { class ClassExtendMacro {
+173
View File
@@ -0,0 +1,173 @@
package hscript.macros;
#if macro
import Type.ValueType;
import haxe.macro.Compiler;
import haxe.macro.Context;
import haxe.macro.Expr;
using Lambda;
using StringTools;
using haxe.macro.Tools;
/**
* Macro used for the `using Class;` keyword
*
* you can make classes be able to be used specifing the classes/packages on Config.hx!
* or implementing the interface `hscript.utils.UsingClass`
* ```haxe
* public static final ALLOWED_USING = ["my.pack.VeryNiceTools"];
* ```
*
* Usage:
*
* ```haxe
* package my.pack;
* // @:usableEntry() // optional
* // @:usableEntry(forceAny) // optional // forces the class to be called with any type
* // @:usableEntry(onlyBasic) // optional // only basic types will be allowed
* // @:usableEntry(onlyBasic, forceAny) // optional // only basic types will be allowed, and the class will be called with any type
* class VeryNiceTools implements hscript.utils.UsingClass {}
* ```
*
* @author NeeEoo
* @see https://github.com/pisayesiwsi/hscript-iris/blob/master/crowplexus/iris/macro/UsingMacro.macro.hx
**/
class UsingMacro {
public static inline final USING_PREFIX = "_HX_USING__";
public static var unallowedMetas:Array<String> = [":noUsing", ":noUse"];
public static function init() {
#if !display
if(Context.defined("display")) return;
for(apply in Config.ALLOWED_USING) {
Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.UsingMacro.build())");
}
#end
}
public static function build() {
var cls: haxe.macro.Type.ClassType = Context.getLocalClass().get();
var fields = Context.getBuildFields();
var key = cls.module;
var fkey = cls.module + "." + cls.name;
var packName = (cls.pack.length > 0 ? cls.pack.join(".") + "." : "") + cls.name;
if(cls.meta.get().find(function(m) return m.name == ':usingProcessed') != null) return fields;
if (Config.DISALLOW_USING.contains(key) || Config.DISALLOW_USING.contains(fkey) || Config.DISALLOW_USING.contains(packName))
return fields;
var entryField = cls.meta.get().find(function(m) return m.name == ':usableEntry');
var hasParams = entryField != null && entryField.params != null;
var forceAny = false;
var onlyBasic = false;
if (hasParams) {
for (i in 0...entryField.params.length) {
if (entryField.params[i].expr.match(EConst(CIdent("onlyBasic"))))
onlyBasic = true;
if (entryField.params[i].expr.match(EConst(CIdent("forceAny"))))
forceAny = true;
}
}
var data: Array<Array<String>> = [];
for (field in fields) {
// functions marked with @:noUsing won't be able to be used by variables
// also if you want it to be usable in source, but not in the script, use @:noUse
for(m in field.meta)
if(unallowedMetas.contains(m.name))
continue;
// It's called "static extensions" for some reason
if(!field.access.contains(AStatic))
continue;
switch (field.kind) {
default:
case FFun(f):
if (f.args.length == 0)
continue;
var arg = f.args[0];
if (arg.type == null)
continue;
var type = arg.type;
var valueType: String = switch (type) {
case TPath({name: "Int", pack: []}):
"TInt";
case TPath({name: "Float", pack: []}):
"TFloat";
case TPath({name: "Single", pack: []}):
"TFloat";
case TPath({name: "String", pack: []}):
"TClass(String)";
case TPath({name: "Bool", pack: []}):
"TBool";
case TPath({name: "Array", pack: []}):
"TClass(Array)";
case TPath({name: "Map", pack: []}):
"TClass(haxe.Constraints.IMap)";
case TPath({name: "Dynamic", pack: []}):
null;
case TPath({name: "Class", pack: []}):
"TClass(null)"; // this feels wrong
case TPath({name: "Enum", pack: []}):
"TEnum(null)";
// case TPath({name: "Void", pack: []}): "ValueType.TVoid";
default:
null; // null acts as a wildcard
}
// MIGHT CRASH COMPILATION? YEAH, IT CRASH ON TYPE PARAMETERS
if (!onlyBasic && valueType == null) {
var rtype:haxe.macro.Type = null;
try {rtype = type.toType();} catch(e) {}
if(rtype != null) {
switch (rtype) {
case TInst(t, []):
valueType = "TClass(" + t.toString() + ")";
default:
}
}
}
if (forceAny) {
valueType = null;
}
data.push([field.name, valueType]);
}
}
if(data.length == 0)
return fields;
fields.push({
name: '$USING_PREFIX${packName.replace(".", "_")}',
access: [APrivate, AStatic],
kind: FVar(macro : Map<String, Type.ValueType>, {
var arr: Array<Expr> = [];
for (i in data)
if (i[1] != null)
arr.push(macro $v{i[0]} => ${Context.parse("Type.ValueType." + i[1], Context.currentPos())});
else
arr.push(macro $v{i[0]} => null);
macro $a{arr};
}),
pos: cls.pos,
});
//var printer = new haxe.macro.Printer();
//trace(printer.printField(fields[fields.length - 1]));
cls.meta.add(':usingProcessed', [], cls.pos);
return fields;
}
}
#end
+364
View File
@@ -0,0 +1,364 @@
package hscript.proxy;
import hscript.customclass.CustomClass;
import hscript.customclass.CustomClassDecl;
import hscript.Tools.HScriptEnum;
import hscript.Tools.EnumValue as HScriptEnumValue;
abstract OneOfTwo<T1, T2>(Dynamic) from T1 from T2 to T1 to T2 {}
@:allow(hscript.customclass.CustomClass)
@:allow(hscript.customclass.CustomClassDecl)
class ProxyType {
/**
Returns the class of `o`, if `o` is a class instance.
If `o` is null or of a different type, null is returned.
In general, type parameter information cannot be obtained at runtime.
**/
inline static function getClass<T>(o:OneOfTwo<T, CustomClass>):Null<Dynamic> {
if(o is CustomClass)
@:privateAccess return cast(o, CustomClass).__class;
return Type.getClass(cast o);
}
/**
Returns the enum of enum instance `o`.
An enum instance is the result of using an enum constructor. Given an
`enum Color { Red; }`, `getEnum(Red)` returns `Enum<Color>`.
If `o` is null, null is returned.
In general, type parameter information cannot be obtained at runtime.
**/
inline static function getEnum(o:OneOfTwo<EnumValue, HScriptEnumValue>):Dynamic {
// TODO: SUPPORT HSCRIPT ENUMS
var isScripted:Bool = Std.isOfType(o, HScriptEnumValue);
if(isScripted) {
return cast(o, HScriptEnumValue).enumParent;
}
return Type.getEnum(cast o);
}
/**
Returns the super-class of class `c`.
If `c` has no super class, null is returned.
If `c` is null, the result is unspecified.
In general, type parameter information cannot be obtained at runtime.
**/
inline static function getSuperClass(c:OneOfTwo<Class<Dynamic>, CustomClassDecl>):Dynamic {
if(c is CustomClassDecl) {
return cast(c, CustomClassDecl).superClassDecl;
}
return Type.getSuperClass(cast c);
}
/**
Returns the name of class `c`, including its path.
If `c` is inside a package, the package structure is returned dot-
separated, with another dot separating the class name:
`pack1.pack2.(...).packN.ClassName`
If `c` is a sub-type of a Haxe module, that module is not part of the
package structure.
If `c` has no package, the class name is returned.
If `c` is null, the result is unspecified.
The class name does not include any type parameters.
**/
inline static function getClassName(c:OneOfTwo<Class<Dynamic>, CustomClassDecl>):String {
if(c is CustomClassDecl) {
var cls:CustomClassDecl = cast c;
var name = cls.classDecl.name;
var pkg = cls.pkg != null ? '${cls.pkg.join(".")}.' : "";
return '$pkg$name';
}
return Type.getClassName(cast c);
}
/**
Returns the name of enum `e`, including its path.
If `e` is inside a package, the package structure is returned dot-
separated, with another dot separating the enum name:
`pack1.pack2.(...).packN.EnumName`
If `e` is a sub-type of a Haxe module, that module is not part of the
package structure.
If `e` has no package, the enum name is returned.
If `e` is null, the result is unspecified.
The enum name does not include any type parameters.
**/
inline static function getEnumName(e:Enum<Dynamic>):String {
var isScripted:Bool = Std.isOfType(e, HScriptEnumValue);
if (isScripted)
return cast(e, HScriptEnumValue).getEnumName();
return Type.getEnumName(e);
}
/**
Resolves a class by name.
If `name` is the path of an existing class, that class is returned.
Otherwise null is returned.
If `name` is null or the path to a different type, the result is
unspecified.
The class name must not include any type parameters.
**/
inline static function resolveClass(name:String):Dynamic {
if(Interp.customClassExist(name)) {
return Interp.getCustomClass(name);
}
return Type.resolveClass(name);
}
/**
Resolves an enum by name.
If `name` is the path of an existing enum, that enum is returned.
Otherwise null is returned.
If `name` is null the result is unspecified.
If `name` is the path to a different type, null is returned.
The enum name must not include any type parameters.
**/
inline static function resolveEnum(name:String):Enum<Dynamic> {
return Type.resolveEnum(name);
}
/**
Creates an instance of class `cl`, using `args` as arguments to the
class constructor.
This function guarantees that the class constructor is called.
Default values of constructors arguments are not guaranteed to be
taken into account.
If `cl` or `args` are null, or if the number of elements in `args` does
not match the expected number of constructor arguments, or if any
argument has an invalid type, or if `cl` has no own constructor, the
result is unspecified.
In particular, default values of constructor arguments are not
guaranteed to be taken into account.
**/
inline static function createInstance<T>(cl:OneOfTwo<Class<T>, CustomClassDecl>, args:Array<Dynamic>):T {
// TODO: somehow pass the ogInterp argument without breaking the original functionality
if(cl is CustomClassDecl) {
return cast new CustomClass(cast cl, args);
}
return Type.createInstance(cast cl, args);
}
/**
Creates an instance of class `cl`.
This function guarantees that the class constructor is not called.
If `cl` is null, the result is unspecified.
**/
inline static function createEmptyInstance<T>(cl:OneOfTwo<Class<T>, CustomClassDecl>):T {
// TODO: same as above
if(cl is CustomClassDecl)
return cast new CustomClass(cast cl, [], null, null, false);
return Type.createEmptyInstance(cast cl);
}
/**
Creates an instance of enum `e` by calling its constructor `constr` with
arguments `params`.
If `e` or `constr` is null, or if enum `e` has no constructor named
`constr`, or if the number of elements in `params` does not match the
expected number of constructor arguments, or if any argument has an
invalid type, the result is unspecified.
**/
inline static function createEnum<T>(e:OneOfTwo<Enum<T>, HScriptEnum>, constr:String, ?params:Array<Dynamic>):T {
// TODO: SUPPORT HSCRIPT ENUMS
var isScripted:Bool = Std.isOfType(e, HScriptEnum);
if(isScripted) {
var scriptEnum:HScriptEnum = cast e;
var enumToCreate = scriptEnum.getEnumValue(constr);
if(Reflect.isFunction(enumToCreate)) {
return Reflect.callMethod(null, enumToCreate, params);
}
return enumToCreate;
}
return Type.createEnum(cast e, constr, params);
}
/**
Creates an instance of enum `e` by calling its constructor number
`index` with arguments `params`.
The constructor indices are preserved from Haxe syntax, so the first
declared is index 0, the next index 1 etc.
If `e` or `constr` is null, or if enum `e` has no constructor named
`constr`, or if the number of elements in `params` does not match the
expected number of constructor arguments, or if any argument has an
invalid type, the result is unspecified.
**/
inline static function createEnumIndex<T>(e:Enum<T>, index:Int, ?params:Array<Dynamic>):T {
// TODO: SUPPORT HSCRIPT ENUMS
return Type.createEnumIndex(e, index, params);
}
/**
Returns a list of the instance fields of class `c`, including
inherited fields.
This only includes fields which are known at compile-time. In
particular, using `getInstanceFields(getClass(obj))` will not include
any fields which were added to `obj` at runtime.
The order of the fields in the returned Array is unspecified.
If `c` is null, the result is unspecified.
**/
inline static function getInstanceFields(c:OneOfTwo<Class<Dynamic>, CustomClassDecl>):Array<String> {
if(c is CustomClassDecl) {
var cls:CustomClassDecl = cast c;
return [for(f in Tools.getClassDeclFields(cls.classDecl)) f.name];
}
return Type.getInstanceFields(cast c);
}
/**
Returns a list of static fields of class `c`.
This does not include static fields of parent classes.
The order of the fields in the returned Array is unspecified.
If `c` is null, the result is unspecified.
**/
inline static function getClassFields(c:OneOfTwo<Class<Dynamic>, CustomClassDecl>):Array<String> {
if(c is CustomClassDecl) {
var cls:CustomClassDecl = cast c;
return [for(f in Tools.getClassDeclFields(cls.classDecl, true)) f.name];
}
return Type.getClassFields(cast c);
}
/**
Returns a list of the names of all constructors of enum `e`.
The order of the constructor names in the returned Array is preserved
from the original syntax.
If `e` is null, the result is unspecified.
**/
inline static function getEnumConstructs(e:OneOfTwo<Enum<Dynamic>, HScriptEnum>):Array<String> {
// TODO: SUPPORT HSCRIPT ENUMS
return Type.getEnumConstructs(cast e);
}
/**
Returns the runtime type of value `v`.
The result corresponds to the type `v` has at runtime, which may vary
per platform. Assumptions regarding this should be minimized to avoid
surprises.
**/
inline static function typeof(v:Dynamic):Type.ValueType {
return Type.typeof(v);
}
/**
Recursively compares two enum instances `a` and `b` by value.
Unlike `a == b`, this function performs a deep equality check on the
arguments of the constructors, if exists.
If `a` or `b` are null, the result is unspecified.
**/
inline static function enumEq(a:Dynamic, b:Dynamic):Bool {
var isScripted:Bool = Std.isOfType(a, HScriptEnumValue) && Std.isOfType(b, HScriptEnumValue);
if (isScripted)
return cast(a, HScriptEnumValue).compare(cast(b, HScriptEnumValue));
return Type.enumEq(a, b);
}
/**
Returns the constructor name of enum instance `e`.
The result String does not contain any constructor arguments.
If `e` is null, the result is unspecified.
**/
inline static function enumConstructor(e:Dynamic):String {
if (Std.isOfType(e, HScriptEnumValue))
return cast(e, HScriptEnumValue).name;
return Type.enumConstructor(e);
}
/**
Returns a list of the constructor arguments of enum instance `e`.
If `e` has no arguments, the result is [].
Otherwise the result are the values that were used as arguments to `e`,
in the order of their declaration.
If `e` is null, the result is unspecified.
**/
inline static function enumParameters(e:Dynamic):Array<Dynamic> {
if (Std.isOfType(e, HScriptEnumValue))
return cast(e, HScriptEnumValue).args;
return Type.enumParameters(e);
}
/**
Returns the index of enum instance `e`.
This corresponds to the original syntactic position of `e`. The index of
the first declared constructor is 0, the next one is 1 etc.
If `e` is null, the result is unspecified.
**/
inline static function enumIndex(e:Dynamic):Int {
if (Std.isOfType(e, HScriptEnumValue))
return cast(e, HScriptEnumValue).index;
return Type.enumIndex(e);
}
/**
Returns a list of all constructors of enum `e` that require no
arguments.
This may return the empty Array `[]` if all constructors of `e` require
arguments.
Otherwise an instance of `e` constructed through each of its non-
argument constructors is returned, in the order of the constructor
declaration.
If `e` is null, the result is unspecified.
**/
inline static function allEnums<T>(e:OneOfTwo<Enum<T>, HScriptEnum>):Array<T> {
// TODO: SUPPORT HSCRIPT ENUMS
var isScripted:Bool = Std.isOfType(e, HScriptEnum);
if(isScripted) {
var hEnum:HScriptEnum = cast e;
return [for(en in Reflect.fields(hEnum.enumValues)) Reflect.field(hEnum, en)];
}
return Type.allEnums(cast e);
}
}
+9
View File
@@ -0,0 +1,9 @@
package hscript.utils;
/**
* This is used to mark classes that can be used with the `using` keyword.
* You can also add @:usableEntry to your class.
* If you wanna force the class to be called with any type, you can add @:usableEntry(forceAny)
**/
@:autoBuild(hscript.macros.UsingMacro.build())
interface UsingClass {}
+17
View File
@@ -0,0 +1,17 @@
package hscript.utils;
typedef UsingCall = (o: Dynamic, f: String, args: Array<Dynamic>) -> Dynamic;
/**
* Code based on Using system from "hscript-iris"
* @see https://github.com/pisayesiwsi/hscript-iris/blob/master/crowplexus/iris/utils/UsingEntry.hx
*/
class UsingEntry {
public var name: String;
public var call: UsingCall;
public function new(name: String, call: UsingCall) {
this.name = name;
this.call = call;
}
}
+45
View File
@@ -0,0 +1,45 @@
package hscript.utils;
import hscript.utils.UsingEntry.UsingCall;
import StringTools;
import Lambda;
class UsingHandler {
public static var usingEntries:Array<UsingEntry> = [
new UsingEntry("StringTools", function(o: Dynamic, f: String, args: Array<Dynamic>): Dynamic {
if (f == "isEof") // has @:noUsing
return null;
switch (Type.typeof(o)) {
case TInt if (f == "hex"):
return StringTools.hex(o, args[0]);
case TClass(String):
if (Reflect.hasField(StringTools, f)) {
var field = Reflect.field(StringTools, f);
if (Reflect.isFunction(field)) {
return Reflect.callMethod(StringTools, field, [o].concat(args));
}
}
default:
}
return null;
}),
new UsingEntry("Lambda", function(o: Dynamic, f: String, args: Array<Dynamic>): Dynamic {
if (Tools.isIterable(o)) {
// TODO: Check if the values are Iterable<T>
if (Reflect.hasField(Lambda, f)) {
var field = Reflect.field(Lambda, f);
if (Reflect.isFunction(field)) {
return Reflect.callMethod(Lambda, field, [o].concat(args));
}
}
}
return null;
}),
];
public static function registerUsingGlobal(name: String, call:UsingCall):UsingEntry {
var entry = new UsingEntry(name, call);
usingEntries.push(entry);
return entry;
}
}