304 lines
11 KiB
C#
304 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Upsilon.BaseTypes;
|
|
using Upsilon.Binder;
|
|
using Upsilon.Binder.VariableSymbols;
|
|
using Upsilon.StandardLibraries;
|
|
using Type = Upsilon.BaseTypes.Type;
|
|
|
|
namespace Upsilon.BoundTypes
|
|
{
|
|
public class UserDataBoundTypeDefinition : BoundTypeDefinition
|
|
{
|
|
public string Name { get; protected set; }
|
|
public Dictionary<string, UserDataBoundProperty> Properties { get; protected set; }
|
|
|
|
internal UserDataBoundTypeDefinition(System.Type backingType, string name)
|
|
: base(new TypeContainer(name), backingType)
|
|
{
|
|
Name = backingType.Name;
|
|
ValidInternalTypes = ValidInternalTypes.Concat(GetParentTypes(backingType)).ToArray();
|
|
}
|
|
|
|
private static IEnumerable<System.Type> GetParentTypes(System.Type type)
|
|
{
|
|
// is there any base type?
|
|
if (type == null)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
// return all implemented or inherited interfaces
|
|
foreach (var i in type.GetInterfaces())
|
|
{
|
|
yield return i;
|
|
}
|
|
|
|
// return all inherited types
|
|
var currentBaseType = type.BaseType;
|
|
while (currentBaseType != null)
|
|
{
|
|
// We don't want the global object type to be returned
|
|
if (currentBaseType == typeof(object))
|
|
break;
|
|
yield return currentBaseType;
|
|
currentBaseType = currentBaseType.BaseType;
|
|
}
|
|
}
|
|
|
|
public UserDataBoundTypeDefinition(string name, Dictionary<string, UserDataBoundProperty> backingType)
|
|
: base(new TypeContainer(name), new System.Type[0])
|
|
{
|
|
Name = name;
|
|
Properties = backingType;
|
|
}
|
|
|
|
public static UserDataBoundTypeDefinition Create(System.Type backingType, string name)
|
|
{
|
|
var obj = new UserDataBoundTypeDefinition(backingType, name)
|
|
{
|
|
Properties = new Dictionary<string, UserDataBoundProperty>(),
|
|
Name = name
|
|
};
|
|
const BindingFlags generalBindAttributes = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
|
|
var fields = backingType.GetFields(generalBindAttributes).Select(x => new UserDataBoundProperty()
|
|
{
|
|
Name = x.Name,
|
|
ActualType = x.FieldType.Name,
|
|
Type = x.FieldType.GetScriptType(),
|
|
});
|
|
foreach (var f in fields)
|
|
{
|
|
obj.Properties.Add(f.Name.ToLowerInvariant(), f);
|
|
}
|
|
|
|
PropertyInfo[] realProperties;
|
|
if (backingType.IsInterface)
|
|
{
|
|
realProperties = new[]{backingType}
|
|
.Concat(backingType.GetInterfaces())
|
|
.SelectMany(x => x.GetProperties(generalBindAttributes)).ToArray();
|
|
}
|
|
else
|
|
{
|
|
realProperties = backingType.GetProperties(generalBindAttributes);
|
|
}
|
|
var properties = realProperties.Select(x => new UserDataBoundProperty()
|
|
{
|
|
Name = x.Name,
|
|
ActualType = x.PropertyType.Name,
|
|
Type = x.PropertyType.GetScriptType(),
|
|
});
|
|
foreach (var f in properties)
|
|
{
|
|
var lowerPropertyName = f.Name.ToLowerInvariant();
|
|
if (!obj.Properties.ContainsKey(lowerPropertyName))
|
|
obj.Properties.Add(lowerPropertyName, f);
|
|
}
|
|
var methods = new Dictionary<string, UserDataBoundMethod>();
|
|
var backingMethods = backingType.GetMethods(generalBindAttributes);
|
|
foreach (var backingMethod in backingMethods)
|
|
{
|
|
if (backingMethod.IsSpecialName)
|
|
continue;
|
|
var methodName = backingMethod.Name;
|
|
var passScriptReference = false;
|
|
var attribute = backingMethod.GetCustomAttribute(typeof(ScriptFunctionAttribute));
|
|
if (attribute is ScriptFunctionAttribute sfa )
|
|
{
|
|
methodName = sfa.Name;
|
|
passScriptReference = sfa.PassScriptReference;
|
|
}
|
|
|
|
var parameters = backingMethod.GetParameters().Select(parameter => new UserDataBoundFunctionParameter()
|
|
{
|
|
Name = parameter.Name,
|
|
ActualType = parameter.ParameterType.Name,
|
|
Type = StaticScope.DeriveValidTypes(parameter.ParameterType),
|
|
IsOptional = parameter.IsOptional
|
|
}).ToArray();
|
|
var option = new UserDataBoundMethodOption(backingMethod.ReturnType.GetScriptType(), parameters, passScriptReference);
|
|
if (methods.TryGetValue(methodName, out var func))
|
|
{
|
|
func.Options.Add(option);
|
|
}
|
|
else
|
|
{
|
|
methods.Add(methodName,
|
|
new UserDataBoundMethod(methodName, new List<UserDataBoundMethodOption>() {option})
|
|
{
|
|
Type = Type.Function
|
|
});
|
|
}
|
|
|
|
}
|
|
foreach (var f in methods)
|
|
{
|
|
var cleanedName = f.Value.Name.ToLowerInvariant();
|
|
// TODO: handle this better, considering overloads
|
|
if (obj.Properties.ContainsKey(cleanedName))
|
|
continue;
|
|
obj.Properties.Add(cleanedName, f.Value);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
public class UserDataBoundEnumDefinition : UserDataBoundTypeDefinition
|
|
{
|
|
public UserDataBoundEnumDefinition(System.Type enumType, string name) : base(enumType, name)
|
|
{
|
|
if (!enumType.IsEnum)
|
|
throw new Exception("Trying to bind an enum with a type that's not an enum");
|
|
|
|
Properties = new Dictionary<string, UserDataBoundProperty>();
|
|
var enumValues = Enum.GetValues(enumType);
|
|
|
|
Name = name;
|
|
|
|
for (var i=0; i < enumValues.Length; i++)
|
|
{
|
|
var value = enumValues.GetValue(i);
|
|
|
|
var valueName = value.ToString().ToLowerInvariant();
|
|
Properties.Add(valueName, new UserDataBoundProperty()
|
|
{
|
|
Name = valueName,
|
|
ActualType = enumType.ToString(),
|
|
Type = new TypeContainer(name)
|
|
});
|
|
}
|
|
}
|
|
|
|
public UserDataBoundEnumDefinition(IEnumerable<string> values, string name) : base(name, new Dictionary<string, UserDataBoundProperty>())
|
|
{
|
|
Properties = new Dictionary<string, UserDataBoundProperty>();
|
|
Name = name;
|
|
|
|
foreach (var value in values)
|
|
{
|
|
var valueName = value.ToLowerInvariant();
|
|
Properties.Add(valueName, new UserDataBoundProperty()
|
|
{
|
|
Name = valueName,
|
|
Type = new TypeContainer(name),
|
|
ActualType = name
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public class UserDataBoundProperty
|
|
{
|
|
public string Name { get; set; }
|
|
public TypeContainer Type { get; set; }
|
|
public virtual string ActualType { get; set; }
|
|
public string Comment { get; set; }
|
|
}
|
|
|
|
public class UserDataBoundFunctionParameter : UserDataBoundProperty
|
|
{
|
|
public bool IsOptional { get; set; }
|
|
}
|
|
|
|
public class UserDataBoundMethod: UserDataBoundProperty
|
|
{
|
|
public override string ActualType => "Function";
|
|
public List<UserDataBoundMethodOption> Options;
|
|
|
|
public UserDataBoundMethod(string name, List<UserDataBoundMethodOption> options)
|
|
{
|
|
Name = name;
|
|
Options = options;
|
|
}
|
|
|
|
public UserDataBoundMethodOption Validate(ImmutableArray<BoundExpression> callingParameters)
|
|
{
|
|
return Options.FirstOrDefault(x => x.ValidateParameters(callingParameters));
|
|
}
|
|
}
|
|
|
|
public class UserDataBoundMethodOption
|
|
{
|
|
public UserDataBoundMethodOption(TypeContainer resultType, UserDataBoundFunctionParameter[] parameters,
|
|
bool passScriptReference)
|
|
{
|
|
ResultType = resultType;
|
|
Parameters = parameters;
|
|
PassScriptReference = passScriptReference;
|
|
NonOptionalParameterCount = Parameters.Count(x => !x.IsOptional);
|
|
}
|
|
|
|
public TypeContainer ResultType { get; }
|
|
public UserDataBoundFunctionParameter[] Parameters { get; }
|
|
public bool PassScriptReference { get; }
|
|
private int NonOptionalParameterCount { get; }
|
|
|
|
public bool ValidateParameters(ImmutableArray<BoundExpression> callingParameters)
|
|
{
|
|
var effectiveLength = callingParameters.Length;
|
|
if (PassScriptReference)
|
|
effectiveLength++;
|
|
|
|
if (effectiveLength < NonOptionalParameterCount
|
|
|| effectiveLength > Parameters.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var callingIndexOffset = 0;
|
|
for (var i = 0; i < callingParameters.Length; i++)
|
|
{
|
|
var functionParameter = Parameters[i];
|
|
if (i == 0 && PassScriptReference)
|
|
{
|
|
callingIndexOffset++;
|
|
continue;
|
|
}
|
|
|
|
var callingParameter = callingParameters[i + callingIndexOffset];
|
|
if (callingParameter.ValueType == Type.Unknown || callingParameter.ValueType == Type.Nil ||
|
|
functionParameter.Type == Type.Unknown)
|
|
continue;
|
|
|
|
if (!functionParameter.Type.Type.HasFlag(callingParameter.ValueType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (functionParameter.Type.Type.HasFlag(Type.UserData))
|
|
{
|
|
var variable = Binder.Binder.ResolveVariable(callingParameter, null) as UserDataVariableSymbol;
|
|
var parent =
|
|
(UserDataBoundTypeDefinition) variable?.BoundTypeDefinition;
|
|
if (variable != null && variable.TypeContainer == Type.UserData)
|
|
{
|
|
if (parent.ValidInternalTypes.Length != 0)
|
|
{
|
|
if (functionParameter.ActualType != null)
|
|
{
|
|
if (!parent.ValidInternalTypes.Any(x =>
|
|
string.Equals(x.Name, functionParameter.ActualType)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (!string.Equals(parent.Name, functionParameter.ActualType, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
} |