Compare commits

...
Author SHA1 Message Date
Jaime Humberto Macias Bustamante 9fa4a548e5 String Interpolation Support
implementation from hscript-iris (by inky03)

Note: it needs optimization
2025-09-06 19:39:57 -07:00
Jaime Humberto Macias Bustamante 18f4ebccd9 Fix small bug with slash literal ("\/") 2025-09-06 19:31:48 -07:00
Jaime Humberto Macias Bustamante 8dcebe3930 Regular Expression support (regex) 2025-09-06 19:31:48 -07:00
Jaime Humberto Macias Bustamante 85f51df7eb fixed accessing class fields from objects
this shouldn't happen, unless "useRedirects" is on.
2025-08-19 19:24:00 -07:00
FrakitsandGitHub 5dce82eaf6 Update haxelib.json 2025-08-15 15:07:28 +03:00
Jaime Humberto Macias Bustamante 0b4675b4ad static property accessor call 2025-08-14 03:07:44 -07:00
Jaime Humberto Macias Bustamante e4861a606a Features Documentation 2025-08-04 13:40:48 -07:00
Jaime Humberto Macias Bustamante bbe9fd255d fixed package parsing issue 2025-08-03 21:46:04 -07:00
Jaime Humberto Macias Bustamante 6bd3f26746 package syntax support
For now it's just sugar syntax.
2025-08-03 21:28:59 -07:00
Jaime Humberto Macias Bustamante 83aa0f3856 fixed variable set bug 2025-08-03 01:08:06 -07:00
Jaime Humberto Macias Bustamante f97a7b8a55 Merge branch 'codename-dev' of github.com:CodenameCrew/hscript-improved into codename-dev 2025-08-03 00:08:46 -07:00
Jaime Humberto Macias Bustamante f936877339 MyCustomClass.new + Optimization
You can now pass the custom class constructor as a field.
2025-08-03 00:08:43 -07:00
Jamextreme140andGitHub 9fbe8a6316 Update README.md 2025-08-02 02:45:30 -07:00
Jaime Humberto Macias Bustamante 7f087eab03 fixed getting property fields on superclass
Basically "Type.getInstanceFields" doesn't take into account property fields, so it uses "Reflect.fields" to fetch the rest of fields.
2025-08-01 16:55:13 -07:00
Jaime Humberto Macias Bustamante 056a2bd791 fixed calling superclass functions 2025-07-29 13:23:13 -07:00
Jaime Humberto Macias Bustamante 902133a8ba goodbye legacy custom class, you will never be missed :3 2025-07-26 21:22:16 -07:00
Jaime Humberto Macias Bustamante 1791187808 Update ClassExtendMacro.hx 2025-07-26 16:09:02 -07:00
Jaime Humberto Macias Bustamante cacb0cdbd6 Cleanup + loopRun
- added loopRun to define custom loops (https://github.com/HaxeFoundation/hscript/commit/20c8bc3f3c72a4dedabf9a647940b9c4f43eedf8)
2025-07-26 15:34:35 -07:00
Jaime Humberto Macias Bustamante 0b1fbf66dc "cast" syntax support
- Now it parses unsafe/safe cast. For now it's just sugar syntax.
2025-07-26 01:59:57 -07:00
Jaime Humberto Macias Bustamante 2c6f48b1bc Update Parser.hx
now enum parses correctly
2025-07-24 14:18:45 -07:00
Jaime Humberto Macias Bustamante ed78ed79d9 Scripted Enums 2025-07-23 19:50:36 -07:00
Jaime Humberto Macias Bustamante 98b97bef66 Scripted Enums WIP 2025-07-21 22:39:08 -07:00
Jaime Humberto Macias Bustamante 003d22286c super call fixes 2025-07-21 16:47:01 -07:00
Jaime Humberto Macias Bustamante 5232f069fb final classes + improved custom class aliases 2025-07-17 14:46:12 -07:00
Jaime Humberto Macias Bustamante 4f13f07071 better custom class import alias 2025-07-17 13:20:03 -07:00
Jaime Humberto Macias Bustamante 70efab47d1 Documentation + custom classes import alias 2025-07-14 12:59:54 -07:00
Jaime Humberto Macias Bustamante c00c0c3257 Create IHScriptAbstractBehaviour.hx 2025-07-12 16:49:04 -07:00
Jaime Humberto Macias Bustamante f8a930cd48 upcasting 2025-07-12 16:13:59 -07:00
Jaime Humberto Macias Bustamante 1d753731e0 better default static extension fetch 2025-07-12 13:44:06 -07:00
Jaime Humberto Macias Bustamante b239ad2790 Update Interp.hx
oops :3
2025-07-11 09:03:29 -07:00
Jaime Humberto Macias Bustamante 97c3f8bb01 small optimization
since Reflect.hasField(cls, name) is quite expensive, we cache the fields, so it gets the field directly without issues.
2025-07-11 08:45:33 -07:00
Jaime Humberto Macias Bustamante 6b54479211 Static Extension support + small fixes
Static extension for real/custom classes. aka. the `using` keyword

Warning: UNTESTED
2025-07-11 04:35:03 -07:00
Jaime Humberto Macias Bustamante fd88d78544 Custom Class Type Check + Getting custom fields
- Now it checks for the custom class type (partial).

- Custom class fields get for generated classes.
2025-07-08 17:06:33 -07:00
Jaime Humberto Macias Bustamante 434ded6503 "null" value fix
CLOSES #9
2025-07-06 21:50:24 -07:00
Jaime Humberto Macias Bustamante a2cdefb369 static fixes :3 2025-07-03 02:44:53 -07:00
Jaime Humberto Macias Bustamante 46285a800e Update CustomClass.hx 2025-07-01 14:34:41 -07:00
Jaime Humberto Macias Bustamante 305ede38cc Update Parser.hx 2025-06-26 16:56:10 -07:00
Jaime Humberto Macias Bustamante f773589a37 Improved "is" operator
Official implementation of "is" operator.
2025-06-26 16:37:04 -07:00
Jaime Humberto Macias Bustamante b9a0706d6d performance fix for js + cleanup 2025-06-26 14:42:53 -07:00
Jaime Humberto Macias Bustamante 85f3830d71 bruh :'3 2025-06-23 18:23:51 -07:00
Jaime Humberto Macias Bustamante 2c6db18d06 Type Parameters parsing for "ENew"
it now parses type parameters on "ENew", ignored by the interpreter.

Example: "var a = new Object<Float>();
2025-06-23 15:12:15 -07:00
Jaime Humberto Macias Bustamante c165cdfc0a another bit of documentation 2025-06-20 15:59:31 -07:00
Jaime Humberto Macias Bustamante b48a3ee9ed Allow for setting fields before super
also improved fetching superclass fields
2025-06-20 14:45:06 -07:00
Jaime Humberto Macias Bustamante 1af4a64b10 Update CustomClassHandler.hx
i forgot to delete this
2025-06-20 01:28:15 -07:00
Jaime Humberto Macias Bustamante 64f287fd6d Better "EVar" pretty print + fix 2025-06-19 20:20:01 -07:00
Jaime Humberto Macias Bustamante c927d324dc Allow accessing other custom classes 2025-06-19 18:45:51 -07:00
Jaime Humberto Macias Bustamante bcd212a450 "null" fix 2025-06-19 15:55:00 -07:00
Jaime Humberto Macias Bustamante d05270d413 extending typed classes (WIP) 2025-06-19 13:14:02 -07:00
Jaime Humberto Macias Bustamante e40d18b8c4 Update AbstractHandler.hx
oops
2025-06-19 12:50:40 -07:00
Jaime Humberto Macias Bustamante 0e977b071c cleanup + optimization 2025-06-19 01:07:48 -07:00
Jaime Humberto Macias Bustamante c79882b75e Implemented Override
untested, still in progress tho
2025-06-18 10:08:59 -07:00
Jaime Humberto Macias Bustamante 251306a3d3 A bit if documentation + cleanup 2025-06-17 23:50:00 -07:00
Jaime Humberto Macias Bustamante 0bd95d402a Update CustomClass.hx
bruh
2025-06-17 22:49:54 -07:00
Jaime Humberto Macias Bustamante 6441acc93d fixes :3 2025-06-17 15:27:21 -07:00
Jaime Humberto Macias Bustamante 04a50580c6 Disallow static fields access from outside instances 2025-06-17 12:33:32 -07:00
Jaime Humberto Macias Bustamante a4d2d4c788 Custom Classes rework progress - 2 2025-06-16 22:24:22 -07:00
Jaime Humberto Macias Bustamante 54ab2fc509 Custom Classes rework progress 2025-06-16 12:44:40 -07:00
Jaime Humberto Macias Bustamante c16a5e99d5 Custom Classes rework (WIP) 2025-06-16 02:56:10 -07:00
Jaime Humberto Macias Bustamante e28b5a6de5 Property Variables
Now property variables fully works.
2025-06-15 23:03:46 -07:00
Jaime Humberto Macias Bustamante 6ac8fc7fe5 Fixed property access check 2025-06-12 14:50:31 -07:00
Jaime Humberto Macias Bustamante 43bd1001d2 "@:isVar" parses correctly now 2025-06-12 14:41:14 -07:00
Jaime Humberto Macias Bustamante 8bdc07c91b oops :3 2025-06-12 14:27:30 -07:00
Jaime Humberto Macias Bustamante a3a8f602ea Mandatory "allowTypes" for Property + "isVar" parsing 2025-06-12 14:24:51 -07:00
Jaime Humberto Macias Bustamante 1a9c0c68ac Update Parser.hx 2025-06-12 13:35:20 -07:00
Jaime Humberto Macias Bustamante 27b26eb2a0 Property Parsing + Simplified Interfaces - Part 2
- Now it parses "(get, set)". For now it's just sugar syntax.
2025-06-12 00:33:20 -07:00
Jaime Humberto Macias Bustamante 1bfe81cfac Simplified Interfaces - Part 1
here we go again...
2025-05-30 22:05:54 -07:00
25 changed files with 2127 additions and 441 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
/hscript.swf /hscript.swf
/release.zip /release.zip
dump/*
tests/bin/* tests/bin/*
+15 -2
View File
@@ -1,16 +1,29 @@
hscript-improved hscript-improved
======= =======
- [Features](docs/FEATURES.md)
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
```xml ```xml
<define name="CUSTOM_CLASSES" /> <define name="CUSTOM_CLASSES" />
``` ```
Warning: custom classes are sometimes broken, would like help to fix them. You can only override functions from the current class, not from the extended part, like you cant override update in FlxText because FlxText doesnt have a update function overriden
or set this in build.hxml
```hxml
-D CUSTOM_CLASSES
```
Current Custom Class Limitations :
- For now, you can only override functions from the current class, not from the extended part, like you cant override update in FlxText because FlxText doesnt have a update function overriden.
- You cannot create custom classes that extends a typed class (those ones that has `<T>`), this will be implemented in the future.
----------- -----------
+83
View File
@@ -0,0 +1,83 @@
# FEATURES
- Custom Classes
- Final Classes
- Static Classes
- Allow for type check (`is`)
- Allow extending other custom classes
- Enums
```haxe
enum TypeValue {
NUMBER(n:Int);
DECIMAL(d:Float, ?p:Int);
CHARACTER(s:String);
BOOLEAN(b:Bool);
}
var type = TypeValue.DECIMAL(10.1234, 2);
// You need to type the full enum field for each case
// i.e. you can't type the enum field directly (limitation for now)
switch(type) {
case TypeValue.NUMBER(number):
trace("number: " + number);
case TypeValue.DECIMAL(decimal, precision):
if(precision != null)
trace("decimal: " + decimal + " | rounded decimal: " + roundDecimal(decimal, precision));
else
trace("decimal: " + decimal);
case TypeValue.CHARACTER(char):
trace("character: " + char);
default:
trace("unknown type");
}
function roundDecimal(Value:Float, Precision:Int) {
var mult:Float = Math.pow(10, Precision);
return Math.fround(Value * mult) / mult;
}
```
- Enum matching with arguments for switch statements (for real and scripted enums)
- Property Fields (`(get, set)` variables)
```haxe
public var myvar(get, set):Int;
var _myvar:Int = 10;
function get_myvar():Int {
return _myvar;
}
function set_myvar(val:Int):Int {
if(val > 10) return _myvar = val;
return val;
}
```
- `@:isVar` metadata support
- Static extension (`using`)
```haxe
using StringTools;
class IntExtender {
static public function triple(i:Int) {
return i * 3;
}
}
// need to create/import the custom class
// before setting the extension (limitation for now)
using IntExtender;
var str = " Hello World! ";
trace(str.trim()); // "Hello World!"
trace(12.triple()); // 36
```
- Support for real and custom classes
- Misc.
- Allow using type parameters for creating objects (i.e. `var a = new TypedObject<Int>();`)
- Allow `package` declaration. Ignored by the interpreter.
+1 -1
View File
@@ -1,3 +1,3 @@
--macro keep('IntIterator') --macro keep('IntIterator')
--macro hscript.macros.UsingHandler.init() --macro hscript.macros.AbstractHandler.init()
--macro hscript.macros.ClassExtendMacro.init() --macro hscript.macros.ClassExtendMacro.init()
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "hscript", "name": "hscript-improved",
"url": "https://github.com/HaxeFoundation/hscript", "url": "https://github.com/HaxeFoundation/hscript",
"license": "MIT", "license": "MIT",
"description": "Haxe Script is a scripting engine for a subset of the Haxe language", "description": "Haxe Script is a scripting engine for a subset of the Haxe language",
+8
View File
@@ -124,10 +124,18 @@ class Bytes {
#end #end
bout.addByte(Type.enumIndex(e)); bout.addByte(Type.enumIndex(e));
switch( e ) { switch( e ) {
case EPackage(n):
// TODO
case EImport(c): case EImport(c):
// TODO // TODO
case EClass(_, _, _, _): case EClass(_, _, _, _):
// TODO // TODO
case EEnum(en):
// TODO
case ECast(e, _):
// TODO
case ERegex(e, f):
// TODO
case EConst(c): case EConst(c):
doEncodeConst(c); doEncodeConst(c);
case EIdent(v): case EIdent(v):
+3
View File
@@ -422,6 +422,9 @@ class Checker {
return makeType(t,e); return makeType(t,e);
case CTOpt(t): case CTOpt(t):
return makeType(t,e); return makeType(t,e);
case CTExpr(_):
error("Unsupported expr type parameter", e);
return null;
} }
} }
+355
View File
@@ -0,0 +1,355 @@
package hscript;
import hscript.utils.UnsafeReflect;
import haxe.Constraints.Function;
using Lambda;
/**
* The Custom Class core.
*
* Provides handlers for custom classes.
*
* @author Jamextreme140
*/
@:access(hscript.CustomClassHandler)
@:access(hscript.Property)
class CustomClass implements IHScriptCustomClassBehaviour {
public var className(get, never):String;
private function get_className():String
return __class.name;
public var __interp:Interp;
public var __real_fields:Array<String> = []; // UNUSED
public var __class__fields:Array<String> = []; // Declared fields
public var __allowSetGet:Bool = true;
var __class:CustomClassHandler;
var __superClass:IHScriptCustomClassBehaviour;
var __upperClass:IHScriptCustomClassBehaviour;
var __constructor:Function;
var __overrideFields:Array<String> = [];
var __cachedFieldSet:Map<String, Dynamic> = null;
var initializing:Bool = false;
public function new(__class:CustomClassHandler, ?args:Array<Dynamic>, ?cachedFieldSet:Map<String, Dynamic>) {
this.__class = __class;
__interp = new Interp();
__interp.errorHandler = __class.__interp.errorHandler;
__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;
for(f => v in __class.__interp.variables) {
if(f == 'new') continue;
if (!__interp.variables.exists(f))
__interp.variables.set(f, v);
}
for (f in __class.fields) {
switch (Tools.expr(f)) {
case EVar(n): __class__fields.push(n);
case EFunction(_, _, n, _, _, _, isOverride):
if(isOverride) __overrideFields.push(n);
__class__fields.push(n);
default: continue;
}
@:privateAccess __interp.exprReturn(f);
}
__interp.scriptObject = this;
initializing = true;
if(cachedFieldSet != null)
for(f => v in cachedFieldSet)
this.hset(f, v);
if (hasField('new')) {
buildConstructor();
call('new', args);
if(__cachedFieldSet != null) {
__cachedFieldSet.clear();
__cachedFieldSet = null;
}
if (this.__superClass == null && __class.extend != null)
__interp.error(ECustom("super() not called"));
} else if (__class.extend != null) {
buildSuperClass(args);
}
initializing = false;
}
function cacheFieldSet(name:String, val:Dynamic) {
if(!initializing) return;
if(__cachedFieldSet == null) __cachedFieldSet = [];
__cachedFieldSet.set(name, val);
}
function buildConstructor() {
__constructor = Reflect.makeVarArgs(buildSuperClass);
}
function buildSuperClass(?args:Array<Dynamic>) {
if (args == null)
args = [];
if (__class.cl == null) {
__interp.error(ECustom('Current class does not have a super'));
return;
}
if (__class.cl is CustomClassHandler) {
var customClass = new CustomClass(__class.cl, args, __cachedFieldSet);
if(__overrideFields.length > 0) {
for (field in __overrideFields) {
var func = __interp.variables.get(field);
customClass.overrideField(field, func);
}
}
customClass.__upperClass = this;
__superClass = customClass;
@:privateAccess __interp.__instanceFields = __interp.__instanceFields.concat(getSuperFields());
} else {
if(__cachedFieldSet != null)
UnsafeReflect.setField(__class.cl, "__cachedFieldSet", __cachedFieldSet);
__superClass = Type.createInstance(__class.cl, args);
var disallowCopy:Array<String> = {
var fieldMap:Map<String, String> = []; // Prevent duplicate values
for(f in Reflect.fields(__superClass).concat(Type.getInstanceFields(Type.getClass(__superClass))))
fieldMap.set(f, f);
fieldMap.array();
}
this.__real_fields = disallowCopy;
@:privateAccess __interp.__instanceFields = __interp.__instanceFields.concat(disallowCopy);
__superClass.__real_fields = this.__real_fields;
__superClass.__class__fields = this.__class__fields;
__superClass.__interp = this.__interp;
}
}
public function call(name:String, ?args:Array<Dynamic>, ?toSuper:Bool = false):Dynamic {
var superFnName:Null<String> = toSuper ? '_HX_SUPER__$name' : null;
var fn:Dynamic = {
if(toSuper && __interp.variables.exists(superFnName)) {
__interp.variables.get(superFnName);
}
else
__interp.variables.get(name);
};
if (fn != null && Reflect.isFunction(fn))
return UnsafeReflect.callMethodUnsafe(null, fn, (args == null) ? [] : args);
else
__interp.error(ECustom('$name doesn\'t exists or is not a function'));
return null;
}
function hasField(name:String) {
return __class__fields.contains(name);
}
function hasStaticField(name:String):Bool {
return __class.hasField(name);
}
function getField(name:String, allowProperty:Bool = true):Dynamic {
var f = __interp.variables.get(name);
if (f != null && allowProperty && f is Property) {
var prop:Property = cast f;
prop.__allowSetGet = this.__allowSetGet;
var r = prop.callGetter(name);
prop.__allowSetGet = null;
return r;
}
return f;
}
function setField(name:String, val:Dynamic):Dynamic {
var f = getField(name, false);
if (f != null && f is Property) {
var prop:Property = cast f;
prop.__allowSetGet = this.__allowSetGet;
var r = prop.callSetter(name, val);
prop.__allowSetGet = null;
return r;
}
__interp.variables.set(name, val);
return val;
}
/**
* Overrides (replaces) the declared function.
* @param name
* @param func
*/
function overrideField(name:String, func:Function) {
var f = getField(name, false);
if(f != null && Reflect.isFunction(f)) {
__interp.variables.set(name, func);
__interp.variables.set('_HX_SUPER__$name', f);
}
else if(__superClass != null && __superClass is CustomClass) {
cast(__superClass, CustomClass).overrideField(name, func);
}
}
function superHasField(name:String) {
if (__superClass == null)
return false;
var realFieldExists = __superClass.__real_fields != null && __superClass.__real_fields.contains(name);
var classFieldExists = __superClass.__class__fields != null && __superClass.__class__fields.contains(name);
if(!realFieldExists && !classFieldExists && __superClass is CustomClass)
return cast(__superClass, CustomClass).superHasField(name);
return realFieldExists || classFieldExists;
}
function getSuperFields():Array<String> {
if(__superClass == null) return [];
var classFields:Map<String, String> = []; // Prevents duplicated values
var cls:Null<IHScriptCustomClassBehaviour> = __superClass;
while (cls != null) {
for(fieldSet in [cls.__class__fields, cls.__real_fields])
for(f in fieldSet)
classFields.set(f, f);
var next:IHScriptCustomClassBehaviour = null;
if(cls is CustomClass)
next = cast(cls, CustomClass).__superClass;
if (next == null)
break;
cls = next;
}
return [for(f in classFields) f];
}
public function hget(name:String):Dynamic {
switch (name) {
case 'superClass': return __superClass;
case 'superConstructor': return __constructor;
default:
if (hasField(name))
return getField(name);
if (hasStaticField(name)) {
__interp.error(ECustom('The field ${name} should be accessed in a static way.'));
return null;
}
if (__superClass != null) {
if (superHasField(name)) {
__superClass.__allowSetGet = this.__allowSetGet;
return __superClass.hget(name);
}
}
throw "field '"
+ name
+ "' does not exist in custom class '"
+ this.className
+ "'"
+ (__superClass != null ? "' or super class '" + Type.getClassName(Type.getClass(this.__superClass)) + "'" : "");
}
return null;
}
public function hset(name:String, val:Dynamic):Dynamic {
if (hasField(name))
return setField(name, val);
if (hasStaticField(name)) {
__interp.error(ECustom('The field ${name} should be accessed in a static way.'));
return null;
}
if (__superClass != null) {
if (superHasField(name)) {
__superClass.__allowSetGet = this.__allowSetGet;
return __superClass.hset(name, val);
}
}
else if(__class.extend != null && initializing) {
cacheFieldSet(name, val);
return val;
}
throw "field '"
+ name
+ "' does not exist in custom class '"
+ this.className
+ "'"
+ (__superClass != null ? "' or super class '" + Type.getClassName(Type.getClass(this.__superClass)) + "'" : "");
return null;
}
// UNUSED
public function __callGetter(name:String):Dynamic {
return null;
}
public function __callSetter(name:String, val:Dynamic):Dynamic {
return null;
}
/**
* 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():IHScriptCustomClassBehaviour {
if(__superClass == null) return null;
var cls:Null<IHScriptCustomClassBehaviour> = __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 is CustomClass ? this : cls;
}
// TODO: scripted safe cast for custom classes
public function getUpperclass():IHScriptCustomClassBehaviour {
if(__upperClass == null) return this;
var cls:CustomClass = cast __upperClass;
while (cls != null) {
var prev:CustomClass = cast cls.__upperClass;
if(prev == null)
break;
cls = prev;
}
return cls;
}
public function toString():String
return className;
}
+121 -169
View File
@@ -1,151 +1,152 @@
package hscript; package hscript;
import hscript.Interp.DeclaredVar; /**
import hscript.utils.UnsafeReflect; * Provides handlers for static custom class fields and instantiation.
using StringTools; */
@:access(hscript.Property)
class CustomClassHandler implements IHScriptCustomConstructor { class CustomClassHandler implements IHScriptCustomConstructor implements IHScriptCustomAccessBehaviour{
public static var staticHandler = new StaticHandler();
public var ogInterp:Interp; public var ogInterp:Interp;
public var name:String; public var name:String;
public var fields:Array<Expr>; public var fields:Array<Expr>;
public var extend:String; public var extend:Null<String>;
public var interfaces:Array<String>; public var interfaces:Array<String>;
public final isFinal:Bool;
public var cl:Class<Dynamic>; public var cl:Dynamic;
public function new(ogInterp:Interp, name:String, fields:Array<Expr>, ?extend:String, ?interfaces:Array<String>) { private var __interp:Interp;
private var __staticFields:Array<String> = [];
public var __allowSetGet:Bool = true;
public function new(ogInterp:Interp, name:String, fields:Array<Expr>, ?extend:String, ?interfaces:Array<String>, ?isFinal:Bool) {
this.ogInterp = ogInterp; this.ogInterp = ogInterp;
this.name = name; this.name = name;
this.fields = fields; this.fields = fields;
this.extend = extend; this.extend = extend;
this.interfaces = interfaces; this.interfaces = interfaces;
this.isFinal = isFinal != null ? isFinal : false;
if(extend != null) {
if(ogInterp.customClasses.exists(extend)) {
var customCls:CustomClassHandler = ogInterp.customClasses.get(extend);
if(customCls.isFinal)
ogInterp.error(ECustom('Cannot extend a final class'));
this.cl = customCls;
}
else
this.cl = 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));
} }
public function hnew(args:Array<Dynamic>):Dynamic { initStatic();
// TODO: clean this up, it sucks, i hate it
// TODO: make static vars work correctly
var interp = new Interp();
interp.errorHandler = ogInterp.errorHandler;
var _class:IHScriptCustomClassBehaviour = Type.createInstance(cl, args);
//var __capturedLocals = ogInterp.duplicate(ogInterp.locals);
//var capturedLocals:Map<String, DeclaredVar> = [];
//for(k=>e in __capturedLocals)
// if (e != null && e.depth <= 0)
// capturedLocals.set(k, e);
var disallowCopy = Type.getInstanceFields(cl);
_class.__real_fields = disallowCopy;
// todo: make it so you can use variables from the same scope as where the class was defined
//for (key => value in capturedLocals) {
// if(!disallowCopy.contains(key)) {
// interp.locals.set(key, {r: value, depth: -1});
// }
//}
for (key => value in ogInterp.variables) {
if(!disallowCopy.contains(key)) {
interp.variables.set(key, value);
}
}
for(key => value in ogInterp.customClasses) {
if(!disallowCopy.contains(key)) {
interp.customClasses.set(key, value);
}
}
// todo: clone static vars, but make it so setting it only sets it on the class
// todo: clone public vars
//trace("Before: " + [for(key => value in interp.variables) key]);
interp.variables.set("super", staticHandler);
var comparisonMap:Map<String, Dynamic> = [];
for(key => value in interp.variables) {
comparisonMap.set(key, value);
} }
_class.__custom__variables = interp.variables; @:access(hscript.Interp)
function initStatic() {
__interp = new Interp();
__interp.errorHandler = ogInterp.errorHandler;
__interp.importFailedCallback = ogInterp.importFailedCallback;
//trace(fields); //__interp.variables = ogInterp.variables;
for(expr in fields) { __interp.usingHandler.usingEntries = ogInterp.usingHandler.usingEntries;
@:privateAccess __interp.publicVariables = ogInterp.publicVariables;
interp.exprReturn(expr); __interp.staticVariables = ogInterp.staticVariables;
__interp.customClasses = ogInterp.customClasses;
for(f => v in ogInterp.variables)
if(!__interp.variables.exists(f))
__interp.variables.set(f, v);
for(e in fields.copy()) {
var validField:Bool = false;
var staticField:Bool = false;
var fieldName:String = "";
switch (Tools.expr(e)) {
case EVar(n, _, _, _, isStatic):
validField = true;
staticField = isStatic;
fieldName = n;
case EFunction(_, _, n, _, _, isStatic, _, _, _, _):
validField = true;
staticField = isStatic;
fieldName = n;
default:
} }
//trace("After: " + [for(key => value in interp.variables) key]); if(staticField && validField) {
__interp.exprReturn(e);
// get only variables that were not set before __staticFields.push(fieldName);
var classVariables = [ fields.remove(e);
for(key => value in interp.variables)
if(!comparisonMap.exists(key) || comparisonMap[key] != value)
key => value
];
//for(variable => value in classVariables) {
// if(variable == "this" || variable == "super" || variable == "new") continue;
// @:privateAccess
// if(!interp.__instanceFields.contains(variable)) {
// interp.__instanceFields.push(variable);
// }
//}
_class.__class__fields = [for(key => value in classVariables) key];
//trace(_class.__class__fields);
//@:privateAccess
//trace(interp.__instanceFields);
_class.__interp = interp;
_class.__allowSetGet = false;
interp.scriptObject = _class;
for(variable => value in interp.variables) {
if(variable == "this" || variable == "super" || variable == "new") continue;
if(variable.startsWith("set_") || variable.startsWith("get_")) {
_class.__allowSetGet = true;
}
}
var newFunc = interp.variables.get("new");
if(newFunc != null) {
var comparisonMap:Map<String, Dynamic> = [];
for(key => value in interp.variables) {
comparisonMap.set(key, value);
}
UnsafeReflect.callMethodUnsafe(null, newFunc, args);
// get only variables that were not set before
var classVariables = [
for(key => value in interp.variables)
if(!comparisonMap.exists(key) || comparisonMap[key] != value)
key => value
];
for(variable => value in classVariables) {
if(variable == "this" || variable == "super" || variable == "new") continue;
@:privateAccess
if(!interp.__instanceFields.contains(variable)) {
interp.__instanceFields.push(variable);
}
if(!_class.__class__fields.contains(variable)) {
_class.__class__fields.push(variable);
} }
} }
} }
public function hnew(args:Array<Dynamic>):Dynamic
return new CustomClass(this, args);
return _class; @:allow(hscript.Interp)
function hasField(name:String) {
return __staticFields.contains(name);
}
function getField(name:String, allowProperty:Bool = true):Dynamic {
var f = __interp.variables.get(name);
if(f is Property && allowProperty) {
var prop:Property = cast f;
prop.__allowSetGet = this.__allowSetGet;
var r = prop.callGetter(name);
prop.__allowSetGet = null;
return r;
}
return f;
}
function setField(name:String, val:Dynamic):Dynamic {
var f = getField(name, false);
if(f is Property) {
var prop:Property = cast f;
prop.__allowSetGet = this.__allowSetGet;
var r = prop.callSetter(name, val);
prop.__allowSetGet = null;
return r;
}
__interp.variables.set(name, val);
return val;
}
public function hget(name:String):Dynamic {
if(name == 'new') {
var __constructor = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
return this.hnew(args);
});
return __constructor;
}
if(hasField(name)) {
return getField(name);
}
throw "field '"+ name+ "' does not exist in class '"+ this.name+ "'";
return null;
}
public function hset(name:String, val:Dynamic):Dynamic {
if(hasField(name))
return setField(name, val);
throw "field '"+ name+ "' does not exist in class '"+ this.name+ "'";
return null;
}
// UNUSED
public function __callGetter(name:String):Dynamic {
return null;
}
public function __callSetter(name:String, val:Dynamic):Dynamic {
return null;
} }
public function toString():String { public function toString():String {
@@ -153,51 +154,6 @@ class CustomClassHandler implements IHScriptCustomConstructor {
} }
} }
class CustomTemplateClass implements IHScriptCustomClassBehaviour implements IHScriptCustomAccessBehaviour {
public var __interp:Interp;
public var __allowSetGet:Bool = true;
public var __custom__variables:Map<String, Dynamic>;
public var __real_fields:Array<String>;
public var __class__fields:Array<String>;
public function hset(name:String, val:Dynamic):Dynamic {
if(__allowSetGet && __custom__variables.exists("set_" + name))
return __callSetter(name, val);
if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
}
if(__real_fields.contains(name)) {
UnsafeReflect.setProperty(this, name, val);
return UnsafeReflect.field(this, name);
}
__custom__variables.set(name, val);
return val;
}
public function hget(name:String):Dynamic {
if(__allowSetGet && __custom__variables.exists("get_" + name))
return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
return UnsafeReflect.getProperty(this, name);
}
public function __callGetter(name:String):Dynamic {
__allowSetGet = false;
var v = __custom__variables.get("get_" + name)();
__allowSetGet = true;
return v;
}
public function __callSetter(name:String, val:Dynamic):Dynamic {
__allowSetGet = false;
var v = __custom__variables.get("set_" + name)(val);
__allowSetGet = true;
return v;
}
}
/** /**
* 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
@@ -236,7 +192,3 @@ class TemplateClass implements IHScriptCustomBehaviour implements IHScriptCustom
return v; return v;
} }
} }
final class StaticHandler {
public function new() {}
}
+30 -4
View File
@@ -34,7 +34,7 @@ typedef UInt64 = #if cpp cpp.UInt64 #else Int #end;
enum Const { enum Const {
CInt( v : Int ); CInt( v : Int );
CFloat( f : Float ); CFloat( f : Float );
CString( s : String ); CString( s : String, ?i : Bool );
} }
#if hscriptPos #if hscriptPos
@@ -53,7 +53,7 @@ enum Expr {
#end #end
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, ?isVar:Bool );
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 );
@@ -69,7 +69,7 @@ enum Expr {
EReturn( ?e : Expr ); EReturn( ?e : Expr );
EArray( e : Expr, index : Expr ); EArray( e : Expr, index : Expr );
EArrayDecl( e : Array<Expr>, ?wantedType: CType ); EArrayDecl( e : Array<Expr>, ?wantedType: CType );
ENew( cl : String, params : Array<Expr> ); ENew( cl : String, params : Array<Expr>, ?paramType:Array<CType> );
EThrow( e : Expr ); EThrow( e : Expr );
ETry( e : Expr, v : String, t : Null<CType>, ecatch : Expr ); ETry( e : Expr, v : String, t : Null<CType>, ecatch : Expr );
EObject( fl : Array<ObjectField> ); EObject( fl : Array<ObjectField> );
@@ -79,8 +79,12 @@ enum Expr {
EMeta( name : String, args : Array<Expr>, e : Expr ); EMeta( name : String, args : Array<Expr>, e : Expr );
ECheckType( e : Expr, t : CType ); ECheckType( e : Expr, t : CType );
EImport( c : String, ?asname:String ); EPackage( ?n:String );
EImport( c : String, ?asname:String, ?isUsing:Bool );
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 );
EEnum( en:EnumDecl, ?isAbstract:Bool );
ECast(e:Expr, ?t:CType);
ERegex(e:String, flags:String);
} }
@:structInit @:structInit
@@ -111,6 +115,18 @@ final class MetadataEntry {
typedef Metadata = Array<MetadataEntry>; typedef Metadata = Array<MetadataEntry>;
@:structInit
final class EnumDecl {
public var name : String;
public var fields : Array<EnumField>;
}
@:structInit
final class EnumField {
public var name : String;
public var args : Array<Argument>;
}
enum CType { enum CType {
CTPath( path : Array<String>, ?params : Array<CType> ); CTPath( path : Array<String>, ?params : Array<CType> );
CTFun( args : Array<CType>, ret : CType ); CTFun( args : Array<CType>, ret : CType );
@@ -118,6 +134,7 @@ enum CType {
CTParent( t : CType ); CTParent( t : CType );
CTOpt( t : CType ); CTOpt( t : CType );
CTNamed( n : String, t : CType ); CTNamed( n : String, t : CType );
CTExpr( e : Expr ); // for type parameters only
} }
#if hscriptPos #if hscriptPos
@@ -198,6 +215,15 @@ enum abstract FieldAccess(UInt8) {
var AMacro; var AMacro;
} }
enum abstract FieldPropertyAccess(UInt8) {
var ADefault;
var ANull;
var AGet;
var ASet;
var ADynamic;
var ANever;
}
enum FieldKind { enum FieldKind {
KFunction( f : FunctionDecl ); KFunction( f : FunctionDecl );
KVar( v : VarDecl ); KVar( v : VarDecl );
+66
View File
@@ -0,0 +1,66 @@
package hscript;
import hscript.utils.UnsafeReflect;
// TODO: EnumTools for scripted enums
/**
* Wrapper class for enums, both for real and scripted.
*/
@:structInit
class HEnum implements IHScriptCustomBehaviour {
private var enumValues(default, null) = {};
public function setEnum(name:String, enumValue:Dynamic):Void {
UnsafeReflect.setField(enumValues, name, enumValue);
}
public function getEnum(name:String):Null<Dynamic> {
if (UnsafeReflect.hasField(enumValues, name))
return UnsafeReflect.field(enumValues, name);
return null;
}
public function hget(name:String):Dynamic {
return getEnum(name);
}
public function hset(name:String, val:Dynamic):Dynamic {
return null;
}
}
@:nullSafety
@:structInit
class HEnumValue {
public var enumName:String;
public var fieldName:String;
public var index:Int;
public var args:Array<Dynamic>;
public function toString():String {
return '$enumName.$fieldName${args.length > 0 ? '(${[for (a in args) a].join(", ")})' : ''}';
}
public inline function getEnumName():String
return this.enumName;
public inline function getConstructorArgs():Array<Dynamic>
return this.args;
public function compare(other:HEnumValue):Bool {
if (enumName != other.enumName || fieldName != other.fieldName)
return false;
if (args.length == 0 && other.args.length == 0)
return true;
if (args.length == 0 || other.args.length == 0)
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;
}
}
+12
View File
@@ -0,0 +1,12 @@
package hscript;
// Soon...
interface IHScriptAbstractBehaviour extends IHScriptCustomBehaviour {
public var hasOp:Bool;
public var hasArr:Bool;
// @:op(A * B), @:op(A++), etc...
public function hop(kind:String, a:Dynamic, ?b:Dynamic):Dynamic;
public function harrayget(key:Dynamic):Dynamic;
public function harrayset(key:Dynamic, val:Dynamic):Dynamic;
}
+4 -4
View File
@@ -1,11 +1,11 @@
package hscript; package hscript;
interface IHScriptCustomAccessBehaviour { /**
* Same Interface as IHScriptCustomBehaviour but for Property.
*/
interface IHScriptCustomAccessBehaviour extends IHScriptCustomBehaviour {
var __allowSetGet:Bool; var __allowSetGet:Bool;
public function hset(name:String, val:Dynamic):Dynamic;
public function hget(name:String):Dynamic;
public function __callGetter(name:String):Dynamic; public function __callGetter(name:String):Dynamic;
public function __callSetter(name:String, val:Dynamic):Dynamic; public function __callSetter(name:String, val:Dynamic):Dynamic;
} }
+17
View File
@@ -1,6 +1,23 @@
package hscript; package hscript;
/**
* Special Interface for handling field access behaviour.
* Basically works like the operator overload `@:op(a.b)`
* for an abstract.
*/
interface IHScriptCustomBehaviour { interface IHScriptCustomBehaviour {
/**
* Field Write Access
* @param name - Field Name
* @param val - Value to assign
* @return Dynamic - The assigned value
*/
public function hset(name:String, val:Dynamic):Dynamic; public function hset(name:String, val:Dynamic):Dynamic;
/**
* Field Read Access
* @param name - Field Name
* @return Dynamic - The returned field
*/
public function hget(name:String):Dynamic; public function hget(name:String):Dynamic;
} }
+4 -9
View File
@@ -1,16 +1,11 @@
package hscript; package hscript;
interface IHScriptCustomClassBehaviour { /**
* Special Interface to make a class usable for Custom Classes.
*/
interface IHScriptCustomClassBehaviour extends IHScriptCustomAccessBehaviour{
public var __interp:Interp; public var __interp:Interp;
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 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;
} }
+3
View File
@@ -1,5 +1,8 @@
package hscript; package hscript;
/**
* Special Interface for handling new instances of an object.
*/
interface IHScriptCustomConstructor { interface IHScriptCustomConstructor {
public function hnew(args:Array<Dynamic>):Dynamic; public function hnew(args:Array<Dynamic>):Dynamic;
} }
+458 -145
View File
@@ -28,7 +28,9 @@
*/ */
package hscript; package hscript;
import hscript.HEnum.HEnumValue;
import haxe.CallStack; import haxe.CallStack;
import hscript.utils.UsingHandler;
import hscript.utils.UnsafeReflect; import hscript.utils.UnsafeReflect;
import haxe.PosInfos; import haxe.PosInfos;
import hscript.Expr; import hscript.Expr;
@@ -65,11 +67,18 @@ class RedeclaredVar {
public var depth:Int; public var depth:Int;
} }
@:access(hscript.CustomClass)
@:analyzer(optimize, local_dce, fusion, user_var_fusion) @:analyzer(optimize, local_dce, fusion, user_var_fusion)
class Interp { class Interp {
public var scriptObject(default, set):Dynamic; private var hasScriptObject(get, never):Bool;
private var _hasScriptObject(default, null):Bool = false; private function get_hasScriptObject():Bool
return scriptObject != null;
private var _scriptObjectType(default, null):ScriptObjectType = SNull; private var _scriptObjectType(default, null):ScriptObjectType = SNull;
var __instanceFields:Array<String> = [];
public var scriptObject(default, set):Dynamic;
public function set_scriptObject(v:Dynamic) { public function set_scriptObject(v:Dynamic) {
switch(Type.typeof(v)) { switch(Type.typeof(v)) {
case TClass(c): // Class Access case TClass(c): // Class Access
@@ -101,13 +110,21 @@ class Interp {
__instanceFields = []; __instanceFields = [];
_scriptObjectType = SNull; _scriptObjectType = SNull;
} }
_hasScriptObject = v != null;
return scriptObject = v; return scriptObject = v;
} }
var inCustomClass(get, never):Bool;
private function get_inCustomClass():Bool
return hasScriptObject && _scriptObjectType == SCustomClass;
var __customClass(get, never):CustomClass;
private function get___customClass():CustomClass
return inCustomClass ? cast scriptObject : null;
public var errorHandler:Error->Void; public var errorHandler:Error->Void;
public var importFailedCallback:Array<String>->Bool; public var importFailedCallback:Array<String>->Bool;
public var customClasses:Map<String, Dynamic>; public var customClasses:Map<String, CustomClassHandler>;
public var variables:Map<String, Dynamic>; public var variables:Map<String, Dynamic>;
public var publicVariables:Map<String, Dynamic>; public var publicVariables:Map<String, Dynamic>;
public var staticVariables:Map<String, Dynamic>; public var staticVariables:Map<String, Dynamic>;
@@ -122,17 +139,21 @@ class Interp {
var returnValue:Dynamic; var returnValue:Dynamic;
var isBypassAccessor:Bool = false; var isBypassAccessor:Bool = false;
var setAlias:Null<String> = null; // Custom Class import alias
var beforeAlias:Null<String> = null;
public var importEnabled:Bool = true; public var importEnabled:Bool = true;
public var allowStaticVariables:Bool = false; public var allowStaticVariables:Bool = false;
public var allowPublicVariables:Bool = false; public var allowPublicVariables:Bool = false;
// TODO: move this to an external class
public var importBlocklist:Array<String> = [ public var importBlocklist:Array<String> = [
// "flixel.FlxG" // "flixel.FlxG"
]; ];
var __instanceFields:Array<String> = []; var usingHandler:UsingHandler;
#if hscriptPos #if hscriptPos
var curExpr:Expr; var curExpr:Expr;
#end #end
@@ -145,10 +166,13 @@ class Interp {
} }
private function resetVariables():Void { private function resetVariables():Void {
customClasses = new Map<String, Dynamic>(); customClasses = new Map<String, CustomClassHandler>();
variables = new Map<String, Dynamic>(); variables = new Map<String, Dynamic>();
publicVariables = new Map<String, Dynamic>(); publicVariables = new Map<String, Dynamic>();
staticVariables = new Map<String, Dynamic>(); staticVariables = new Map<String, Dynamic>();
usingHandler = new UsingHandler();
variables.set("null", null); variables.set("null", null);
variables.set("true", true); variables.set("true", true);
variables.set("false", false); variables.set("false", false);
@@ -223,7 +247,17 @@ class Interp {
Std.isOfType(expr1, IMap); Std.isOfType(expr1, IMap);
default: default:
var expr2:Dynamic = expr(e2); var expr2:Dynamic = expr(e2);
expr2 != null ? Std.isOfType(expr1, expr2) : false; if(expr2 != null) {
if(expr1 is CustomClass && expr2 is CustomClassHandler) {
var objName = cast(expr1, CustomClass).className;
var clsName = cast(expr2, CustomClassHandler).name;
objName == clsName;
}
else
Std.isOfType(expr1, expr2);
}
else
false;
} }
} }
@@ -246,22 +280,13 @@ class Interp {
case EIdent(id): case EIdent(id):
var l = locals.get(id); var l = locals.get(id);
if (l == null) { if (l == null) {
if (_hasScriptObject && !varExists(id)) { if (hasScriptObject && !varExists(id)) {
var instanceHasField = __instanceFields.contains(id); var instanceHasField = __instanceFields.contains(id);
if (_scriptObjectType == SObject && instanceHasField) { if (_scriptObjectType == SObject && instanceHasField) {
UnsafeReflect.setField(scriptObject, id, v); UnsafeReflect.setField(scriptObject, id, v);
return v; return v;
} else if (_scriptObjectType == SCustomClass && instanceHasField) { } else if((_scriptObjectType == SCustomClass && instanceHasField) || _scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomClassBehaviour = cast scriptObject;
if(isBypassAccessor) {
obj.__allowSetGet = false;
var res = obj.hset(id, v);
obj.__allowSetGet = true;
return res;
}
return obj.hset(id, v);
} else if(_scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomAccessBehaviour = cast scriptObject; var obj:IHScriptCustomAccessBehaviour = cast scriptObject;
if(isBypassAccessor) { if(isBypassAccessor) {
obj.__allowSetGet = false; obj.__allowSetGet = false;
@@ -290,8 +315,16 @@ class Interp {
setVar(id, v); setVar(id, v);
} }
} else { } else {
var obj = resolve(id, false, false);
if (obj != null && obj is Property) {
var prop:Property = cast obj;
return prop.callSetter(id, v);
}
setVar(id, v); setVar(id, v);
} }
} else if (l.r is Property) {
var prop:Property = cast l.r;
return prop.callSetter(id, v);
} else { } else {
l.r = v; l.r = v;
if (l.depth == 0) { if (l.depth == 0) {
@@ -330,22 +363,13 @@ class Interp {
var l = locals.get(id); var l = locals.get(id);
v = fop(expr(e1), expr(e2)); v = fop(expr(e1), expr(e2));
if (l == null) { if (l == null) {
if(_hasScriptObject && !varExists(id)) { if(hasScriptObject && !varExists(id)) {
var instanceHasField = __instanceFields.contains(id); var instanceHasField = __instanceFields.contains(id);
if (_scriptObjectType == SObject && instanceHasField) { if (_scriptObjectType == SObject && instanceHasField) {
UnsafeReflect.setField(scriptObject, id, v); UnsafeReflect.setField(scriptObject, id, v);
return v; return v;
} else if (_scriptObjectType == SCustomClass && instanceHasField) { } else if((_scriptObjectType == SCustomClass && instanceHasField) || _scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomClassBehaviour = cast scriptObject;
if(isBypassAccessor) {
obj.__allowSetGet = false;
var res = obj.hset(id, v);
obj.__allowSetGet = true;
return res;
}
return obj.hset(id, v);
} else if(_scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomAccessBehaviour = cast scriptObject; var obj:IHScriptCustomAccessBehaviour = cast scriptObject;
if(isBypassAccessor) { if(isBypassAccessor) {
obj.__allowSetGet = false; obj.__allowSetGet = false;
@@ -374,11 +398,20 @@ class Interp {
setVar(id, v); setVar(id, v);
} }
} else { } else {
var obj = resolve(id, true, false);
if (obj != null && obj is Property) {
var prop:Property = cast obj;
return prop.callSetter(id, v);
}
setVar(id, v); setVar(id, v);
} }
} }
else { else {
var l = locals.get(id); var l = locals.get(id);
if (l.r is Property) {
var prop:Property = cast l.r;
return prop.callSetter(id, v);
}
l.r = v; l.r = v;
if (l.depth == 0) { if (l.depth == 0) {
setVar(id, v); setVar(id, v);
@@ -417,19 +450,45 @@ class Interp {
var l = locals.get(id); var l = locals.get(id);
if(l != null) { if(l != null) {
var v:Dynamic = l.r; var v:Dynamic = l.r;
var prop:Property = null;
if (v is Property) {
prop = cast v;
v = prop.callGetter(id);
}
if (prefix) { if (prefix) {
v += delta; v += delta;
if (prop != null)
prop.callSetter(id, v);
else
l.r = v; l.r = v;
} else } else {
if (prop != null)
prop.callSetter(id, v + delta);
else
l.r = v + delta; l.r = v + delta;
}
return v; return v;
} else { } else {
var v:Dynamic = resolve(id); var v:Dynamic = resolve(id, true, false);
var prop:Property = null;
if (v is Property) {
prop = cast v;
v = prop.callGetter(id);
}
if (prefix) { if (prefix) {
v += delta; v += delta;
if (prop != null)
prop.callSetter(id, v);
else
setVar(id, v); setVar(id, v);
} else } else {
if (prop != null)
prop.callSetter(id, v + delta);
else
setVar(id, v + delta); setVar(id, v + delta);
}
return v; return v;
} }
case EField(e, f, s): case EField(e, f, s):
@@ -535,11 +594,11 @@ class Interp {
public inline function error(e:#if hscriptPos ErrorDef #else Error #end, rethrow = false):Dynamic { 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 hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end
if (rethrow) { if (rethrow)
this.rethrow(e); this.rethrow(e);
} else { else
throw e; throw e;
}
return null; return null;
} }
@@ -551,27 +610,41 @@ class Interp {
#end #end
} }
public function resolve(id:String, doException:Bool = true):Dynamic { public function resolve(id:String, doException:Bool = true, allowProperty:Bool = true):Dynamic {
if (id == null) if (id == null)
return null; return null;
id = StringTools.trim(id); id = StringTools.trim(id);
if(inCustomClass && id == 'super') {
var customClass:IHScriptCustomClassBehaviour = cast scriptObject;
var superClass = customClass.hget('superClass');
return superClass == null ? customClass.hget('superConstructor') : superClass;
}
if (locals.exists(id)) { if (locals.exists(id)) {
var l = locals.get(id); var l = locals.get(id);
if(l != null) if(l != null) {
if(l.r != null && l.r is Property && allowProperty)
return cast(l.r, Property).callGetter(id);
else
return l.r; return l.r;
}
}
for(map in [variables, publicVariables, staticVariables]) {
if(map.exists(id)) {
var r:Null<Dynamic> = map.get(id);
if(r != null && r is Property && allowProperty)
return cast(r, Property).callGetter(id);
else
return r;
}
} }
if(variables.exists(id))
return variables.get(id);
if(publicVariables.exists(id))
return publicVariables.get(id);
if(staticVariables.exists(id))
return staticVariables.get(id);
if(customClasses.exists(id)) if(customClasses.exists(id))
return customClasses.get(id); return customClasses.get(id);
if (_hasScriptObject) { if (hasScriptObject) {
// search in object // search in object
if (id == "this") { if (id == "this") {
return scriptObject; return scriptObject;
@@ -580,16 +653,7 @@ class Interp {
if (_scriptObjectType == SObject && instanceHasField) { if (_scriptObjectType == SObject && instanceHasField) {
return UnsafeReflect.field(scriptObject, id); return UnsafeReflect.field(scriptObject, id);
} else if(_scriptObjectType == SCustomClass && instanceHasField) { } else if((_scriptObjectType == SCustomClass && instanceHasField) || _scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomClassBehaviour = cast scriptObject;
if(isBypassAccessor) {
obj.__allowSetGet = false;
var res = obj.hget(id);
obj.__allowSetGet = true;
return res;
}
return obj.hget(id);
} else if(_scriptObjectType == SAccessBehaviourObject) {
var obj:IHScriptCustomAccessBehaviour = cast scriptObject; var obj:IHScriptCustomAccessBehaviour = cast scriptObject;
if(isBypassAccessor) { if(isBypassAccessor) {
obj.__allowSetGet = false; obj.__allowSetGet = false;
@@ -639,9 +703,14 @@ class Interp {
var e = e.e; var e = e.e;
#end #end
switch (e) { switch (e) {
case EClass(name, fields, extend, interfaces): case EPackage(_):
if (customClasses.exists(name)) case EClass(name, fields, extend, interfaces, isFinal):
error(EAlreadyExistingClass(name)); var oldName:String = name;
var hasAlias:Bool = (setAlias != null && beforeAlias == oldName);
var toSetName:String = hasAlias ? setAlias : oldName;
if (customClasses.exists(toSetName))
error(EAlreadyExistingClass(toSetName));
inline function importVar(thing:String):String { inline function importVar(thing:String):String {
if (thing == null) if (thing == null)
@@ -649,8 +718,14 @@ class Interp {
final variable:Class<Any> = variables.exists(thing) ? cast variables.get(thing) : null; final variable:Class<Any> = variables.exists(thing) ? cast variables.get(thing) : null;
return variable == null ? thing : Type.getClassName(variable); return variable == null ? thing : Type.getClassName(variable);
} }
customClasses.set(name, new CustomClassHandler(this, name, fields, importVar(extend), [for (i in interfaces) importVar(i)])); var cls:CustomClassHandler = new CustomClassHandler(this, oldName, fields, importVar(extend), [for (i in interfaces) importVar(i)], isFinal);
case EImport(c, n): customClasses.set(toSetName, cls);
if(hasAlias) {
customClasses.set(oldName, cls); // Allow usage in the same module
beforeAlias = null;
setAlias = null;
}
case EImport(c, n, isUsing):
if (!importEnabled) if (!importEnabled)
return null; return null;
var splitClassName = [for (e in c.split(".")) e.trim()]; var splitClassName = [for (e in c.split(".")) e.trim()];
@@ -660,8 +735,21 @@ class Interp {
var oldClassName = realClassName; var oldClassName = realClassName;
var oldSplitName = splitClassName.copy(); 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; return null;
}
if(customClasses.exists(toSetName)) { // custom class is already parsed and imported
// NOTE: you will need to create/import
// the custom class first before
// setting the extension
if(isUsing && !usingHandler.entryExists(toSetName))
setCustomClassUsing(toSetName, customClasses.get(toSetName));
return null;
}
var realClassName = getLocalImportRedirect(realClassName); var realClassName = getLocalImportRedirect(realClassName);
@@ -697,18 +785,26 @@ class Interp {
} }
if (cl == null && en == null) { if (cl == null && en == null) {
if (importFailedCallback == null || !importFailedCallback(oldSplitName)) beforeAlias = claVarName;
setAlias = n;
if (importFailedCallback == null || !importFailedCallback(oldSplitName)){
beforeAlias = null;
setAlias = null;
error(EInvalidClass(oldClassName)); error(EInvalidClass(oldClassName));
}
} else { } else {
if (en != null) { if (en != null) {
if(isUsing) error(EInvalidClass(oldClassName));
// ENUM!!!! // ENUM!!!!
var enumThingy = {}; var enumThingy:HEnum = {};
for (c in en.getConstructors()) { for (c in en.getConstructors()) {
try { try {
UnsafeReflect.setField(enumThingy, c, en.createByName(c)); //UnsafeReflect.setField(enumThingy, c, en.createByName(c));
enumThingy.setEnum(c, en.createByName(c));
} catch(e) { } catch(e) {
try { try {
UnsafeReflect.setField(enumThingy, c, UnsafeReflect.field(en, c)); //UnsafeReflect.setField(enumThingy, c, UnsafeReflect.field(en, c));
enumThingy.setEnum(c, UnsafeReflect.field(en, c));
} catch(ex) { } catch(ex) {
throw e; throw e;
} }
@@ -716,12 +812,81 @@ class Interp {
} }
variables.set(toSetName, enumThingy); variables.set(toSetName, enumThingy);
} else { } else {
if(isUsing) setUsing(toSetName, cl);
variables.set(toSetName, cl); variables.set(toSetName, cl);
} }
} }
return null; return null;
case EEnum(en, _): // TODO: enum abstracts
var enumThingy:HEnum = {};
var enumName = en.name;
var enumFields = en.fields;
for (i => ef in enumFields) {
var fieldName = ef.name;
if(ef.args.length < 1) {
var enumValue:HEnumValue = {
enumName: enumName,
fieldName: fieldName,
index: i,
args: []
}
enumThingy.setEnum(fieldName, enumValue);
}
else {
var params = ef.args;
var hasOpt = false, minParams = 0;
for (p in params) {
if (p.opt)
hasOpt = true;
else
minParams++;
}
var f = function(args:Array<Dynamic>):HEnumValue {
if (((args == null) ? 0 : args.length) != params.length) {
if (args.length < minParams) {
var str = "Invalid number of parameters. Got " + args.length + ", required " + minParams;
if (enumName != null)
str += " for enum '" + enumName + "'";
error(ECustom(str));
}
// make sure mandatory args are forced
var args2 = [];
var extraParams = args.length - minParams;
var pos = 0;
for (p in params)
if (p.opt) {
if (extraParams > 0) {
args2.push(args[pos++]);
extraParams--;
} else
args2.push(null);
} else
args2.push(args[pos++]);
args = args2;
}
return {
enumName: enumName,
fieldName: fieldName,
index: i,
args: args
};
};
var f = Reflect.makeVarArgs(f);
enumThingy.setEnum(fieldName, f);
}
}
variables.set(en.name, enumThingy);
case ECast(e, _): // TODO
return expr(e);
case ERegex(e, f):
return new EReg(e, f);
case EConst(c): case EConst(c):
switch (c) { switch (c) {
case CInt(v): return v; case CInt(v): return v;
@@ -730,9 +895,30 @@ class Interp {
} }
case EIdent(id): case EIdent(id):
return resolve(id); return resolve(id);
case EVar(n, _, e, isPublic, isStatic): case EVar(n, _, e, isPublic, isStatic, _, isFinal, _, getter, setter, isVar):
var hasGetSet:Bool = (getter != null || setter != null);
if(depth > 0 && hasGetSet) {
error(ECustom("Property Accessor for local variables is not allowed"));
return null;
}
declared.push({n: n, old: locals.get(n), depth: depth}); declared.push({n: n, old: locals.get(n), depth: depth});
locals.set(n, {r: (e == null) ? null : expr(e), depth: depth}); var r:Dynamic = (e == null) ? null : expr(e);
var declProp:Property = null;
if (hasGetSet) {
declProp = {
r: r,
getter: getter,
setter: setter,
isVar: isVar,
isStatic: isStatic,
interp: this,
}
}
var declVar:DeclaredVar = {
r: (declProp == null) ? r : declProp,
depth: depth
};
locals.set(n, declVar);
if (depth == 0) { if (depth == 0) {
if(allowStaticVariables && isStatic == true) { if(allowStaticVariables && isStatic == true) {
if(!staticVariables.exists(n)) // make it so it only sets it once if(!staticVariables.exists(n)) // make it so it only sets it once
@@ -779,7 +965,7 @@ class Interp {
error(EInvalidOp(op)); error(EInvalidOp(op));
} }
case ECall(e, params): case ECall(e, params):
var args:Array<Dynamic> = [for(p in params) expr(p)]; var args:Array<Dynamic> = makeArgs(params);
switch (Tools.expr(e)) { switch (Tools.expr(e)) {
case EField(e, f, s): case EField(e, f, s):
@@ -995,10 +1181,8 @@ class Interp {
} else { } else {
return arr[index]; return arr[index];
} }
case ENew(cl, params): case ENew(cl, params, _):
var a = []; var a:Array<Dynamic> = makeArgs(params);
for (e in params)
a.push(expr(e));
return cnew(cl, a); return cnew(cl, a);
case EThrow(e): case EThrow(e):
throw expr(e); throw expr(e);
@@ -1033,14 +1217,55 @@ class Interp {
case ETernary(econd, e1, e2): case ETernary(econd, e1, e2):
return if (expr(econd) == true) expr(e1) else expr(e2); return if (expr(econd) == true) expr(e1) else expr(e2);
case ESwitch(e, cases, def): case ESwitch(e, cases, def):
var old = declared.length;
var val:Dynamic = expr(e); var val:Dynamic = expr(e);
var match = false; var match = false;
for (c in cases) { for (c in cases) {
for (v in c.values) for (v in c.values) {
// https://github.com/FunkinCrew/hscript/blob/funkin-dev/hscript/Interp.hx#L531
switch (Tools.expr(v)) {
case ECall(e, params):
switch (Tools.expr(e)) {
case EField(_, f):
var isScripted:Bool = val is HEnumValue;
var valStr:String = '';
var valEnum:HEnumValue = null;
if(isScripted) {
valEnum = cast val;
valStr = valEnum.fieldName;
}
else {
valStr = cast val;
valStr = valStr.substring(0, valStr.indexOf("("));
}
if(valStr == f) {
var valParams = isScripted ? valEnum.getConstructorArgs() : Type.enumParameters(val);
for (i => p in params) {
switch (Tools.expr(p)) {
case EIdent(n):
declared.push({
n: n,
old: {r: locals.get(n), depth: depth},
depth: depth
});
locals.set(n, {r: valParams[i], depth: depth});
default:
}
}
match = true;
break;
}
default:
}
default:
if (expr(v) == val) { if (expr(v) == val) {
match = true; match = true;
break; break;
} }
}
}
if (match) { if (match) {
val = expr(c.expr); val = expr(c.expr);
break; break;
@@ -1048,6 +1273,7 @@ class Interp {
} }
if (!match) if (!match)
val = def == null ? null : expr(def); val = def == null ? null : expr(def);
restore(old);
return val; return val;
case EMeta(a, b, e): case EMeta(a, b, e):
var oldAccessor = isBypassAccessor; var oldAccessor = isBypassAccessor;
@@ -1067,17 +1293,8 @@ class Interp {
function doWhileLoop(econd:Expr, e:Expr):Void { function doWhileLoop(econd:Expr, e:Expr):Void {
var old = declared.length; var old = declared.length;
do { do {
try { if (!loopRun(() -> expr(e)))
expr(e);
} catch (err:Stop) {
switch (err) {
case SContinue:
case SBreak:
break; break;
case SReturn:
throw err;
}
}
} while (expr(econd) == true); } while (expr(econd) == true);
restore(old); restore(old);
} }
@@ -1085,41 +1302,52 @@ class Interp {
function whileLoop(econd:Expr, e:Expr):Void { function whileLoop(econd:Expr, e:Expr):Void {
var old = declared.length; var old = declared.length;
while (expr(econd) == true) { while (expr(econd) == true) {
try { if (!loopRun(() -> expr(e)))
expr(e);
} catch (err:Stop) {
switch (err) {
case SContinue:
case SBreak:
break; break;
case SReturn:
throw err;
}
}
} }
restore(old); restore(old);
} }
function makeIterator(v:Dynamic, ?allowKeyValue = false):Iterator<Dynamic> { function makeIterator(v:Dynamic, ?allowKeyValue = false):Iterator<Dynamic> {
#if ((flash && !flash9) || (php && !php7 && haxe_ver < '4.0.0')) #if js
if (v.iterator != null) // don't use try/catch (very slow)
if(v is Array) {
return allowKeyValue ? (v:Array<Dynamic>).keyValueIterator() : (v:Array<Dynamic>).iterator();
}
if(allowKeyValue && v.keyValueIterator != null)
v = v.keyValueIterator();
else if (v.iterator != null)
v = v.iterator(); v = v.iterator();
#else #else
if(allowKeyValue) { if(allowKeyValue)
try try v = v.keyValueIterator() catch (e:Dynamic) {};
v = v.keyValueIterator()
catch (e:Dynamic) {}; if(v.hasNext == null || v.next == null)
try v = v.iterator() catch (e:Dynamic) {};
#end
if (v.hasNext == null || v.next == null) error(EInvalidIterator(v));
return v;
} }
if(v.hasNext == null || v.next == null) { function makeArgs(params:Array<Expr>):Array<Dynamic> {
try var args:Array<Dynamic> = [];
v = v.iterator() for (p in params) {
catch (e:Dynamic) {}; switch (Tools.expr(p)) {
case EIdent(id):
var ident = resolve(id);
if (ident is CustomClass) {
var customClass:CustomClass = cast ident; // Pass the underlying superclass if exist
args.push(customClass.__superClass != null ? customClass.getSuperclass() : customClass);
} else {
args.push(ident);
} }
#end default:
if (v.hasNext == null || v.next == null) args.push(expr(p));
error(EInvalidIterator(v)); }
return v; }
return args;
} }
function forLoop(n:String, it:Expr, e:Expr, ?ithv:String):Void { function forLoop(n:String, it:Expr, e:Expr, ?ithv:String):Void {
@@ -1136,19 +1364,26 @@ class Interp {
if(isKeyValue) if(isKeyValue)
locals.set(ithv, {r: next.key, depth: depth}); locals.set(ithv, {r: next.key, depth: depth});
locals.set(n, {r: isKeyValue ? next.value : next, depth: depth}); locals.set(n, {r: isKeyValue ? next.value : next, depth: depth});
if (!loopRun(() -> expr(e)))
break;
}
restore(old);
}
inline function loopRun(f:Void -> Void) {
var cont = true;
try { try {
expr(e); f();
} catch (err:Stop) { } catch (err:Stop) {
switch (err) { switch (err) {
case SContinue: case SContinue:
case SBreak: case SBreak:
break; cont = false;
case SReturn: case SReturn:
throw err; throw err;
} }
} }
} return cont;
restore(old);
} }
inline function isMap(o:Dynamic):Bool { inline function isMap(o:Dynamic):Bool {
@@ -1195,7 +1430,7 @@ class Interp {
if (o == null) if (o == null)
error(EInvalidAccess(f)); error(EInvalidAccess(f));
var cls = Type.getClass(o); var cls:Null<Class<Dynamic>> = useRedirects ? Type.getClass(o) : null;
if (useRedirects && { if (useRedirects && {
var cl:Null<String> = getClassType(o, cls); var cl:Null<String> = getClassType(o, cls);
cl != null && getRedirects.exists(cl) && (_getRedirect = getRedirects[cl]) != null; cl != null && getRedirects.exists(cl) && (_getRedirect = getRedirects[cl]) != null;
@@ -1203,17 +1438,6 @@ class Interp {
return _getRedirect(o, f); return _getRedirect(o, f);
} }
if(o is IHScriptCustomClassBehaviour) {
var obj:IHScriptCustomClassBehaviour = cast o;
if(isBypassAccessor) {
obj.__allowSetGet = false;
var res = obj.hget(f);
obj.__allowSetGet = true;
return res;
}
return obj.hget(f);
}
if (o is IHScriptCustomAccessBehaviour) { if (o is IHScriptCustomAccessBehaviour) {
var obj:IHScriptCustomAccessBehaviour = cast o; var obj:IHScriptCustomAccessBehaviour = cast o;
if(isBypassAccessor) { if(isBypassAccessor) {
@@ -1229,16 +1453,28 @@ class Interp {
var obj:IHScriptCustomBehaviour = cast o; var obj:IHScriptCustomBehaviour = cast o;
return obj.hget(f); return obj.hget(f);
} }
var v = null; var v:Null<Dynamic> = null;
if(isBypassAccessor) { if(isBypassAccessor) {
if ((v = UnsafeReflect.field(o, f)) == null) if ((v = UnsafeReflect.field(o, f)) == null && useRedirects)
v = Reflect.field(cls, f); v = Reflect.field(cls, f);
} }
if(v == null) { if(v == null) {
if ((v = UnsafeReflect.getProperty(o, f)) == null) #if php
// https://github.com/HaxeFoundation/haxe/issues/4915
try {
if ((v = UnsafeReflect.getProperty(o, f)) == null && useRedirects)
v = Reflect.getProperty(cls, f); v = Reflect.getProperty(cls, f);
} }
catch(e:Dynamic) {
if ((v = UnsafeReflect.field(o, f)) == null && useRedirects)
v = Reflect.field(cls, f);
}
#else
if ((v = UnsafeReflect.getProperty(o, f)) == null && useRedirects)
v = Reflect.getProperty(cls, f);
#end
}
return v; return v;
} }
@@ -1252,17 +1488,6 @@ class Interp {
}) })
return _setRedirect(o, f, v); return _setRedirect(o, f, v);
if(o is IHScriptCustomClassBehaviour) {
var obj:IHScriptCustomClassBehaviour = cast o;
if(isBypassAccessor) {
obj.__allowSetGet = false;
var res = obj.hset(f, v);
obj.__allowSetGet = true;
return res;
}
return obj.hset(f, v);
}
if (o is IHScriptCustomAccessBehaviour) { if (o is IHScriptCustomAccessBehaviour) {
var obj:IHScriptCustomAccessBehaviour = cast o; var obj:IHScriptCustomAccessBehaviour = cast o;
if(isBypassAccessor) { if(isBypassAccessor) {
@@ -1287,17 +1512,106 @@ class Interp {
return v; return v;
} }
function fcall(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic { // STATIC EXTENSION ("USING")
if(_hasScriptObject && o == CustomClassHandler.staticHandler) {
return UnsafeReflect.callMethodUnsafe(scriptObject, UnsafeReflect.field(scriptObject, "_HX_SUPER__" + f), args); // Real class static extension
function setUsing(name:String, obj:Dynamic) {
if (usingHandler.entryExists(name)) return;
if (UsingHandler.defaultExtension.exists(name)) {
var us:UsingEntry = UsingHandler.defaultExtension.get(name);
usingHandler.usingEntries.set(name, us);
return;
} }
return call(o, get(o, f), args); if (obj == null) error(ECustom("Unknown using class " + name));
var fn:Dynamic->String->Array<Dynamic>->Dynamic = null;
var fields:Array<String> = [];
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>) {
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 {
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, [o].concat(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) {
if (o == scriptObject.__superClass) {
if (scriptObject.__superClass is CustomClass)
return cast(scriptObject.__superClass, CustomClass).call(f, args, true);
else
return UnsafeReflect.callMethodUnsafe(scriptObject.__superClass, UnsafeReflect.field(scriptObject.__superClass, '_HX_SUPER__$f'), args);
}
}
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
#if js
if (func == null && f == "contains") {
func = get(o, "includes");
}
#end
return call(o, func, args);
} }
function call(o:Dynamic, f:Dynamic, args:Array<Dynamic>):Dynamic { function call(o:Dynamic, f:Dynamic, args:Array<Dynamic>):Dynamic {
if(f == CustomClassHandler.staticHandler) {
return null;
}
return UnsafeReflect.callMethodSafe(o, f, args); return UnsafeReflect.callMethodSafe(o, f, args);
} }
@@ -1308,8 +1622,7 @@ class Interp {
if (c is IHScriptCustomConstructor) { if (c is IHScriptCustomConstructor) {
var c:IHScriptCustomConstructor = cast c; var c:IHScriptCustomConstructor = cast c;
return c.hnew(args); return c.hnew(args);
} else { } else
return Type.createInstance(c, args); return Type.createInstance(c, args);
} }
} }
}
+2
View File
@@ -127,6 +127,8 @@ class Macro {
tf.push( { name : f.name, meta : meta, doc : null, access : [], kind : FVar(convertType(f.t), null), pos : p } ); tf.push( { name : f.name, meta : meta, doc : null, access : [], kind : FVar(convertType(f.t), null), pos : p } );
} }
TAnonymous(tf); TAnonymous(tf);
case CTExpr(_):
throw "assert";
}; };
} }
+415 -22
View File
@@ -20,9 +20,10 @@
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
package hscript; package hscript;
import haxe.rtti.Meta;
import hscript.Expr; import hscript.Expr;
using StringTools;
enum Token { enum Token {
TEof; TEof;
TConst( c : Const ); TConst( c : Const );
@@ -42,6 +43,7 @@ enum Token {
TDoubleDot; TDoubleDot;
TMeta( s : String ); TMeta( s : String );
TPrepro( s : String ); TPrepro( s : String );
TRegex( e : String, f:String );
} }
@:structInit @:structInit
@@ -71,6 +73,11 @@ class Parser {
**/ **/
public var allowJSON : Bool; public var allowJSON : Bool;
/**
activate Regular Expression parsing
**/
public var allowRegex : Bool;
/** /**
allow types declarations allow types declarations
**/ **/
@@ -97,6 +104,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;
@@ -112,6 +121,8 @@ class Parser {
#end #end
static inline var regexFlags: String = "igmsu";
public function new() { public function new() {
line = 1; line = 1;
opChars = "+*/-=!><&|^%~"; opChars = "+*/-=!><&|^%~";
@@ -132,13 +143,21 @@ class Parser {
]; ];
opPriority = new Map(); opPriority = new Map();
opRightAssoc = new Map(); opRightAssoc = new Map();
#if (haxe >= "4.0.0")
for(i => p in priorities)
for(x in p) {
opPriority.set(x, i);
if( i == 9 ) opRightAssoc.set(x, true);
}
#else
for( i in 0...priorities.length ) for( i in 0...priorities.length )
for( x in priorities[i] ) { for( x in priorities[i] ) {
opPriority.set(x, i); opPriority.set(x, i);
if( i == 9 ) opRightAssoc.set(x, true); if( i == 9 ) opRightAssoc.set(x, true);
} }
#end
for( x in ["!", "++", "--", "~"] ) // unary "-" handled in parser directly! for( x in ["!", "++", "--", "~"] ) // unary "-" handled in parser directly!
opPriority.set(x, x == "++" || x == "--" ? -1 : -2); opPriority.set(x, (x == "++" || x == "--") ? -1 : -2);
} }
public inline function error( err:#if hscriptPos ErrorDef #else Error #end, pmin:Int, pmax:Int ) { public inline function error( err:#if hscriptPos ErrorDef #else Error #end, pmin:Int, pmax:Int ) {
@@ -272,7 +291,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;
@@ -321,7 +340,7 @@ class Parser {
if( !allowJSON ) if( !allowJSON )
unexpected(tk); unexpected(tk);
switch( c ) { switch( c ) {
case CString(s): id = s; case CString(s, _): id = s;
default: unexpected(tk); default: unexpected(tk);
} }
case TBrClose: case TBrClose:
@@ -352,12 +371,17 @@ class Parser {
var p1 = tokenMin; var p1 = tokenMin;
#end #end
switch( tk ) { switch( tk ) {
case TId(id): case TId(id): // TODO: direct access to classes by typing their path
var e = parseStructure(id, oldPos); var e = parseStructure(id, oldPos);
if( e == null ) if( e == null )
e = mk(EIdent(id)); e = mk(EIdent(id));
return parseExprNext(e); return parseExprNext(e);
case TConst(c): case TConst(c):
switch(c) {
case CString(s, i):
if(i) return parseExprNext(interpolateString(s));
default:
}
return parseExprNext(mk(EConst(c))); return parseExprNext(mk(EConst(c)));
case TPOpen: case TPOpen:
tk = token(); tk = token();
@@ -463,15 +487,25 @@ class Parser {
case TBkOpen: case TBkOpen:
var a = []; var a = [];
tk = token(); tk = token();
var first = 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]) ) {
@@ -487,8 +521,13 @@ class Parser {
} }
return parseExprNext(mk(EArrayDecl(a, nextType), p1)); return parseExprNext(mk(EArrayDecl(a, nextType), p1));
case TMeta(id) if( allowMetadata ): case TMeta(id) if( allowMetadata ):
if(id == ':isVar') isVar = true;
var args = parseMetaArgs(); var args = parseMetaArgs();
return mk(EMeta(id, args, parseExpr()),p1); var e = parseExpr();
isVar = false;
return mk(EMeta(id, args, e),p1);
case TRegex(e, f) if(allowRegex):
return mk(ERegex(e, f), p1);
default: default:
return unexpected(tk); return unexpected(tk);
} }
@@ -574,7 +613,8 @@ class Parser {
return mk(EBinop(op,e1,e),pmin(e1),pmax(e1)); return mk(EBinop(op,e1,e),pmin(e1),pmax(e1));
return switch( expr(e) ) { return switch( expr(e) ) {
case EBinop(op2,e2,e3): case EBinop(op2,e2,e3):
if( opPriority.get(op) <= opPriority.get(op2) && !opRightAssoc.exists(op) ) var delta = opPriority.get(op) - opPriority.get(op2);
if( delta < 0 || (delta == 0 && !opRightAssoc.exists(op)) )
mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3)); mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3));
else else
mk(EBinop(op, e1, e), pmin(e1), pmax(e)); mk(EBinop(op, e1, e), pmin(e1), pmax(e));
@@ -619,7 +659,7 @@ class Parser {
if( semic ) push(TSemicolon); if( semic ) push(TSemicolon);
} }
mk(EIf(cond,e1,e2),p1,(e2 == null) ? tokenMax : pmax(e2)); mk(EIf(cond,e1,e2),p1,(e2 == null) ? tokenMax : pmax(e2));
case "override": case "override": // BIG TODO: OPTIMIZE ALL THIS BELOW UNTIL "inline"
nextIsOverride = true; nextIsOverride = true;
var nextToken = token(); var nextToken = token();
switch(nextToken) { switch(nextToken) {
@@ -827,7 +867,42 @@ class Parser {
} }
} }
var ident = getIdent(); var ident = getIdent();
var get:Null<FieldPropertyAccess> = null;
var set:Null<FieldPropertyAccess> = null;
var hasGetSet:Bool = false;
var oldIsVar:Bool = isVar;
var tk = token(); var tk = token();
if( tk == TPOpen) {
if( id == "final" )
unexpected(tk);
var getId = getIdent();
switch (getId) {
case 'default': get = ADefault;
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': set = ADefault;
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 +916,13 @@ 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)); if(isVar) isVar = false;
mk(EVar(ident, t, e, nextIsPublic, nextIsStatic, nextIsPrivate, id == "final", nextIsInline, get, set, oldIsVar), p1, (e == null) ? tokenMax : pmax(e));
case "while": case "while":
var econd = parseExpr(); var econd = parseExpr();
var e = parseExpr(); var e = parseExpr();
@@ -892,11 +972,23 @@ 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 "import": case "package":
var tk = token();
push(tk);
if(tk == TSemicolon)
return mk(EPackage(null), p1);
var pkg:String = parsePath().join('.');
ensure(TSemicolon);
push(TSemicolon);
mk(EPackage(pkg), p1);
case "import" | "using":
var isUsing = id == "using";
var oldReadPos = readPos; var oldReadPos = readPos;
var tk = token(); var tk = token();
switch( tk ) { switch( tk ) {
case TPOpen: case TPOpen:
if(isUsing) error(ECustom('Expected identifier'),tokenMin,tokenMax);
var tok = token(); var tok = token();
switch(tok) { switch(tok) {
case TConst(c): case TConst(c):
@@ -922,6 +1014,7 @@ class Parser {
t = token(); t = token();
if( t != TDot ) { if( t != TDot ) {
if(t.match(TId("as"))) { if(t.match(TId("as"))) {
if(isUsing) error(ECustom('Expected . or ;'),tokenMin,tokenMax);
t = token(); t = token();
switch( t ) { switch( t ) {
case TId(id): case TId(id):
@@ -946,7 +1039,7 @@ class Parser {
ensure(TSemicolon); ensure(TSemicolon);
push(TSemicolon); push(TSemicolon);
var p = path.join("."); var p = path.join(".");
mk(EImport(p, asname),p1); mk(EImport(p, asname, isUsing),p1);
default: default:
unexpected(tk); unexpected(tk);
null; null;
@@ -1034,6 +1127,65 @@ class Parser {
push(tk); push(tk);
mk(EClass(name, fields, extend, interfaces, nextIsFinal, nextIsPrivate), p1); mk(EClass(name, fields, extend, interfaces, nextIsFinal, nextIsPrivate), p1);
case "enum": // TODO: enum abstract
var name = getIdent();
ensure(TBrOpen);
// TODO: optimize this
var fields:Array<EnumField> = [];
var fieldName:String = '';
var enumArgs:Array<Argument> = null;
//var tk = token();
while(!maybe(TBrClose)) {
var tk = token();
switch(tk) {
//case TBrClose:
// break;
case TSemicolon | TComma:
if(fieldName.trim().length == 0) continue;
fields.push({
name: fieldName,
args: enumArgs == null ? [] : enumArgs
});
fieldName = '';
enumArgs = null;
case TPOpen:
if(enumArgs != null) {
error(ECustom("Cannot have multiple argument lists in one enum constructor"), tokenMin, tokenMax);
break;
}
enumArgs = parseFunctionArgs(true);
default:
if(fieldName.trim().length != 0) {
error(ECustom("Expected comma or semicolon"), tokenMin, tokenMax);
break;
}
push(tk);
fieldName = getIdent();
}
}
mk(EEnum({ name: name, fields: fields }, false), p1);
case "cast":
var tk = token();
var e:Expr = null;
var t:CType = null; // null if is unsafe cast
switch(tk) {
case TPOpen:
e = parseExpr();
ensure(TComma);
t = parseType();
ensure(TPClose);
default:
push(tk);
e = parseExpr();
}
mk(ECast(e, t), p1);
case "return": case "return":
var tk = token(); var tk = token();
push(tk); push(tk);
@@ -1041,12 +1193,47 @@ class Parser {
mk(EReturn(e),p1,if( e == null ) tokenMax else pmax(e)); mk(EReturn(e),p1,if( e == null ) tokenMax else pmax(e));
case "new": case "new":
var a = []; var a = [];
var params:Null<Array<CType>> = null;
a.push(getIdent()); a.push(getIdent());
while( true ) { while( true ) {
var tk = token(); var tk = token();
switch( tk ) { switch( tk ) {
case TDot: case TDot:
a.push(getIdent()); a.push(getIdent());
case TOp(op) :
if( op == '<' ) {
params = [];
while(true) {
switch(token()) {
case TConst(c):
params.push(CTExpr(mk(EConst(c))));
case tk:
push(tk);
params.push(parseType());
}
tk = token();
switch(tk) {
case TComma: continue;
case TOp(op):
if( op == ">") break;
if( op.charCodeAt(0) == ">".code ) {
#if hscriptPos
tokens.add({ t : TOp(op.substr(1)), min : tokenMax - op.length - 1, max : tokenMax });
#else
tokens.add(TOp(op.substr(1)));
#end
break;
}
default:
}
unexpected(tk);
break;
}
}
else {
unexpected(tk);
break;
}
case TPOpen: case TPOpen:
break; break;
default: default:
@@ -1055,7 +1242,7 @@ class Parser {
} }
} }
var args = parseExprList(TPClose); var args = parseExprList(TPClose);
mk(ENew(a.join("."), args), p1); mk(ENew(a.join("."), args, params), p1);
case "throw": case "throw":
var e = parseExpr(); var e = parseExpr();
mk(EThrow(e),p1,pmax(e)); mk(EThrow(e),p1,pmax(e));
@@ -1185,6 +1372,8 @@ class Parser {
return parseExprNext(mk(EUnop(op,false,e1),pmin(e1))); return parseExprNext(mk(EUnop(op,false,e1),pmin(e1)));
} }
return makeBinop(op,e1,parseExpr()); return makeBinop(op,e1,parseExpr());
case TId(op) if( opPriority.exists(op) ):
return parseExprNext(makeBinop(op,e1,parseExpr()));
case TDot | TQuestionDot: case TDot | TQuestionDot:
var field = getIdent(); var field = getIdent();
return parseExprNext(mk(EField(e1, field, tk == TQuestionDot), pmin(e1))); return parseExprNext(mk(EField(e1, field, tk == TQuestionDot), pmin(e1)));
@@ -1205,7 +1394,7 @@ class Parser {
} }
} }
function parseFunctionArgs():Array<Argument> { function parseFunctionArgs(?enumArgs:Bool = false):Array<Argument> {
var args:Array<Argument> = []; var args:Array<Argument> = [];
var tk = token(); var tk = token();
if( tk != TPClose ) { if( tk != TPClose ) {
@@ -1231,6 +1420,7 @@ class Parser {
if( maybe(TDoubleDot) ) if( maybe(TDoubleDot) )
arg.t = parseType(); arg.t = parseType();
if( maybe(TOp("="))) { if( maybe(TOp("="))) {
if(enumArgs) unexpected(TOp("="));
arg.value = parseExpr(); arg.value = parseExpr();
arg.opt = true; arg.opt = true;
} }
@@ -1289,7 +1479,13 @@ class Parser {
if( op == "<" ) { if( op == "<" ) {
params = []; params = [];
while( true ) { while( true ) {
switch( token() ) {
case TConst(c):
params.push(CTExpr(mk(EConst(c))));
case tk:
push(tk);
params.push(parseType()); params.push(parseType());
}
t = token(); t = token();
switch( t ) { switch( t ) {
case TComma: continue; case TComma: continue;
@@ -1448,6 +1644,118 @@ class Parser {
return args; return args;
} }
// From hscript-iris: https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Parser.hx#L409
// by inky03
function interpolateString(s:String):Expr {
var exprs:Array<Expr> = [];
var dollarPos:Int = s.indexOf('$');
if(dollarPos == -1)
return mk(EConst(CString(s)));
final singleFirst:EReg = ~/[a-zA-Z_]/i;
final singleExpr:EReg = ~/[a-zA-Z0-9_]/i;
// TODO: optimize this using regex
while(dollarPos > -1) {
var pos:Int = dollarPos;
var pre:String = s.substr(0, pos);
var next:String = s.charAt(++pos);
if(next == '{') {
if (pre != '')
exprs.push(mk(EConst(CString(pre))));
var exprStr:String = '';
var depth:Int = 1;
while (true) {
next = s.charAt(++pos);
if (next == '{') {
depth++;
} else if (next == '}') {
depth--;
}
if (depth < 1)
break;
if (pos >= s.length) {
error(EUnterminatedString, pos, pos);
}
exprStr += next;
}
if (exprStr.trim() == '') {
error(ECustom("Expression cannot be empty"), pos, pos);
}
var prevChar = char;
var prevInput = input;
var prevReadPos = readPos; // a bit stupid innit???
#if hscriptPos
var prevOrigin = origin;
#end
var expr = parseString('($exprStr)' #if hscriptPos, origin #end);
readPos = prevReadPos; // rolling back parser state because otherwise we get problems...
input = prevInput;
char = prevChar;
#if hscriptPos
origin = prevOrigin;
#end
exprs.push(expr);
pos++;
}
else if(singleFirst.match(next)) {
if (pre != '')
exprs.push(mk(EConst(CString(pre))));
var ident: String = '';
while(singleExpr.match(next)) {
ident += next;
next = s.charAt(++pos);
}
exprs.push(mk(EIdent(ident)));
}
else if (next == '$') {
var secondToNext: String = s.charAt(pos);
if (secondToNext == "$") { // if its another dollar, skip...
s = pre + s.substr(pos, pos + 1); // remove $ ahead of the current one
break;
}
exprs.push(mk(EConst(CString(pre + '$'))));
}
s = s.substr(pos++);
dollarPos = s.indexOf('$');
}
if (exprs.length == 0) {
return mk(EConst(CString(s)));
} else {
exprs.push(mk(EConst(CString(s))));
var expr:Null<Expr> = exprs.shift();
while(true) {
if(exprs.length == 0) break;
expr = mk(EBinop('+', expr, exprs.shift()));
}
return expr;
}
}
function checkAccess(get:FieldPropertyAccess, set:FieldPropertyAccess, ?expr:Expr, ?type:CType) {
#if hscriptPos
var p1 = tokenMin;
#end
if(!allowTypes) {
error(ECustom('"allowTypes" must be enabled for Property fields'), p1, pmax(expr));
return;
}
switch([get, set]) {
case [AGet, ASet] | [AGet, ANever] | [ANever, ASet]:
if(isVar && (expr == null && type == null))
error(ECustom('Property requires type-hint or initialization'), p1, pmax(expr));
if(expr != null && !isVar)
error(ECustom("Attempt to assign on field that is not a real variable"), p1, pmax(expr));
else if(type == null && !isVar)
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> {
@@ -1658,8 +1966,9 @@ class Parser {
return StringTools.fastCodeAt(input, readPos++); return StringTools.fastCodeAt(input, readPos++);
} }
function readString( until:Int ):String { function readString( until:Int, regex:Bool = false, interpolate:Bool = false ):String {
var c = 0; var c = 0;
var prev = 0;
var b = new StringBuf(); var b = new StringBuf();
var esc = false; var esc = false;
var old = line; var old = line;
@@ -1706,18 +2015,93 @@ class Parser {
b.addChar(k); b.addChar(k);
default: invalidChar(c); default: invalidChar(c);
} }
} else if( c == 92 ) } else if( c == 92 && !regex)
esc = true; esc = true;
else if( c == until ) else if( c == until ) {
if(regex && prev == 92) {
b.addChar(c);
prev = c;
}
else
break; break;
}
else if (c == 36 && interpolate) { // brace for impact !!
// From hscript-iris: https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Parser.hx#L1665
// by inky03
// TODO: optimize this
b.addChar(c);
var next = readChar();
if (next == 123) {
b.addChar(next);
var depth:Int = 0;
while (true) {
next = readChar();
if (StringTools.isEof(next)) {
error(EUnterminatedString, p1, p1);
}
b.addChar(next);
if (next == "'".code) {
var nextStr:String = readString("'".code, false, true);
for (char in nextStr) {
b.addChar(char);
}
b.addChar("'".code);
next = readChar();
b.addChar(next);
}
if (next == 125) {
depth--;
if (depth < 0)
break;
}
}
} else {
readPos--;
}
}
else { else {
if( c == 10 ) line++; if( c == 10 ) line++;
b.addChar(c); b.addChar(c);
if(regex)
prev = c;
} }
} }
return b.toString(); return b.toString();
} }
function readFlags():String {
if(!allowRegex) return null;
var c = 0;
var b = new StringBuf();
var old = line;
var s = input;
#if hscriptPos
var p1 = readPos - 1;
#end
while(true) {
var c = readChar();
if( StringTools.isEof(c) ) {
line = old;
error(EUnterminatedString, p1, p1);
break;
}
// semicolon
if(c == 59) {
this.char = c;
break;
}
var f = String.fromCharCode(c);
if(regexFlags.indexOf(f) != -1)
b.addChar(c);
else
invalidChar(c);
}
return b.toString();
}
function token():Token { function token():Token {
//function token(?infos : Null<haxe.PosInfos>) { //function token(?infos : Null<haxe.PosInfos>) {
//function ttrace(v:Dynamic, ?infos : Null<haxe.PosInfos>) { //function ttrace(v:Dynamic, ?infos : Null<haxe.PosInfos>) {
@@ -1885,7 +2269,10 @@ class Parser {
case "}".code: return TBrClose; case "}".code: return TBrClose;
case "[".code: return TBkOpen; case "[".code: return TBkOpen;
case "]".code: return TBkClose; case "]".code: return TBkClose;
case "'".code, '"'.code: return TConst( CString(readString(char)) ); case "'".code:
return TConst( CString(readString(char, false, true), true) );
case '"'.code:
return TConst( CString(readString(char), false) );
case "?".code: case "?".code:
char = readChar(); char = readChar();
switch (char) { switch (char) {
@@ -1941,7 +2328,7 @@ class Parser {
invalidChar(char); invalidChar(char);
default: default:
if( ops[char] ) { if( ops[char] ) {
var op = String.fromCharCode(char); var op:String = String.fromCharCode(char);
while( true ) { while( true ) {
char = readChar(); char = readChar();
if( StringTools.isEof(char) ) char = 0; if( StringTools.isEof(char) ) char = 0;
@@ -1951,6 +2338,11 @@ class Parser {
} }
var pop = op; var pop = op;
op += String.fromCharCode(char); op += String.fromCharCode(char);
if( allowRegex && op == "~/") {
var e = readString(char, true);
var f = readFlags();
return TRegex(e, f);
}
if( !opPriority.exists(op) && opPriority.exists(pop) ) { if( !opPriority.exists(op) && opPriority.exists(pop) ) {
if( op == "//" || op == "/*" ) if( op == "//" || op == "/*" )
return tokenComment(op,char); return tokenComment(op,char);
@@ -1966,7 +2358,7 @@ class Parser {
if( StringTools.isEof(char) ) char = 0; if( StringTools.isEof(char) ) char = 0;
if( !idents[char] ) { if( !idents[char] ) {
this.char = char; this.char = char;
if(id == "is") return TOp("is"); //if(id == "is") return TOp("is");
return TId(id); return TId(id);
} }
id += String.fromCharCode(char); id += String.fromCharCode(char);
@@ -2141,7 +2533,7 @@ class Parser {
return switch(c) { return switch(c) {
case CInt(v): Std.string(v); case CInt(v): Std.string(v);
case CFloat(f): Std.string(f); case CFloat(f): Std.string(f);
case CString(s): s; // TODO : escape + quote case CString(s, _): s; // TODO : escape + quote
} }
} }
@@ -2165,6 +2557,7 @@ class Parser {
case TDoubleDot: ":"; case TDoubleDot: ":";
case TMeta(id): "@" + id; case TMeta(id): "@" + id;
case TPrepro(id): "#" + id; case TPrepro(id): "#" + id;
case TRegex(e, f): '~/$e/$f';
} }
} }
+103 -10
View File
@@ -98,6 +98,8 @@ class Printer {
add("("); add("(");
type(t); type(t);
add(")"); add(")");
case CTExpr(e):
expr(e);
} }
} }
@@ -113,26 +115,80 @@ class Printer {
add("??NULL??"); add("??NULL??");
return; return;
} }
switch( #if hscriptPos e.e #else e #end ) { switch(Tools.expr(e)) {
case EImport(c, n): case EPackage(n):
add("import " + c); add('package');
if(n != null)
add(' $n');
add(';\n');
case EImport(c, n, u):
add('${u ? 'using' : 'import'} $c');
if(n != null) if(n != null)
add(' as $n'); add(' as $n');
case EClass(name, fields, extend, interfaces): case EClass(name, fields, extend, interfaces, fnal):
var isFinal = fnal != null && fnal;
if(isFinal)
add('final ');
add('class $name'); add('class $name');
if (extend != null) if (extend != null)
add(' extends $extend'); add(' extends $extend');
for(_interface in interfaces) { for(_interface in interfaces) {
add(' implements $_interface'); add(' implements $_interface');
} }
add(' {\n');
tabs += "\t"; tabs += "\t";
add(" {\n");
for( e in fields ) {
add(tabs);
expr(e);
//add(";\n");
}
//for(field in fields) { //for(field in fields) {
// expr(field); // expr(field);
//} //}
tabs = tabs.substr(1); tabs = tabs.substr(1);
add("}"); add("}");
case EEnum(en, _): // TODO: enum abstracts
add('enum ${en.name}');
if(en.fields.length == 0) {
add(' {}');
return;
}
tabs += "\t";
add(" {\n");
for(e in en.fields) {
add(tabs);
add(e.name);
if(e.args.length > 0) {
add("(");
var first = true;
for( a in e.args ) {
if( first ) first = false else add(", ");
if( a.opt ) add("?");
add(a.name);
addType(a.t);
}
add(')');
}
add(";\n");
}
tabs = tabs.substr(1);
add("}");
case ECast(e, t):
var safe = t != null;
add("cast ");
if(safe) add("(");
expr(e);
if(safe) {
add(", ");
addType(t);
add(")");
}
case ERegex(e, f):
add('~/$e/$f');
add(';\n');
case EConst(c): case EConst(c):
switch( c ) { switch( c ) {
case CInt(i): add(i); case CInt(i): add(i);
@@ -141,8 +197,35 @@ class Printer {
} }
case EIdent(v): case EIdent(v):
add(v); add(v);
case EVar(n, t, e): // TODO: static, public, override case EVar(n, t, e, p, s, pr, isFinal, isInline, get, set, _):
add("var " + n); if(p) add("public ");
else if(pr) add("private ");
if(s) add("static ");
if(isInline) add("inline ");
if(isFinal) add("final " + n);
else add("var " + n);
if(get != null || set != null) {
add("(");
switch(get) {
case ADefault: add("default, ");
case ANull: add("null, ");
case AGet: add("get, ");
case ADynamic: add("dynamic, ");
case ANever: add("never, ");
default:
}
switch(set) {
case ADefault: add("default");
case ANull: add("null");
case ASet: add("set");
case ADynamic: add("dynamic");
case ANever: add("never");
default:
}
add(")");
}
addType(t); addType(t);
if( e != null ) { if( e != null ) {
add(" = "); add(" = ");
@@ -182,7 +265,7 @@ class Printer {
case ECall(e, args): case ECall(e, args):
if( e == null ) if( e == null )
expr(e); expr(e);
else switch( #if hscriptPos e.e #else e #end ) { else switch( Tools.expr(e)) {
case EField(_), EIdent(_), EConst(_): case EField(_), EIdent(_), EConst(_):
expr(e); expr(e);
default: default:
@@ -264,8 +347,18 @@ class Printer {
expr(e); expr(e);
} }
add("]"); add("]");
case ENew(cl, args): case ENew(cl, args, params):
add("new " + cl + "("); add("new " + cl);
if(params != null) {
add("<");
var first = true;
for( p in params ) {
if( first ) first = false else add(", ");
type(p);
}
add(">");
}
add("(");
var first = true; var first = true;
for( e in args ) { for( e in args ) {
if( first ) first = false else add(", "); if( first ) first = false else add(", ");
+128
View File
@@ -0,0 +1,128 @@
package hscript;
import hscript.utils.UnsafeReflect;
import hscript.Interp;
import hscript.Expr.FieldPropertyAccess;
/**
* Special variable that handles 'getter/setter' function calls
* depending of the read/write access combination.
*
* Example:
* ```haxe
* public var myvar(get, set):Int;
* var _myvar:Int = 10;
*
* function get_myvar():Int {
* return _myvar;
* }
*
* function set_myvar(val:Int):Int {
* if(val > 10) return _myvar = val;
* return val;
* }
* ```
*
* @see https://haxe.org/manual/class-field-property.html
*/
@:access(hscript.Interp)
@:structInit
class Property {
private static inline var GET = 'get_';
private static inline var SET = 'set_';
public var r:Dynamic;
public var getter:FieldPropertyAccess;
public var setter:FieldPropertyAccess;
public var isStatic(get, never):Bool;
function get_isStatic() {
return __isStatic && interp.allowStaticVariables;
}
var isVar:Bool;
var interp:Interp;
public function new(r:Dynamic, getter:FieldPropertyAccess, setter:FieldPropertyAccess, isVar:Bool, isStatic:Bool, interp:Interp) {
this.r = r;
this.getter = getter;
this.setter = setter;
this.isVar = isVar;
this.__isStatic = isStatic;
this.interp = interp;
}
var __allowReadAccess:Bool = false;
var __allowWriteAccess:Bool = false;
var __allowSetGet:Null<Bool> = null;
final __isStatic:Bool = false;
public function callGetter(name:String) {
switch (getter) {
case AGet | ADynamic:
var fName:String = '$GET$name';
if (!__allowReadAccess && (__allowSetGet != null && __allowSetGet || !interp.isBypassAccessor)) {
if (varExists(fName)) {
return callAccessor(fName);
} else
interp.error(ECustom('Method $fName required by property $name is missing'));
} else {
if ((setter == ADefault || setter == ANull) || isVar)
return r;
else
interp.error(ECustom('Field $name cannot be accessed because it is not a real variable${interp.isBypassAccessor ? '. Add @:isVar to enable it' : ''}'));
}
case ANever:
interp.error(ECustom('This expression cannot be accessed for reading'));
default:
}
return r;
}
public function callSetter(name:String, val:Dynamic) {
switch (setter) {
case ASet | ADynamic:
var fName:String = '$SET$name';
if (!__allowWriteAccess && (__allowSetGet != null && __allowSetGet || !interp.isBypassAccessor)) {
if (varExists(fName))
return callAccessor(fName, [val], true);
else
interp.error(ECustom('Method $fName required by property $name is missing'));
} else {
if ((getter == ADefault || getter == ANull) || isVar)
return r = val;
else
interp.error(ECustom('Field $name cannot be accessed because it is not a real variable${interp.isBypassAccessor ? '. Add @:isVar to enable it' : ''}'));
}
case ANever:
interp.error(ECustom('This expression cannot be accessed for writing'));
default:
}
return r = val;
}
private function callAccessor(f:String, ?args:Array<Dynamic>, isWrite:Bool = false):Dynamic {
var fn = isStatic ? interp.staticVariables.get(f) : interp.variables.get(f);
var rt:Dynamic = null;
if (fn != null && Reflect.isFunction(fn)) {
if (isWrite) __allowWriteAccess = true;
else __allowReadAccess = true;
rt = UnsafeReflect.callMethodUnsafe(null, fn, args == null ? [] : args);
if (isWrite) __allowWriteAccess = false;
else __allowReadAccess = false;
return rt;
} else
interp.error(ECustom('Method $f required by property ${f.substr(3)} is missing'));
return rt;
}
private function varExists(n:String) {
return isStatic ? interp.staticVariables.exists(n) : interp.variables.exists(n);
}
}
+24 -1
View File
@@ -59,7 +59,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 +91,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: expr(e);
} }
return mk(edef, e); return mk(edef, e);
} }
@@ -111,4 +112,26 @@ class Tools {
#end #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
}
public static inline function isCustomAbstract(obj:Dynamic):Bool
return obj != null && obj is IHScriptAbstractBehaviour;
} }
@@ -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
} }
+156 -37
View File
@@ -10,10 +10,10 @@ import haxe.macro.Type.FieldKind;
import haxe.macro.Type.ClassField; import haxe.macro.Type.ClassField;
import haxe.macro.Type.VarAccess; import haxe.macro.Type.VarAccess;
import haxe.macro.*; import haxe.macro.*;
import Sys;
using StringTools; using StringTools;
// BIG TODO: make typed classes scriptable
class ClassExtendMacro { class ClassExtendMacro {
public static inline final FUNC_PREFIX = "_HX_SUPER__"; public static inline final FUNC_PREFIX = "_HX_SUPER__";
public static inline final CLASS_SUFFIX = "_HSX"; public static inline final CLASS_SUFFIX = "_HSX";
@@ -29,6 +29,7 @@ class ClassExtendMacro {
for(apply in Config.ALLOWED_CUSTOM_CLASSES) { for(apply in Config.ALLOWED_CUSTOM_CLASSES) {
Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.ClassExtendMacro.build())"); Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.ClassExtendMacro.build())");
} }
//Context.onAfterTyping(buildTyped);
#end #end
#end #end
} }
@@ -61,6 +62,7 @@ class ClassExtendMacro {
if(key == "away3d.tools.commands.Weld") return fields; // Error: Unknown identifier if(key == "away3d.tools.commands.Weld") return fields; // Error: Unknown identifier
if(fkey == "hscript.CustomClassHandler.TemplateClass") return fields; // Error: Redefined if(fkey == "hscript.CustomClassHandler.TemplateClass") return fields; // Error: Redefined
if(fkey == "hscript.CustomClassHandler.CustomTemplateClass") return fields; // Error: Redefined if(fkey == "hscript.CustomClassHandler.CustomTemplateClass") return fields; // Error: Redefined
if(fkey == "hscript.CustomClass") return fields; // Error: Redefined
if(key == "sys.thread.EventLoop") return fields; // Error: cant override force inlined if(key == "sys.thread.EventLoop") return fields; // Error: cant override force inlined
if(Config.DISALLOW_CUSTOM_CLASSES.contains(cl.module) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey)) return fields; if(Config.DISALLOW_CUSTOM_CLASSES.contains(cl.module) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey)) return fields;
if(cl.module.contains("_")) return fields; // Weird issue, sorry if(cl.module.contains("_")) return fields; // Weird issue, sorry
@@ -211,6 +213,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))
@@ -246,29 +257,26 @@ class ClassExtendMacro {
overrideExpr = macro { overrideExpr = macro {
var name:String = $v{name}; var name:String = $v{name};
if (__custom__variables != null) { if (__interp != null && __class__fields.contains(name)) {
if(__custom__variables.exists(name)) {
var v:Dynamic = null; var v:Dynamic = null;
if (Reflect.isFunction(v = __custom__variables.get(name))) { if (Reflect.isFunction(v = __interp.variables.get(name))) {
return v($a{arguments}); return v($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 (__interp != null && __class__fields.contains(name)) {
if(__custom__variables.exists(name)) {
var v:Dynamic = null; var v:Dynamic = null;
if (Reflect.isFunction(v = __custom__variables.get(name))) { if (Reflect.isFunction(v = __interp.variables.get(name))) {
v($a{arguments}); v($a{arguments});
return; return;
} }
} }
}
super.$name($a{arguments}); super.$name($a{arguments});
}; };
} }
@@ -331,7 +339,6 @@ class ClassExtendMacro {
pack: cl.pack.copy(), pack: cl.pack.copy(),
name: cl.name name: cl.name
}, [ }, [
{name: "IHScriptCustomAccessBehaviour", 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';
@@ -341,20 +348,27 @@ class ClassExtendMacro {
// Adding hscript getters and setters // Adding hscript getters and setters
shadowClass.fields.push({
name: "__cachedFieldSet",
pos: Context.currentPos(),
kind: FVar(macro: Map<String, Dynamic>),
access: [APublic, AStatic]
});
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({ shadowClass.fields.push({
name: "__custom__variables", name: "__custom__variables",
pos: Context.currentPos(), pos: Context.currentPos(),
kind: FVar(macro: Map<String, Dynamic>), kind: FVar(macro: Map<String, Dynamic>),
access: [APublic] access: [APublic]
}); });
*/
shadowClass.fields.push({ shadowClass.fields.push({
name: "__allowSetGet", name: "__allowSetGet",
pos: Context.currentPos(), pos: Context.currentPos(),
@@ -383,10 +397,7 @@ class ClassExtendMacro {
ret: macro: Dynamic, ret: macro: Dynamic,
params: [], params: [],
expr: macro { expr: macro {
__allowSetGet = false; return null;
var v = __custom__variables.get("get_" + name)();
__allowSetGet = true;
return v;
}, },
args: [ args: [
{ {
@@ -407,10 +418,7 @@ class ClassExtendMacro {
ret: macro: Dynamic, ret: macro: Dynamic,
params: [], params: [],
expr: macro { expr: macro {
__allowSetGet = false; return null;
var v = __custom__variables.get("set_" + name)(val);
__allowSetGet = true;
return v;
}, },
args: [ args: [
{ {
@@ -472,30 +480,80 @@ class ClassExtendMacro {
var hgetField = if(hasHgetInSuper) { var hgetField = if(hasHgetInSuper) {
macro { macro {
if(__allowSetGet && __custom__variables.exists("get_" + name)) if (__interp != null) {
return __callGetter(name); if(__class__fields.contains(name)) {
if (__custom__variables.exists(name)) var v:Dynamic = __interp.variables.get(name);
return __custom__variables.get(name); if(v != null && v is hscript.Property)
return cast(v, hscript.Property).callGetter(name);
return v;
}
else @:privateAccess {
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
while(cls != null) {
if(cls.hasField(name))
return cls.getField(name);
var prev:hscript.CustomClass = cast cls.__upperClass;
if(prev == null)
break;
cls = prev;
}
}
}
return super.hget(name); return super.hget(name);
} }
} else { } else {
macro { macro {
if(__allowSetGet && __custom__variables.exists("get_" + name)) if (__interp != null) {
return __callGetter(name); if(__class__fields.contains(name)) {
if (__custom__variables.exists(name)) var v:Dynamic = __interp.variables.get(name);
return __custom__variables.get(name); if(v != null && v is hscript.Property)
return cast(v, hscript.Property).callGetter(name);
return v;
}
else @:privateAccess {
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
while(cls != null) {
if(cls.hasField(name))
return cls.getField(name);
var prev:hscript.CustomClass = cast cls.__upperClass;
if(prev == null)
break;
cls = prev;
}
}
}
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)) if (__interp != null) {
return __callSetter(name, val); if(__class__fields.contains(name)) {
if (__custom__variables.exists(name)) { var v:Dynamic = __interp.variables.get(name);
__custom__variables.set(name, val); if(v != null && v is hscript.Property)
return cast(v, hscript.Property).callSetter(name, val);
__interp.variables.set(name, val);
return val; return val;
} }
else @:privateAccess {
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
while(cls != null) {
if(cls.hasField(name))
return cls.setField(name, val);
var prev:hscript.CustomClass = cast cls.__upperClass;
if(prev == null)
break;
cls = prev;
}
}
}
if(__real_fields.contains(name)) { if(__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 +562,33 @@ class ClassExtendMacro {
} }
} else { } else {
macro { macro {
if(__allowSetGet && __custom__variables.exists("set_" + name)) if (__interp != null) {
return __callSetter(name, val); if(__class__fields.contains(name)) {
if (__custom__variables.exists(name)) { var v:Dynamic = __interp.variables.get(name);
__custom__variables.set(name, val); if(v != null && v is hscript.Property)
return cast(v, hscript.Property).callSetter(name, val);
__interp.variables.set(name, val);
return val; return val;
} }
else @:privateAccess {
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
while(cls != null) {
if(cls.hasField(name))
return cls.setField(name, val);
var prev:hscript.CustomClass = cast cls.__upperClass;
if(prev == null)
break;
cls = prev;
}
}
}
if(__real_fields.contains(name)) { if(__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); //__custom__variables.set(name, val);
return val; return val;
} }
} }
@@ -579,6 +653,51 @@ 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(__cachedFieldSet != null) {
for(k => v in __cachedFieldSet) {
Reflect.setProperty(this, k, v);
}
__cachedFieldSet.clear();
__cachedFieldSet = null;
}
}
}),
};
}
static function buildTyped(modules:Array<haxe.macro.Type.ModuleType>) {
for(m in modules) {
switch(m) {
case TClassDecl(c):
var cl = c.get();
if (cl.isAbstract || cl.isExtern || cl.isFinal || cl.isInterface)
continue;
if (cl.params.length == 0)
continue;
if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith(CLASS_SUFFIX) && !cl.name.endsWith("_HSC"))
buildTypedClass(cl);
default:
}
}
}
static function buildTypedClass(cl:ClassType) {}
static function buildShadowClass(cl:ClassType) {}
} }
#else #else
class ClassExtendMacro { class ClassExtendMacro {
+82
View File
@@ -0,0 +1,82 @@
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 {
// Predefined static extension classes
public static final defaultExtension:Map<String, UsingEntry> = [
"StringTools" => { // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/iris/Iris.hx#L45
fields: Type.getClassFields(StringTools),
call: 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):
var field = UnsafeReflect.field(StringTools, f);
if (UnsafeReflect.isFunction(field)) UnsafeReflect.callMethodUnsafe(StringTools, field, [o].concat(args)); else null;
default:
null;
}
}
},
"Lambda" => { // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/iris/Iris.hx#L62
fields: Type.getClassFields(Lambda),
call: function(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
if (o != null && o.iterator != null) {
var field = UnsafeReflect.field(Lambda, f);
if (UnsafeReflect.isFunction(field)) {
return UnsafeReflect.callMethodUnsafe(Lambda, field, [o].concat(args));
}
}
return null;
}
}
];
@: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);
}
}