From ed78ed79d9355a007507f0031007ff4e4edcb1dd Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Wed, 23 Jul 2025 19:50:36 -0700 Subject: [PATCH] Scripted Enums --- hscript/Bytes.hx | 2 + hscript/Expr.hx | 13 +++++ hscript/HEnum.hx | 72 +++++++++++++++++++++------- hscript/Interp.hx | 115 +++++++++++++++++++++++++++++++++++++++++++-- hscript/Parser.hx | 53 +++++++++++++++++++-- hscript/Printer.hx | 28 +++++++++++ hscript/Tools.hx | 3 +- 7 files changed, 262 insertions(+), 24 deletions(-) diff --git a/hscript/Bytes.hx b/hscript/Bytes.hx index dd69069..3d1a279 100644 --- a/hscript/Bytes.hx +++ b/hscript/Bytes.hx @@ -128,6 +128,8 @@ class Bytes { // TODO case EClass(_, _, _, _): // TODO + case EEnum(en): + // TODO case EConst(c): doEncodeConst(c); case EIdent(v): diff --git a/hscript/Expr.hx b/hscript/Expr.hx index f866f1e..19c6b79 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -81,6 +81,7 @@ enum Expr { EImport( c : String, ?asname:String, ?isUsing:Bool ); EClass( name:String, fields:Array, ?extend:String, interfaces:Array, ?isFinal:Bool, ?isPrivate:Bool ); + EEnum( en:EnumDecl, ?isAbstract:Bool ); } @:structInit @@ -111,6 +112,18 @@ final class MetadataEntry { typedef Metadata = Array; +@:structInit +final class EnumDecl { + public var name : String; + public var fields : Array; +} + +@:structInit +final class EnumField { + public var name : String; + public var args : Array; +} + enum CType { CTPath( path : Array, ?params : Array ); CTFun( args : Array, ret : CType ); diff --git a/hscript/HEnum.hx b/hscript/HEnum.hx index 5c68c64..3290958 100644 --- a/hscript/HEnum.hx +++ b/hscript/HEnum.hx @@ -2,25 +2,65 @@ package hscript; import hscript.utils.UnsafeReflect; -// TODO: scripted enums +// TODO: EnumTools for scripted enums +/** + * Wrapper class for enums, both for real and scripted. + */ @:structInit -class HEnum implements IHScriptCustomBehaviour{ - private var enumValues(default, null) = {}; +class HEnum implements IHScriptCustomBehaviour { + private var enumValues(default, null) = {}; - public function setEnum(name:String, enumValue:Dynamic):Void { - UnsafeReflect.setField(enumValues, name, enumValue); - } + public function setEnum(name:String, enumValue:Dynamic):Void { + UnsafeReflect.setField(enumValues, name, enumValue); + } - public function getEnum(name:String):Null { - if(Reflect.hasField(enumValues, name)) return Reflect.field(enumValues, name); - return null; - } + public function getEnum(name:String):Null { + if (UnsafeReflect.hasField(enumValues, name)) + return UnsafeReflect.field(enumValues, name); + return null; + } - public function hget(name:String):Dynamic { - return getEnum(name); - } + public function hget(name:String):Dynamic { + return getEnum(name); + } - public function hset(name:String, val:Dynamic):Dynamic { - return null; - } + 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; + + 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 + 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; + } } diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 55042ac..5799445 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -28,6 +28,7 @@ */ package hscript; +import hscript.HEnum.HEnumValue; import haxe.CallStack; import hscript.utils.UsingHandler; import hscript.utils.UnsafeReflect; @@ -817,6 +818,70 @@ class Interp { 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):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 EConst(c): switch (c) { case CInt(v): return v; @@ -1146,14 +1211,55 @@ class Interp { case ETernary(econd, e1, e2): return if (expr(econd) == true) expr(e1) else expr(e2); case ESwitch(e, cases, def): + var old = declared.length; var val:Dynamic = expr(e); var match = false; for (c in cases) { - for (v in c.values) - if (expr(v) == val) { - match = true; - break; + 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) { + match = true; + break; + } } + } + if (match) { val = expr(c.expr); break; @@ -1161,6 +1267,7 @@ class Interp { } if (!match) val = def == null ? null : expr(def); + restore(old); return val; case EMeta(a, b, e): var oldAccessor = isBypassAccessor; diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 046acc0..d0a1261 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -23,6 +23,8 @@ package hscript; import haxe.rtti.Meta; import hscript.Expr; +using StringTools; + enum Token { TEof; TConst( c : Const ); @@ -362,7 +364,7 @@ class Parser { var p1 = tokenMin; #end switch( tk ) { - case TId(id): + case TId(id): // TODO: direct access to classes by typing their path var e = parseStructure(id, oldPos); if( e == null ) e = mk(EIdent(id)); @@ -643,7 +645,7 @@ class Parser { if( semic ) push(TSemicolon); } 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; var nextToken = token(); switch(nextToken) { @@ -1101,6 +1103,50 @@ class Parser { push(tk); 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 = []; + var fieldName:String = ''; + var enumArgs:Array = null; + var tk = token(); + + while(!maybe(TBrClose)) { + 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 "return": var tk = token(); push(tk); @@ -1309,7 +1355,7 @@ class Parser { } } - function parseFunctionArgs():Array { + function parseFunctionArgs(?enumArgs:Bool = false):Array { var args:Array = []; var tk = token(); if( tk != TPClose ) { @@ -1335,6 +1381,7 @@ class Parser { if( maybe(TDoubleDot) ) arg.t = parseType(); if( maybe(TOp("="))) { + if(enumArgs) unexpected(TOp("=")); arg.value = parseExpr(); arg.opt = true; } diff --git a/hscript/Printer.hx b/hscript/Printer.hx index 58bee51..8039bef 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -141,6 +141,34 @@ class Printer { // expr(field); //} + tabs = tabs.substr(1); + 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 EConst(c): diff --git a/hscript/Tools.hx b/hscript/Tools.hx index 9dd47a2..429e474 100644 --- a/hscript/Tools.hx +++ b/hscript/Tools.hx @@ -59,7 +59,7 @@ class Tools { if( def != null ) f(def); case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e); case ECheckType(e,_): f(e); - + default: } } @@ -91,6 +91,7 @@ class Tools { case ECheckType(e,t): ECheckType(f(e), t); case EImport(c): EImport(c); case EClass(name, el, extend, interfaces): EClass(name, [for( e in el ) f(e)], extend, interfaces); + default: expr(e); } return mk(edef, e); }