/// <summary> /// Writes the field code to the specified output. /// </summary> /// <param name="output">The output.</param> /// <param name="indentation">The current indentation.</param> /// <param name="generationFlags">The user type generation flags.</param> public virtual void WriteFieldCode(IndentedWriter output, int indentation, UserTypeGenerationFlags generationFlags) { if (generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment) && !string.IsNullOrEmpty(FieldTypeInfoComment)) { output.WriteLine(indentation, FieldTypeInfoComment); } if (!string.IsNullOrEmpty(ConstantValue)) { output.WriteLine(indentation, "public static {0} {1} = ({0}){2};", FieldType, FieldName, GetConstantValue()); } else if (Static && !UseUserMember) { output.WriteLine(indentation, "{3} static {0} {1} = {2};", FieldType, FieldName, ConstructorText, Access); } else if (UseUserMember && CacheResult) { output.WriteLine(indentation, "{3}{4} {0}UserMember<{1}> {2};", Static ? "static " : "", FieldType, FieldName, Access, OverrideWithNew ? " new" : ""); } else if (CacheResult) { output.WriteLine(indentation, "{3}{4} {0}{1} {2};", Static ? "static " : "", FieldType, FieldName, Access, OverrideWithNew ? " new" : ""); } if (generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment)) { output.WriteLine(); } }
/// <summary> /// Writes the property code to the specified output. /// </summary> /// <param name="output">The output.</param> /// <param name="indentation">The current indentation.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="firstField">if set to <c>true</c> this is the first field in the user type.</param> public virtual void WritePropertyCode(IndentedWriter output, int indentation, UserTypeGenerationFlags generationFlags, ref bool firstField) { if (string.IsNullOrEmpty(ConstantValue)) { if (generationFlags.HasFlag(UserTypeGenerationFlags.SingleLineProperty)) { if (firstField) { output.WriteLine(); firstField = false; } if (!UseUserMember && !CacheResult && generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment) && !string.IsNullOrEmpty(FieldTypeInfoComment)) { output.WriteLine(indentation, FieldTypeInfoComment); } if (UseUserMember && CacheResult) { output.WriteLine(indentation, "public {0}{1} {2} {{ get {{ return {3}.Value; }} }}", Static ? "static " : "", FieldType, PropertyName, FieldName); } else if (CacheResult) { output.WriteLine(indentation, "public {0}{1} {2} {{ get {{ return {3}; }} }}", Static ? "static " : "", FieldType, PropertyName, FieldName); } else { output.WriteLine(indentation, "public {0}{1} {2} {{ get {{ return {3}; }} }}", Static ? "static " : "", FieldType, PropertyName, ConstructorText); } } else { output.WriteLine(); if (!UseUserMember && !CacheResult && generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment) && !string.IsNullOrEmpty(FieldTypeInfoComment)) { output.WriteLine(indentation, FieldTypeInfoComment); } output.WriteLine(indentation, "public {0}{1} {2}", Static ? "static " : "", FieldType, PropertyName); output.WriteLine(indentation++, "{{"); output.WriteLine(indentation, "get"); output.WriteLine(indentation++, "{{"); if (UseUserMember && CacheResult) { output.WriteLine(indentation, "return {0}.Value;", FieldName); } else if (CacheResult) { output.WriteLine(indentation, "return {0};", FieldName); } else { output.WriteLine(indentation, "return {0};", ConstructorText); } output.WriteLine(--indentation, "}}"); output.WriteLine(--indentation, "}}"); } } }
/// <summary> /// Writes the code for this user type to the specified output. /// </summary> /// <param name="output">The output.</param> /// <param name="error">The error text writer.</param> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="indentation">The current indentation.</param> public override void WriteCode(IndentedWriter output, TextWriter error, UserTypeFactory factory, UserTypeGenerationFlags generationFlags, int indentation = 0) { // Check if we need to write namespace string nameSpace = (DeclaredInType as NamespaceUserType)?.FullClassName ?? Namespace; string enumBasicType = GetEnumBasicType(Symbol); if ((DeclaredInType == null || (!generationFlags.HasFlag(UserTypeGenerationFlags.SingleFileExport) && DeclaredInType is NamespaceUserType)) && !string.IsNullOrEmpty(nameSpace)) { output.WriteLine(indentation, "namespace {0}", nameSpace); output.WriteLine(indentation++, "{{"); } // Write beginning of the enumeration if (generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment)) { output.WriteLine(indentation, "// {0} (original name: {1})", ClassName, Symbol.Name); } if (AreValuesFlags()) { output.WriteLine(indentation, @"[System.Flags]"); } if (Symbol.Size != 0) { output.WriteLine(indentation, @"public enum {0} : {1}", ClassName, enumBasicType); } else { output.WriteLine(indentation, @"public enum {0}", ClassName); } output.WriteLine(indentation++, @"{{"); // Write values foreach (var enumValue in Symbol.EnumValues) { string value = enumValue.Item2; if (!FitsBasicType(enumBasicType, ref value)) { output.WriteLine(indentation, "{0} = ({1}){2},", enumValue.Item1, enumBasicType, value); } else { output.WriteLine(indentation, "{0} = {1},", enumValue.Item1, value); } } // Enumeration end output.WriteLine(--indentation, @"}}"); if ((DeclaredInType == null || (!generationFlags.HasFlag(UserTypeGenerationFlags.SingleFileExport) && DeclaredInType is NamespaceUserType)) && !string.IsNullOrEmpty(nameSpace)) { output.WriteLine(--indentation, "}}"); } }
/// <summary> /// Extracts all fields from the user type. /// </summary> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> protected override IEnumerable <UserTypeField> ExtractFields(UserTypeFactory factory, UserTypeGenerationFlags generationFlags) { ExportStaticFields = true; var fields = Symbol.Fields.OrderBy(s => s.Name).ToArray(); bool useThisClass = generationFlags.HasFlag(UserTypeGenerationFlags.UseDirectClassAccess); HashSet <string> usedNames = new HashSet <string>(); foreach (var field in fields) { if (string.IsNullOrEmpty(field.Type.Name)) { continue; } if (IsFieldFiltered(field)) { continue; } field.PropertyName = NormalizeSymbolNamespace(UserTypeField.GetPropertyName(field, this)); while (usedNames.Contains(field.PropertyName)) { field.PropertyName += "_"; } if (field.Name.Contains("@") || field.PropertyName.Length > 511) { // Skip names containing '@' continue; } // Skip fields that are actual values of enum values if (field.Type.Tag == CodeTypeTag.Enum && field.Type.EnumValues.Any(t => t.Item1 == field.Name)) { continue; } var userField = ExtractField(field, factory, generationFlags, forceIsStatic: true); if (field.Type.Tag == CodeTypeTag.Pointer) { // Do not use const values for pointers. // We do not allow user type implicit conversion from integers. userField.ConstantValue = string.Empty; } userField.FieldName = NormalizeSymbolNamespace(userField.FieldName); userField.PropertyName = NormalizeSymbolNamespace(userField.PropertyName); yield return(userField); usedNames.Add(field.PropertyName); } foreach (var field in GetAutoGeneratedFields(false, useThisClass)) { yield return(field); } }
/// <summary> /// Writes the code for this user type to the specified output. /// </summary> /// <param name="output">The output.</param> /// <param name="error">The error text writer.</param> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="indentation">The current indentation.</param> public override void WriteCode(IndentedWriter output, TextWriter error, UserTypeFactory factory, UserTypeGenerationFlags generationFlags, int indentation = 0) { string[] namespaces = this.namespaces; if (generationFlags.HasFlag(UserTypeGenerationFlags.GenerateNamespaceAsStaticClass)) { namespaces = NamespaceSymbol.Split(".".ToCharArray()); } // Declared In Type with namespace if (DeclaredInType != null || generationFlags.HasFlag(UserTypeGenerationFlags.GenerateNamespaceAsStaticClass)) { foreach (string innerClass in namespaces) { output.WriteLine(indentation, "public static partial class {0}", innerClass); output.WriteLine(indentation++, @"{{"); } } else { output.WriteLine(indentation, "namespace {0}", Namespace); output.WriteLine(indentation++, @"{{"); } // Inner types foreach (var innerType in InnerTypes) { output.WriteLine(); innerType.WriteCode(output, error, factory, generationFlags, indentation); } // Declared In Type with namespace if (DeclaredInType != null || generationFlags.HasFlag(UserTypeGenerationFlags.GenerateNamespaceAsStaticClass)) { foreach (string innerClass in namespaces) { output.WriteLine(--indentation, "}}"); } } else { output.WriteLine(--indentation, "}}"); } }
/// <summary> /// Generates the code for user type and creates a file for it. /// </summary> /// <param name="userType">The user type.</param> /// <param name="factory">The user type factory.</param> /// <param name="outputDirectory">The output directory where code file will be stored.</param> /// <param name="errorOutput">The error output.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="generatedFiles">The list of already generated files.</param> /// <returns>Tuple of generated code and filename</returns> private static Tuple <string, string> GenerateCode(UserType userType, UserTypeFactory factory, string outputDirectory, TextWriter errorOutput, UserTypeGenerationFlags generationFlags, ConcurrentDictionary <string, string> generatedFiles) { Symbol symbol = userType.Symbol; if (symbol != null && symbol.Tag == SymTagEnum.SymTagBaseType) { // ignore Base (Primitive) types. return(Tuple.Create("", "")); } bool allParentsAreNamespaces = true; for (UserType parentType = userType.DeclaredInType; parentType != null && allParentsAreNamespaces; parentType = parentType.DeclaredInType) { allParentsAreNamespaces = parentType is NamespaceUserType; } if (userType is NamespaceUserType || !allParentsAreNamespaces) { return(Tuple.Create("", "")); } string classOutputDirectory = outputDirectory; string nameSpace = (userType.DeclaredInType as NamespaceUserType)?.FullClassName ?? userType.Namespace; if (!string.IsNullOrEmpty(nameSpace)) { classOutputDirectory = Path.Combine(classOutputDirectory, nameSpace.Replace(".", "\\").Replace(":", ".")); } Directory.CreateDirectory(classOutputDirectory); bool isEnum = userType is EnumUserType; string filename = string.Format(@"{0}\{1}{2}.exported.cs", classOutputDirectory, userType.ConstructorName, isEnum ? "_enum" : ""); int index = 1; while (true) { if (generatedFiles.TryAdd(filename.ToLowerInvariant(), filename)) { break; } filename = string.Format(@"{0}\{1}{2}_{3}.exported.cs", classOutputDirectory, userType.ConstructorName, isEnum ? "_enum" : "", index++); } using (TextWriter output = new StreamWriter(filename)) using (StringWriter stringOutput = new StringWriter()) { userType.WriteCode(new IndentedWriter(stringOutput, generationFlags.HasFlag(UserTypeGenerationFlags.CompressedOutput)), errorOutput, factory, generationFlags); string text = stringOutput.ToString(); output.WriteLine(text); return(Tuple.Create(text, filename)); } }
/// <summary> /// Extracts all fields from the user type. /// </summary> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> protected override IEnumerable <UserTypeField> ExtractFields(UserTypeFactory factory, UserTypeGenerationFlags generationFlags) { ExportStaticFields = true; var fields = Symbol.Fields.OrderBy(s => s.Name).ToArray(); bool useThisClass = generationFlags.HasFlag(UserTypeGenerationFlags.UseClassFieldsFromDiaSymbolProvider); string previousName = ""; foreach (var field in fields) { if (string.IsNullOrEmpty(field.Type.Name)) { continue; } if (IsFieldFiltered(field) || field.Name == previousName) { continue; } if (field.Name.Contains("@")) { // Skip names contaings '@' continue; } // Skip fields that are actual values of enum values if (field.Type.Tag == Dia2Lib.SymTagEnum.SymTagEnum && field.Type.GetEnumValues().Any(t => t.Item1 == field.Name)) { continue; } var userField = ExtractField(field, factory, generationFlags, forceIsStatic: true); if (field.Type.Tag == Dia2Lib.SymTagEnum.SymTagPointerType) { // Do not use const values for pointers. // We do not allow user type implicit conversion from integers. userField.ConstantValue = string.Empty; } userField.FieldName = NormalizeSymbolNamespace(userField.FieldName); userField.PropertyName = NormalizeSymbolNamespace(userField.PropertyName); yield return(userField); previousName = field.Name; } foreach (var field in GetAutoGeneratedFields(false, useThisClass)) { yield return(field); } }
/// <summary> /// Writes the code for this user type to the specified output. /// </summary> /// <param name="output">The output.</param> /// <param name="error">The error text writer.</param> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="indentation">The current indentation.</param> public override void WriteCode(IndentedWriter output, TextWriter error, UserTypeFactory factory, UserTypeGenerationFlags generationFlags, int indentation = 0) { // Check if we need to write namespace string nameSpace = (DeclaredInType as NamespaceUserType)?.FullClassName ?? Namespace; if ((DeclaredInType == null || (!generationFlags.HasFlag(UserTypeGenerationFlags.SingleFileExport) && DeclaredInType is NamespaceUserType)) && !string.IsNullOrEmpty(nameSpace)) { output.WriteLine(indentation, "namespace {0}", nameSpace); output.WriteLine(indentation++, "{{"); } // Write beginning of the enumeration if (generationFlags.HasFlag(UserTypeGenerationFlags.GenerateFieldTypeInfoComment)) output.WriteLine(indentation, "// {0} (original name: {1})", ClassName, Symbol.Name); if (AreValuesFlags()) output.WriteLine(indentation, @"[System.Flags]"); if (Symbol.Size != 0) output.WriteLine(indentation, @"public enum {0} : {1}", ClassName, GetEnumBasicType(Symbol)); else output.WriteLine(indentation, @"public enum {0}", ClassName); output.WriteLine(indentation++, @"{{"); // Write values foreach (var enumValue in Symbol.GetEnumValues()) { output.WriteLine(indentation, "{0} = {1},", enumValue.Item1, enumValue.Item2); } // Enumeration end output.WriteLine(--indentation, @"}}"); if ((DeclaredInType == null || (!generationFlags.HasFlag(UserTypeGenerationFlags.SingleFileExport) && DeclaredInType is NamespaceUserType)) && !string.IsNullOrEmpty(nameSpace)) { output.WriteLine(--indentation, "}}"); } }
/// <summary> /// Extracts all fields from the user type. /// </summary> /// <param name="factory">The user type factory.</param> /// <param name="generationFlags">The user type generation flags.</param> protected override IEnumerable<UserTypeField> ExtractFields(UserTypeFactory factory, UserTypeGenerationFlags generationFlags) { ExportStaticFields = true; var fields = Symbol.Fields.OrderBy(s => s.Name).ToArray(); bool useThisClass = generationFlags.HasFlag(UserTypeGenerationFlags.UseClassFieldsFromDiaSymbolProvider); string previousName = ""; foreach (var field in fields) { if (string.IsNullOrEmpty(field.Type.Name)) continue; if (IsFieldFiltered(field) || field.Name == previousName) continue; if (field.Name.Contains("@")) { // Skip names contaings '@' continue; } // Skip fields that are actual values of enum values if (field.Type.Tag == Dia2Lib.SymTagEnum.SymTagEnum && field.Type.GetEnumValues().Any(t => t.Item1 == field.Name)) continue; var userField = ExtractField(field, factory, generationFlags, forceIsStatic: true); if (field.Type.Tag == Dia2Lib.SymTagEnum.SymTagPointerType) { // Do not use const values for pointers. // We do not allow user type implicit conversion from integers. userField.ConstantValue = string.Empty; } userField.FieldName = NormalizeSymbolNamespace(userField.FieldName); userField.PropertyName = NormalizeSymbolNamespace(userField.PropertyName); yield return userField; previousName = field.Name; } foreach (var field in GetAutoGeneratedFields(false, useThisClass)) yield return field; }
/// <summary> /// Generates the code for user type in single file. /// </summary> /// <param name="output">The output text writer.</param> /// <param name="userType">The user type.</param> /// <param name="factory">The user type factory.</param> /// <param name="errorOutput">The error output.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <returns><c>true</c> if code was generated for the type; otherwise <c>false</c></returns> private static bool GenerateCodeInSingleFile(TextWriter output, UserType userType, UserTypeFactory factory, TextWriter errorOutput, UserTypeGenerationFlags generationFlags) { Symbol symbol = userType.Symbol; if (symbol != null && symbol.Tag == CodeTypeTag.BuiltinType) { // Ignore built-in types. return(false); } if (userType.DeclaredInType != null) { return(false); } userType.WriteCode(new IndentedWriter(output, generationFlags.HasFlag(UserTypeGenerationFlags.CompressedOutput)), errorOutput, factory, generationFlags); return(true); }
/// <summary> /// Adds the symbol to user type factory and generates the user type. /// </summary> /// <param name="symbol">The non-template symbol.</param> /// <param name="type">The XML type description.</param> /// <param name="nameSpace">The namespace.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <returns>Generated user type for the specified symbol.</returns> internal UserType AddSymbol(Symbol symbol, XmlType type, string nameSpace, UserTypeGenerationFlags generationFlags) { UserType userType; if (symbol.Tag == SymTagEnum.SymTagEnum) { userType = new EnumUserType(symbol, nameSpace); } else if (symbol.Tag == SymTagEnum.SymTagExe) { userType = new GlobalsUserType(symbol, type, nameSpace); } else if (generationFlags.HasFlag(UserTypeGenerationFlags.GeneratePhysicalMappingOfUserTypes)) { userType = new PhysicalUserType(symbol, type, nameSpace); } else { userType = new UserType(symbol, type, nameSpace); } symbol.UserType = userType; return userType; }
/// <summary> /// Adds the symbol to user type factory and generates the user type. /// </summary> /// <param name="symbol">The non-template symbol.</param> /// <param name="type">The XML type description.</param> /// <param name="nameSpace">The namespace.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <returns>Generated user type for the specified symbol.</returns> internal UserType AddSymbol(Symbol symbol, XmlType type, string nameSpace, UserTypeGenerationFlags generationFlags) { UserType userType; if (symbol.Tag == CodeTypeTag.Enum) { userType = new EnumUserType(symbol, nameSpace, this); } else if (symbol.Tag == CodeTypeTag.ModuleGlobals) { userType = new GlobalsUserType(symbol, type, nameSpace, this); } else if (generationFlags.HasFlag(UserTypeGenerationFlags.GeneratePhysicalMappingOfUserTypes)) { userType = new PhysicalUserType(symbol, type, nameSpace, this); } else { userType = new UserType(symbol, type, nameSpace, this); } symbol.UserType = userType; return(userType); }
/// <summary> /// Generates user type field based on the specified symbol field and all other fields that are prepared for this function. /// Do not use this function directly, unless you are calling it from overridden function. /// </summary> /// <param name="field">The symbol field.</param> /// <param name="fieldType">The field tree type.</param> /// <param name="factory">The user type factory.</param> /// <param name="simpleFieldValue">The code foe "simple field value" used when creating transformation.</param> /// <param name="gettingField">The code for getting field variable.</param> /// <param name="isStatic">if set to <c>true</c> generated field should be static.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="extractingBaseClass">if set to <c>true</c> user type field is being generated for getting base class.</param> protected override UserTypeField ExtractFieldInternal(SymbolField field, TypeTree fieldType, UserTypeFactory factory, string simpleFieldValue, string gettingField, bool isStatic, UserTypeGenerationFlags generationFlags, bool extractingBaseClass) { // Physical code generation make sense only for non-static fields if (!isStatic) { bool lazyCacheUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.LazyCacheUserTypeFields); bool cacheUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.CacheUserTypeFields); bool cacheStaticUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.CacheStaticUserTypeFields); string constructorText = ""; string fieldName = field.Name; string fieldTypeString = fieldType.GetTypeString(); BasicTypeTree baseType = fieldType as BasicTypeTree; ArrayTypeTree codeArrayType = fieldType as ArrayTypeTree; UserTypeTree userType = fieldType as UserTypeTree; TransformationTypeTree transformationType = fieldType as TransformationTypeTree; bool isEmbedded = field.Type.Tag != CodeTypeTag.Pointer; // Specialization for basic types if (baseType != null) { if (baseType.BasicType == "string") { int charSize = field.Type.ElementType.Size; constructorText = string.Format("ReadString(GetCodeType().Module.Process, ReadPointer(memoryBuffer, memoryBufferOffset + {0}, {1}), {2})", field.Offset, field.Type.Size, charSize); } else if (baseType.BasicType != "NakedPointer") { if (field.LocationType == LocationType.BitField) { constructorText = string.Format("Read{0}(memoryBuffer, memoryBufferOffset + {1}, {2}, {3})", baseType.GetTypeString().UppercaseFirst(), field.Offset, field.Size, field.BitPosition); } else { constructorText = string.Format("Read{0}(memoryBuffer, memoryBufferOffset + {1})", baseType.GetTypeString().UppercaseFirst(), field.Offset); } } } // Specialization for arrays else if (codeArrayType != null) { if (codeArrayType.ElementType is BasicTypeTree) { baseType = (BasicTypeTree)codeArrayType.ElementType; if (baseType != null && baseType.BasicType != "string" && baseType.BasicType != "NakedPointer") { int arraySize = field.Type.Size; int elementSize = field.Type.ElementType.Size; if (baseType.BasicType == "char") { constructorText = string.Format("Read{0}Array(memoryBuffer, memoryBufferOffset + {1}, {2}, {3})", baseType.GetTypeString().UppercaseFirst(), field.Offset, arraySize / elementSize, elementSize); } else { constructorText = string.Format("Read{0}Array(memoryBuffer, memoryBufferOffset + {1}, {2})", baseType.GetTypeString().UppercaseFirst(), field.Offset, arraySize / elementSize); } fieldTypeString = baseType.GetTypeString() + "[]"; } } } // Specialization for user types else if (userType != null && !extractingBaseClass) { if (!(userType.UserType is EnumUserType)) { string thisClassCodeType; if (IsTypeUsingStaticCodeType(this)) { thisClassCodeType = ClassCodeType; } else { thisClassCodeType = "thisClass.Value.GetCodeType()"; usedThisClass = true; } // Check if type is embedded if (!isEmbedded) { // If user type is not embedded, we do have pointer inside of our memory buffer that we can read directly if (IsTypeUsingStaticCodeType(this)) { constructorText = string.Format("ReadPointer<{0}>({4}, \"{1}\", memoryBuffer, memoryBufferOffset + {2}, {3})", fieldTypeString, fieldName, field.Offset, field.Type.Size, ClassCodeType); } else { constructorText = string.Format("ReadPointer<{0}>(thisClass, \"{1}\", memoryBuffer, memoryBufferOffset + {2}, {3})", fieldTypeString, fieldName, field.Offset, field.Type.Size); usedThisClass = true; } // Do downcasting if field has vtable if (userType.UserType.Symbol.HasVTable() && userType.UserType.DerivedClasses.Count > 0) { constructorText += ".DowncastObject()"; } } else { // If user type is embedded, we can reuse memory buffer that we already have in this class string fieldAddress = string.Format("memoryBufferAddress + (ulong)(memoryBufferOffset + {0})", field.Offset); string fieldCodeType = string.Format("{0}.GetClassFieldType(\"{1}\")", thisClassCodeType, fieldName); if (IsTypeUsingStaticCodeType(userType.UserType)) { fieldCodeType = string.Format("{0}.{1}", userType.UserType.FullClassName, ClassCodeType); } else if (IsTypeUsingStaticCodeType(this)) { fieldCodeType = AddFieldCodeType(fieldName); } constructorText = string.Format("new {0}(memoryBuffer, memoryBufferOffset + {1}, memoryBufferAddress, {2}, {3}, \"{4}\")", fieldTypeString, field.Offset, fieldCodeType, fieldAddress, fieldName); } } else { // TODO: This is enum. Read how much enum base type is big and just cast to enum type... } } // Specialization for transformations else if (transformationType != null) { if (!isEmbedded) { string thisClassCodeType; if (IsTypeUsingStaticCodeType(this)) { thisClassCodeType = ClassCodeType; } else { thisClassCodeType = "thisClass.Value.GetCodeType()"; usedThisClass = true; } string fieldAddress = string.Format("memoryBufferAddress + (ulong)(memoryBufferOffset + {0})", field.Offset); string fieldVariable = string.Format("Variable.CreateNoCast({0}.GetClassFieldType(\"{1}\"), {2}, \"{1}\")", thisClassCodeType, fieldName, fieldAddress); if (transformationType.Transformation.Transformation.HasPhysicalConstructor) { fieldVariable = string.Format("{0}, memoryBuffer, memoryBufferOffset + {1}, memoryBufferAddress", fieldVariable, field.Offset); } simpleFieldValue = fieldVariable; constructorText = string.Format("new {0}({1})", fieldTypeString, fieldVariable); } } // If we found suitable physical representation, generate the field if (!string.IsNullOrEmpty(constructorText)) { return new UserTypeField() { ConstructorText = constructorText, FieldName = "_" + fieldName, FieldType = fieldTypeString, FieldTypeInfoComment = string.Format("// {0} {1};", field.Type.Name, fieldName), PropertyName = UserTypeField.GetPropertyName(field, this), Static = isStatic, UseUserMember = lazyCacheUserTypeFields, CacheResult = cacheUserTypeFields || (isStatic && cacheStaticUserTypeFields), SimpleFieldValue = simpleFieldValue, } } ; } return(base.ExtractFieldInternal(field, fieldType, factory, simpleFieldValue, gettingField, isStatic, generationFlags, extractingBaseClass)); }
/// <summary> /// Generates user types using the specified XML configuration. /// </summary> /// <param name="xmlConfig">The XML configuration.</param> /// <param name="logger">The logger text writer. If set to null, Console.Out will be used.</param> /// <param name="errorLogger">The error logger text writer. If set to null, Console.Error will be used.</param> public void Generate(XmlConfig xmlConfig, TextWriter logger = null, TextWriter errorLogger = null) { ConcurrentDictionary <string, string> generatedFiles = new ConcurrentDictionary <string, string>(); var syntaxTrees = new ConcurrentBag <SyntaxTree>(); string currentDirectory = Directory.GetCurrentDirectory(); string outputDirectory = currentDirectory + "\\output\\"; // Check logger and error logger if (errorLogger == null) { errorLogger = Console.Error; } if (logger == null) { logger = Console.Out; } Initialize(xmlConfig, logger, errorLogger); // Create output directory Directory.CreateDirectory(outputDirectory); // Generate user types GenerateUserTypes(); // Use IL code writer for assembly generation. if (xmlConfig.GenerateAssemblyWithILWriter && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { logger.Write("IL emitting time..."); codeWriter.GenerateBinary(userTypes, xmlConfig.GeneratedAssemblyName, !xmlConfig.DisablePdbGeneration, dependentAssemblies.Select(da => ResolveAssemblyPath(da))); logger.WriteLine(" {0}", stopwatch.Elapsed); } else { // Code generation and saving it to disk logger.Write("Saving code to disk..."); if (!generationOptions.HasFlag(UserTypeGenerationFlags.SingleFileExport)) { // Generate Code Parallel.ForEach(userTypes, (symbolEntry) => { Tuple <string, string> result = GenerateCode(codeWriter, symbolEntry, userTypeFactory, outputDirectory, errorLogger, generationOptions, generatedFiles); string text = result.Item1; string filename = result.Item2; if (!string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName) && !string.IsNullOrEmpty(text)) { lock (syntaxTrees) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(text, path: filename, encoding: System.Text.UTF8Encoding.Default)); } } }); } else { string filename = string.Format(@"{0}\everything.exported.cs", outputDirectory); string code = GenerateSingleFile(); generatedFiles.TryAdd(filename.ToLowerInvariant(), filename); if (!generationOptions.HasFlag(UserTypeGenerationFlags.DontSaveGeneratedCodeFiles)) { File.WriteAllText(filename, code); } if (!string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { logger.WriteLine(" {0}", stopwatch.Elapsed); logger.Write("Parsing generated code with Roslyn..."); syntaxTrees.Add(CSharpSyntaxTree.ParseText(code, path: filename, encoding: UTF8Encoding.Default)); } } logger.WriteLine(" {0}", stopwatch.Elapsed); // Compiling the code if (!string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { string dllFilename = Path.Combine(outputDirectory, xmlConfig.GeneratedAssemblyName); string pdbFilename = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(dllFilename) + ".pdb"); using (var dllStream = new FileStream(dllFilename, FileMode.Create)) using (var pdbStream = new FileStream(pdbFilename, FileMode.Create)) { if (GenerateRoslynAssembly(syntaxTrees, dllStream, pdbStream)) { logger.WriteLine("DLL size: {0}", dllStream.Position); logger.WriteLine("PDB size: {0}", pdbStream.Position); } } logger.WriteLine("Compiling: {0}", stopwatch.Elapsed); } // Generating props file if (!string.IsNullOrEmpty(xmlConfig.GeneratedPropsFileName)) { using (TextWriter output = new StreamWriter(outputDirectory + xmlConfig.GeneratedPropsFileName, false /* append */, System.Text.Encoding.UTF8, 16 * 1024 * 1024)) { output.WriteLine(@"<?xml version=""1.0"" encoding=""utf-8""?>"); output.WriteLine(@"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">"); output.WriteLine(@" <ItemGroup>"); foreach (var file in generatedFiles.Values) { output.WriteLine(@" <Compile Include=""{0}"" />", file); } output.WriteLine(@" </ItemGroup>"); output.WriteLine(@"</Project>"); } } } logger.WriteLine("Total time: {0}", stopwatch.Elapsed); }
/// <summary> /// Generates user types using the specified XML configuration. /// </summary> /// <param name="xmlConfig">The XML configuration.</param> /// <param name="logger">The logger text writer. If set to null, Console.Out will be used.</param> /// <param name="errorLogger">The error logger text writer. If set to null, Console.Error will be used.</param> public void Generate(XmlConfig xmlConfig, TextWriter logger = null, TextWriter errorLogger = null) { ConcurrentDictionary <string, string> generatedFiles = new ConcurrentDictionary <string, string>(); var syntaxTrees = new ConcurrentBag <SyntaxTree>(); string currentDirectory = Directory.GetCurrentDirectory(); string outputDirectory = currentDirectory + "\\output\\"; // Check logger and error logger if (errorLogger == null) { errorLogger = Console.Error; } if (logger == null) { logger = Console.Out; } Initialize(xmlConfig, logger, errorLogger); // Create output directory Directory.CreateDirectory(outputDirectory); // Generate user types GenerateUserTypes(); // Code generation and saving it to disk logger.Write("Saving code to disk..."); if (!generationOptions.HasFlag(UserTypeGenerationFlags.SingleFileExport)) { // Generate Code Parallel.ForEach(userTypes, (symbolEntry) => { Tuple <string, string> result = GenerateCode(symbolEntry, userTypeFactory, outputDirectory, errorLogger, generationOptions, generatedFiles); string text = result.Item1; string filename = result.Item2; if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName) && !string.IsNullOrEmpty(text)) { lock (syntaxTrees) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(text, path: filename, encoding: System.Text.UTF8Encoding.Default)); } } }); } else { string filename = string.Format(@"{0}\everything.exported.cs", outputDirectory); string code = GenerateSingleFile(); generatedFiles.TryAdd(filename.ToLowerInvariant(), filename); if (!generationOptions.HasFlag(UserTypeGenerationFlags.DontSaveGeneratedCodeFiles)) { File.WriteAllText(filename, code); } if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(code, path: filename, encoding: UTF8Encoding.Default)); } } logger.WriteLine(" {0}", stopwatch.Elapsed); // Compiling the code string[] dependentAssemblies = new string[] { "CsDebugScript.Engine.dll", "CsDebugScript.CommonUserTypes.dll", }; if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { List <MetadataReference> references = new List <MetadataReference> { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), }; foreach (string dependentAssembly in dependentAssemblies) { if (!xmlConfig.ReferencedAssemblies.Any(a => a.Path.EndsWith(dependentAssembly))) { references.Add(MetadataReference.CreateFromFile(ResolveAssemblyPath(dependentAssembly))); } } references.AddRange(xmlConfig.ReferencedAssemblies.Select(r => MetadataReference.CreateFromFile(ResolveAssemblyPath(r.Path)))); foreach (var includedFile in includedFiles) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(includedFile.Path), path: includedFile.Path, encoding: System.Text.UTF8Encoding.Default)); } CSharpCompilation compilation = CSharpCompilation.Create( Path.GetFileNameWithoutExtension(xmlConfig.GeneratedAssemblyName), syntaxTrees: syntaxTrees, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, platform: Platform.AnyCpu)); logger.WriteLine("Syntax trees: {0}", syntaxTrees.Count); string dllFilename = Path.Combine(outputDirectory, xmlConfig.GeneratedAssemblyName); string pdbFilename = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(dllFilename) + ".pdb"); using (var dllStream = new FileStream(dllFilename, FileMode.Create)) using (var pdbStream = new FileStream(pdbFilename, FileMode.Create)) { var result = compilation.Emit(dllStream, !xmlConfig.DisablePdbGeneration ? pdbStream : null); if (!result.Success) { IEnumerable <Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); errorLogger.WriteLine("Compile errors (top 1000):"); foreach (var diagnostic in failures.Take(1000)) { errorLogger.WriteLine(diagnostic); } } else { logger.WriteLine("DLL size: {0}", dllStream.Position); logger.WriteLine("PDB size: {0}", pdbStream.Position); } } logger.WriteLine("Compiling: {0}", stopwatch.Elapsed); } // Check whether we should generate assembly if (!xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { #if NET461 var codeProvider = new CSharpCodeProvider(); var compilerParameters = new CompilerParameters() { IncludeDebugInformation = !xmlConfig.DisablePdbGeneration, OutputAssembly = outputDirectory + xmlConfig.GeneratedAssemblyName, }; List <string> assembliesList = new List <string>(); foreach (var assemblyPath in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).Select(a => a.Location)) { string assemblyName = Path.GetFileName(assemblyPath); if (!xmlConfig.ReferencedAssemblies.Any(a => a.Path.EndsWith(assemblyName))) { assembliesList.Add(assemblyPath); } } foreach (string dependentAssembly in dependentAssemblies) { if (!xmlConfig.ReferencedAssemblies.Any(a => a.Path.EndsWith(dependentAssembly))) { assembliesList.Add(ResolveAssemblyPath(dependentAssembly)); } } assembliesList.AddRange(xmlConfig.ReferencedAssemblies.Select(r => ResolveAssemblyPath(r.Path)).ToArray()); const string MicrosoftCSharpDll = "Microsoft.CSharp.dll"; if (!assembliesList.Where(a => a.Contains(MicrosoftCSharpDll)).Any()) { assembliesList.Add(MicrosoftCSharpDll); } Dictionary <string, string> uniqueAssemblies = new Dictionary <string, string>(); foreach (string assemblyString in assembliesList) { string key = Path.GetFileName(assemblyString); if (!uniqueAssemblies.ContainsKey(key)) { uniqueAssemblies.Add(key, assemblyString); } } compilerParameters.ReferencedAssemblies.AddRange(uniqueAssemblies.Values.ToArray()); var filesToCompile = generatedFiles.Values.Concat(includedFiles.Select(f => f.Path)).ToArray(); var compileResult = codeProvider.CompileAssemblyFromFile(compilerParameters, filesToCompile); if (compileResult.Errors.Count > 0) { errorLogger.WriteLine("Compile errors (top 1000):"); foreach (CompilerError err in compileResult.Errors.Cast <CompilerError>().Take(1000)) { errorLogger.WriteLine(err); } } logger.WriteLine("Compiling: {0}", stopwatch.Elapsed); #else throw new Exception(".NET standard must use Roslyn to generate assemblies."); #endif } // Generating props file if (!string.IsNullOrEmpty(xmlConfig.GeneratedPropsFileName)) { using (TextWriter output = new StreamWriter(outputDirectory + xmlConfig.GeneratedPropsFileName, false /* append */, System.Text.Encoding.UTF8, 16 * 1024 * 1024)) { output.WriteLine(@"<?xml version=""1.0"" encoding=""utf-8""?>"); output.WriteLine(@"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">"); output.WriteLine(@" <ItemGroup>"); foreach (var file in generatedFiles.Values) { output.WriteLine(@" <Compile Include=""{0}"" />", file); } output.WriteLine(@" </ItemGroup>"); output.WriteLine(@"</Project>"); } } logger.WriteLine("Total time: {0}", stopwatch.Elapsed); }
/// <summary> /// Generates the code for user type and creates a file for it. /// </summary> /// <param name="codeWriter">The code writer used to output generated code.</param> /// <param name="userType">The user type.</param> /// <param name="factory">The user type factory.</param> /// <param name="outputDirectory">The output directory where code file will be stored.</param> /// <param name="errorOutput">The error output.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="generatedFiles">The list of already generated files.</param> /// <returns>Tuple of generated code and filename</returns> private static Tuple <string, string> GenerateCode(ICodeWriter codeWriter, UserType userType, UserTypeFactory factory, string outputDirectory, TextWriter errorOutput, UserTypeGenerationFlags generationFlags, ConcurrentDictionary <string, string> generatedFiles) { Symbol symbol = userType.Symbol; if (symbol != null && symbol.Tag == CodeTypeTag.BuiltinType) { // Ignore built-in types. return(Tuple.Create("", "")); } bool allParentsAreNamespaces = true; for (UserType parentType = userType.DeclaredInType; parentType != null && allParentsAreNamespaces; parentType = parentType.DeclaredInType) { allParentsAreNamespaces = parentType is NamespaceUserType; } if (userType is NamespaceUserType || !allParentsAreNamespaces) { return(Tuple.Create("", "")); } string classOutputDirectory = outputDirectory; string nameSpace = (userType.DeclaredInType as NamespaceUserType)?.FullTypeName ?? userType.Namespace; if (!string.IsNullOrEmpty(nameSpace)) { classOutputDirectory = Path.Combine(classOutputDirectory, nameSpace.Replace(".", "\\").Replace(":", ".")); } if (!generationFlags.HasFlag(UserTypeGenerationFlags.DontSaveGeneratedCodeFiles)) { Directory.CreateDirectory(classOutputDirectory); } bool isEnum = userType is EnumUserType; string filename = string.Format(@"{0}\{1}{2}.exported.cs", classOutputDirectory, userType.ConstructorName, isEnum ? "_enum" : ""); int index = 1; while (true) { if (generatedFiles.TryAdd(filename.ToLowerInvariant(), filename)) { break; } filename = string.Format(@"{0}\{1}{2}_{3}.exported.cs", classOutputDirectory, userType.ConstructorName, isEnum ? "_enum" : "", index++); } StringBuilder stringOutput = new StringBuilder(); codeWriter.WriteUserType(userType, stringOutput); string text = stringOutput.ToString(); if (!generationFlags.HasFlag(UserTypeGenerationFlags.DontSaveGeneratedCodeFiles)) { File.WriteAllText(filename, text); } return(Tuple.Create(text, filename)); }
/// <summary> /// Generates user types using the specified XML configuration. /// </summary> /// <param name="xmlConfig">The XML configuration.</param> /// <param name="logger">The logger text writer. If set to null, Console.Out will be used.</param> /// <param name="errorLogger">The error logger text writer. If set to null, Console.Error will be used.</param> public static void Generate(XmlConfig xmlConfig, TextWriter logger = null, TextWriter errorLogger = null) { var sw = System.Diagnostics.Stopwatch.StartNew(); XmlModule[] xmlModules = xmlConfig.Modules; XmlType[] typeNames = xmlConfig.Types; XmlIncludedFile[] includedFiles = xmlConfig.IncludedFiles; XmlReferencedAssembly[] referencedAssemblies = xmlConfig.ReferencedAssemblies; UserTypeGenerationFlags generationOptions = xmlConfig.GetGenerationFlags(); ConcurrentDictionary <string, string> generatedFiles = new ConcurrentDictionary <string, string>(); var syntaxTrees = new ConcurrentBag <SyntaxTree>(); string currentDirectory = Directory.GetCurrentDirectory(); string outputDirectory = currentDirectory + "\\output\\"; // Check logger and error logger if (errorLogger == null) { errorLogger = Console.Error; } if (logger == null) { logger = Console.Out; } // Create output directory Directory.CreateDirectory(outputDirectory); // Verify that included files exist if (!string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { foreach (var file in includedFiles) { if (!File.Exists(file.Path)) { throw new FileNotFoundException("", file.Path); } } } // Loading modules ConcurrentDictionary <Module, XmlModule> modules = new ConcurrentDictionary <Module, XmlModule>(); ConcurrentDictionary <XmlModule, Symbol[]> globalTypesPerModule = new ConcurrentDictionary <XmlModule, Symbol[]>(); logger.Write("Loading modules..."); Parallel.ForEach(xmlModules, (xmlModule) => { Module module = Module.Open(xmlModule); modules.TryAdd(module, xmlModule); }); logger.WriteLine(" {0}", sw.Elapsed); // Enumerating symbols logger.Write("Enumerating symbols..."); Parallel.ForEach(modules, (mm) => { XmlModule xmlModule = mm.Value; Module module = mm.Key; string moduleName = xmlModule.Name; string nameSpace = xmlModule.Namespace; List <Symbol> symbols = new List <Symbol>(); foreach (var type in typeNames) { Symbol[] foundSymbols = module.FindGlobalTypeWildcard(type.NameWildcard); if (foundSymbols.Length == 0) { errorLogger.WriteLine("Symbol not found: {0}", type.Name); } else { symbols.AddRange(foundSymbols); } } symbols.AddRange(module.GetAllTypes()); globalTypesPerModule.TryAdd(xmlModule, symbols.ToArray()); }); List <Symbol> allSymbols = new List <Symbol>(); Symbol[][] symbolsPerModule = globalTypesPerModule.Select(ss => ss.Value).ToArray(); int maxSymbols = symbolsPerModule.Max(ss => ss.Length); for (int i = 0; i < maxSymbols; i++) { for (int j = 0; j < symbolsPerModule.Length; j++) { if (i < symbolsPerModule[j].Length) { allSymbols.Add(symbolsPerModule[j][i]); } } } logger.WriteLine(" {0}", sw.Elapsed); #if false // Initialize symbol fields and base classes logger.Write("Initializing symbol values..."); Parallel.ForEach(Partitioner.Create(allSymbols), (symbol) => { var fields = symbol.Fields; var baseClasses = symbol.BaseClasses; }); logger.WriteLine(" {0}", sw.Elapsed); #endif logger.Write("Deduplicating symbols..."); // Group duplicated symbols Dictionary <string, List <Symbol> > symbolsByName = new Dictionary <string, List <Symbol> >(); Dictionary <Symbol, List <Symbol> > duplicatedSymbols = new Dictionary <Symbol, List <Symbol> >(); foreach (var symbol in allSymbols) { List <Symbol> symbols; if (!symbolsByName.TryGetValue(symbol.Name, out symbols)) { symbolsByName.Add(symbol.Name, symbols = new List <Symbol>()); } bool found = false; foreach (var s in symbols.ToArray()) { if (s.Size != 0 && symbol.Size != 0 && s.Size != symbol.Size) { #if DEBUG logger.WriteLine("{0}!{1} ({2}) {3}!{4} ({5})", s.Module.Name, s.Name, s.Size, symbol.Module.Name, symbol.Name, symbol.Size); #endif continue; } if (s.Size == 0 && symbol.Size != 0) { List <Symbol> duplicates; if (!duplicatedSymbols.TryGetValue(s, out duplicates)) { duplicatedSymbols.Add(s, duplicates = new List <Symbol>()); } duplicatedSymbols.Remove(s); duplicates.Add(s); duplicatedSymbols.Add(symbol, duplicates); symbols.Remove(s); symbols.Add(symbol); } else { List <Symbol> duplicates; if (!duplicatedSymbols.TryGetValue(s, out duplicates)) { duplicatedSymbols.Add(s, duplicates = new List <Symbol>()); } duplicates.Add(symbol); } found = true; break; } if (!found) { symbols.Add(symbol); } } // Unlink duplicated symbols if two or more are named the same foreach (var symbols in symbolsByName.Values) { if (symbols.Count <= 1) { continue; } foreach (var s in symbols.ToArray()) { List <Symbol> duplicates; if (!duplicatedSymbols.TryGetValue(s, out duplicates)) { continue; } symbols.AddRange(duplicates); duplicatedSymbols.Remove(s); } } // Extracting deduplicated symbols Dictionary <string, Symbol[]> deduplicatedSymbols = new Dictionary <string, Symbol[]>(); Dictionary <Symbol, string> symbolNamespaces = new Dictionary <Symbol, string>(); foreach (var symbols in symbolsByName.Values) { if (symbols.Count != 1) { foreach (var s in symbols) { symbolNamespaces.Add(s, modules[s.Module].Namespace); } } else { Symbol symbol = symbols.First(); List <Symbol> duplicates; if (!duplicatedSymbols.TryGetValue(symbol, out duplicates)) { duplicates = new List <Symbol>(); } duplicates.Insert(0, symbol); deduplicatedSymbols.Add(symbol.Name, duplicates.ToArray()); foreach (var s in duplicates) { symbolNamespaces.Add(s, xmlConfig.CommonTypesNamespace); } } } var globalTypes = symbolsByName.SelectMany(s => s.Value).ToArray(); logger.WriteLine(" {0}", sw.Elapsed); logger.WriteLine(" Total symbols: {0}", globalTypesPerModule.Sum(gt => gt.Value.Length)); logger.WriteLine(" Unique symbol names: {0}", symbolsByName.Count); logger.WriteLine(" Dedupedlicated symbols: {0}", globalTypes.Length); // Initialize GlobalCache with deduplicatedSymbols GlobalCache.Update(deduplicatedSymbols); // Collecting types logger.Write("Collecting types..."); var factory = new UserTypeFactory(xmlConfig.Transformations); List <UserType> userTypes = new List <UserType>(); foreach (var module in modules.Keys) { userTypes.Add(factory.AddSymbol(module.GlobalScope, new XmlType() { Name = "ModuleGlobals" }, modules[module].Namespace, generationOptions)); } ConcurrentBag <Symbol> simpleSymbols = new ConcurrentBag <Symbol>(); Dictionary <Tuple <string, string>, List <Symbol> > templateSymbols = new Dictionary <Tuple <string, string>, List <Symbol> >(); Parallel.ForEach(Partitioner.Create(globalTypes), (symbol) => { string symbolName = symbol.Name; // TODO: Add configurable filter // if (symbolName.StartsWith("$") || symbolName.StartsWith("__vc_attributes") || symbolName.Contains("`anonymous-namespace'") || symbolName.Contains("`anonymous namespace'") || symbolName.Contains("::$") || symbolName.Contains("`")) { return; } // Do not handle template referenced arguments if (symbolName.Contains("&")) { // TODO: Convert this to function pointer return; } // TODO: C# doesn't support lengthy names if (symbolName.Length > 160) { return; } // TODO: For now remove all unnamed-type symbols string scopedClassName = symbol.Namespaces.Last(); if (scopedClassName.StartsWith("<") || symbolName.Contains("::<")) { return; } // Check if symbol contains template type. if (SymbolNameHelper.ContainsTemplateType(symbolName)) { List <string> namespaces = symbol.Namespaces; string className = namespaces.Last(); var symbolId = Tuple.Create(symbolNamespaces[symbol], SymbolNameHelper.CreateLookupNameForSymbol(symbol)); lock (templateSymbols) { if (templateSymbols.ContainsKey(symbolId) == false) { templateSymbols[symbolId] = new List <Symbol>() { symbol } } ; else { templateSymbols[symbolId].Add(symbol); } } // TODO: // Do not add physical types for template specialization (not now) // do if types contains static fields // nested in templates } else { simpleSymbols.Add(symbol); } }); logger.WriteLine(" {0}", sw.Elapsed); // Populate Templates logger.Write("Populating templates..."); foreach (List <Symbol> symbols in templateSymbols.Values) { Symbol symbol = symbols.First(); string symbolName = SymbolNameHelper.CreateLookupNameForSymbol(symbol); XmlType type = new XmlType() { Name = symbolName }; userTypes.AddRange(factory.AddSymbols(symbols, type, symbolNamespaces[symbol], generationOptions)); } logger.WriteLine(" {0}", sw.Elapsed); // Specialized class logger.Write("Populating specialized classes..."); foreach (Symbol symbol in simpleSymbols) { userTypes.Add(factory.AddSymbol(symbol, null, symbolNamespaces[symbol], generationOptions)); } logger.WriteLine(" {0}", sw.Elapsed); // To solve template dependencies. Update specialization arguments once all the templates has been populated. logger.Write("Updating template arguments..."); foreach (TemplateUserType templateUserType in userTypes.OfType <TemplateUserType>()) { foreach (TemplateUserType specializedTemplateUserType in templateUserType.SpecializedTypes) { if (!specializedTemplateUserType.UpdateTemplateArguments(factory)) { #if DEBUG logger.WriteLine("Template user type cannot be updated: {0}", specializedTemplateUserType.Symbol.Name); #endif } } } logger.WriteLine(" {0}", sw.Elapsed); // Post processing user types (filling DeclaredInType) logger.Write("Post processing user types..."); var namespaceTypes = factory.ProcessTypes(userTypes, symbolNamespaces).ToArray(); userTypes.AddRange(namespaceTypes); logger.WriteLine(" {0}", sw.Elapsed); // Code generation and saving it to disk logger.Write("Saving code to disk..."); if (!generationOptions.HasFlag(UserTypeGenerationFlags.SingleFileExport)) { // Generate Code Parallel.ForEach(userTypes, (symbolEntry) => { Tuple <string, string> result = GenerateCode(symbolEntry, factory, outputDirectory, errorLogger, generationOptions, generatedFiles); string text = result.Item1; string filename = result.Item2; if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName) && !string.IsNullOrEmpty(text)) { lock (syntaxTrees) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(text, path: filename, encoding: System.Text.UTF8Encoding.Default)); } } }); } else { string filename = string.Format(@"{0}\everything.exported.cs", outputDirectory); HashSet <string> usings = new HashSet <string>(); foreach (var symbolEntry in userTypes) { foreach (var u in symbolEntry.Usings) { usings.Add(u); } } generatedFiles.TryAdd(filename.ToLowerInvariant(), filename); using (StringWriter stringOutput = new StringWriter()) using (TextWriter masterOutput = !xmlConfig.DontSaveGeneratedCodeFiles ? new StreamWriter(filename, false /* append */, System.Text.Encoding.UTF8, 16 * 1024 * 1024) : TextWriter.Null) { foreach (var u in usings.OrderBy(s => s)) { masterOutput.WriteLine("using {0};", u); if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { stringOutput.WriteLine("using {0};", u); } } masterOutput.WriteLine(); if (xmlConfig.GenerateAssemblyWithRoslyn) { stringOutput.WriteLine(); } ObjectPool <StringWriter> stringWriterPool = new ObjectPool <StringWriter>(() => new StringWriter()); Parallel.ForEach(userTypes, (symbolEntry) => { var output = stringWriterPool.GetObject(); output.GetStringBuilder().Clear(); GenerateCodeInSingleFile(output, symbolEntry, factory, errorLogger, generationOptions); string text = output.ToString(); if (!string.IsNullOrEmpty(text)) { lock (masterOutput) { masterOutput.WriteLine(text); if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { stringOutput.WriteLine(text); } } } stringWriterPool.PutObject(output); }); if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(stringOutput.ToString(), path: filename, encoding: UTF8Encoding.Default)); } } } logger.WriteLine(" {0}", sw.Elapsed); // Compiling the code string binFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { List <MetadataReference> references = new List <MetadataReference> { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) }; references.AddRange(xmlConfig.ReferencedAssemblies.Select(r => MetadataReference.CreateFromFile(r.Path))); foreach (var includedFile in includedFiles) { syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(includedFile.Path), path: includedFile.Path, encoding: System.Text.UTF8Encoding.Default)); } CSharpCompilation compilation = CSharpCompilation.Create( Path.GetFileNameWithoutExtension(xmlConfig.GeneratedAssemblyName), syntaxTrees: syntaxTrees, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, platform: Platform.X64)); logger.WriteLine("Syntax trees: {0}", syntaxTrees.Count); string dllFilename = Path.Combine(outputDirectory, xmlConfig.GeneratedAssemblyName); string pdbFilename = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(dllFilename) + ".pdb"); using (var dllStream = new FileStream(dllFilename, FileMode.Create)) using (var pdbStream = new FileStream(pdbFilename, FileMode.Create)) { var result = compilation.Emit(dllStream, !xmlConfig.DisablePdbGeneration ? pdbStream : null); if (!result.Success) { IEnumerable <Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); errorLogger.WriteLine("Compile errors (top 1000):"); foreach (var diagnostic in failures.Take(1000)) { errorLogger.WriteLine(diagnostic); } } else { logger.WriteLine("DLL size: {0}", dllStream.Position); logger.WriteLine("PDB size: {0}", pdbStream.Position); } } logger.WriteLine("Compiling: {0}", sw.Elapsed); } // Check whether we should generate assembly if (!xmlConfig.GenerateAssemblyWithRoslyn && !string.IsNullOrEmpty(xmlConfig.GeneratedAssemblyName)) { var codeProvider = new CSharpCodeProvider(); var compilerParameters = new CompilerParameters() { IncludeDebugInformation = !xmlConfig.DisablePdbGeneration, OutputAssembly = outputDirectory + xmlConfig.GeneratedAssemblyName, }; compilerParameters.ReferencedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).Select(a => a.Location).ToArray()); //compilerParameters.ReferencedAssemblies.AddRange(referencedAssemblies); const string MicrosoftCSharpDll = "Microsoft.CSharp.dll"; if (!compilerParameters.ReferencedAssemblies.Cast <string>().Where(a => a.Contains(MicrosoftCSharpDll)).Any()) { compilerParameters.ReferencedAssemblies.Add(MicrosoftCSharpDll); } compilerParameters.ReferencedAssemblies.Add(Path.Combine(binFolder, "CsDebugScript.Engine.dll")); compilerParameters.ReferencedAssemblies.Add(Path.Combine(binFolder, "CsDebugScript.CommonUserTypes.dll")); var filesToCompile = generatedFiles.Values.Union(includedFiles.Select(f => f.Path)).ToArray(); var compileResult = codeProvider.CompileAssemblyFromFile(compilerParameters, filesToCompile); if (compileResult.Errors.Count > 0) { errorLogger.WriteLine("Compile errors (top 1000):"); foreach (CompilerError err in compileResult.Errors.Cast <CompilerError>().Take(1000)) { errorLogger.WriteLine(err); } } logger.WriteLine("Compiling: {0}", sw.Elapsed); } // Generating props file if (!string.IsNullOrEmpty(xmlConfig.GeneratedPropsFileName)) { using (TextWriter output = new StreamWriter(outputDirectory + xmlConfig.GeneratedPropsFileName, false /* append */, System.Text.Encoding.UTF8, 16 * 1024 * 1024)) { output.WriteLine(@"<?xml version=""1.0"" encoding=""utf-8""?>"); output.WriteLine(@"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">"); output.WriteLine(@" <ItemGroup>"); foreach (var file in generatedFiles.Values) { output.WriteLine(@" <Compile Include=""{0}"" />", file); } output.WriteLine(@" </ItemGroup>"); output.WriteLine(@"</Project>"); } } logger.WriteLine("Total time: {0}", sw.Elapsed); }
/// <summary> /// Generates user type field based on the specified symbol field and all other fields that are prepared for this function. /// Do not use this function directly, unless you are calling it from overridden function. /// </summary> /// <param name="field">The symbol field.</param> /// <param name="fieldType">The field tree type.</param> /// <param name="factory">The user type factory.</param> /// <param name="simpleFieldValue">The code foe "simple field value" used when creating transformation.</param> /// <param name="gettingField">The code for getting field variable.</param> /// <param name="isStatic">if set to <c>true</c> generated field should be static.</param> /// <param name="generationFlags">The user type generation flags.</param> /// <param name="extractingBaseClass">if set to <c>true</c> user type field is being generated for getting base class.</param> protected override UserTypeField ExtractFieldInternal(SymbolField field, TypeTree fieldType, UserTypeFactory factory, string simpleFieldValue, string gettingField, bool isStatic, UserTypeGenerationFlags generationFlags, bool extractingBaseClass) { // Physical code generation make sense only for non-static fields if (!isStatic) { bool lazyCacheUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.LazyCacheUserTypeFields); bool cacheUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.CacheUserTypeFields); bool cacheStaticUserTypeFields = generationFlags.HasFlag(UserTypeGenerationFlags.CacheStaticUserTypeFields); string constructorText = ""; string fieldName = field.Name; string fieldTypeString = fieldType.GetTypeString(); BasicTypeTree baseType = fieldType as BasicTypeTree; ArrayTypeTree codeArrayType = fieldType as ArrayTypeTree; UserTypeTree userType = fieldType as UserTypeTree; TransformationTypeTree transformationType = fieldType as TransformationTypeTree; bool isEmbedded = field.Type.Tag != SymTagEnum.SymTagPointerType; // Specialization for basic types if (baseType != null) { if (baseType.BasicType == "string") { int charSize = field.Type.ElementType.Size; constructorText = string.Format("ReadString(GetCodeType().Module.Process, ReadPointer(memoryBuffer, memoryBufferOffset + {0}, {1}), {2})", field.Offset, field.Type.Size, charSize); } else if (baseType.BasicType != "NakedPointer") { if (field.LocationType == LocationType.BitField) constructorText = string.Format("Read{0}(memoryBuffer, memoryBufferOffset + {1}, {2}, {3})", baseType.GetTypeString().UppercaseFirst(), field.Offset, field.Size, field.BitPosition); else constructorText = string.Format("Read{0}(memoryBuffer, memoryBufferOffset + {1})", baseType.GetTypeString().UppercaseFirst(), field.Offset); } } // Specialization for arrays else if (codeArrayType != null) { if (codeArrayType.ElementType is BasicTypeTree) { baseType = (BasicTypeTree)codeArrayType.ElementType; if (baseType != null && baseType.BasicType != "string" && baseType.BasicType != "NakedPointer") { int arraySize = field.Type.Size; int elementSize = field.Type.ElementType.Size; if (baseType.BasicType == "char") constructorText = string.Format("Read{0}Array(memoryBuffer, memoryBufferOffset + {1}, {2}, {3})", baseType.GetTypeString().UppercaseFirst(), field.Offset, arraySize / elementSize, elementSize); else constructorText = string.Format("Read{0}Array(memoryBuffer, memoryBufferOffset + {1}, {2})", baseType.GetTypeString().UppercaseFirst(), field.Offset, arraySize / elementSize); fieldTypeString = baseType.GetTypeString() + "[]"; } } } // Specialization for user types else if (userType != null && !extractingBaseClass) { if (!(userType.UserType is EnumUserType)) { string thisClassCodeType; if (IsTypeUsingStaticCodeType(this)) thisClassCodeType = ClassCodeType; else { thisClassCodeType = "thisClass.Value.GetCodeType()"; usedThisClass = true; } // Check if type is embedded if (!isEmbedded) { // If user type is not embedded, we do have pointer inside of our memory buffer that we can read directly if (IsTypeUsingStaticCodeType(this)) constructorText = string.Format("ReadPointer<{0}>({4}, \"{1}\", memoryBuffer, memoryBufferOffset + {2}, {3})", fieldTypeString, fieldName, field.Offset, field.Type.Size, ClassCodeType); else { constructorText = string.Format("ReadPointer<{0}>(thisClass, \"{1}\", memoryBuffer, memoryBufferOffset + {2}, {3})", fieldTypeString, fieldName, field.Offset, field.Type.Size); usedThisClass = true; } // Do downcasting if field has vtable if (userType.UserType.Symbol.HasVTable() && userType.UserType.DerivedClasses.Count > 0) constructorText += ".DowncastObject()"; } else { // If user type is embedded, we can reuse memory buffer that we already have in this class string fieldAddress = string.Format("memoryBufferAddress + (ulong)(memoryBufferOffset + {0})", field.Offset); string fieldCodeType = string.Format("{0}.GetClassFieldType(\"{1}\")", thisClassCodeType, fieldName); if (IsTypeUsingStaticCodeType(userType.UserType)) { fieldCodeType = string.Format("{0}.{1}", userType.UserType.FullClassName, ClassCodeType); } else if (IsTypeUsingStaticCodeType(this)) { fieldCodeType = AddFieldCodeType(fieldName); } constructorText = string.Format("new {0}(memoryBuffer, memoryBufferOffset + {1}, memoryBufferAddress, {2}, {3}, \"{4}\")", fieldTypeString, field.Offset, fieldCodeType, fieldAddress, fieldName); } } else { // TODO: This is enum. Read how much enum base type is big and just cast to enum type... } } // Specialization for transformations else if (transformationType != null) { if (!isEmbedded) { string thisClassCodeType; if (IsTypeUsingStaticCodeType(this)) thisClassCodeType = ClassCodeType; else { thisClassCodeType = "thisClass.Value.GetCodeType()"; usedThisClass = true; } string fieldAddress = string.Format("memoryBufferAddress + (ulong)(memoryBufferOffset + {0})", field.Offset); string fieldVariable = string.Format("Variable.CreateNoCast({0}.GetClassFieldType(\"{1}\"), {2}, \"{1}\")", thisClassCodeType, fieldName, fieldAddress); if (transformationType.Transformation.Transformation.HasPhysicalConstructor) { fieldVariable = string.Format("{0}, memoryBuffer, memoryBufferOffset + {1}, memoryBufferAddress", fieldVariable, field.Offset); } simpleFieldValue = fieldVariable; constructorText = string.Format("new {0}({1})", fieldTypeString, fieldVariable); } } // If we found suitable physical representation, generate the field if (!string.IsNullOrEmpty(constructorText)) return new UserTypeField() { ConstructorText = constructorText, FieldName = "_" + fieldName, FieldType = fieldTypeString, FieldTypeInfoComment = string.Format("// {0} {1};", field.Type.Name, fieldName), PropertyName = UserTypeField.GetPropertyName(fieldName, this), Static = isStatic, UseUserMember = lazyCacheUserTypeFields, CacheResult = cacheUserTypeFields || (isStatic && cacheStaticUserTypeFields), SimpleFieldValue = simpleFieldValue, }; } return base.ExtractFieldInternal(field, fieldType, factory, simpleFieldValue, gettingField, isStatic, generationFlags, extractingBaseClass); }