Scripted Enums

This commit is contained in:
Jaime Humberto Macias Bustamante
2025-07-23 19:50:36 -07:00
parent 98b97bef66
commit ed78ed79d9
7 changed files with 262 additions and 24 deletions
+2
View File
@@ -128,6 +128,8 @@ class Bytes {
// TODO
case EClass(_, _, _, _):
// TODO
case EEnum(en):
// TODO
case EConst(c):
doEncodeConst(c);
case EIdent(v):
+13
View File
@@ -81,6 +81,7 @@ enum Expr {
EImport( c : String, ?asname:String, ?isUsing:Bool );
EClass( name:String, fields:Array<Expr>, ?extend:String, interfaces:Array<String>, ?isFinal:Bool, ?isPrivate:Bool );
EEnum( en:EnumDecl, ?isAbstract:Bool );
}
@:structInit
@@ -111,6 +112,18 @@ final class 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 {
CTPath( path : Array<String>, ?params : Array<CType> );
CTFun( args : Array<CType>, ret : CType );
+56 -16
View File
@@ -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<Dynamic> {
if(Reflect.hasField(enumValues, name)) return Reflect.field(enumValues, name);
return null;
}
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 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<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;
}
}
+111 -4
View File
@@ -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<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 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;
+50 -3
View File
@@ -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<EnumField> = [];
var fieldName:String = '';
var enumArgs:Array<Argument> = 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<Argument> {
function parseFunctionArgs(?enumArgs:Bool = false):Array<Argument> {
var args:Array<Argument> = [];
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;
}
+28
View File
@@ -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):
+2 -1
View File
@@ -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);
}