From 172fdc9f6763ff507c1c985a6d1de7d9dd133418 Mon Sep 17 00:00:00 2001 From: Yoshman29 <81191621+YoshiCrafter29@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:34:41 +0200 Subject: [PATCH] early custom classes --- source/funkin/game/Character.hx | 1 - source/funkin/macros/ScriptsMacro.hx | 1 + source/funkin/macros/SoftcodedClassMacro.hx | 126 ++++++++++++++++++-- source/funkin/scripting/HScript.hx | 61 ++++++++-- source/funkin/scripting/Script.hx | 65 +++++++++- source/funkin/utils/CoolUtil.hx | 4 + 6 files changed, 238 insertions(+), 20 deletions(-) diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index f67e7bab..be8435a4 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -27,7 +27,6 @@ using StringTools; @:allow(funkin.desktop.editors.CharacterEditor) @:allow(funkin.game.StrumLine) @:allow(funkin.game.PlayState) -@:build(funkin.macros.SoftcodedClassMacro.build()) class Character extends FunkinSprite implements IBeatReceiver implements IOffsetCompatible { private var __stunnedTime:Float = 0; diff --git a/source/funkin/macros/ScriptsMacro.hx b/source/funkin/macros/ScriptsMacro.hx index 16dbaf85..f75ee6f9 100644 --- a/source/funkin/macros/ScriptsMacro.hx +++ b/source/funkin/macros/ScriptsMacro.hx @@ -16,6 +16,7 @@ class ScriptsMacro { // FOR ABSTRACTS Compiler.addGlobalMetadata('haxe.xml', '@:build(hscript.UsingHandler.build())'); + Compiler.addGlobalMetadata('haxe.CallStack', '@:build(hscript.UsingHandler.build())'); } } #end \ No newline at end of file diff --git a/source/funkin/macros/SoftcodedClassMacro.hx b/source/funkin/macros/SoftcodedClassMacro.hx index 5c8fba21..a3e7068e 100644 --- a/source/funkin/macros/SoftcodedClassMacro.hx +++ b/source/funkin/macros/SoftcodedClassMacro.hx @@ -1,19 +1,35 @@ package funkin.macros; +using StringTools; class SoftcodedClassMacro { #if macro public static function init() { trace("cock and balls"); - // Compiler.addGlobalMetadata('funkin.game', '@:build(funkin.macros.SoftcodedClassMacro.build())'); + Compiler.addGlobalMetadata('funkin', '@:build(funkin.macros.SoftcodedClassMacro.build())'); + Compiler.addGlobalMetadata('flixel', '@:build(funkin.macros.SoftcodedClassMacro.build())'); } public static function build():Array { var fields = Context.getBuildFields(); var cl = Context.getLocalClass(); - var funcs:Array = []; if (cl != null) { + var scriptClassType = TPath({ + pack: ['funkin', 'scripting'], + name: 'Script', + sub: 'ScriptClass' + }); + + var funcs:Array = []; + funcs.push({ + pos: Context.currentPos(), + name: '__scriptClass', + kind: FVar(scriptClassType, macro null) + }); var c = cl.get(); + trace(c.name); + if (c.name.endsWith('__Softcoded')) return fields; + if (c.params.length > 0) return fields; // NOT SUPPORTED if (c.isAbstract) return fields; for(m in c.meta.get()) { @@ -25,7 +41,19 @@ class SoftcodedClassMacro { } } - for(f in fields) { + var shadowClass = macro class { + + }; + + shadowClass.name = '${c.name}__Softcoded'; + shadowClass.fields = funcs; + shadowClass.pack = c.pack; // todo + shadowClass.kind = TDClass({ + pack: c.pack, + name: c.name + }, [], false, false, false); + + for(f in [for(f in fields) f]) { var valid = true; if (f.access != null) for(e in f.access) { switch (e) { @@ -56,12 +84,60 @@ class SoftcodedClassMacro { }] : []) }}; }; + + var isVoid = fun.ret.match(TPath({name: "Void"})); + + if (!isVoid) + funcExpr = macro return ${funcExpr}; var accessors = []; if (f.access != null) for(f in f.access) accessors.push(f); accessors.remove(AOverride); - if (f.name != "new") { + if (f.name == "new") { + var constructorExpr = macro { + ${{ + expr: ECall({ + pos: Context.currentPos(), + expr: EConst(CIdent("super")) + }, fun.args != null ? [for(a in fun.args) { + pos: Context.currentPos(), + expr: EConst(CIdent(a.name)) + }] : []), + pos: Context.currentPos() + }} + + __scriptClass = scrObj; + + __scriptClass.call("new", ${{ + pos: Context.currentPos(), + expr: EArrayDecl([for(a in fun.args) { + pos: Context.currentPos(), + expr: EConst(CIdent(a.name)) + }]) + }}, this); + + } + var func:Function = { + expr: constructorExpr, + args: [for(a in fun.args) a] + }; + func.args.insert(0, { + name: "scrObj", + type: scriptClassType + }); + funcs.push({ + pos: Context.currentPos(), + name: 'new', + kind: FFun(func), + access: [for(a in accessors) a] + }); + + + var printer = new Printer(); + + continue; + } else { var func:Function = { expr: funcExpr, args: fun.args @@ -70,13 +146,13 @@ class SoftcodedClassMacro { pos: Context.currentPos(), name: '__super__${f.name}', kind: FFun(func), - access: accessors + access: [for(a in accessors) a] }); } accessors.push(AOverride); - var overrideFuncExpr = macro { + var overrideFuncExpr = isVoid ? macro { var fieldName = ${{ pos: Context.currentPos(), expr: EConst(CString(f.name, DoubleQuotes)) @@ -88,7 +164,21 @@ class SoftcodedClassMacro { pos: Context.currentPos(), expr: EConst(CIdent(a.name)) }]) - }}); + }}, this); + } else ${funcExpr} + } : macro { + var fieldName = ${{ + pos: Context.currentPos(), + expr: EConst(CString(f.name, DoubleQuotes)) + }}; + if (__scriptClass != null && __scriptClass.hasField(fieldName) && Reflect.isFunction(__scriptClass.get(fieldName))) { + return __scriptClass.call(fieldName, ${{ + pos: Context.currentPos(), + expr: EArrayDecl([for(a in fun.args) { + pos: Context.currentPos(), + expr: EConst(CIdent(a.name)) + }]) + }}, this); } else ${funcExpr} }; @@ -103,14 +193,28 @@ class SoftcodedClassMacro { access: accessors }); - - - var printer = new Printer(); - trace(printer.printField(funcs[funcs.length - 1])); default: } } + + var imports = [for(i in Context.getLocalImports()) i]; + imports.push({ + path: [for(p in c.module.split(".")) { + pos: Context.currentPos(), + name: p + }], + mode: IAll + }); + imports.push({ + path: [for(p in c.module.split(".")) { + pos: Context.currentPos(), + name: p + }], + mode: INormal + }); + + Context.defineModule(c.module + "__Softcoded", [shadowClass], imports); } return fields; } diff --git a/source/funkin/scripting/HScript.hx b/source/funkin/scripting/HScript.hx index c30dc966..be6f6761 100644 --- a/source/funkin/scripting/HScript.hx +++ b/source/funkin/scripting/HScript.hx @@ -1,5 +1,6 @@ package funkin.scripting; +import haxe.io.Path; import funkin.scripting.Script.ScriptClass; import hscript.Expr.ClassDecl; import hscript.Expr.ModuleDecl; @@ -14,6 +15,7 @@ class HScript extends Script { public var expr:Expr; public var decls:Array = null; public var code:String; + public var folderlessPath:String; public static function initParser() { var parser = new Parser(); @@ -29,7 +31,10 @@ class HScript extends Script { code = Assets.getText(path); parser = initParser(); + folderlessPath = Path.directory(path); + interp.errorHandler = _errorHandler; + interp.importFailedCallback = importFailedCallback; interp.staticVariables = Script.staticVariables; interp.allowStaticVariables = interp.allowPublicVariables = true; @@ -49,6 +54,25 @@ class HScript extends Script { } } + private function importFailedCallback(cl:Array):Bool { + var path = cl.join("/"); + + var scr = Script.create(Paths.script('classes/$path', null, false)); + if (!(scr is DummyScript)) { + // script is valid + var cla = scr.getClass(cl.last()); + if (cla != null) { + interp.variables.set(cl.last(), Script.createCustomClass(cla)); + } else { + interp.variables.set(cl.last(), scr); + } + + return true; + } + + return false; + } + private function _errorHandler(error:Error) { var fn = '$fileName:${error.line}: '; @@ -131,16 +155,18 @@ class HScript extends Script { if (decls == null) { decls = parser.parseModule(code, fileName); } + var imports:Array> = []; for(d in decls) { switch(d) { case DPackage(path): // ignore case DImport(path, everything): - // ignore + imports.push(path); case DClass(c): - if (c.name == name) { - - } + trace(c.name); + trace(name); + if (c.name == name) + return new HScriptClass(c, interp, imports, folderlessPath); case DTypedef(c): // ignore } @@ -221,17 +247,23 @@ class HScriptClass extends ScriptClass { var interp:Interp; - public function new(decl:ClassDecl, sInterp:Interp) { + public function new(decl:ClassDecl, sInterp:Interp, classesToImport:Array>, forderlessPath:String) { super(); this.name = decl.name; + switch(decl.extend) { + case CTPath(p, params): + this.classPath = p.join("."); + default: + + } interp = new Interp(); interp.errorHandler = sInterp.errorHandler; interp.staticVariables = sInterp.staticVariables; - interp.allowStaticVariables = interp.allowPublicVariables = false; - // interp.execute({ e : EBlock(decl.fields), pmin : 0, pmax : 0, origin : fileName, line : 0 }); + interp.allowStaticVariables = interp.allowPublicVariables = true; + interp.importFailedCallback = importFailedCallback; var parser = HScript.initParser(); @@ -246,7 +278,22 @@ class HScriptClass extends ScriptClass { } } + public override function hasField(field:String) { + return interp.variables.exists(field); + } public override function get(field:String) { return interp.variables[field]; } + + public override function set(field:String, v:Dynamic) { + interp.variables[field] = v; + } + + public override function onCall(field:String, parameters:Array, parent:Dynamic) { + interp.scriptObject = parent; + var v = interp.variables[field]; + if (v != null && Reflect.isFunction(v)) + return (parameters != null && parameters.length > 0) ? Reflect.callMethod(null, v, parameters) : v(); + return null; + } } \ No newline at end of file diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx index 22698725..c90bdaa4 100644 --- a/source/funkin/scripting/Script.hx +++ b/source/funkin/scripting/Script.hx @@ -3,6 +3,7 @@ package funkin.scripting; import lime.app.Application; import flixel.util.FlxDestroyUtil.IFlxDestroyable; import haxe.io.Path; +import hscript.IHScriptCustomConstructor; /** * Class used for scripting. @@ -154,6 +155,15 @@ class Script extends FlxBasic implements IFlxDestroyable { return new DummyScript(path); } + public static function createCustomClass(s:ScriptClass) { + var t:Class = cast Type.resolveClass('${s.classPath}__Softcoded'); + if (t == null) { + Logs.trace('Class at ${s.classPath} was not found.', ERROR); + return null; + } + return new ScriptCustomClass(s, t); + } + /** * Creates a new instance of the script class. * @param path @@ -277,11 +287,64 @@ class Script extends FlxBasic implements IFlxDestroyable { } class ScriptClass { + public var classPath:String = null; + public var folderlessPath:String = null; + public function new() {} public function get(field:String):Dynamic {return null;} public function set(field:String, v:Dynamic) {} - public function call(field:String, ?args:Array) {return null;} + public function hasField(field:String):Bool {return false;} + + /** + * Calls the function `func` defined in the class. + * @param func Name of the function + * @param parameters (Optional) Parameters of the function. + * @return Result (if void, then null) + */ + public function call(func:String, parameters:Array, parent:Dynamic):Dynamic { + var result = onCall(func, parameters == null ? [] : parameters, parent); + return result; + } + + public function onCall(func:String, parameters:Array, parent:Dynamic):Dynamic {return null;} + + private function importFailedCallback(cl:Array):Bool { + var path = cl.join("/"); + + var scr = Script.create(Paths.script('$folderlessPath$path', null, true)); + if (!(scr is DummyScript)) { + // script is valid + var cla = scr.getClass(cl.last()); + if (cla != null) { + set(cl.last(), Script.createCustomClass(cla)); + } else { + set(cl.last(), scr); + } + + return true; + } + + return false; + } +} + +class ScriptCustomClass implements IHScriptCustomConstructor { + var scrClass:ScriptClass; + var scrType:Class; + public function new(scrClass:ScriptClass, scrType:Class) { + this.scrClass = scrClass; + this.scrType = scrType; + } + public function hnew(...args:Dynamic):Dynamic { + var a = [for(arg in args) arg]; + a.insert(0, scrClass); + return Type.createInstance(scrType, a); + } +} + +interface IScriptClassInstance { + } \ No newline at end of file diff --git a/source/funkin/utils/CoolUtil.hx b/source/funkin/utils/CoolUtil.hx index c696f143..fba42a43 100644 --- a/source/funkin/utils/CoolUtil.hx +++ b/source/funkin/utils/CoolUtil.hx @@ -21,12 +21,16 @@ import flixel.graphics.frames.FlxFrame; import flixel.graphics.frames.FlxFramesCollection; import flash.geom.ColorTransform; import funkin.chart.Chart; +import haxe.CallStack; using StringTools; @:allow(funkin.game.PlayState) class CoolUtil { + public static function getLastExceptionStack():String { + return CallStack.toString(CallStack.exceptionStack()); + } /* * Returns `v` if not null, `defaultValue` otherwise. * @param v The value