412 lines
16 KiB
C#
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; }
|
|
}
|
|
}
|
|
|
|
}
|
|
} |