using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using static UdonSharp.Compiler.UdonSharpCompiler; namespace UdonSharp.Compiler { /// /// Handles compiling a class into Udon assembly /// public class CompilationModule { public UdonSharpProgramAsset programAsset { get; private set; } UdonSharpSettings settings; public ResolverContext resolver { get; private set; } public SymbolTable moduleSymbols { get; private set; } public LabelTable moduleLabels { get; private set; } public HashSet fieldsWithInitializers; public ClassDefinition compiledClassDefinition = null; public int ErrorCount { get; private set; } = 0; public CompilationModule(UdonSharpProgramAsset sourceAsset) { programAsset = sourceAsset; resolver = new ResolverContext(); moduleSymbols = new SymbolTable(resolver, null); moduleLabels = new LabelTable(); fieldsWithInitializers = new HashSet(); if (programAsset.sourceCsScript == null) throw new System.ArgumentException($"Asset '{AssetDatabase.GetAssetPath(programAsset)}' does not have a valid program source to compile from"); settings = UdonSharpSettings.GetSettings(); } void LogException(CompileTaskResult result, System.Exception e, SyntaxNode node, out string logMessage) { logMessage = ""; if (node != null) { FileLinePositionSpan lineSpan = node.GetLocation().GetLineSpan(); CompileError error = new CompileError(); error.script = programAsset.sourceCsScript; error.errorStr = $"{e.GetType()}: {e.Message}"; error.lineIdx = lineSpan.StartLinePosition.Line; error.charIdx = lineSpan.StartLinePosition.Character; result.compileErrors.Add(error); } else { logMessage = e.ToString(); Debug.LogException(e); } #if UDONSHARP_DEBUG Debug.LogException(e); Debug.LogError(e.StackTrace); #endif } public CompileTaskResult Compile(List classDefinitions, Microsoft.CodeAnalysis.SyntaxTree syntaxTree, string sourceCode, bool isEditorBuild) { CompileTaskResult result = new CompileTaskResult(); result.programAsset = programAsset; moduleSymbols.OpenSymbolTable(); ClassDebugInfo debugInfo = null; if (settings == null || settings.buildDebugInfo) { debugInfo = new ClassDebugInfo(sourceCode, settings == null || settings.includeInlineCode); } UdonSharpFieldVisitor fieldVisitor = new UdonSharpFieldVisitor(fieldsWithInitializers, resolver, moduleSymbols, moduleLabels, classDefinitions, debugInfo); try { fieldVisitor.Visit(syntaxTree.GetRoot()); } catch (System.Exception e) { LogException(result, e, fieldVisitor.visitorContext.currentNode, out string logMessage); programAsset.compileErrors.Add(logMessage); ErrorCount++; } if (ErrorCount > 0) return result; MethodVisitor methodVisitor = new MethodVisitor(resolver, moduleSymbols, moduleLabels); try { methodVisitor.Visit(syntaxTree.GetRoot()); } catch (System.Exception e) { LogException(result, e, methodVisitor.visitorContext.currentNode, out string logMessage); programAsset.compileErrors.Add(logMessage); ErrorCount++; } if (ErrorCount > 0) return result; ASTVisitor visitor = new ASTVisitor(resolver, moduleSymbols, moduleLabels, methodVisitor.definedMethods, classDefinitions, debugInfo); try { visitor.Visit(syntaxTree.GetRoot()); visitor.VerifyIntegrity(); } catch (System.Exception e) { LogException(result, e, visitor.visitorContext.currentNode, out string logMessage); programAsset.compileErrors.Add(logMessage); ErrorCount++; } if (ErrorCount > 0) { return result; } moduleSymbols.CloseSymbolTable(); if (ErrorCount == 0) { compiledClassDefinition = classDefinitions.Find(e => e.userClassType == visitor.visitorContext.behaviourUserType); string dataBlock = BuildHeapDataBlock(); string codeBlock = visitor.GetCompiledUasm(); result.compiledAssembly = dataBlock + codeBlock; result.symbolCount = (uint)(moduleSymbols.GetAllUniqueChildSymbols().Count + visitor.GetExternStrCount()); programAsset.behaviourIDHeapVarName = visitor.GetIDHeapVarName(); programAsset.fieldDefinitions = fieldVisitor.visitorContext.localFieldDefinitions; programAsset.behaviourSyncMode = visitor.visitorContext.behaviourSyncMode; if (debugInfo != null) debugInfo.FinalizeDebugInfo(); UdonSharpEditorCache.Instance.SetDebugInfo(programAsset, isEditorBuild ? UdonSharpEditorCache.DebugInfoType.Editor : UdonSharpEditorCache.DebugInfoType.Client, debugInfo); } return result; } private string BuildHeapDataBlock() { AssemblyBuilder builder = new AssemblyBuilder(); HashSet uniqueSymbols = new HashSet(); builder.AppendLine(".data_start", 0); builder.AppendLine("", 0); List allSymbols = moduleSymbols.GetAllUniqueChildSymbols(); foreach (SymbolDefinition symbol in allSymbols) { if (symbol.declarationType.HasFlag(SymbolDeclTypeFlags.Public) && !symbol.declarationType.HasFlag(SymbolDeclTypeFlags.Readonly)) builder.AppendLine($".export {symbol.symbolUniqueName}", 1); } foreach (SymbolDefinition symbol in allSymbols) { if (symbol.syncMode != UdonSyncMode.NotSynced) builder.AppendLine($".sync {symbol.symbolUniqueName}, {System.Enum.GetName(typeof(UdonSyncMode), symbol.syncMode).ToLowerInvariant()}", 1); } builder.AppendLine("", 0); // Prettify the symbol order in the data block // Reflection info goes first so that we can use it for knowing what script threw an error from in game logs foreach (SymbolDefinition symbol in moduleSymbols.GetAllUniqueChildSymbols() .OrderBy(e => (e.declarationType & SymbolDeclTypeFlags.Reflection) != 0) .ThenBy(e => (e.declarationType & SymbolDeclTypeFlags.Public) != 0) .ThenBy(e => (e.declarationType & SymbolDeclTypeFlags.Private) != 0) .ThenBy(e => (e.declarationType & SymbolDeclTypeFlags.This) != 0) .ThenBy(e => (e.declarationType & SymbolDeclTypeFlags.Internal) == 0) .ThenBy(e => (e.declarationType &SymbolDeclTypeFlags.Constant) != 0) .ThenByDescending(e => e.symbolCsType.Name) //.ThenByDescending(e => e.symbolUniqueName) .Reverse() ) { if ((symbol.declarationType & SymbolDeclTypeFlags.This) != 0) builder.AppendLine($"{symbol.symbolUniqueName}: %{symbol.symbolResolvedTypeName}, this", 1); else builder.AppendLine($"{symbol.symbolUniqueName}: %{symbol.symbolResolvedTypeName}, null", 1); } builder.AppendLine("", 0); builder.AppendLine(".data_end", 0); builder.AppendLine("", 0); return builder.GetAssemblyStr(); } } }