11package de .peeeq .wurstscript .translation .lua .translation ;
22
33import de .peeeq .datastructures .UnionFind ;
4+ import de .peeeq .wurstscript .ast .ClassDef ;
45import de .peeeq .wurstscript .ast .Element ;
56import de .peeeq .wurstscript .ast .FuncDef ;
67import de .peeeq .wurstscript .ast .NameDef ;
8+ import de .peeeq .wurstscript .ast .WParameter ;
79import de .peeeq .wurstscript .ast .WPackage ;
810import de .peeeq .wurstscript .jassIm .*;
911import de .peeeq .wurstscript .luaAst .*;
1517import de .peeeq .wurstscript .utils .Utils ;
1618import org .jetbrains .annotations .NotNull ;
1719
20+ import java .nio .file .Files ;
21+ import java .nio .file .Path ;
22+ import java .nio .file .StandardOpenOption ;
1823import java .util .*;
1924import java .util .regex .Pattern ;
2025import java .util .stream .Stream ;
@@ -88,6 +93,8 @@ public class LuaTranslator {
8893 "leaderboardFromIndex" , "multiboardFromIndex" , "trackableFromIndex" , "lightningFromIndex" ,
8994 "ubersplatFromIndex" , "framehandleFromIndex" , "oskeytypeFromIndex"
9095 );
96+ private static final boolean DEBUG_LUA_DISPATCH = "1" .equals (System .getenv ("WURST_DEBUG_LUA_DISPATCH" ))
97+ || Boolean .getBoolean ("wurst.debug.lua.dispatch" );
9198
9299 final ImProg prog ;
93100 final LuaCompilationUnit luaModel ;
@@ -432,10 +439,20 @@ private void normalizeMethodNames() {
432439 }
433440 }
434441
435- // give all related methods the same name
436- for (Map .Entry <ImMethod , Set <ImMethod >> entry : methodUnions .groups ().entrySet ()) {
437- String name = uniqueName (entry .getKey ().getName ());
438- for (ImMethod method : entry .getValue ()) {
442+ // give all related methods the same name in deterministic order
443+ List <List <ImMethod >> groups = new ArrayList <>();
444+ for (Set <ImMethod > group : methodUnions .groups ().values ()) {
445+ List <ImMethod > sortedGroup = new ArrayList <>(group );
446+ sortedGroup .sort (Comparator .comparing (this ::methodSortKey ));
447+ groups .add (sortedGroup );
448+ }
449+ groups .sort (Comparator .comparing (g -> g .isEmpty () ? "" : methodSortKey (g .get (0 ))));
450+ for (List <ImMethod > group : groups ) {
451+ if (group .isEmpty ()) {
452+ continue ;
453+ }
454+ String name = uniqueName (group .get (0 ).getName ());
455+ for (ImMethod method : group ) {
439456 method .setName (name );
440457 }
441458 }
@@ -907,8 +924,7 @@ private void createClassInitFunction(ImClass c, LuaVariable classVar, LuaMethod
907924 private void initClassTables (ImClass c ) {
908925 LuaVariable classVar = luaClassVar .getFor (c );
909926 // create methods:
910- Set <String > methods = new HashSet <>();
911- createMethods (c , classVar , methods );
927+ createMethods (c , classVar );
912928
913929 // set supertype metadata:
914930 LuaTableFields superClasses = LuaAst .LuaTableFields ();
@@ -929,25 +945,270 @@ private void initClassTables(ImClass c) {
929945
930946 }
931947
932- private void createMethods (ImClass c , LuaVariable classVar , Set <String > methods ) {
933- for (ImMethod method : c .getMethods ()) {
934- if (methods .contains (method .getName ())) {
948+ private void createMethods (ImClass c , LuaVariable classVar ) {
949+ List <ImMethod > allMethods = collectMethodsInHierarchy (c );
950+ Set <ImMethod > inHierarchy = new HashSet <>(allMethods );
951+ UnionFind <ImMethod > unions = new UnionFind <>();
952+ for (ImMethod method : allMethods ) {
953+ unions .find (method );
954+ for (ImMethod subMethod : method .getSubMethods ()) {
955+ if (inHierarchy .contains (subMethod )) {
956+ unions .union (method , subMethod );
957+ }
958+ }
959+ }
960+
961+ Map <ImMethod , List <ImMethod >> groupedMethods = new HashMap <>();
962+ for (ImMethod method : allMethods ) {
963+ ImMethod root = unions .find (method );
964+ groupedMethods .computeIfAbsent (root , k -> new ArrayList <>()).add (method );
965+ }
966+
967+ List <List <ImMethod >> groups = new ArrayList <>(groupedMethods .values ());
968+ groups .sort (Comparator .comparing (group -> group .isEmpty () ? "" : methodSortKey (group .get (0 ))));
969+ Map <String , ImMethod > slotToImpl = new TreeMap <>();
970+ for (List <ImMethod > groupMethods : groups ) {
971+ if (groupMethods == null || groupMethods .isEmpty ()) {
935972 continue ;
936973 }
937- methods .add (method .getName ());
938- if (method .getIsAbstract ()) {
974+ groupMethods .sort (Comparator .comparing (this ::methodSortKey ));
975+ ImMethod chosen = chooseBestImplementationForClass (c , groupMethods );
976+ if (chosen == null || chosen .getIsAbstract () || chosen .getImplementation () == null ) {
977+ continue ;
978+ }
979+ Set <String > slotNames = collectDispatchSlotNames (c , groupMethods );
980+ for (String slotName : slotNames ) {
981+ ImMethod current = slotToImpl .get (slotName );
982+ if (current == null || compareDispatchCandidates (c , chosen , current ) < 0 ) {
983+ slotToImpl .put (slotName , chosen );
984+ }
985+ }
986+ String debugKey = groupMethods .get (0 ).getName ();
987+ debugDispatchGroup (c , debugKey , slotNames , groupMethods , chosen );
988+ }
989+ for (Map .Entry <String , ImMethod > e : slotToImpl .entrySet ()) {
990+ ImMethod impl = e .getValue ();
991+ if (impl == null || impl .getImplementation () == null ) {
939992 continue ;
940993 }
941994 luaModel .add (LuaAst .LuaAssignment (LuaAst .LuaExprFieldAccess (
942995 LuaAst .LuaExprVarAccess (classVar ),
943- method . getName ()),
944- LuaAst .LuaExprFuncRef (luaFunc .getFor (method .getImplementation ()))
996+ e . getKey ()),
997+ LuaAst .LuaExprFuncRef (luaFunc .getFor (impl .getImplementation ()))
945998 ));
946999 }
947- // also create links for inherited methods
1000+ }
1001+
1002+ private Set <String > collectDispatchSlotNames (ImClass receiverClass , List <ImMethod > groupMethods ) {
1003+ Set <String > slotNames = new TreeSet <>();
1004+ Set <String > semanticNames = new TreeSet <>();
1005+ for (ImMethod m : groupMethods ) {
1006+ if (m == null ) {
1007+ continue ;
1008+ }
1009+ String methodName = m .getName ();
1010+ if (!methodName .isEmpty ()) {
1011+ slotNames .add (methodName );
1012+ }
1013+ ImClass owner = m .attrClass ();
1014+ String semanticName = semanticNameFromMethodName (methodName );
1015+ if (!semanticName .isEmpty ()) {
1016+ semanticNames .add (semanticName );
1017+ }
1018+ if (owner != null && !semanticName .isEmpty ()) {
1019+ slotNames .add (owner .getName () + "_" + semanticName );
1020+ }
1021+ }
1022+ if (receiverClass != null && !semanticNames .isEmpty ()) {
1023+ Set <String > classNames = new TreeSet <>();
1024+ collectClassNamesInHierarchy (receiverClass , classNames , new HashSet <>());
1025+ for (String className : classNames ) {
1026+ for (String semanticName : semanticNames ) {
1027+ slotNames .add (className + "_" + semanticName );
1028+ }
1029+ }
1030+ }
1031+ return slotNames ;
1032+ }
1033+
1034+ private void collectClassNamesInHierarchy (ImClass c , Set <String > out , Set <ImClass > visited ) {
1035+ if (c == null || !visited .add (c )) {
1036+ return ;
1037+ }
1038+ out .add (c .getName ());
9481039 for (ImClassType sc : c .getSuperClasses ()) {
949- createMethods (sc .getClassDef (), classVar , methods );
1040+ collectClassNamesInHierarchy (sc .getClassDef (), out , visited );
1041+ }
1042+ }
1043+
1044+ private List <ImMethod > collectMethodsInHierarchy (ImClass c ) {
1045+ List <ImMethod > result = new ArrayList <>();
1046+ collectMethodsInHierarchy (c , result , new HashSet <>());
1047+ result .sort (Comparator .comparing (this ::methodSortKey ));
1048+ return result ;
1049+ }
1050+
1051+ private void collectMethodsInHierarchy (ImClass c , List <ImMethod > out , Set <ImClass > visited ) {
1052+ if (c == null || !visited .add (c )) {
1053+ return ;
1054+ }
1055+ out .addAll (c .getMethods ());
1056+ List <ImClassType > superClasses = new ArrayList <>(c .getSuperClasses ());
1057+ superClasses .sort (Comparator .comparing (t -> classSortKey (t .getClassDef ())));
1058+ for (ImClassType sc : superClasses ) {
1059+ collectMethodsInHierarchy (sc .getClassDef (), out , visited );
1060+ }
1061+ }
1062+
1063+ private ImMethod chooseBestImplementationForClass (ImClass receiverClass , List <ImMethod > candidates ) {
1064+ List <ImMethod > concrete = new ArrayList <>();
1065+ for (ImMethod m : candidates ) {
1066+ if (!m .getIsAbstract () && m .getImplementation () != null ) {
1067+ concrete .add (m );
1068+ }
1069+ }
1070+ if (concrete .isEmpty ()) {
1071+ return null ;
1072+ }
1073+ concrete .sort ((a , b ) -> compareDispatchCandidates (receiverClass , a , b ));
1074+ return concrete .get (0 );
1075+ }
1076+
1077+ private int compareDispatchCandidates (ImClass receiverClass , ImMethod a , ImMethod b ) {
1078+ boolean aLocal = isImplementationFromClass (a , receiverClass );
1079+ boolean bLocal = isImplementationFromClass (b , receiverClass );
1080+ if (aLocal != bLocal ) {
1081+ return aLocal ? -1 : 1 ;
1082+ }
1083+ int aDist = classDistance (receiverClass , a .attrClass ());
1084+ int bDist = classDistance (receiverClass , b .attrClass ());
1085+ if (aDist != bDist ) {
1086+ return Integer .compare (aDist , bDist );
1087+ }
1088+ boolean aNoOp = isNoOpImplementation (a );
1089+ boolean bNoOp = isNoOpImplementation (b );
1090+ if (aNoOp != bNoOp ) {
1091+ return aNoOp ? 1 : -1 ;
1092+ }
1093+ return methodSortKey (a ).compareTo (methodSortKey (b ));
1094+ }
1095+
1096+ private boolean isImplementationFromClass (ImMethod method , ImClass ownerClass ) {
1097+ if (method == null || ownerClass == null || method .getImplementation () == null ) {
1098+ return false ;
1099+ }
1100+ return method .getImplementation ().getName ().startsWith (ownerClass .getName () + "_" );
1101+ }
1102+
1103+ private boolean isNoOpImplementation (ImMethod method ) {
1104+ return method != null
1105+ && method .getImplementation () != null
1106+ && method .getImplementation ().getName ().contains ("NoOpState_" );
1107+ }
1108+
1109+ private int classDistance (ImClass from , ImClass to ) {
1110+ if (from == null || to == null ) {
1111+ return Integer .MAX_VALUE ;
1112+ }
1113+ if (from == to ) {
1114+ return 0 ;
1115+ }
1116+ ArrayDeque <ImClass > queue = new ArrayDeque <>();
1117+ Map <ImClass , Integer > dist = new HashMap <>();
1118+ queue .add (from );
1119+ dist .put (from , 0 );
1120+ while (!queue .isEmpty ()) {
1121+ ImClass current = queue .removeFirst ();
1122+ int currentDist = dist .get (current );
1123+ List <ImClassType > superClasses = new ArrayList <>(current .getSuperClasses ());
1124+ superClasses .sort (Comparator .comparing (t -> classSortKey (t .getClassDef ())));
1125+ for (ImClassType sc : superClasses ) {
1126+ ImClass next = sc .getClassDef ();
1127+ if (next == null || dist .containsKey (next )) {
1128+ continue ;
1129+ }
1130+ int nextDist = currentDist + 1 ;
1131+ if (next == to ) {
1132+ return nextDist ;
1133+ }
1134+ dist .put (next , nextDist );
1135+ queue .add (next );
1136+ }
1137+ }
1138+ return Integer .MAX_VALUE ;
1139+ }
1140+
1141+ private void debugDispatchGroup (ImClass receiverClass , String key , Set <String > slotNames , List <ImMethod > groupMethods , ImMethod chosen ) {
1142+ if (!DEBUG_LUA_DISPATCH && !isSuspiciousGroup (slotNames , groupMethods , chosen )) {
1143+ return ;
1144+ }
1145+ String chosenImpl = chosen != null && chosen .getImplementation () != null ? chosen .getImplementation ().getName () : "null" ;
1146+ StringBuilder candidates = new StringBuilder ();
1147+ List <ImMethod > sorted = new ArrayList <>(groupMethods );
1148+ sorted .sort (Comparator .comparing (this ::methodSortKey ));
1149+ for (ImMethod m : sorted ) {
1150+ String impl = m .getImplementation () != null ? m .getImplementation ().getName () : "null" ;
1151+ if (candidates .length () > 0 ) {
1152+ candidates .append ("; " );
1153+ }
1154+ candidates .append (m .getName ()).append ("->" ).append (impl ).append ("@" ).append (classSortKey (m .attrClass ()));
1155+ }
1156+ System .err .println ("[LuaDispatch] class=" + classSortKey (receiverClass )
1157+ + " key=" + key
1158+ + " slots=" + slotNames
1159+ + " chosen=" + chosenImpl
1160+ + " candidates=[" + candidates + "]" );
1161+ if (DEBUG_LUA_DISPATCH ) {
1162+ String line = "[LuaDispatch] class=" + classSortKey (receiverClass )
1163+ + " key=" + key
1164+ + " slots=" + slotNames
1165+ + " chosen=" + chosenImpl
1166+ + " candidates=[" + candidates + "]"
1167+ + System .lineSeparator ();
1168+ try {
1169+ Files .writeString (Path .of ("C:/Users/Frotty/Documents/GitHub/WurstScript/lua-dispatch-debug.log" ),
1170+ line , StandardOpenOption .CREATE , StandardOpenOption .APPEND );
1171+ } catch (Exception ignored ) {
1172+ }
1173+ }
1174+ }
1175+
1176+ private boolean isSuspiciousGroup (Set <String > slotNames , List <ImMethod > groupMethods , ImMethod chosen ) {
1177+ if (slotNames .size () > 1 ) {
1178+ return true ;
1179+ }
1180+ boolean hasNonNoOp = false ;
1181+ for (ImMethod m : groupMethods ) {
1182+ if (!isNoOpImplementation (m ) && m .getImplementation () != null ) {
1183+ hasNonNoOp = true ;
1184+ break ;
1185+ }
1186+ }
1187+ return hasNonNoOp && isNoOpImplementation (chosen );
1188+ }
1189+
1190+ private String methodSortKey (ImMethod m ) {
1191+ String owner = classSortKey (m .attrClass ());
1192+ String impl = m .getImplementation () != null ? m .getImplementation ().getName () : "" ;
1193+ return owner + "|" + m .getName () + "|" + impl ;
1194+ }
1195+
1196+ private String semanticNameFromMethodName (String methodName ) {
1197+ if (methodName == null || methodName .isEmpty ()) {
1198+ return "" ;
1199+ }
1200+ int lastUnderscore = methodName .lastIndexOf ('_' );
1201+ if (lastUnderscore >= 0 && lastUnderscore + 1 < methodName .length ()) {
1202+ return methodName .substring (lastUnderscore + 1 );
1203+ }
1204+ return methodName ;
1205+ }
1206+
1207+ private String classSortKey (ImClass c ) {
1208+ if (c == null ) {
1209+ return "" ;
9501210 }
1211+ return c .getName ();
9511212 }
9521213
9531214 @ NotNull
0 commit comments