Skip to content

Commit

Permalink
Add feature to generate builtin types from DLLs (#20)
Browse files Browse the repository at this point in the history
* Takes in a new --dll argument to parse DLLs
* Generates builtin_types.json after parsing DLL + PDB
* Language version upgraded to C#13 (net8.0)
* Filters in only public & non-desugared types and methods
  • Loading branch information
tuxology authored Feb 28, 2024
1 parent 8ad13f8 commit 70c4c2e
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 8 deletions.
3 changes: 2 additions & 1 deletion DotNetAstGen/DotNetAstGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Version Condition=" '$(NEXT_VERSION)' == '' ">0.0.1-local</Version>
<Version Condition=" '$(NEXT_VERSION)' != '' ">$(NEXT_VERSION)</Version>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SelfContained>true</SelfContained>
Expand All @@ -17,6 +17,7 @@
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

Expand Down
101 changes: 95 additions & 6 deletions DotNetAstGen/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,26 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Mono.Cecil;
using Mono.Cecil.Rocks;

namespace DotNetAstGen
{
public class MethodInfo
{
public string? name { get; set; }
public string? returnType { get; set; }
public List<List<string>>? parameterTypes { get; set; }
public bool isStatic { get; set; }
}

public class ClassInfo
{
public string? name { get; set; }
public List<MethodInfo>? methods { get; set; }
public List<object>? fields { get; set; }
}

internal class Program
{
public static ILoggerFactory? LoggerFactory;
Expand Down Expand Up @@ -39,10 +56,24 @@ public static void Main(string[] args)
_logger = LoggerFactory.CreateLogger<Program>();
_logger.LogDebug("Show verbose output.");

_RunAstGet(
options.InputFilePath,
new DirectoryInfo(options.OutputDirectory),
options.ExclusionRegex);
if (options.InputFilePath != "") {
_RunAstGet(
options.InputFilePath,
new DirectoryInfo(options.OutputDirectory),
options.ExclusionRegex);
}

if (options.InputDLLFilePath != "") {
var dllName = Path.GetFileNameWithoutExtension(options.InputDLLFilePath);
int lastDotIndex = dllName.LastIndexOf('.');
var jsonName = lastDotIndex >= 0 ? Path.GetDirectoryName(options.InputDLLFilePath) + "\\" + dllName.Substring(lastDotIndex + 1) + ".json" : Path.GetDirectoryName(options.InputDLLFilePath) + "\\" + dllName + ".json";
if (options.OutputBuiltInJsonPath != "")
{
jsonName = options.OutputBuiltInJsonPath;
}
ProcessDll(options.InputDLLFilePath, jsonName);
}

});
}

Expand Down Expand Up @@ -149,6 +180,58 @@ private static void _AstForFile(
_logger?.LogError("Error encountered while parsing '{filePath}': {errorMsg}", fullPath, e.Message);
}
}

static void ProcessDll(string dllPath, string jsonPath)
{
var p = new ReaderParameters();
p.ReadSymbols = true;

var classInfoList = new List<ClassInfo>();

using var x = AssemblyDefinition.ReadAssembly(dllPath, p);
Regex typeFilter = new Regex("^(<PrivateImplementationDetails>|<Module>|.*AnonymousType|.*\\/).*", RegexOptions.IgnoreCase);
Regex methodFilter = new Regex("^.*\\.(ctor|cctor)", RegexOptions.IgnoreCase);

foreach (var typ in x.MainModule.GetAllTypes().DistinctBy(t => t.FullName).Where(t => t.Name != null).Where(t => !typeFilter.IsMatch(t.FullName)))
{
var classInfo = new ClassInfo();
var methodInfoList = new List<MethodInfo>();

foreach (var method in typ.Methods.Where(m => !methodFilter.IsMatch(m.Name)).Where( m => m.IsPublic))
{
var methodInfo = new MethodInfo();
var parameterTypesList = new List<List<string>>();
methodInfo.name = method.Name;
methodInfo.returnType = method.ReturnType.ToString();
methodInfo.isStatic = method.IsStatic;
foreach (var param in method.Parameters)
{
parameterTypesList.Add([param.Name, param.ParameterType.FullName]);
}
methodInfo.parameterTypes = parameterTypesList;
methodInfoList.Add(methodInfo);
}

classInfo.methods = methodInfoList;
classInfo.fields = [];
classInfo.name = typ.FullName;
classInfoList.Add(classInfo);
}

var namespaceStructure = new Dictionary<string, List<ClassInfo>>();
foreach (var c in classInfoList)
{
var parentNamespace = string.Join(".", c.name.Split('.').Reverse().Skip(1).Reverse());

Check warning on line 224 in DotNetAstGen/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 224 in DotNetAstGen/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 224 in DotNetAstGen/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 224 in DotNetAstGen/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 224 in DotNetAstGen/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

if (!namespaceStructure.ContainsKey(parentNamespace))
namespaceStructure[parentNamespace] = new List<ClassInfo>();

namespaceStructure[parentNamespace].Add(c);
}

var jsonString = JsonConvert.SerializeObject(namespaceStructure, Formatting.Indented);
File.WriteAllText(jsonPath, jsonString);
}
}


Expand All @@ -157,13 +240,19 @@ internal class Options
[Option('d', "debug", Required = false, HelpText = "Enable verbose output.")]
public bool Debug { get; set; } = false;

[Option('i', "input", Required = true, HelpText = "Input file or directory.")]
[Option('i', "input", Required = false, HelpText = "Input file or directory.")]
public string InputFilePath { get; set; } = "";

[Option('o', "input", Required = false, HelpText = "Output directory. (default `./.ast`)")]
[Option('o', "output", Required = false, HelpText = "Output directory. (default `./.ast`)")]
public string OutputDirectory { get; set; } = ".ast";

[Option('e', "exclude", Required = false, HelpText = "Exclusion regex for while files to filter out.")]
public string? ExclusionRegex { get; set; } = null;

[Option('l', "dll", Required = false, HelpText = "Input DLL file. Ensure a .pdb file is present of same name alongside DLL.")]
public string InputDLLFilePath { get; set; } = "";

[Option('b', "builtin", Required = false, HelpText = "The output JSON file. (default `./builtin_types.json`)")]
public string OutputBuiltInJsonPath { get; set; } = "";
}
}
2 changes: 1 addition & 1 deletion DotNetAstGen/SyntaxMetaDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private static SyntaxMetaData GetNodeMetadata(SyntaxNode node)
);
}

public void SetValue(object target, object value)
public void SetValue(object target, object? value)
{
// ignore
}
Expand Down

0 comments on commit 70c4c2e

Please sign in to comment.