Upsilon/Upsilon/BoundTypes/UserDataBoundTypeDefinition.cs

306 lines
11 KiB
C#
Raw Permalink Normal View History

2018-12-05 14:14:31 +00:00
using System;
2018-11-29 17:09:08 +00:00
using System.Collections.Generic;
2018-12-12 16:28:12 +00:00
using System.Collections.Immutable;
2018-12-03 17:32:27 +00:00
using System.Linq;
using System.Reflection;
2018-11-29 17:09:08 +00:00
using Upsilon.BaseTypes;
2018-12-12 16:28:12 +00:00
using Upsilon.Binder;
using Upsilon.Binder.VariableSymbols;
using Upsilon.StandardLibraries;
2018-12-05 14:14:31 +00:00
using Type = Upsilon.BaseTypes.Type;
2018-11-29 17:09:08 +00:00
namespace Upsilon.BoundTypes
{
public class UserDataBoundTypeDefinition : BoundTypeDefinition
{
public string Name { get; protected set; }
2018-12-05 14:14:31 +00:00
public Dictionary<string, UserDataBoundProperty> Properties { get; protected set; }
2018-11-29 17:09:08 +00:00
internal UserDataBoundTypeDefinition(System.Type backingType, string name)
: base(new TypeContainer(name), backingType)
2018-11-29 17:09:08 +00:00
{
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;
if (currentBaseType == typeof(ValueType))
break;
yield return currentBaseType;
currentBaseType = currentBaseType.BaseType;
}
2018-12-05 14:14:31 +00:00
}
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)
2018-12-05 14:14:31 +00:00
{
var obj = new UserDataBoundTypeDefinition(backingType, name)
2018-12-05 14:14:31 +00:00
{
Properties = new Dictionary<string, UserDataBoundProperty>(),
Name = name
2018-12-05 14:14:31 +00:00
};
const BindingFlags generalBindAttributes = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
var fields = backingType.GetFields(generalBindAttributes).Select(x => new UserDataBoundProperty()
2018-12-03 17:32:27 +00:00
{
2018-12-05 14:14:31 +00:00
Name = x.Name,
2018-12-03 17:32:27 +00:00
ActualType = x.FieldType.Name,
2018-12-05 14:14:31 +00:00
Type = x.FieldType.GetScriptType(),
2018-12-03 17:32:27 +00:00
});
foreach (var f in fields)
{
2018-12-05 14:14:31 +00:00
obj.Properties.Add(f.Name.ToLowerInvariant(), f);
2018-12-03 17:32:27 +00:00
}
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()
2018-12-03 17:32:27 +00:00
{
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);
2018-12-03 17:32:27 +00:00
}
2019-01-20 20:00:01 +00:00
var methods = new Dictionary<string, UserDataBoundMethod>();
var backingMethods = backingType.GetMethods(generalBindAttributes);
foreach (var backingMethod in backingMethods)
2018-12-03 17:32:27 +00:00
{
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;
}
2018-12-12 16:28:12 +00:00
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);
2019-01-20 20:00:01 +00:00
if (methods.TryGetValue(methodName, out var func))
{
2019-01-20 20:00:01 +00:00
func.Options.Add(option);
}
else
{
methods.Add(methodName,
2019-01-20 21:27:47 +00:00
new UserDataBoundMethod(methodName, new List<UserDataBoundMethodOption>() {option})
{
Type = Type.Function
});
2019-01-20 20:00:01 +00:00
}
}
2018-12-03 17:32:27 +00:00
foreach (var f in methods)
{
2019-01-20 20:00:01 +00:00
var cleanedName = f.Value.Name.ToLowerInvariant();
2018-12-07 15:51:17 +00:00
// TODO: handle this better, considering overloads
if (obj.Properties.ContainsKey(cleanedName))
continue;
2019-01-20 20:00:01 +00:00
obj.Properties.Add(cleanedName, f.Value);
2018-12-03 17:32:27 +00:00
}
2018-12-05 14:14:31 +00:00
return obj;
2018-11-29 17:09:08 +00:00
}
2018-12-05 14:14:31 +00:00
}
2018-11-29 17:09:08 +00:00
2018-12-05 14:14:31 +00:00
public class UserDataBoundEnumDefinition : UserDataBoundTypeDefinition
{
public UserDataBoundEnumDefinition(System.Type enumType, string name) : base(enumType, name)
2018-11-29 17:09:08 +00:00
{
2018-12-05 14:14:31 +00:00
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;
2018-12-05 14:14:31 +00:00
for (var i=0; i < enumValues.Length; i++)
{
var value = enumValues.GetValue(i);
var valueName = value.ToString().ToLowerInvariant();
Properties.Add(valueName, new UserDataBoundProperty()
2018-12-05 14:14:31 +00:00
{
Name = valueName,
ActualType = enumType.ToString(),
Type = new TypeContainer(name)
2018-12-05 14:14:31 +00:00
});
}
2018-11-29 17:09:08 +00:00
}
2018-12-13 20:18:12 +00:00
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();
2018-12-13 20:18:12 +00:00
Properties.Add(valueName, new UserDataBoundProperty()
{
Name = valueName,
Type = new TypeContainer(name),
ActualType = name
2018-12-13 20:18:12 +00:00
});
}
}
2018-11-29 17:09:08 +00:00
}
public class UserDataBoundProperty
{
public string Name { get; set; }
public TypeContainer Type { get; set; }
2018-12-03 17:32:27 +00:00
public virtual string ActualType { get; set; }
2018-11-29 17:09:08 +00:00
public string Comment { get; set; }
}
2018-12-03 17:32:27 +00:00
2018-12-12 16:28:12 +00:00
public class UserDataBoundFunctionParameter : UserDataBoundProperty
{
public bool IsOptional { get; set; }
}
2018-12-03 17:32:27 +00:00
public class UserDataBoundMethod: UserDataBoundProperty
{
public override string ActualType => "Function";
2019-01-20 20:00:01 +00:00
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)
2019-01-20 20:00:01 +00:00
{
ResultType = resultType;
Parameters = parameters;
PassScriptReference = passScriptReference;
NonOptionalParameterCount = Parameters.Count(x => !x.IsOptional);
2019-01-20 20:00:01 +00:00
}
public TypeContainer ResultType { get; }
public UserDataBoundFunctionParameter[] Parameters { get; }
public bool PassScriptReference { get; }
private int NonOptionalParameterCount { get; }
2018-12-12 16:28:12 +00:00
2019-01-20 20:00:01 +00:00
public bool ValidateParameters(ImmutableArray<BoundExpression> callingParameters)
2018-12-12 16:28:12 +00:00
{
var effectiveLength = callingParameters.Length;
if (PassScriptReference)
effectiveLength++;
if (effectiveLength < NonOptionalParameterCount
|| effectiveLength > Parameters.Length)
2018-12-12 16:28:12 +00:00
{
2019-01-20 20:00:01 +00:00
return false;
2018-12-12 16:28:12 +00:00
}
var callingIndexOffset = 0;
2018-12-12 16:28:12 +00:00
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)
2018-12-12 16:28:12 +00:00
continue;
if (!functionParameter.Type.Type.HasFlag(callingParameter.ValueType))
2018-12-12 16:28:12 +00:00
{
2019-01-20 20:00:01 +00:00
return false;
2018-12-12 16:28:12 +00:00
}
2019-01-20 20:00:01 +00:00
if (functionParameter.Type.Type.HasFlag(Type.UserData))
{
var variable = Binder.Binder.ResolveVariable(callingParameter, null) as UserDataVariableSymbol;
var parent =
(UserDataBoundTypeDefinition) variable?.BoundTypeDefinition;
2019-01-20 20:00:01 +00:00
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;
}
}
}
}
2018-12-12 16:28:12 +00:00
}
2019-01-20 20:00:01 +00:00
return true;
2018-12-12 16:28:12 +00:00
}
2018-12-03 17:32:27 +00:00
}
2018-11-29 17:09:08 +00:00
}