Upsilon-VsCode/UpsilonLanguageServer/UpsilonLanguageServer/ReferenceHandler/LoadReferenceLibraries.cs

412 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Upsilon.BaseTypes.UserData;
using Upsilon.Binder;
using Upsilon.Binder.VariableSymbols;
using Upsilon.BoundTypes;
using Upsilon.StandardLibraries;
using Type = Upsilon.BaseTypes.Type;
namespace UpsilonLanguageServer.ReferenceHandler
{
public static class LoadReferenceLibraries
{
private static List<string> References { get; set; } = new List<string>();
public static void ReloadReferences(string workSpace, string jsonString)
{
var obj = JObject.Parse(jsonString);
if (!obj.TryGetValue("references", StringComparison.InvariantCultureIgnoreCase, out var references))
return;
if (!(references is JArray referencesArray))
return;
var values = referencesArray.Values<string>();
foreach (var reference in values)
{
var path = Path.Combine(workSpace, reference);
if (!File.Exists(path))
{
throw new Exception($"Can't read file {path}. It doesn't exist.");
}
if (Path.GetExtension(path) != ".dll")
continue;
References.Add(path);
}
References = References.Distinct().ToList();
LoadAssemblies(References);
}
private static Dictionary<string, string> _translatedType;
private static void LoadAssemblies(IReadOnlyCollection<string> locations)
{
var resolver = new DefaultAssemblyResolver();
foreach (var location in locations)
{
var dir = Directory.GetParent(location);
resolver.AddSearchDirectory(dir.FullName);
}
_translatedType = new Dictionary<string, string>();
var types = locations.Select(x =>
ModuleDefinition.ReadModule(x, new ReaderParameters() {AssemblyResolver = resolver}))
.SelectMany(y => y.Types)
.ToArray();
var requiredUserDataAttribute = typeof(UpsilonUserDataAttribute).FullName;
var userDataTypesEnumerable = types
.SelectMany(x => GetDefinitions(x, requiredUserDataAttribute));
var userDataTypes = new Dictionary<string, TypeDefinition>();
foreach (var tuple in userDataTypesEnumerable)
{
if (!userDataTypes.ContainsKey(tuple.Item1))
userDataTypes.Add(tuple.Item1, tuple.Item2);
}
foreach (var definition in userDataTypes)
{
_translatedType.Add(definition.Value.FullName, definition.Key);
}
var result = new Dictionary<string, Data>();
foreach (var definition in userDataTypes)
{
var name = definition.Key;
var type = definition.Value;
var typeData = ExtractData(type);
result.Add(name, typeData);
if (typeData is EnumData enumData)
{
BoundTypeHandler.LoadUserDataTypeDefinition(new UserDataBoundEnumDefinition(enumData.Values, name));
}
else
{
var dic = new Dictionary<string, UserDataBoundProperty>();
foreach (var property in typeData.Properties)
{
var propertyName = property.Key;
var propertyValue = property.Value;
var scriptType = BoundTypeParser.ParseType(propertyValue.Type);
if (propertyValue is MethodData method)
{
dic.Add(propertyName.ToLowerInvariant(), new UserDataBoundMethod(propertyName,
method.Options.Select(
x =>
{
var returnType = BoundTypeParser.ParseType(x.Returns);
return new UserDataBoundMethodOption(returnType, x.Parameters.Select(y =>
new UserDataBoundFunctionParameter()
{
Name = y.Key,
Type = BoundTypeParser.ParseType(y.Value.Type),
Comment = y.Value.Comment,
ActualType = y.Value.Type,
IsOptional = y.Value.IsOptional
}).ToArray(), false);
}).ToList())
{
Type = Type.Function
});
}
else
{
dic.Add(propertyName.ToLowerInvariant(), new UserDataBoundProperty()
{
Name = propertyName,
ActualType = propertyValue.Type,
Comment = propertyValue.Comment,
Type = scriptType
});
}
}
BoundTypeHandler.LoadUserDataTypeDefinition(new UserDataBoundTypeDefinition(name, dic));
}
}
var requiredStaticAttribute = typeof(UpsilonCreateStaticAttribute).FullName;
var staticUserDataTypes = types
.SelectMany(x => GetDefinitions(x, requiredStaticAttribute))
.ToDictionary(x => x.Item1, x => x.Item2);
foreach (var staticUserDataType in staticUserDataTypes)
{
var variableSymbol = CreateVariableSymbol(staticUserDataType.Key, staticUserDataType.Value);
StaticScope.BoundScope.AssignToNearest(variableSymbol);
}
}
private static VariableSymbol CreateVariableSymbol(string name, TypeDefinition def)
{
var type = BoundTypeParser.ParseType(name);
if (type == Type.UserData)
{
var boundTypeName = _translatedType[def.FullName];
var boundType = BoundTypeHandler.GetTypeDefinition(boundTypeName);
return new UserDataVariableSymbol(name, boundType, true);
}
return new VariableSymbol(name, type, true);
}
private static List<(string, TypeDefinition)> GetDefinitions(TypeDefinition typeDefinition,
string requiredAttribute)
{
var ls = new List<(string, TypeDefinition)>();
foreach (var nestedType in typeDefinition.NestedTypes)
{
var definitions = GetDefinitions(nestedType, requiredAttribute);
if (definitions.Count > 0)
{
ls.AddRange(definitions);
}
}
if (!typeDefinition.HasCustomAttributes) return ls;
foreach (var customAttribute in typeDefinition.CustomAttributes)
{
if (!string.Equals(customAttribute.AttributeType.FullName, requiredAttribute))
{
continue;
}
var name = customAttribute.ConstructorArguments[0].Value.ToString();
ls.Add((name, typeDefinition));
}
return ls;
}
private static Data ExtractData(TypeDefinition type)
{
if (type.IsEnum)
{
var enumTypeData = new EnumData()
{
Values = type.Fields.Where(x => x.IsPublic && !x.IsSpecialName).Select(x => x.Name).ToList()
};
return enumTypeData;
}
var typeData = new Data(){Properties = new Dictionary<string, Property>()};
if (type.BaseType != null)
{
try
{
var resolved = type.BaseType.Resolve();
if (resolved.FullName != "System.Object")
{
var b = ExtractData(type.BaseType.Resolve());
foreach (var property in b.Properties)
{
typeData.Properties.Add(property.Key, property.Value);
}
}
}
catch
{
//ignore
}
}
if (type.HasInterfaces)
{
var interfaces = type.Interfaces;
foreach (var @interface in interfaces)
{
try
{
var extract = ExtractData(@interface.InterfaceType.Resolve());
foreach (var property in extract.Properties)
{
if (!typeData.Properties.ContainsKey(property.Key))
typeData.Properties.Add(property.Key, property.Value);
}
}
catch
{
// ignore
}
}
}
foreach (var fieldDefinition in type.Fields)
{
if (!fieldDefinition.IsPublic)
continue;
var parsedFieldType = GetParsedTypeName(fieldDefinition.FieldType);
if (typeData.Properties.ContainsKey(fieldDefinition.Name))
{
typeData.Properties[fieldDefinition.Name] = new Property()
{
Type = parsedFieldType
};
}
else
{
typeData.Properties.Add(fieldDefinition.Name, new Property()
{
Type = parsedFieldType
});
}
}
foreach (var propertyDefinition in type.Properties)
{
if (!propertyDefinition.GetMethod.IsPublic)
continue;
var parsedFieldType = GetParsedTypeName(propertyDefinition.PropertyType);
if (typeData.Properties.ContainsKey(propertyDefinition.Name))
{
typeData.Properties[propertyDefinition.Name] = new Property()
{
Type = parsedFieldType
};
}
else
{
typeData.Properties.Add(propertyDefinition.Name, new Property()
{
Type = parsedFieldType
});
}
}
var methodDic = new Dictionary<string, MethodData>();
foreach (var methodDefinition in type.Methods)
{
if (!methodDefinition.IsPublic)
continue;
if (methodDefinition.IsConstructor)
continue;
if (typeData.Properties.ContainsKey(methodDefinition.Name))
continue;
if (methodDefinition.IsGetter || methodDefinition.IsSetter)
continue;
var returnType = GetParsedTypeName(methodDefinition.ReturnType);
var parameters = new Dictionary<string, MethodDataOption.MethodParameter>();
foreach (var parameter in methodDefinition.Parameters)
{
var parameterType = GetParsedTypeName(parameter.ParameterType);
var optional = parameter.IsOptional;
parameters.Add(parameter.Name, new MethodDataOption.MethodParameter()
{
Type = parameterType,
IsOptional = optional
});
}
var option = new MethodDataOption()
{
Returns = returnType,
Parameters = parameters
};
if (methodDic.TryGetValue(methodDefinition.Name, out var data))
{
data.Options.Add(option);
}
else
{
methodDic.Add(methodDefinition.Name, new MethodData()
{
Type = "function",
Options = new List<MethodDataOption>(){option}
});
}
}
foreach (var methodData in methodDic)
{
typeData.Properties.Add(methodData.Key, methodData.Value);
}
return typeData;
}
private static string GetParsedTypeName(TypeReference type)
{
if (string.Equals(type.FullName, "System.Void"))
return "nil";
if (string.Equals(type.FullName, "System.String"))
return "string";
if (string.Equals(type.FullName, "System.Boolean"))
return "bool";
if (type.IsPrimitive)
return "number";
if (type.IsArray && type is ArrayType arrayType)
{
var t = arrayType.ElementType;
return $"table (number, {GetParsedTypeName(t)})";
}
if (type is GenericInstanceType genericType)
{
if (type.FullName.StartsWith("System.Collections.Generic.Dictionary"))
{
var generics = genericType.GenericArguments;
if (generics.Count != 2)
return "table";
var key = GetParsedTypeName(generics[0]);
var value = GetParsedTypeName(generics[1]);
return $"table ({key}, {value})";
}
if (type.FullName.StartsWith("System.Collections.Generic.List"))
{
var generics = genericType.GenericArguments;
if (generics.Count != 1)
return "table";
var value = GetParsedTypeName(generics[0]);
return $"table (number, {value})";
}
}
if (_translatedType.TryGetValue(type.FullName, out var s))
{
return s;
}
return "unknown";
}
public class Data
{
public Dictionary<string, Property> Properties { get; set; }
}
public class EnumData : Data
{
[JsonProperty("__type")] public string SpecialType = "enum";
public List<string> Values = new List<string>();
}
public class Property
{
public string Type { get; set; }
public string Comment { get; set; }
}
public class MethodData : Property
{
public List<MethodDataOption> Options { get; set; }
}
public class MethodDataOption
{
public string Returns { get; set; }
public Dictionary<string, MethodParameter> Parameters { get; set; }
public class MethodParameter
{
public string Type { get; set; }
public string Comment { get; set; }
public bool IsOptional { get; set; }
}
}
}
}