blob: 833ef94ffa23e5b178eaef736453b855482b1c2e [file] [log] [blame]
//
// Copyright (c) 2010-2024 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure;
using Antmicro.Renode.Exceptions;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Peripherals.Miscellaneous;
using Antmicro.Renode.PlatformDescription.Syntax;
using Antmicro.Renode.Utilities;
using Antmicro.Renode.Utilities.Collections;
using Sprache;
using Range = Antmicro.Renode.Core.Range;
namespace Antmicro.Renode.PlatformDescription
{
using DependencyGraph = Dictionary<Entry, Dictionary<Entry, ReferenceValue>>;
public sealed class CreationDriver
{
public CreationDriver(Machine machine, IUsingResolver usingResolver, IInitHandler initHandler)
{
this.usingResolver = usingResolver;
this.machine = machine;
this.initHandler = initHandler;
variableStore = new VariableStore();
processedDescriptions = new List<Description>();
objectValueUpdateQueue = new Queue<ObjectValue>();
objectValueInitQueue = new Queue<ObjectValue>();
usingsBeingProcessed = new Stack<string>();
irqCombiners = new Dictionary<IrqDestination, IrqCombinerConnection>();
PrepareVariables();
}
public void ProcessDescription(string description)
{
ProcessInner("", description);
}
public void ProcessFile(string path)
{
if(!File.Exists(path))
{
throw new RecoverableException(string.Format("Could not find file '{0}'.", path));
}
var source = File.ReadAllText(path);
usingsBeingProcessed.Push(Path.GetFullPath(path)); // don't need to pop since stack is cleared within ProcessInner
ProcessInner(path, source);
}
private void ProcessInner(string file, string source)
{
try
{
ValidatePreMerge(file, source, "");
var mergedEntries = variableStore.GetMergedEntries();
foreach(var entry in mergedEntries)
{
ValidateEntryPostMerge(entry);
}
var sortedForCreation = SortEntriesForCreation(mergedEntries);
var irqConnectionCount = new Dictionary<IrqDestination, int>();
foreach(var entry in sortedForCreation)
{
CreateFromEntry(entry);
var irqs = entry.Attributes.OfType<IrqAttribute>()
.SelectMany(attr => attr.Destinations)
.Where(dest => dest.DestinationPeripheral != null);
foreach(var irq in irqs)
{
var destinationPeripheralName = irq.DestinationPeripheral.Reference.Value;
var destinationLocalIndex = irq.DestinationPeripheral.LocalIndex;
var destinationIndex = irq.Destinations.Single().Ends.Single().Number;
var key = new IrqDestination(destinationPeripheralName, destinationLocalIndex, destinationIndex);
if(!irqConnectionCount.TryGetValue(key, out var count))
{
count = 0;
}
irqConnectionCount[key] = count + 1;
}
}
foreach(var pair in irqConnectionCount.Where(pair => pair.Value > 1))
{
irqCombiners[pair.Key] = new IrqCombinerConnection(new CombinedInput(pair.Value));
}
foreach(var entry in sortedForCreation)
{
SetPropertiesAndConnectInterrupts(entry.Variable.Value, entry.Attributes);
}
UpdatePropertiesAndInterruptsOnUpdateQueue();
var sortedForRegistration = SortEntriesForRegistration(mergedEntries);
var entriesToRegister = sortedForRegistration.Where(x => x.RegistrationInfos != null);
do
{
entriesToRegister = RegisterFromEntries(entriesToRegister);
} while(entriesToRegister.Any());
while(objectValueInitQueue.Count > 0)
{
var objectValue = objectValueInitQueue.Dequeue();
initHandler.Execute(objectValue, objectValue.Attributes.OfType<InitAttribute>().Single().Lines,
x => HandleInitableError(x, objectValue));
}
foreach(var entry in sortedForRegistration)
{
var initAttribute = entry.Attributes.OfType<InitAttribute>().SingleOrDefault();
if(initAttribute == null)
{
continue;
}
initHandler.Execute(entry, initAttribute.Lines, x => HandleInitableError(x, entry));
}
}
finally
{
variableStore.Clear();
processedDescriptions.Clear();
objectValueUpdateQueue.Clear();
objectValueInitQueue.Clear();
usingsBeingProcessed.Clear();
irqCombiners.Clear();
PrepareVariables();
}
machine.PostCreationActions();
}
private void ValidatePreMerge(string file, string source, string prefix)
{
var parsedDescription = ParseDescription(source, file);
processedDescriptions.Add(parsedDescription);
foreach(var usingEntry in parsedDescription.Usings)
{
ProcessUsing(usingEntry, prefix, file);
}
variableStore.CurrentScope = file;
var currentEntries = parsedDescription.Entries.ToList();
if(!string.IsNullOrEmpty(prefix))
{
SyntaxTreeHelpers.VisitSyntaxTree<IPrefixable>(parsedDescription, x => x.Prefix(prefix));
}
SyntaxTreeHelpers.VisitSyntaxTree<ReferenceValue>(parsedDescription, x => x.Scope = file);
ValidateEntriesPreMerge(currentEntries);
}
private Description ParseDescription(string description, string fileName)
{
var output = PreLexer.Process(description, fileName).Aggregate((x, y) => x + Environment.NewLine + y);
var input = new Input(output);
var result = Grammar.Description(input);
if(!result.WasSuccessful)
{
var message = "Syntax error, " + result.Message;
if(result.Expectations.Any())
{
message += string.Format("; expected {0}", result.Expectations.Aggregate((x, y) => x + " or " + y));
}
HandleError(ParsingError.SyntaxError, WithPositionForSyntaxErrors.FromResult(result, fileName, description), message, false);
}
result.Value.FileName = fileName;
result.Value.Source = description;
return result.Value;
}
private void PrepareVariables()
{
// machine is always there and is not a peripheral
variableStore.AddBuiltinOrAlreadyRegisteredVariable(Machine.MachineKeyword, machine);
var peripherals = machine.GetRegisteredPeripherals().Where(x => !string.IsNullOrEmpty(x.Name)).Select(x => Tuple.Create(x.Peripheral, x.Name)).Distinct().ToDictionary(x => x.Item1, x => x.Item2);
foreach(var peripheral in peripherals)
{
variableStore.AddBuiltinOrAlreadyRegisteredVariable(peripheral.Value, peripheral.Key);
}
}
private void ProcessUsing(UsingEntry usingEntry, string parentPrefix, string includingFile)
{
var filePath = usingResolver.Resolve(usingEntry.Path, includingFile);
if(!File.Exists(filePath))
{
HandleError(ParsingError.UsingFileNotFound, usingEntry.Path,
string.Format("Using '{0}' resolved as '{1}' does not exist.", usingEntry.Path, filePath), true);
}
var fullFilePath = Path.GetFullPath(filePath);
if(usingsBeingProcessed.Contains(fullFilePath))
{
var segments = new List<string> { fullFilePath };
string currentSegment;
do
{
currentSegment = usingsBeingProcessed.Pop();
segments.Add(currentSegment);
} while(currentSegment != fullFilePath);
HandleError(ParsingError.RecurringUsing, usingEntry,
string.Format("There is a cycle in using file depenedncy. The path is as follows: {0}.",
Environment.NewLine + segments.Aggregate((x, y) => x + Environment.NewLine + "=> " + y)),
false);
}
usingsBeingProcessed.Push(fullFilePath);
var source = File.ReadAllText(filePath);
ValidatePreMerge(filePath, source, parentPrefix + usingEntry.Prefix);
usingsBeingProcessed.Pop();
}
private List<Entry> SortEntriesForCreation(IEnumerable<Entry> entries)
{
var graph = BuildDependencyGraph(entries, entryObjectsToSkip: typeof(Antmicro.Renode.PlatformDescription.Syntax.RegistrationInfo));
return SortEntries(entries, graph, ParsingError.CreationOrderCycle);
}
private List<Entry> SortEntriesForRegistration(IEnumerable<Entry> entries)
{
var graph = BuildDependencyGraph(entries, entryObjectsToSkip: typeof(Antmicro.Renode.PlatformDescription.Syntax.ConstructorOrPropertyAttribute));
return SortEntries(entries, graph, ParsingError.RegistrationOrderCycle);
}
private List<Entry> SortEntries(IEnumerable<Entry> entries, DependencyGraph graph, ParsingError cycleErrorType)
{
var result = new List<Entry>();
var toVisit = new HashSet<Entry>(entries);
var stackVisited = new HashSet<Entry>();
while(toVisit.Count > 0)
{
var element = toVisit.First();
WalkGraph(element, graph, new ReferenceValueStack(null, null, element), stackVisited, result, toVisit, cycleErrorType);
}
return result;
}
private void WalkGraph(Entry entry, DependencyGraph graph, ReferenceValueStack referenceValueStack,
HashSet<Entry> stackVisited, List<Entry> result, HashSet<Entry> toVisit, ParsingError cycleErrorType)
{
if(!toVisit.Contains(entry))
{
return;
}
if(!stackVisited.Add(entry))
{
// we have a cycle, let's find the path
var last = referenceValueStack;
var path = new Stack<ReferenceValueStack>();
path.Push(referenceValueStack);
referenceValueStack = referenceValueStack.Previous;
while(last.Value.Value != referenceValueStack.Entry.VariableName)
{
path.Push(referenceValueStack);
referenceValueStack = referenceValueStack.Previous;
}
var message = new StringBuilder();
message.Append("Dependency cycle has been found. The path is as follows:");
ReferenceValueStack pathElement = null;
while(path.Count > 0)
{
pathElement = path.Pop();
message.AppendLine();
message.AppendFormat("Entry '{0}' at {1} references '{2}' at {3}.",
pathElement.Previous.Entry.VariableName, GetFormattedPosition(pathElement.Previous.Entry.Type),
pathElement.Value.Value, GetFormattedPosition(pathElement.Value));
}
HandleError(cycleErrorType, pathElement.Entry, message.ToString(), false);
}
var edgesTo = graph[entry];
foreach(var edgeTo in edgesTo)
{
WalkGraph(edgeTo.Key, graph, new ReferenceValueStack(referenceValueStack, edgeTo.Value, edgeTo.Key), stackVisited, result, toVisit, cycleErrorType);
}
stackVisited.Remove(entry);
result.Add(entry);
toVisit.Remove(entry);
}
// the dependency graph works as follows:
// it is incidence dictionary, so that if a depends on b, then we have entry in the dictionary
// for a and this is another dictionary in which there is an entry for b and the value of such entry
// is a syntax element (ReferenceValue) that states such dependency
private DependencyGraph BuildDependencyGraph(IEnumerable<Entry> source, Type entryObjectsToSkip)
{
var result = new DependencyGraph();
foreach(var from in source)
{
var localDictionary = new Dictionary<Entry, ReferenceValue>();
result.Add(from, localDictionary);
SyntaxTreeHelpers.VisitSyntaxTree<ConstructorOrPropertyAttribute>(from, ctorAttribute =>
{
if(ctorAttribute.IsPropertyAttribute)
{
return;
}
var referenceValue = ctorAttribute.Value as ReferenceValue;
if(referenceValue == null)
{
return;
}
// it is easier to track dependency on variables than on entries (because there is connection refVal -> variable and entry -> variable)
var variable = variableStore.GetVariableFromReference(referenceValue);
if(variable.DeclarationPlace == DeclarationPlace.BuiltinOrAlreadyRegistered)
{
return;
}
var to = source.Single(x => x.Variable.Equals(variable));
if(!localDictionary.ContainsKey(to))
{
// this way we favour shorter paths when tracking dependency
localDictionary.Add(to, referenceValue);
}
}, (obj, isChildOfEntry) =>
{
var objAsPropertyOrAttribute = (obj as ConstructorOrPropertyAttribute);
var isNotProperty = (objAsPropertyOrAttribute == null) || !objAsPropertyOrAttribute.IsPropertyAttribute;
var isNotOfSkippedType = !(isChildOfEntry && (obj.GetType() == entryObjectsToSkip));
return isNotProperty && isNotOfSkippedType;
});
}
return result;
}
private void ValidateEntriesPreMerge(List<Entry> entries)
{
foreach(var entry in entries)
{
Variable variable;
if(!entry.Attributes.Any() && entry.RegistrationInfos == null && entry.Type == null)
{
HandleError(ParsingError.EmptyEntry, entry, "Entry cannot be empty.", false);
}
var wasDeclared = variableStore.TryGetVariableInLocalScope(entry.VariableName, out variable);
if(entry.Type == null)
{
if(!wasDeclared)
{
HandleError(ParsingError.TypeNotSpecifiedInFirstVariableUse, entry,
string.Format("First entry for variable '{0}' file does not contain a type name.", entry.VariableName), false);
}
}
else
{
if(wasDeclared)
{
string restOfErrorMessage;
if(variable.DeclarationPlace != DeclarationPlace.BuiltinOrAlreadyRegistered)
{
restOfErrorMessage = string.Format(", previous declaration was {0}", variable.DeclarationPlace.GetFriendlyDescription());
}
else
{
restOfErrorMessage = " as an already registered peripheral or builtin";
}
HandleError(ParsingError.VariableAlreadyDeclared, entry,
string.Format("Variable '{0}' was already defined{1}.", entry.VariableName, restOfErrorMessage), false);
}
var resolved = ResolveTypeOrThrow(entry.Type, entry.Type);
variable = variableStore.DeclareVariable(entry.VariableName, resolved, entry.StartPosition, entry.IsLocal);
}
variable.AddEntry(entry);
if(entry.Type == null)
{
var updatingCtorAttribute = entry.Attributes.OfType<ConstructorOrPropertyAttribute>().FirstOrDefault(x => !x.IsPropertyAttribute);
if(updatingCtorAttribute != null)
{
var position = GetFormattedPosition(updatingCtorAttribute);
Logger.Log(LogLevel.Debug, "At {0}: updating constructors of the entry declared earlier.", position);
}
}
}
foreach(var entry in entries)
{
ValidateEntryPreMerge(entry);
}
}
private void ValidateEntryPreMerge(Entry entry)
{
var entryType = variableStore.GetVariableInLocalScope(entry.VariableName).VariableType;
if(entry.Alias != null)
{
if(entry.RegistrationInfos == null)
{
HandleError(ParsingError.AliasWithoutRegistration, entry.Alias,
string.Format("Entry '{0}' has an alias '{1}', while not having a registration info.", entry.VariableName, entry.Alias.Value), true);
}
if(entry.RegistrationInfos.First().Register == null)
{
HandleError(ParsingError.AliasWithNoneRegistration, entry.Alias,
string.Format("Entry '{0}' has an alias '{1}', while having a none registration info.", entry.VariableName, entry.Alias.Value), true);
}
}
if(entry.RegistrationInfos != null)
{
foreach(var registrationInfo in entry.RegistrationInfos)
{
if(registrationInfo.Register == null) // if the register is null, then this is registration canceling entry
{
break;
}
Variable registerVariable;
if(!variableStore.TryGetVariableFromReference(registrationInfo.Register, out registerVariable))
{
HandleError(ParsingError.MissingReference, registrationInfo.Register,
string.Format("Undefined register '{0}'.", registrationInfo.Register), true);
}
var registerInterfaces = registerVariable.VariableType.GetInterfaces().Where(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IPeripheralRegister<,>)
&& x.GetGenericArguments()[0].IsAssignableFrom(entryType)).ToArray();
if(registerInterfaces.Length == 0)
{
HandleError(ParsingError.NoUsableRegisterInterface, registrationInfo.Register,
string.Format("Register '{0}' of type '{1}' does not provide an interface to register '{2}' of type '{3}'.",
registrationInfo.Register.Value, registerVariable.VariableType, entry.VariableName, entryType), true);
}
var possibleTypes = registerInterfaces.Select(x => x.GetGenericArguments()[1]).ToArray();
var registrationPoint = registrationInfo.RegistrationPoint;
var usefulRegistrationPointTypes = new List<Type>();
var friendlyName = "Registration point";
// reference and object values are type checked
var referenceRegPoint = registrationPoint as ReferenceValue;
var objectRegPoint = registrationPoint as ObjectValue;
if(referenceRegPoint != null)
{
usefulRegistrationPointTypes.AddRange(ValidateReference(friendlyName, possibleTypes, referenceRegPoint));
}
else if(objectRegPoint != null)
{
usefulRegistrationPointTypes.AddRange(ValidateObjectValue(friendlyName, possibleTypes, objectRegPoint));
}
else
{
// for simple values we try to find a ctor
var ctors = FindUsableRegistrationPoints(possibleTypes, registrationPoint);
if(ctors.Count == 0)
{
// fall back to the null registration point if possible and it makes sense for the registree
// (do not allow it for bus peripherals if there is a bus registration available)
if(registrationPoint == null
&& possibleTypes.Contains(typeof(NullRegistrationPoint))
&& !(typeof(IBusPeripheral).IsAssignableFrom(entryType) && possibleTypes.Any(t => typeof(IBusRegistration).IsAssignableFrom(t))))
{
usefulRegistrationPointTypes.Add(typeof(NullRegistrationPoint));
}
else
{
HandleError(ParsingError.NoCtorForRegistrationPoint, registrationPoint ?? registrationInfo.Register, "Could not find any suitable constructor for this registration point.", true);
}
}
else if(ctors.Count > 1)
{
HandleError(ParsingError.AmbiguousCtorForRegistrationPoint, registrationPoint ?? registrationInfo.Register,
"Ambiguous choice between constructors for registration point:" + Environment.NewLine +
ctors.Select(x => GetFriendlyConstructorName(x.Item1)).Aggregate((x, y) => x + Environment.NewLine + y), true);
}
else
{
registrationInfo.Constructor = ctors[0].Item1;
registrationInfo.ConvertedValue = ctors[0].Item2;
usefulRegistrationPointTypes.Add(ctors[0].Item1.ReflectedType);
}
}
// our first criterium is registration point type (we seek the most derived), then we check for the registree (peripheral)
if(!FindMostDerived(usefulRegistrationPointTypes))
{
HandleError(ParsingError.AmbiguousRegistrationPointType, registrationInfo.RegistrationPoint,
string.Format("Registration point is ambiguous, at least two types can be used with given value in register '{0}': '{1}' and '{2}'", registrationInfo.Register.Value,
usefulRegistrationPointTypes[usefulRegistrationPointTypes.Count - 2],
usefulRegistrationPointTypes[usefulRegistrationPointTypes.Count - 1]), false);
}
var usefulRegistreeTypes = registerInterfaces.Where(x => x.GenericTypeArguments[1] == usefulRegistrationPointTypes[0]).Select(x => x.GenericTypeArguments[0]).ToList();
if(!FindMostDerived(usefulRegistreeTypes))
{
HandleError(ParsingError.AmbiguousRegistree, registrationInfo.RegistrationPoint,
string.Format("For given registration point of type '{3}', at least two types of registree can be used in register '{0}': '{1}' and '{2}'", registrationInfo.Register.Value,
usefulRegistreeTypes[usefulRegistreeTypes.Count - 2],
usefulRegistreeTypes[usefulRegistreeTypes.Count - 1],
usefulRegistrationPointTypes[0]), false);
}
registrationInfo.RegistrationInterface = typeof(IPeripheralRegister<,>).MakeGenericType(new[] { usefulRegistreeTypes[0], usefulRegistrationPointTypes[0] });
}
}
if(entry.Attributes == null)
{
return;
}
var ctorOrPropertyAttributes = entry.Attributes.OfType<ConstructorOrPropertyAttribute>();
CheckRepeatedCtorAttributes(ctorOrPropertyAttributes);
CheckRepeatedInitAttributes(entry.Attributes.OfType<InitAttribute>());
foreach(var attribute in entry.Attributes)
{
ValidateAttributePreMerge(entryType, attribute);
}
// checking overlapping irqs is here because ValidateAttributePreMerge will find sources for default irqs
// and this method assumes that such operation has already happened
CheckOverlappingIrqs(entry.Attributes.OfType<IrqAttribute>());
entry.FlattenIrqAttributes();
}
private bool FindMostDerived(List<Type> types)
{
while(types.Count > 1)
{
var type1 = types[types.Count - 2];
var type2 = types[types.Count - 1];
// note that if (*) and (**) are true, then type1 == type2
if(type1.IsAssignableFrom(type2)) // (*)
{
types.Remove(type1);
}
else if(type2.IsAssignableFrom(type1)) // (**)
{
types.Remove(type2);
}
else
{
return false;
}
}
return true;
}
private void ValidateEntryPostMerge(Entry entry)
{
// we have to find a constructor for this entry - if it is to be constructed (e.g. sysbus entry is not)
// we also have to find constructors for all of the object values within this entry
if(entry.Type != null)
{
entry.Constructor = FindConstructor(entry.Variable.VariableType,
entry.Attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute), entry.Type);
}
else
{
var constructorAttribute = entry.Attributes.OfType<ConstructorOrPropertyAttribute>().FirstOrDefault(x => !x.IsPropertyAttribute);
if(constructorAttribute != null)
{
HandleError(ParsingError.CtorAttributesInNonCreatingEntry, constructorAttribute, "Constructor attribute within entry for variable that is not created.", false);
}
}
SyntaxTreeHelpers.VisitSyntaxTree<ObjectValue>(entry, objectValue =>
{
objectValue.Constructor = FindConstructor(objectValue.ObjectValueType,
objectValue.Attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute), objectValue);
});
if(entry.Attributes.Any(x => x is InitAttribute))
{
ValidateInitable(entry);
}
}
private void ValidateInitable(IInitable initable)
{
string errorMessage;
if(!initHandler.Validate(initable, out errorMessage))
{
HandleInitableError(errorMessage, initable);
}
}
private void HandleInitableError(string message, IInitable initable)
{
HandleError(ParsingError.InitSectionValidationError, initable.Attributes.Single(x => x is InitAttribute), message, false);
}
private void CreateFromEntry(Entry entry)
{
if(entry.Type == null)
{
return;
}
var constructor = entry.Constructor;
entry.Variable.Value = CreateAndHandleError(constructor, entry.Attributes, string.Format("'{0}'", entry.VariableName), entry.Type);
}
private object CreateFromObjectValue(ObjectValue value)
{
var constructor = value.Constructor;
var result = CreateAndHandleError(value.Constructor, value.Attributes, string.Format("object value of type '{0}'", value.ObjectValueType.Name), value);
if(value.Object != null)
{
HandleInternalError(value);
}
value.Object = result;
objectValueUpdateQueue.Enqueue(value);
if(value.Attributes.Any(x => x is InitAttribute))
{
objectValueInitQueue.Enqueue(value);
}
return result;
}
private object CreateAndHandleError(ConstructorInfo constructor, IEnumerable<Syntax.Attribute> attributes, string friendlyName, IWithPosition responsibleSyntaxElement)
{
object result = null;
try
{
result = constructor.Invoke(PrepareConstructorParameters(constructor, attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute)));
}
catch(TargetInvocationException exception)
{
var constructionException = exception.InnerException as ConstructionException;
if(constructionException == null)
{
throw;
}
var exceptionMessage = new StringBuilder();
exceptionMessage.AppendLine(constructionException.Message);
for(var innerException = constructionException.InnerException; innerException != null; innerException = innerException.InnerException)
{
exceptionMessage.AppendLine(innerException.Message);
}
var message = string.Format("Exception was thrown during construction of {0}:{1}{2}", friendlyName, Environment.NewLine, exceptionMessage);
HandleError(ParsingError.ConstructionException, responsibleSyntaxElement, message, false);
}
return result;
}
private void UpdatePropertiesAndInterruptsOnUpdateQueue()
{
while(objectValueUpdateQueue.Count > 0)
{
var objectValue = objectValueUpdateQueue.Dequeue();
SetPropertiesAndConnectInterrupts(objectValue.Object, objectValue.Attributes);
}
}
private object[] PrepareConstructorParameters(ConstructorInfo constructor, IEnumerable<ConstructorOrPropertyAttribute> attributes)
{
var parameters = constructor.GetParameters();
var parameterValues = new object[parameters.Length];
for(var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
var attribute = attributes.SingleOrDefault(x => x.Name == parameter.Name);
if(attribute == null)
{
FillDefaultParameter(ref parameterValues[i], parameter);
continue;
}
if(TryConvertSimpleValue(parameter.ParameterType, attribute.Value, out parameterValues[i]).ResultType == ConversionResultType.ConversionSuccessful)
{
continue;
}
var referenceValue = attribute.Value as ReferenceValue;
if(referenceValue != null)
{
parameterValues[i] = variableStore.GetVariableFromReference(referenceValue).Value;
if(parameterValues[i] == null)
{
HandleInternalError(referenceValue); // should not be null at this point
}
continue;
}
var objectValue = attribute.Value as ObjectValue;
if(objectValue != null)
{
parameterValues[i] = CreateFromObjectValue(objectValue);
continue;
}
HandleInternalError(); // should not reach here
}
return parameterValues;
}
private void FillDefaultParameter(ref object destination, ParameterInfo parameter)
{
if(parameter.HasDefaultValue)
{
destination = parameter.DefaultValue;
}
else
{
if(!TryGetValueOfOurDefaultParameter(parameter.ParameterType, out destination))
{
HandleInternalError();
}
}
}
private List<Tuple<ConstructorInfo, object>> FindUsableRegistrationPoints(Type[] registrationPointTypes, Value value)
{
var result = new List<Tuple<ConstructorInfo, object>>();
foreach(var type in registrationPointTypes)
{
IEnumerable<ConstructorInfo> ctors = type.GetConstructors();
if(value == null)
{
ctors = ctors.Where(x => !x.GetParameters().TakeWhile(y => !y.HasDefaultValue).Any());
result.AddRange(ctors.Select(x => Tuple.Create(x, (object)null)));
}
else
{
ctors = ctors.Where(x =>
{
var parameters = x.GetParameters();
if(parameters.Length == 0)
{
return false;
}
if(parameters.Length == 1)
{
return true;
}
return parameters[1].HasDefaultValue; // if second is optional, all other are optional
});
foreach(var ctor in ctors)
{
var firstParamType = ctor.GetParameters()[0].ParameterType;
object convertedValue;
var conversionResult = TryConvertSimpleValue(firstParamType, value, out convertedValue);
switch(conversionResult.ResultType)
{
case ConversionResultType.ConversionSuccessful:
result.Add(Tuple.Create(ctor, convertedValue));
break;
case ConversionResultType.ConversionNotApplied:
HandleInternalError(value); // should not reach here
break;
}
}
}
}
return result.Distinct().ToList();
}
private void SetPropertiesAndConnectInterrupts(object objectToSetOn, IEnumerable<Syntax.Attribute> attributes)
{
var objectType = objectToSetOn.GetType();
var propertyAttributes = attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => x.IsPropertyAttribute);
foreach(var attribute in propertyAttributes)
{
if(attribute.Value == null)
{
continue;
}
try
{
attribute.Property.GetSetMethod().Invoke(objectToSetOn, new[] { ConvertFromValue(attribute.Property.PropertyType, attribute.Value) });
}
catch(TargetInvocationException exception)
{
var recoverableException = exception.InnerException as RecoverableException;
if(recoverableException == null)
{
throw;
}
HandleError(ParsingError.PropertySettingException, attribute, string.Format("Exception was thrown when setting property '{0}'", attribute.Name), false);
}
}
var irqAttributes = attributes.OfType<IrqAttribute>();
foreach(var multiplexedAttributes in irqAttributes)
{
foreach(var attribute in multiplexedAttributes.Destinations)
{
if(attribute.DestinationPeripheral == null)
{
// irq -> none case, we can simply ignore it
continue;
}
// at this moment all irq attributes are of simple type (i.e. a->b@c)
var destinationReference = attribute.DestinationPeripheral.Reference;
var destination = variableStore.GetVariableFromReference(destinationReference).Value;
IGPIO source;
IGPIOReceiver destinationReceiver;
var irqEnd = multiplexedAttributes.Sources.Single().Ends.Single();
if(irqEnd.PropertyName != null)
{
source = (IGPIO)GetGpioProperties(objectType).Single(x => x.Name == irqEnd.PropertyName).GetValue(objectToSetOn);
if(source == null)
{
HandleError(ParsingError.UninitializedSourceIrqObject, multiplexedAttributes,
$"{objectToSetOn} has uninitialized IRQ object {irqEnd.PropertyName}", false);
continue;
}
}
else
{
var connections = ((INumberedGPIOOutput)objectToSetOn).Connections;
if(!connections.ContainsKey(irqEnd.Number))
{
HandleError(ParsingError.IrqSourcePinDoesNotExist, multiplexedAttributes,
$"{objectToSetOn} doesn't have IRQ {irqEnd.Number}.\nAvailable IRQs: {Misc.PrettyPrintCollection(connections.Keys)}.", false);
continue;
}
source = connections[irqEnd.Number];
if(source == null)
{
HandleError(ParsingError.UninitializedSourceIrqObject, multiplexedAttributes,
$"{objectToSetOn} has uninitialized IRQ {irqEnd.Number}", false);
continue;
}
}
var localIndex = attribute.DestinationPeripheral.LocalIndex;
var index = attribute.Destinations.Single().Ends.Single().Number;
if(localIndex.HasValue)
{
destinationReceiver = ((ILocalGPIOReceiver)destination).GetLocalReceiver(localIndex.Value);
}
else
{
destinationReceiver = (IGPIOReceiver)destination;
}
var key = new IrqDestination(destinationReference.Value, localIndex, index);
if(irqCombiners.TryGetValue(key, out var combinerConnection))
{
// Connect the first one to the old destination
var combiner = combinerConnection.Combiner;
if(combinerConnection.nextConnectionIndex == 0)
{
combiner.OutputLine.Connect(destinationReceiver, index);
}
destinationReceiver = combiner;
index = combinerConnection.nextConnectionIndex++;
}
source.Connect(destinationReceiver, index);
}
}
}
private List<Entry> RegisterFromEntries(IEnumerable<Entry> sortedEntries)
{
var result = new List<Entry>();
foreach(var entry in sortedEntries)
{
if(!TryRegisterFromEntry(entry))
{
result.Add(entry);
}
}
return result;
}
private bool AreAllParentsRegistered(Entry entry)
{
foreach(var registrationInfo in entry.RegistrationInfos)
{
if(registrationInfo.Register == null)
{
return true;
}
var register = variableStore.GetVariableFromReference(registrationInfo.Register).Value;
var registerAsPeripheral = register as IPeripheral;
if(registerAsPeripheral == null)
{
HandleError(ParsingError.CastException, registrationInfo.Register,
string.Format("Exception was thrown during registration of '{0}' in '{1}':{2}'{1}' does not implement IPeripheral.",
entry.VariableName, registrationInfo.Register.Value, Environment.NewLine), false);
}
if(!machine.IsRegistered((IPeripheral)register))
{
return false;
}
}
return true;
}
private bool TryRegisterFromEntry(Entry entry)
{
if(!AreAllParentsRegistered(entry))
{
return false;
}
foreach(var registrationInfo in entry.RegistrationInfos)
{
if(registrationInfo.Register == null)
{
// registration canceling entry (i.e. @none)
// it may not coexist with other entries, so we return
return true;
}
var register = variableStore.GetVariableFromReference(registrationInfo.Register).Value;
IRegistrationPoint registrationPoint;
if(registrationInfo.Constructor != null)
{
var constructorParameters = registrationInfo.Constructor.GetParameters();
var constructorParameterValues = new object[constructorParameters.Length];
int i;
if(registrationInfo.ConvertedValue == null)
{
i = 0;
}
else
{
constructorParameterValues[0] = registrationInfo.ConvertedValue;
i = 1;
}
for(; i < constructorParameters.Length; i++)
{
FillDefaultParameter(ref constructorParameterValues[i], constructorParameters[i]);
}
registrationPoint = (IRegistrationPoint)registrationInfo.Constructor.Invoke(constructorParameterValues);
}
else
{
var referenceRegPoint = registrationInfo.RegistrationPoint as ReferenceValue;
var objectRegPoint = registrationInfo.RegistrationPoint as ObjectValue;
if(referenceRegPoint != null)
{
registrationPoint = (IRegistrationPoint)variableStore.GetVariableFromReference(referenceRegPoint).Value;
}
else if(objectRegPoint != null)
{
registrationPoint = (IRegistrationPoint)CreateFromObjectValue(objectRegPoint);
UpdatePropertiesAndInterruptsOnUpdateQueue();
}
else
{
// it might be that this is real NullRegistrationPoint
if(registrationInfo.RegistrationPoint != null)
{
HandleInternalError(registrationInfo.RegistrationPoint);
}
registrationPoint = NullRegistrationPoint.Instance;
}
}
try
{
registrationInfo.RegistrationInterface.GetMethod("Register").Invoke(register, new[] { entry.Variable.Value, registrationPoint });
}
catch(TargetInvocationException exception)
{
var recoverableException = exception.InnerException as RecoverableException;
if(recoverableException == null)
{
throw;
}
HandleError(ParsingError.RegistrationException, registrationInfo.Register,
string.Format("Exception was thrown during registration of '{0}' in '{1}':{2}{3}",
entry.VariableName, registrationInfo.Register.Value, Environment.NewLine, recoverableException.Message), false);
}
}
try
{
machine.SetLocalName((IPeripheral)entry.Variable.Value, entry.Alias != null ? entry.Alias.Value : entry.VariableName);
}
catch(RecoverableException exception)
{
HandleError(ParsingError.NameSettingException, (IWithPosition)entry.Alias ?? entry.RegistrationInfos.First().Register,
string.Format("Exception was thrown during setting a name: {0}{1}", Environment.NewLine, exception.Message), false);
}
return true;
}
private void ValidateAttributePreMerge(Type objectType, Syntax.Attribute syntaxAttribute)
{
var ctorOrPropertyAttribute = syntaxAttribute as ConstructorOrPropertyAttribute;
// at this point we can only fully validate properties, because only after merge we will know which ctor to choose
if(ctorOrPropertyAttribute != null)
{
if(ctorOrPropertyAttribute.IsPropertyAttribute)
{
var name = ctorOrPropertyAttribute.Name;
var propertyInfo = objectType.GetProperty(name);
if(propertyInfo != null)
{
ValidateProperty(propertyInfo, ctorOrPropertyAttribute);
}
else
{
HandleError(ParsingError.PropertyDoesNotExist, syntaxAttribute,
string.Format("Property '{0}' does not exist in type '{1}.", ctorOrPropertyAttribute.Name, objectType), false);
}
ctorOrPropertyAttribute.Property = propertyInfo;
return;
}
// for ctor attributes we only check object values and whether reference exists
var objectValue = ctorOrPropertyAttribute.Value as ObjectValue;
var referenceValue = ctorOrPropertyAttribute.Value as ReferenceValue;
if(referenceValue != null)
{
// we only check whether this reference exists, its type cannot be checked at this point, therefore friendly name does not matter
ValidateReference("", new[] { typeof(object) }, referenceValue);
}
if(objectValue != null)
{
ValidateObjectValue(string.Format("Constructor parameter {0}", ctorOrPropertyAttribute.Name), new[] { typeof(object) }, objectValue);
}
}
var irqAttribute = syntaxAttribute as IrqAttribute;
if(irqAttribute != null)
{
foreach(var attribute in irqAttribute.Destinations)
{
Variable irqDestinationVariable;
if(attribute.DestinationPeripheral != null)
{
if(!variableStore.TryGetVariableFromReference(attribute.DestinationPeripheral.Reference, out irqDestinationVariable))
{
HandleError(ParsingError.IrqDestinationDoesNotExist, attribute.DestinationPeripheral,
string.Format("Irq destination '{0}' does not exist.", attribute.DestinationPeripheral.Reference.Value), true);
}
if(attribute.DestinationPeripheral.LocalIndex.HasValue && !typeof(ILocalGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType))
{
HandleError(ParsingError.NotLocalGpioReceiver, attribute.DestinationPeripheral,
string.Format("Used local irq destination, while type '{0}' does not implement ILocalGPIOReceiver.", irqDestinationVariable.VariableType), true);
}
}
else
{
// irq -> none case
irqDestinationVariable = null;
}
if(irqAttribute.Sources == null)
{
var gpioProperties = GetGpioProperties(objectType).ToArray();
if(gpioProperties.Length == 0)
{
HandleError(ParsingError.IrqSourceDoesNotExist, irqAttribute,
string.Format("Type '{0}' does not contain any property of type GPIO.", objectType), false);
}
if(gpioProperties.Length > 1)
{
// If we get more than one property, try to further narrow the list - search if there is one with DefaultInterruptAttribute
gpioProperties = gpioProperties.Where(p => p.GetCustomAttribute(typeof(DefaultInterruptAttribute)) != null).ToArray();
}
// Now, there are the following possibilities:
// (1) either there is only one GPIO in the peripheral model
// (2) there are multiple GPIOs but one is marked with DefaultInterruptAttribute
// (3) there are multiple GPIOs but none is marked with DefaultInterruptAttribute
// (4) there are multiple GPIOs and several are marked with DefaultInterruptAttribute
// The clause below covers options (3) and (4) - it impossible to determine default interrupt here.
if(gpioProperties.Length != 1)
{
HandleError(ParsingError.AmbiguousDefaultIrqSource, irqAttribute,
"Ambiguous choice of default interrupt." +
$"\nThere are the following properties of GPIO type available: {Misc.PrettyPrintCollection(GetGpioProperties(objectType), e => e.Name)}.",
false);
}
// we can now fill the missing source so that we can treat default irqs as normal ones from this point on
irqAttribute.SetDefaultSource(gpioProperties[0].Name);
}
else
{
if(attribute.Destinations != null)
{
// for irq -> none arity is always correct
var leftArity = irqAttribute.Sources.Sum(x => x.Ends.Count());
var rightArity = attribute.Destinations.Sum(x => x.Ends.Count());
if(leftArity != rightArity)
{
HandleError(ParsingError.WrongIrqArity, irqAttribute,
string.Format("Irq arity does not match. It is {0} on the left side and {1} on the right side.", leftArity, rightArity), false);
}
}
foreach(var source in irqAttribute.Sources)
{
foreach(var end in source.Ends)
{
if(end.PropertyName != null)
{
var gpioProperty = objectType.GetProperty(end.PropertyName);
if(gpioProperty == null || gpioProperty.PropertyType != typeof(GPIO))
{
HandleError(ParsingError.IrqSourceDoesNotExist, source,
string.Format("Property '{0}' does not exist in '{1}' or is not of the GPIO type.", end.PropertyName, objectType), true);
}
}
else
{
if(!typeof(INumberedGPIOOutput).IsAssignableFrom(objectType))
{
HandleError(ParsingError.IrqSourceIsNotNumberedGpioOutput, source,
string.Format("Type '{0}' is not a numbered gpio output, while numbered output was used.", objectType), true);
}
}
}
}
}
if(irqDestinationVariable != null &&
!(typeof(IGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType) || typeof(ILocalGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType)))
{
HandleError(ParsingError.IrqDestinationIsNotIrqReceiver, attribute.DestinationPeripheral,
string.Format("Type '{0}' does not implement IGPIOReceiver or ILocalGPIOReceiver and cannot be a destination of interrupts.", irqDestinationVariable.VariableType), false);
}
return;
}
}
}
private void ValidateProperty(PropertyInfo propertyInfo, ConstructorOrPropertyAttribute attribute)
{
var value = attribute.Value;
var propertyType = propertyInfo.PropertyType;
if(propertyInfo.GetSetMethod() == null)
{
HandleError(ParsingError.PropertyNotWritable, attribute, string.Format("Property {0} does not have setter.", propertyInfo.Name), false);
}
var propertyFriendlyName = string.Format("Property '{0}'", attribute.Name);
// verification:
// - X: none attributes (none values) are not checked
// - simple values are simply converted
// - references are type checked
// - inline objects are recursively type checked
if(attribute.Value == null)
{
return;
}
var referenceValue = attribute.Value as ReferenceValue;
var objectValue = attribute.Value as ObjectValue;
if(referenceValue != null)
{
ValidateReference(propertyFriendlyName, new[] { propertyInfo.PropertyType }, referenceValue);
}
else if(objectValue != null)
{
ValidateObjectValue(propertyFriendlyName, new[] { propertyInfo.PropertyType }, objectValue);
}
else
{
ConvertFromValue(propertyInfo.PropertyType, attribute.Value);
}
}
private IEnumerable<Type> ValidateReference(string friendlyName, Type[] typesToAssign, ReferenceValue value)
{
Variable referenceVariable;
if(!variableStore.TryGetVariableFromReference(value, out referenceVariable))
{
HandleError(ParsingError.MissingReference, value, string.Format("Undefined reference '{0}'.", value.Value), true);
}
var result = typesToAssign.Where(x => x.IsAssignableFrom(referenceVariable.VariableType));
if(!result.Any())
{
string typeListing = GetTypeListing(typesToAssign);
HandleError(ParsingError.TypeMismatch, value,
string.Format("{0} of {3} is not assignable from reference '{1}' of type '{2}'.",
friendlyName, value.Value, referenceVariable.VariableType, typeListing), true);
}
return result;
}
private IEnumerable<Type> ValidateObjectValue(string friendlyName, Type[] typesToAssign, ObjectValue value)
{
var objectValueType = ResolveTypeOrThrow(value.TypeName, value);
value.ObjectValueType = objectValueType;
var result = typesToAssign.Where(x => x.IsAssignableFrom(objectValueType));
if(!result.Any())
{
var typeListing = GetTypeListing(typesToAssign);
HandleError(ParsingError.TypeMismatch, value,
string.Format("{0} of {1} is not assignable from object of type '{2}'.", friendlyName, typeListing, objectValueType), false);
}
foreach(var attribute in value.Attributes)
{
ValidateAttributePreMerge(objectValueType, attribute);
}
if(value.Attributes.Any(x => x is InitAttribute))
{
ValidateInitable(value);
}
return result;
}
private void CheckRepeatedCtorAttributes(IEnumerable<ConstructorOrPropertyAttribute> attributes)
{
var names = new HashSet<string>();
foreach(var attribute in attributes)
{
if(!names.Add(attribute.Name))
{
HandleError(ParsingError.PropertyOrCtorNameUsedMoreThanOnce, attribute,
string.Format("{1} '{0}' is used more than once.", attribute.Name, attribute.IsPropertyAttribute ? "Property" : "Constructor argument"), false);
}
}
}
private void CheckOverlappingIrqs(IEnumerable<IrqAttribute> attributes)
{
var sources = new HashSet<IrqEnd>();
var destinations = new HashSet<Tuple<string, int?, int>>();
foreach(var attribute in attributes)
{
foreach(var source in attribute.Sources)
{
foreach(var end in source.Ends)
{
if(!sources.Add(end))
{
// source position information can only be obtained if this is not a default irq source
// if it is - we use the whole attribute
HandleError(ParsingError.IrqSourceUsedMoreThanOnce, source.StartPosition != null ? (IWithPosition)source : attribute,
string.Format("Interrupt '{0}' has already been used as a source in this entry.", end.ToShortString()), true);
}
}
}
foreach(var multiplexedDestination in attribute.Destinations)
{
if(multiplexedDestination.DestinationPeripheral != null)
{
// for irq -> none case this test does not make sense
foreach(var destination in multiplexedDestination.Destinations)
{
foreach(var end in destination.Ends)
{
if(!destinations.Add(Tuple.Create(multiplexedDestination.DestinationPeripheral.Reference.Value, multiplexedDestination.DestinationPeripheral.LocalIndex, end.Number)))
{
HandleError(ParsingError.IrqDestinationUsedMoreThanOnce, destination,
string.Format("Destination '{0}:{1}' has already been used as a destination in this entry.", multiplexedDestination.DestinationPeripheral, end.ToShortString()), true);
}
}
}
}
}
}
}
private void CheckRepeatedInitAttributes(IEnumerable<InitAttribute> attributes)
{
var secondInitAttribute = attributes.Skip(1).FirstOrDefault();
if(secondInitAttribute != null)
{
HandleError(ParsingError.MoreThanOneInitAttribute, secondInitAttribute, "Entry can contain only one init attribute.", false);
}
}
private ConversionResult TryConvertSimplestValue<T>(Value value, Type expectedType, Type comparedType, string typeName, ref object result) where T : Value, ISimplestValue
{
var tValue = value as T;
if(tValue == null)
{
return ConversionResult.ConversionNotApplied;
}
if(comparedType != expectedType)
{
return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
}
result = tValue.ConvertedValue;
return ConversionResult.Success;
}
private ConversionResult TryConvertSimpleValue(Type expectedType, Value value, out object result)
{
result = null;
if(value is EmptyValue)
{
result = expectedType.IsValueType ? Activator.CreateInstance(expectedType) : null;
return ConversionResult.Success;
}
var numericalValue = value as NumericalValue;
var enumValue = value as EnumValue;
var results = new[]
{
TryConvertSimplestValue<StringValue>(value, expectedType, typeof(string), "string", ref result),
TryConvertSimplestValue<BoolValue>(value, expectedType, typeof(bool), "bool", ref result),
TryConvertSimplestValue<RangeValue>(value, expectedType, typeof(Range), "range", ref result)
};
var meaningfulResult = results.FirstOrDefault(x => x.ResultType != ConversionResultType.ConversionNotApplied);
if(meaningfulResult != null)
{
return meaningfulResult;
}
if(numericalValue != null)
{
// numbers can be interpreted as enums either when they match a numerical value of one
// of the enum's entries or when the enum has AllowAnyNumericalValueAttribute defined
if(expectedType.IsEnum && SmartParser.Instance.TryParse(numericalValue.Value, expectedType, out result))
{
return ConversionResult.Success;
}
if(!NumericTypes.Contains(expectedType) || !SmartParser.Instance.TryParse(numericalValue.Value, expectedType, out result))
{
return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
}
return ConversionResult.Success;
}
if(enumValue != null)
{
if(!expectedType.IsEnum)
{
return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
}
var namespaceAndType = expectedType.Namespace.Split('.').Concat(new[] { expectedType.Name });
var givenReversedTypeAndNamespace = enumValue.TypeAndReversedNamespace;
// zip compares elements of the namespace from the end one by one and looks for the first difference
var error = givenReversedTypeAndNamespace.Zip(namespaceAndType.Reverse(), (first, second) => first != second ? Tuple.Create(first, second) : null).FirstOrDefault(x => x != null);
if(error != null)
{
return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.EnumMismatch,
$"Enum namespace or type mismatch, expected '{error.Item2}' instead of '{error.Item1}'.");
}
if(!SmartParser.Instance.TryParse(enumValue.Value, expectedType, out result))
{
return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.EnumMismatch,
$"Unexpected enum value '{enumValue.Value}'.{Environment.NewLine}{Environment.NewLine} Valid values:{Environment.NewLine}{GetValidEnumValues(expectedType)}");
}
return ConversionResult.Success;
}
return ConversionResult.ConversionNotApplied;
}
private object ConvertFromValue(Type expectedType, Value value)
{
object result;
var simpleConversionResult = TryConvertSimpleValue(expectedType, value, out result);
if(simpleConversionResult.ResultType == ConversionResultType.ConversionSuccessful)
{
return result;
}
if(simpleConversionResult.ResultType == ConversionResultType.ConversionUnsuccesful)
{
HandleError(simpleConversionResult.Error, value, simpleConversionResult.Message, true);
}
var objectValue = value as ObjectValue;
if(objectValue != null)
{
return CreateFromObjectValue(objectValue);
}
var referenceValue = value as ReferenceValue;
if(referenceValue != null)
{
return variableStore.GetVariableFromReference(referenceValue).Value;
}
HandleInternalError(value); // should not reach here
return null;
}
private ConstructorInfo FindConstructor(Type type, IEnumerable<ConstructorOrPropertyAttribute> attributes, IWithPosition responsibleObject)
{
var constructorSelectionReport = new LazyList<string>();
var result = new List<ConstructorInfo>();
var availableCtors = type.GetConstructors();
// here we group attributes into two elements of a dictionary: keyed true, having a value and keyed false, having being of a "x: none" form
var attributeGroups = attributes.GroupBy(x => x.Value != null).ToDictionary(x => x.Key, x => (IEnumerable<ConstructorOrPropertyAttribute>)x);
// we can simply treat x: none entries as non existiting (they are only important during merge phase)
attributes = attributeGroups.ContainsKey(true) ? attributeGroups[true] : new ConstructorOrPropertyAttribute[0];
attributes = attributes.ToArray();
if(attributeGroups.ContainsKey(false))
{
constructorSelectionReport.Add(() => "Following attributes were set to none (and therefore ignored):");
foreach(var attribute in attributeGroups[false])
{
constructorSelectionReport.Add(() => string.Format(" '{0}' at {1}", attribute.Name, GetFormattedPosition(attribute)));
}
}
foreach(var ctor in availableCtors)
{
constructorSelectionReport.Add(() => "");
constructorSelectionReport.Add(() => string.Format("Considering ctor {0}.", GetFriendlyConstructorName(ctor)));
var unusedAttributes = new HashSet<ConstructorOrPropertyAttribute>(attributes);
foreach(var argument in ctor.GetParameters())
{
var correspondingAttribute = attributes.SingleOrDefault(x => x.Name == argument.Name);
if(correspondingAttribute == null)
{
object defaultValue = null;
// let's check if we can fill this argument with default value
if(!argument.HasDefaultValue && !TryGetValueOfOurDefaultParameter(argument.ParameterType, out defaultValue))
{
constructorSelectionReport.Add(() => string.Format(" Could not find corresponding attribute for parameter '{0}' of type '{1}' and it is not a default parameter. Rejecting constructor.",
argument.Name, argument.ParameterType));
goto next;
}
if(defaultValue == null)
{
defaultValue = argument.DefaultValue;
}
constructorSelectionReport.Add(() => string.Format(" Parameter '{0}' of type '{1}' filled with default value = '{2}'.", argument.Name, argument.ParameterType, defaultValue));
continue;
}
if(typeof(IMachine).IsAssignableFrom(argument.ParameterType))
{
constructorSelectionReport.Add(() => $" Value provided for parameter {argument.Name} is of internal Machine type and it cannot be assigned by user. Rejecting contructor.");
goto next;
}
constructorSelectionReport.Add(() => string.Format(" For parameter '{0}' of type '{1}' found attribute at {3} with value {2}",
argument.Name, argument.ParameterType, correspondingAttribute.Value, GetFormattedPosition(correspondingAttribute)));
object convertedObject;
var simpleConversionResult = TryConvertSimpleValue(argument.ParameterType, correspondingAttribute.Value, out convertedObject);
if(simpleConversionResult.ResultType == ConversionResultType.ConversionUnsuccesful)
{
constructorSelectionReport.Add(() => " There was an error when converting the value:");
constructorSelectionReport.Add(() => " " + simpleConversionResult.Message);
goto next; // same as above
}
if(simpleConversionResult.ResultType == ConversionResultType.ConversionSuccessful)
{
constructorSelectionReport.Add(() => string.Format(" Value converted succesfully, it is = '{0}'", convertedObject));
unusedAttributes.Remove(correspondingAttribute);
continue;
}
var referenceValue = correspondingAttribute.Value as ReferenceValue;
if(referenceValue != null)
{
if(argument.ParameterType.IsAssignableFrom(variableStore.GetVariableFromReference(referenceValue).VariableType))
{
constructorSelectionReport.Add(() => " Parameter is assignable from the reference value.");
unusedAttributes.Remove(correspondingAttribute);
continue;
}
else
{
constructorSelectionReport.Add(() => " Parameter is not assignable from the reference value, constructor rejected.");
goto next;
}
}
var objectValue = correspondingAttribute.Value as ObjectValue;
if(objectValue != null)
{
if(argument.ParameterType.IsAssignableFrom(objectValue.ObjectValueType))
{
constructorSelectionReport.Add(() => " Parameter is assignable from the object value.");
unusedAttributes.Remove(correspondingAttribute);
continue;
}
else
{
constructorSelectionReport.Add(() => " Parameter is not assignable from the object value, constructor rejected.");
goto next;
}
}
}
if(unusedAttributes.Count == 0)
{
constructorSelectionReport.Add(() => " No unused attributes, constructor accepted.");
result.Add(ctor);
}
else
{
constructorSelectionReport.Add(() => string.Format(" Constructor rejected, {0} unused attributes left:", unusedAttributes.Count));
foreach(var attribute in unusedAttributes)
{
constructorSelectionReport.Add(() => string.Format(" '{0}' with value '{1}' at {2}", attribute.Name, attribute.Value, GetFormattedPosition(attribute)));
}
}
next:;
}
if(result.Count == 1)
{
return result[0];
}
var constructorSelectionReportAsString = $"{Environment.NewLine}Constructor selection report:{Environment.NewLine}{string.Join(Environment.NewLine, constructorSelectionReport.ToList())}";
if(result.Count == 0)
{
HandleError(ParsingError.NoCtor, responsibleObject,
string.Format("Could not find suitable constructor for type '{0}'.{1}", type, constructorSelectionReportAsString), false);
}
else
{
var prettyPrintedCtors = result.Select(x => x.GetParameters().Select(y => y.Name + ": " + y.ParameterType).Aggregate((y, z) => y + ", " + z))
.Select(x => string.Format("({0})", x)).Aggregate((x, y) => x + Environment.NewLine + y);
HandleError(ParsingError.AmbiguousCtor, responsibleObject,
string.Format("Ambiguous choice between constructors for type '{0}':{1}{2}{3}", type, Environment.NewLine, prettyPrintedCtors, constructorSelectionReportAsString), false);
}
HandleInternalError();
return result[0]; // will not reach here
}
private bool TryGetValueOfOurDefaultParameter(Type type, out object value)
{
value = null;
if(typeof(IMachine).IsAssignableFrom(type))
{
value = machine;
return true;
}
return false;
}
private void HandleInternalError(IWithPosition failingObject = null,
[CallerMemberName] string callingMethod = "",
[CallerFilePath] string callingFilePath = "",
[CallerLineNumber] int callingFileLineNumber = 0)
{
var message = string.Format("Internal error during processing in function '{0}' at {1}:{2}.", callingMethod, callingFilePath, callingFileLineNumber);
if(failingObject != null)
{
HandleError(ParsingError.InternalError, failingObject, message, false);
}
else
{
throw new ParsingException(ParsingError.InternalError, message);
}
}
private void HandleError(ParsingError error, IWithPosition failingObject, string message, bool longMark)
{
string source, fileName;
if(!GetElementSourceAndPath(failingObject, out fileName, out source))
{
HandleInternalError();
}
var lineNumber = failingObject.StartPosition.Line;
var columnNumber = failingObject.StartPosition.Column;
var messageBuilder = new StringBuilder();
messageBuilder.AppendFormat("Error E{0:D2}: ", (int)error);
messageBuilder.AppendLine(message);
messageBuilder.AppendFormat("At {2}{0}:{1}:", lineNumber, columnNumber, fileName == "" ? "" : fileName + ':');
messageBuilder.AppendLine();
var sourceInLines = source.Replace("\r", string.Empty).Split(new[] { '\n' }, StringSplitOptions.None);
var problematicLine = sourceInLines[lineNumber - 1];
messageBuilder.AppendLine(problematicLine);
messageBuilder.Append(' ', columnNumber - 1);
messageBuilder.Append('^', longMark ? Math.Min(problematicLine.Length - (columnNumber - 1), failingObject.Length) : 1);
throw new ParsingException(error, messageBuilder.ToString());
}
private Type ResolveTypeOrThrow(string typeName, IWithPosition syntaxElement)
{
var extendedTypeName = typeName.StartsWith(DefaultNamespace, StringComparison.Ordinal) ? typeName : DefaultNamespace + typeName;
var manager = TypeManager.Instance;
var result = manager.TryGetTypeByName(typeName) ?? manager.TryGetTypeByName(extendedTypeName);
if(result == null)
{
HandleError(ParsingError.TypeNotResolved, syntaxElement, string.Format("Could not resolve type: '{0}'.", typeName), true);
}
return result;
}
private string GetFormattedPosition(IWithPosition element)
{
string path, unused;
if(!GetElementSourceAndPath(element, out path, out unused))
{
HandleInternalError();
}
return string.Format("{2}{0}:{1}", element.StartPosition.Line, element.StartPosition.Column, path == "" ? "" : path + ':');
}
private bool GetElementSourceAndPath(IWithPosition element, out string file, out string source)
{
var syntaxErrorPosition = element as WithPositionForSyntaxErrors;
if(syntaxErrorPosition != null)
{
file = syntaxErrorPosition.FileName;
source = syntaxErrorPosition.Source;
return true;
}
foreach(var description in processedDescriptions)
{
if(SyntaxTreeHelpers.ScanFor(description, element))
{
file = description.FileName;
source = description.Source;
return true;
}
}
source = null;
file = null;
return false;
}
private static string GetTypeListing(Type[] typesToAssign)
{
return typesToAssign.Length == 1 ? string.Format("type '{0}'", typesToAssign[0])
: "possible types " + typesToAssign.Select(x => string.Format("'{0}'", x.Name)).Aggregate((x, y) => x + ", " + y);
}
private static string GetFriendlyConstructorName(ConstructorInfo ctor)
{
var parameters = ctor.GetParameters();
if(parameters.Length == 0)
{
return string.Format("{0} with no parameters", ctor.DeclaringType);
}
return string.Format("{0} with the following parameters: [{1}]", ctor.DeclaringType, parameters.Select(x => x.ParameterType + (x.HasDefaultValue ? " (optional)" : ""))
.Aggregate((x, y) => x + ", " + y));
}
private static IEnumerable<PropertyInfo> GetGpioProperties(Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => typeof(GPIO).IsAssignableFrom(x.PropertyType));
}
private static string GetValidEnumValues(Type expectedType)
{
var validValues = new StringBuilder();
foreach(var field in Enum.GetValues(expectedType))
{
validValues.AppendLine($" {expectedType.Name}.{field},");
}
return validValues.ToString();
}
private readonly Machine machine;
private readonly IUsingResolver usingResolver;
private readonly IInitHandler initHandler;
private readonly VariableStore variableStore;
private readonly List<Description> processedDescriptions;
private readonly Queue<ObjectValue> objectValueUpdateQueue;
private readonly Queue<ObjectValue> objectValueInitQueue;
private readonly Stack<string> usingsBeingProcessed;
private readonly Dictionary<IrqDestination, IrqCombinerConnection> irqCombiners;
private static readonly HashSet<Type> NumericTypes = new HashSet<Type>(new []
{
typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
typeof(int), typeof(uint), typeof(long), typeof(ulong),
typeof(decimal), typeof(float), typeof(double)
}.Select(x => new[] { x, typeof(Nullable<>).MakeGenericType(x)}).SelectMany(x => x));
private const string DefaultNamespace = "Antmicro.Renode.Peripherals.";
private const string TypeMismatchMessage = "Type mismatch. Expected {0}.";
private class WithPositionForSyntaxErrors : IWithPosition
{
public static WithPositionForSyntaxErrors FromResult<T>(IResult<T> result, string fileName, string source)
{
var position = new Position(result.Remainder.Position, result.Remainder.Line, result.Remainder.Column);
return new WithPositionForSyntaxErrors(1, position, fileName, source);
}
public int Length { get; private set; }
public Position StartPosition { get; private set; }
public string FileName { get; private set; }
public string Source { get; private set; }
private WithPositionForSyntaxErrors(int length, Position startPosition, string fileName, string source)
{
Length = length;
StartPosition = startPosition;
FileName = fileName;
Source = source;
}
}
private class ReferenceValueStack
{
public ReferenceValueStack(ReferenceValueStack previous, ReferenceValue value, Entry entry)
{
Previous = previous;
Value = value;
Entry = entry;
}
public ReferenceValueStack Previous { get; private set; }
public ReferenceValue Value { get; private set; }
public Entry Entry { get; private set; }
}
private struct IrqDestination
{
public IrqDestination(string peripheralName, int? localIndex, int index)
{
PeripheralName = peripheralName;
LocalIndex = localIndex;
Index = index;
}
public readonly string PeripheralName;
public readonly int? LocalIndex;
public readonly int Index;
}
private struct IrqCombinerConnection
{
public IrqCombinerConnection(CombinedInput combiner)
{
Combiner = combiner;
nextConnectionIndex = 0;
}
public int nextConnectionIndex;
public readonly CombinedInput Combiner;
}
}
}