public static Type GetTypeFromTypeStringWithErrors(this TypeResolverGroup typeResolverGroup, string typeString) { var type = typeResolverGroup.GetTypeFromTypeString(typeString); if (type == null) { Debug.LogError($"Unable to find type {typeString} in type resolver group"); } return(type); }
public void AddGenericArguments(TDType rootType, TypeResolverGroup typeResolverGroup, params string[] strings) { if (!GeneratedGenericArguments) { foreach (var genericArgument in strings) { GenericArguments.Add( UdonTypeExporter.GetOrCreateTDType(rootType, genericArgument, typeResolverGroup)); GeneratedGenericArguments = true; } } }
static void ExportUdonTypes() { string path = EditorUtility.SaveFilePanel("Save Udon Types", "", "UdonNodeInfo", "dll"); var typeResolver = new TypeResolverGroup(new List <IUAssemblyTypeResolver>() { new SystemTypeResolver(), new UnityEngineTypeResolver(), new VRCSDK2TypeResolver(), new UdonTypeResolver(), new ExceptionTypeResolver(), new UdonBehaviourTypeResolver(), }); var rootType = new TDType(); try { EditorUtility.DisplayProgressBar("Progress", "Parsing Definitions...", 1f / 2); ParseDefinitions(rootType, typeResolver); EditorUtility.DisplayProgressBar("Progress", "Saving to file...", 2f / 2); string codeString = UdonTypeDLLExporter.ExportTDTypeAsDLL(rootType, typeResolver); CompilerParameters parameters = new CompilerParameters(); parameters.GenerateExecutable = false; parameters.CompilerOptions = "-nostdlib -noconfig"; parameters.OutputAssembly = "Output.dll"; CompilerResults r = CodeDomProvider.CreateProvider("CSharp") .CompileAssemblyFromSource(parameters, codeString); foreach (var s in r.Output) { if (s.Contains("warning")) { continue; } Debug.Log(s); } File.WriteAllLines("source.txt", new[] { codeString }); File.Copy("Output.dll", path, true); } finally { EditorUtility.ClearProgressBar(); } Debug.Log($"Done\nOutput to: {path}"); }
public static void AddTypeInterfaces(this Type type, HashSet <string> interfaces, TypeResolverGroup typeResolverGroup) { if (type == null) { return; } foreach (var Interface in type.GetInterfaces()) { if (typeResolverGroup.GetTypeFromTypeString(UdonTypeExporter.GenerateUdonName(Interface)) != null) { interfaces.Add(UdonTypeExporter.GetTypeFullName(Interface)); Interface.AddTypeInterfaces(interfaces, typeResolverGroup); } } }
private void HandlePushLabel(string rawLine, string[] instructionArgs, Stack <object> stack, TypeResolverGroup typeResolver, Dictionary <string, uint> symbolNameToAddress) { if (instructionArgs.Length != 2) { throw new InvalidOperationException( $"PUSHLABEL instruction requires 1 arguments(AssemblyVariableName). EX: 'PUSHLABEL \"_update\"'"); } var labelName = instructionArgs[1].Replace("\"", ""); if (symbolNameToAddress.TryGetValue(labelName, out uint address)) { stack.Push(address); } else { throw new InvalidOperationException( $"Unable to find label '{labelName}'"); } }
private void HandleSetHeapInstruction(string rawLine, string[] instructionArgs, Stack <object> stack, TypeResolverGroup typeResolver) { if (instructionArgs.Length != 2) { throw new InvalidOperationException( $"SETHEAP instruction requires 1 arguments(AssemblyVariableName). EX: 'SETHEAP \"variableName\"'"); } var variableName = instructionArgs[1].Replace("\"", ""); var addr = program.SymbolTable.GetAddressFromSymbol(variableName); var data = stack.Pop(); if (data is Type) { program.Heap.SetHeapVariable(addr, data, typeof(Type)); //Prevents .GetType from getting RuntimeType } else { program.Heap.SetHeapVariable(addr, data, data.GetType()); } }
private void HandlePushEnumInstruction(string rawLine, string[] instructionArgs, Stack <object> stack, TypeResolverGroup typeResolver) { if (instructionArgs.Length != 3) { throw new InvalidOperationException( $"PUSHENUM requires 2 arguments(EnumType, EnumValueName). EX: 'PUSHENUM UnityEngineFFTWindow Rectangular'"); } var enumName = instructionArgs[1]; var enumValueName = instructionArgs[2]; var enumType = typeResolver.GetTypeFromTypeString(enumName); if (enumType == null) { throw new InvalidOperationException( $"Unable to find type '{enumName}'."); } object output = Enum.Parse(enumType, enumValueName); stack.Push(output); }
private void HandleConstructInstruction(string rawLine, string[] instructionArgs, Stack <object> stack, TypeResolverGroup typeResolver) { if (instructionArgs.Length != 3) { throw new InvalidOperationException( $"CONSTRUCT instruction requires 2 arguments(TypeName, ArgumentCount). EX: 'CONSTRUCT UnityEngineVector2 2'"); } var typeName = instructionArgs[1]; var constructorArgumentcount = int.Parse(instructionArgs[2]); Stack <object> flipped = new Stack <object>(); for (int i = 0; i < constructorArgumentcount; i++) { flipped.Push(stack.Pop()); } object obj = Activator.CreateInstance(typeResolver.GetTypeFromTypeString(typeName), flipped.ToArray()); stack.Push(obj); }
private static string RecurseInheritedType(Type originalType, Type type, TypeResolverGroup typeResolverGroup) { if (type == null || type == typeof(object)) { return(""); } if (originalType == type) { return(RecurseInheritedType(originalType, type.BaseType, typeResolverGroup)); } if (typeResolverGroup.GetTypeFromTypeString(UdonTypeExporter.GenerateUdonName(type)) != null) { return(UdonTypeExporter.GetTypeFullName(type)); } if (type.BaseType != null) { return(RecurseInheritedType(originalType, type.BaseType, typeResolverGroup)); } return(""); }
private static void ParseDefinitions( TDType rootType, TypeResolverGroup typeResolverGroup) { foreach (var definition in UdonEditorManager.Instance.GetNodeDefinitions()) { if (StartsWithIgnoredKeyword(definition) | IsSpecialDefinition(definition)) { continue; } //Try to match by the non constructor regex, if it fails fallback to the constructor regex. //Perhaps they can be combined but this works. var match = NonCtorRegex.Match(definition.fullName); if (match.Groups.Count != NonCtorRegex.GetGroupNumbers().Length) { match = CtorRegex.Match(definition.fullName); } var groups = match.Groups; //Make sure all groups are filled. If not then the regex failed. if (groups.Count == NonCtorRegex.GetGroupNumbers().Length) { var definitionName = groups["namespace"].ToString(); var methodType = groups["methodType"].ToString(); var methodName = groups["methodName"].ToString(); var inputsRaw = groups["inputs"].ToString(); var methodOutput = groups["outputs"].ToString(); //For some reason underscores are allowed and I'm not quite sure how to deal with them, so let's just do this //Replace with -, split by _, replace - with _ inputsRaw = inputsRaw.Replace("VRCSDKBaseVRC_", "VRCSDKBaseVRC-"); var methodInputs = inputsRaw.Split('_'); for (int i = 0; i < methodInputs.Length; i++) { methodInputs[i] = methodInputs[i].Replace("-", "_"); } var isStatic = (definition.inputNames.Length > 0 && definition.inputNames[0] != "instance"); //Some of the methods don't have any inputs(so definition.inputNames[0] doesn't exist) so we have to check the wrapper try { int outputCount = definition.outputs[0] != typeof(void) ? 1 : 0; int inputParameterCount = Wrapper.GetExternFunctionParameterCount(definition.fullName) - outputCount; if (definition.inputNames.Length == 0 && inputParameterCount == 0) { isStatic = true; } } catch //Catch because the wrapper just throws for some unsupported externs that exist in node definitions { } var fullUdonExternString = definition.fullName; var definitionType = typeResolverGroup.GetTypeFromTypeStringWithErrors(definitionName); var namespaceName = GetTypeFullName(definitionType); var definitionTDType = GetOrCreateTDType(rootType, namespaceName, typeResolverGroup); definitionTDType.UdonName = GenerateUdonName(definitionType); definitionTDType.CSharpType = definitionType; //Create TDTypes for all C# types encountered in the definition, and attach methods to them for each of the Extern functions var method = new Method { FullUdonExternString = fullUdonExternString, MethodName = methodName, MethodType = methodType, IsStatic = isStatic }; foreach (var udonTypeName in methodInputs) { if (udonTypeName != "") { var thisType = typeResolverGroup.GetTypeFromTypeStringWithErrors(udonTypeName); var typeName = GetTypeFullName(thisType); TDType tdType = GetOrCreateTDType(rootType, typeName, typeResolverGroup); tdType.UdonName = GenerateUdonName(thisType); tdType.CSharpType = thisType; if (typeResolverGroup.GetTypeFromTypeStringWithErrors(tdType.UdonName) != thisType) { Debug.LogError( $"Could not generate proper udon name for {thisType}. Generated: {tdType.UdonName}"); } method.Inputs.Add(tdType); } } if (methodOutput != "") { var thisType = typeResolverGroup.GetTypeFromTypeStringWithErrors(methodOutput); TDType tdType = GetOrCreateTDType(rootType, GetTypeFullName(thisType), typeResolverGroup); tdType.UdonName = GenerateUdonName(thisType); tdType.CSharpType = thisType; method.Output = tdType; } if (method.IsStatic) { definitionTDType.StaticMethods.Add(method); } else { definitionTDType.NonStaticMethods.Add(method); } } else { Debug.LogError($"Unhandled definition: {definition.fullName}"); } } }
public static TDType GetOrCreateTDType(TDType rootType, string fullName, TypeResolverGroup typeResolverGroup) { bool containsGenericArguments = fullName.Contains("<"); Queue <string> namespaces; string genericArguments = null; if (containsGenericArguments) //Generic types { var match = GenericsRegex.Match(fullName); var groups = match.Groups; var baseType = groups["GenericBaseType"].ToString(); genericArguments = groups["GenericArguments"].ToString(); namespaces = new Queue <string>(baseType.Split('.')); } else { namespaces = new Queue <string>(fullName.Split('.')); } var current = rootType; while (namespaces.Count > 0) { var name = namespaces.Dequeue(); //Only the full string is "generic", so it must be the last thing in the queue. //IE. System.Collections.Generic isn't generic itself, but System.Collections.Generic.List is generic bool isGeneric = containsGenericArguments && namespaces.Count == 0; var child = current.Children.Find(x => x.TypeName == name && x.IsGeneric == isGeneric && genericArguments == x.InputGenericArguments); if (child != null) { //Go down tree current = child; } else { //Create an go down tree var type = new TDType { NamespaceName = current.FullName, FullName = (current.FullName != null ? current.FullName + "." : "") + name, TypeName = name, InputGenericArguments = genericArguments }; string attemptedUdonName = GenerateUdonName(type.FullName, true); //Try to generate udon name and set it if it's correct. if (typeResolverGroup.GetTypeFromTypeString(attemptedUdonName) != null) { type.UdonName = attemptedUdonName; } current.Children.Add(type); current = type; current.IsGeneric = isGeneric; } } if (current.IsGeneric) { current.IsGeneric = true; if (!genericArguments.Contains("<")) { current.AddGenericArguments(rootType, typeResolverGroup, genericArguments.Replace(" ", "").Split(',')); } else { //Only one thing contains a nested generic argument in Udon currently //and luckily it looks like "thing<other<something, else>>" //which means it's a single layer of nesting //So for now we can just pass "other<something, else>" to GetOrCreateTDType //In the future this might change? current.AddGenericArguments(rootType, typeResolverGroup, genericArguments); } } if (fullName.Contains("[]")) { //Add base type for arrays GetOrCreateTDType(rootType, fullName.Replace("[]", ""), typeResolverGroup); } return(current); }
private void HandlePushInstruction(string rawLine, string[] instructionArgs, Stack <object> stack, TypeResolverGroup typeResolver) { if (instructionArgs.Length < 3) { throw new InvalidOperationException( $"PUSH instruction requires 2 arguments(TypeName, literal). EX: 'PUSH SystemSingle 5'"); } var typeName = instructionArgs[1]; var literal = rawLine.Substring(rawLine.IndexOf(typeName) + typeName.Length + 1); object output = null; switch (typeName) { case "SystemSingle": output = Single.Parse(literal); break; case "SystemDouble": output = Double.Parse(literal); break; case "SystemInt64": output = Int64.Parse(literal); break; case "SystemInt32": output = Int32.Parse(literal); break; case "SystemInt16": output = Int16.Parse(literal); break; case "SystemUInt64": output = UInt64.Parse(literal); break; case "SystemUInt32": output = UInt32.Parse(literal); break; case "SystemUInt16": output = UInt16.Parse(literal); break; case "SystemString": output = literal.Trim('"'); break; case "SystemBoolean": output = Boolean.Parse(literal); break; case "SystemChar": output = char.Parse(literal); break; case "SystemByte": output = byte.Parse(literal); break; case "SystemSByte": output = sbyte.Parse(literal); break; case "SystemType": output = typeResolver.GetTypeFromTypeString(literal); if (output == null) { throw new InvalidOperationException( $"Unable to find type '{literal}'."); } break; default: throw new InvalidOperationException($"PUSH literal unsupported for type {typeName}"); } stack.Push(output); }
public static string GetFirstInheritedUdonType(this Type c, TypeResolverGroup typeResolverGroup) { return(RecurseInheritedType(c, c, typeResolverGroup)); }
public static string ExportTDTypeAsDLL(TDType root, TypeResolverGroup typeResolverGroup) { HashSet <Class> parentlessClasses = new HashSet <Class>(); HashSet <Class> extenstionClasses = new HashSet <Class>(); Dictionary <string, Class> fullNameToClass = new Dictionary <string, Class>(); void WriteTypeCode(TDType type) { if (string.IsNullOrEmpty(type.FullName) || type.FullName.Contains("*")) { return; } var fullName = type.FullName.Replace("[]", "Array"); var typeName = type.TypeName.Replace("[]", "Array"); bool extensionClass = type.FullName.EndsWith("[]"); string dictionaryKey = type.IsGeneric ? type.FullName + type.GenericArguments.Count : type.FullName; //Create or get class if (!fullNameToClass.TryGetValue(dictionaryKey, out var Class)) { Class = new Class { UdonName = type.UdonName, TypeName = typeName, IsExtentionClass = extensionClass, Type = type, FullName = type.FullName }; Class.IsNamespace = Class.TypeName == "System" || Class.TypeName == "Collections" || (string.IsNullOrEmpty(Class.UdonName) && type.GenericArguments.Count == 0); if (type.CSharpType != null) { Class.IsEnum = type.CSharpType.IsEnum; Class.IsInterface = type.CSharpType.IsInterface; Class.IsStruct = type.CSharpType.IsValueType; if (!Class.IsEnum && !Class.IsExtentionClass) { Class.InheritedClass = type.CSharpType.GetFirstInheritedUdonType(typeResolverGroup); } if (!Class.IsExtentionClass) { type.CSharpType.AddTypeInterfaces(Class.Interfaces, typeResolverGroup); } if (Class.IsEnum) { var names = Enum.GetNames(type.CSharpType); foreach (var name in names) { Class.EnumNames.Add(name); } } } fullNameToClass[dictionaryKey] = Class; //Add new class to children if (string.IsNullOrEmpty(type.NamespaceName) || Class.IsNamespace) { parentlessClasses.Add(Class); } else { if (fullNameToClass.TryGetValue(type.NamespaceName, out var Parent)) { if (extensionClass) { extenstionClasses.Add(Class); } else { Parent.Children.Add(Class); } } else { Debug.LogError($"Parent class not created {type.NamespaceName}"); } } } if (type.UdonName != null) { AddMethods(type.StaticMethods, Class, extensionClass, type.FullName); AddMethods(type.NonStaticMethods, Class, extensionClass, type.FullName); } } void VisitType(TDType type) { WriteTypeCode(type); foreach (var child in type.Children) { VisitType(child); } } //Visit tree and build classes/namespaces VisitType(root); //Required for -nostdlib var collections = fullNameToClass["System.Collections"].Methods; collections.AppendLine("public interface IEnumerable { }"); var system = fullNameToClass["System"].Methods; system.AppendLine("public abstract class ValueType { }"); system.AppendLine("public abstract class Enum : ValueType { }"); system.AppendLine("public class Attribute { }"); system.AppendLine("public abstract class Delegate { }"); system.AppendLine("public abstract class MulticastDelegate : Delegate { }"); system.AppendLine("public struct IntPtr { }"); system.AppendLine("public struct UIntPtr { }"); system.AppendLine("public struct RuntimeTypeHandle { }"); system.AppendLine("public struct RuntimeMethodHandle { }"); system.AppendLine("public struct RuntimeFieldHandle { }"); system.AppendLine("public interface IDisposable { }"); system.AppendLine("public sealed class ParamArrayAttribute : Attribute { }"); //Write all namespaces out var fullCode = new StringBuilder(); foreach (var Class in parentlessClasses) { fullCode.AppendLine(Class.GenerateCode()); } foreach (var Class in extenstionClasses) { fullCode.AppendLine(Class.GenerateCode()); } fullCode.AppendLine(attributeClassString); return(fullCode.ToString()); }