1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using System.IO;
10 using System.Linq;
11 using System.Reflection;
12 using System.Runtime.CompilerServices;
13 using System.Text;
14 using Antmicro.Renode.Core;
15 using Antmicro.Renode.Core.Structure;
16 using Antmicro.Renode.Exceptions;
17 using Antmicro.Renode.Logging;
18 using Antmicro.Renode.Peripherals;
19 using Antmicro.Renode.Peripherals.Bus;
20 using Antmicro.Renode.Peripherals.CPU;
21 using Antmicro.Renode.Peripherals.Miscellaneous;
22 using Antmicro.Renode.PlatformDescription.Syntax;
23 using Antmicro.Renode.Utilities;
24 using Antmicro.Renode.Utilities.Collections;
25 using Sprache;
26 
27 using Range = Antmicro.Renode.Core.Range;
28 
29 namespace Antmicro.Renode.PlatformDescription
30 {
31     using DependencyGraph = Dictionary<Entry, Dictionary<Entry, ReferenceValue>>;
32 
33     public sealed partial class CreationDriver
34     {
CreationDriver(Machine machine, IUsingResolver usingResolver, IInitHandler initHandler)35         public CreationDriver(Machine machine, IUsingResolver usingResolver, IInitHandler initHandler)
36         {
37             this.usingResolver = usingResolver;
38             this.machine = machine;
39             this.initHandler = initHandler;
40             variableStore = new VariableStore();
41             processedDescriptions = new List<Description>();
42             objectValueUpdateQueue = new Queue<ObjectValue>();
43             objectValueInitQueue = new Queue<ObjectValue>();
44             usingsBeingProcessed = new Stack<string>();
45             irqCombiners = new Dictionary<IrqDestination, IrqCombinerConnection>();
46             PrepareVariables();
47         }
48 
ProcessDescription(string description)49         public void ProcessDescription(string description)
50         {
51             ProcessInner("", description);
52         }
53 
ProcessFile(string path)54         public void ProcessFile(string path)
55         {
56             if(!File.Exists(path))
57             {
58                 throw new RecoverableException(string.Format("Could not find file '{0}'.", path));
59             }
60             var source = File.ReadAllText(path);
61             usingsBeingProcessed.Push(Path.GetFullPath(path)); // don't need to pop since stack is cleared within ProcessInner
62             ProcessInner(path, source);
63         }
64 
ProcessVariableDeclarations(Description description, string prefix)65         private void ProcessVariableDeclarations(Description description, string prefix)
66         {
67             // Collect all variable declarations (entries where there is a type specified).
68             // These can be anywhere in the usings hierarchy, as long as there is only one.
69             var entries = description.Entries;
70             foreach(var entry in entries)
71             {
72                 if(entry.Type != null)
73                 {
74                     var wasDeclared = variableStore.TryGetVariableInLocalScope(entry.VariableName, out var variable);
75                     if(!wasDeclared)
76                     {
77                         var resolved = ResolveTypeOrThrow(entry.Type, entry.Type);
78                         variable = variableStore.DeclareVariable(entry.VariableName, resolved, entry.StartPosition, entry.IsLocal);
79                     }
80                     else
81                     {
82                         HandleDoubleDeclarationError(variable, entry);
83                     }
84                 }
85             }
86         }
87 
CollectVariableEntries(Description description, string prefix)88         private void CollectVariableEntries(Description description, string prefix)
89         {
90             // Having collected all variable declarations, go through the entries again and collect them in the correct
91             // order for merging later.
92             // At this point we can also validate if there were declaration errors (e. g. variable without type).
93             var entries = description.Entries;
94 
95             foreach(var entry in entries)
96             {
97                 if(!entry.Attributes.Any() && entry.RegistrationInfos == null && entry.Type == null)
98                 {
99                     HandleError(ParsingError.EmptyEntry, entry, "Entry cannot be empty.", false);
100                 }
101 
102                 var wasDeclared = variableStore.TryGetVariableInLocalScope(entry.VariableName, out var variable);
103                 if(!wasDeclared)
104                 {
105                     HandleError(ParsingError.VariableNeverDeclared, entry,
106                                     string.Format("Variable '{0}' is never declared - type is unknown.", entry.VariableName), false);
107                 }
108 
109                 if(entry.Type == null)
110                 {
111                     var updatingCtorAttribute = entry.Attributes.OfType<ConstructorOrPropertyAttribute>().FirstOrDefault(x => !x.IsPropertyAttribute);
112                     if(updatingCtorAttribute != null)
113                     {
114                         var position = GetFormattedPosition(updatingCtorAttribute);
115                         Logger.Log(LogLevel.Debug, "At {0}: updating constructors of the entry declared earlier.", position);
116                     }
117                 }
118 
119                 variable.AddEntry(entry);
120             }
121 
122             foreach(var entry in entries)
123             {
124                 ProcessEntryPreMerge(entry);
125             }
126         }
127 
HandleDoubleDeclarationError(Variable variable, Entry entry)128         private void HandleDoubleDeclarationError(Variable variable, Entry entry)
129         {
130             string restOfErrorMessage;
131             if(variable.DeclarationPlace != DeclarationPlace.BuiltinOrAlreadyRegistered)
132             {
133                 restOfErrorMessage = string.Format(", previous declaration was {0}", variable.DeclarationPlace.GetFriendlyDescription());
134             }
135             else
136             {
137                 restOfErrorMessage = " as an already registered peripheral or builtin";
138             }
139             HandleError(ParsingError.VariableAlreadyDeclared, entry,
140                         string.Format("Variable '{0}' was already declared{1}.", entry.VariableName, restOfErrorMessage), false);
141         }
142 
ProcessInner(string file, string source)143         private void ProcessInner(string file, string source)
144         {
145             try
146             {
147                 var usingsGraph = new UsingsGraph(this, file, source);
148                 usingsGraph.TraverseDepthFirst(ProcessVariableDeclarations);
149                 usingsGraph.TraverseDepthFirst(CollectVariableEntries);
150                 var mergedEntries = variableStore.GetMergedEntries();
151                 foreach(var entry in mergedEntries)
152                 {
153                     ProcessEntryPostMerge(entry);
154                 }
155 
156                 var sortedForCreation = SortEntriesForCreation(mergedEntries);
157                 var irqConnectionCount = new Dictionary<IrqDestination, int>();
158                 foreach(var entry in sortedForCreation)
159                 {
160                     CreateFromEntry(entry);
161 
162                     var irqs = entry.Attributes.OfType<IrqAttribute>()
163                         .SelectMany(attr => attr.Destinations)
164                         .Where(dest => dest.DestinationPeripheral != null);
165                     foreach(var irq in irqs)
166                     {
167                         var destinationPeripheralName = irq.DestinationPeripheral.Reference.Value;
168                         var destinationLocalIndex = irq.DestinationPeripheral.LocalIndex;
169                         var destinationIndex = irq.Destinations.Single().Ends.Single().Number;
170                         var key = new IrqDestination(destinationPeripheralName, destinationLocalIndex, destinationIndex);
171                         if(!irqConnectionCount.TryGetValue(key, out var count))
172                         {
173                             count = 0;
174                         }
175                         irqConnectionCount[key] = count + 1;
176                     }
177                 }
178 
179                 foreach(var pair in irqConnectionCount.Where(pair => pair.Value > 1))
180                 {
181                     irqCombiners[pair.Key] = new IrqCombinerConnection(new CombinedInput(pair.Value));
182                 }
183 
184                 foreach(var entry in sortedForCreation)
185                 {
186                     SetPropertiesAndConnectInterrupts(entry.Variable.Value, entry.Attributes);
187                 }
188                 UpdatePropertiesAndInterruptsOnUpdateQueue();
189 
190                 var sortedForRegistration = SortEntriesForRegistration(mergedEntries);
191                 var entriesToRegister = sortedForRegistration.Where(x => x.RegistrationInfos != null);
192                 do
193                 {
194                     entriesToRegister = RegisterFromEntries(entriesToRegister);
195                 } while(entriesToRegister.Any());
196 
197                 while(objectValueInitQueue.Count > 0)
198                 {
199                     var objectValue = objectValueInitQueue.Dequeue();
200                     initHandler.Execute(objectValue, objectValue.Attributes.OfType<InitAttribute>().Single().Lines,
201                                         x => HandleInitableError(x, objectValue));
202                 }
203                 foreach(var entry in sortedForRegistration)
204                 {
205                     var initAttribute = entry.Attributes.OfType<InitAttribute>().SingleOrDefault();
206                     if(initAttribute == null)
207                     {
208                         continue;
209                     }
210                     initHandler.Execute(entry, initAttribute.Lines, x => HandleInitableError(x, entry));
211                 }
212             }
213             finally
214             {
215                 variableStore.Clear();
216                 processedDescriptions.Clear();
217                 objectValueUpdateQueue.Clear();
218                 objectValueInitQueue.Clear();
219                 usingsBeingProcessed.Clear();
220                 irqCombiners.Clear();
221                 PrepareVariables();
222             }
223             machine.PostCreationActions();
224         }
225 
ParseDescription(string description, string fileName)226         private Description ParseDescription(string description, string fileName)
227         {
228             var output = PreLexer.Process(description, fileName).Aggregate((x, y) => x + Environment.NewLine + y);
229             var input = new Input(output);
230             var result = Grammar.Description(input);
231             if(!result.WasSuccessful)
232             {
233                 var message = "Syntax error, " + result.Message;
234                 if(result.Expectations.Any())
235                 {
236                     message += string.Format("; expected {0}", result.Expectations.Aggregate((x, y) => x + " or " + y));
237                 }
238                 HandleError(ParsingError.SyntaxError, WithPositionForSyntaxErrors.FromResult(result, fileName, description), message, false);
239             }
240             result.Value.FileName = fileName;
241             result.Value.Source = description;
242             return result.Value;
243         }
244 
PrepareVariables()245         private void PrepareVariables()
246         {
247             // machine is always there and is not a peripheral
248             variableStore.AddBuiltinOrAlreadyRegisteredVariable(Machine.MachineKeyword, machine);
249             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);
250             foreach(var peripheral in peripherals)
251             {
252                 variableStore.AddBuiltinOrAlreadyRegisteredVariable(peripheral.Value, peripheral.Key);
253             }
254         }
255 
SortEntriesForCreation(IEnumerable<Entry> entries)256         private List<Entry> SortEntriesForCreation(IEnumerable<Entry> entries)
257         {
258             var graph = BuildDependencyGraph(entries, creationNotRegistration: true);
259             return SortEntries(entries, graph, ParsingError.CreationOrderCycle);
260         }
261 
SortEntriesForRegistration(IEnumerable<Entry> entries)262         private List<Entry> SortEntriesForRegistration(IEnumerable<Entry> entries)
263         {
264             var graph = BuildDependencyGraph(entries, creationNotRegistration: false);
265             return SortEntries(entries, graph, ParsingError.RegistrationOrderCycle);
266         }
267 
SortEntries(IEnumerable<Entry> entries, DependencyGraph graph, ParsingError cycleErrorType)268         private List<Entry> SortEntries(IEnumerable<Entry> entries, DependencyGraph graph, ParsingError cycleErrorType)
269         {
270             var result = new List<Entry>();
271             var toVisit = new HashSet<Entry>(entries);
272             var stackVisited = new HashSet<Entry>();
273             while(toVisit.Count > 0)
274             {
275                 var element = toVisit.First();
276                 WalkGraph(element, graph, new ReferenceValueStack(null, null, element), stackVisited, result, toVisit, cycleErrorType);
277             }
278             return result;
279         }
280 
WalkGraph(Entry entry, DependencyGraph graph, ReferenceValueStack referenceValueStack, HashSet<Entry> stackVisited, List<Entry> result, HashSet<Entry> toVisit, ParsingError cycleErrorType)281         private void WalkGraph(Entry entry, DependencyGraph graph, ReferenceValueStack referenceValueStack,
282                                HashSet<Entry> stackVisited, List<Entry> result, HashSet<Entry> toVisit, ParsingError cycleErrorType)
283         {
284             if(!toVisit.Contains(entry))
285             {
286                 return;
287             }
288             if(!stackVisited.Add(entry))
289             {
290                 // we have a cycle, let's find the path
291                 var last = referenceValueStack;
292                 var path = new Stack<ReferenceValueStack>();
293                 path.Push(referenceValueStack);
294                 referenceValueStack = referenceValueStack.Previous;
295                 while(last.Value.Value != referenceValueStack.Entry.VariableName)
296                 {
297                     path.Push(referenceValueStack);
298                     referenceValueStack = referenceValueStack.Previous;
299                 }
300 
301                 var message = new StringBuilder();
302                 message.Append("Dependency cycle has been found. The path is as follows:");
303                 ReferenceValueStack pathElement = null;
304                 while(path.Count > 0)
305                 {
306                     pathElement = path.Pop();
307                     message.AppendLine();
308                     message.AppendFormat("Entry '{0}' at {1} references '{2}' at {3}.",
309                                          pathElement.Previous.Entry.VariableName, GetFormattedPosition(pathElement.Previous.Entry.Type),
310                                          pathElement.Value.Value, GetFormattedPosition(pathElement.Value));
311                 }
312                 HandleError(cycleErrorType, pathElement.Entry, message.ToString(), false);
313             }
314             var edgesTo = graph[entry];
315             foreach(var edgeTo in edgesTo)
316             {
317                 WalkGraph(edgeTo.Key, graph, new ReferenceValueStack(referenceValueStack, edgeTo.Value, edgeTo.Key), stackVisited, result, toVisit, cycleErrorType);
318             }
319             stackVisited.Remove(entry);
320             result.Add(entry);
321             toVisit.Remove(entry);
322         }
323 
324         // the dependency graph works as follows:
325         // it is incidence dictionary, so that if a depends on b, then we have entry in the dictionary
326         // for a and this is another dictionary in which there is an entry for b and the value of such entry
327         // is a syntax element (ReferenceValue) that states such dependency
BuildDependencyGraph(IEnumerable<Entry> source, bool creationNotRegistration)328         private DependencyGraph BuildDependencyGraph(IEnumerable<Entry> source, bool creationNotRegistration)
329         {
330             var result = new DependencyGraph();
331             foreach(var from in source)
332             {
333                 var localDictionary = new Dictionary<Entry, ReferenceValue>();
334                 result.Add(from, localDictionary);
335                 SyntaxTreeHelpers.VisitSyntaxTree<IVisitable>(from, ctorElement =>
336                 {
337                     ReferenceValue maybeReferenceValue;
338                     if(ctorElement is ConstructorOrPropertyAttribute ctorAttribute)
339                     {
340                         if(ctorAttribute.IsPropertyAttribute)
341                         {
342                             return;
343                         }
344                         maybeReferenceValue = ctorAttribute.Value as ReferenceValue;
345                     }
346                     else if(ctorElement is RegistrationInfo ctorRegistrationInfo)
347                     {
348                         maybeReferenceValue = ctorRegistrationInfo.Register;
349                     }
350                     else
351                     {
352                         return;
353                     }
354 
355                     if(maybeReferenceValue == null)
356                     {
357                         return;
358                     }
359                     var referenceValue = maybeReferenceValue;
360 
361                     // it is easier to track dependency on variables than on entries (because there is connection refVal -> variable and entry -> variable)
362                     var variable = variableStore.GetVariableFromReference(referenceValue);
363                     if(variable.DeclarationPlace == DeclarationPlace.BuiltinOrAlreadyRegistered)
364                     {
365                         return;
366                     }
367                     var to = source.Single(x => x.Variable.Equals(variable));
368                     if(!localDictionary.ContainsKey(to))
369                     {
370                         // this way we favour shorter paths when tracking dependency
371                         localDictionary.Add(to, referenceValue);
372                     }
373                 }, (obj, isChildOfEntry) =>
374                 {
375                     var objAsPropertyOrAttribute = (obj as ConstructorOrPropertyAttribute);
376                     var isNotProperty = (objAsPropertyOrAttribute == null) || !objAsPropertyOrAttribute.IsPropertyAttribute;
377 
378                     var skipType = creationNotRegistration
379                         ? obj is RegistrationInfo
380                         : obj is IrqAttribute || obj is ConstructorOrPropertyAttribute;
381                     var isNotOfSkippedType = !(isChildOfEntry && skipType);
382 
383                     return isNotProperty && isNotOfSkippedType;
384                 });
385             }
386             return result;
387         }
388 
ProcessEntryPreMerge(Entry entry)389         private void ProcessEntryPreMerge(Entry entry)
390         {
391             var entryType = variableStore.GetVariableInLocalScope(entry.VariableName).VariableType;
392             if(entry.Alias != null)
393             {
394                 if(entry.RegistrationInfos == null)
395                 {
396                     HandleError(ParsingError.AliasWithoutRegistration, entry.Alias,
397                                 string.Format("Entry '{0}' has an alias '{1}', while not having a registration info.", entry.VariableName, entry.Alias.Value), true);
398                 }
399                 if(entry.RegistrationInfos.First().Register == null)
400                 {
401                     HandleError(ParsingError.AliasWithNoneRegistration, entry.Alias,
402                                 string.Format("Entry '{0}' has an alias '{1}', while having a none registration info.", entry.VariableName, entry.Alias.Value), true);
403                 }
404             }
405             if(entry.RegistrationInfos != null)
406             {
407                 foreach(var registrationInfo in entry.RegistrationInfos)
408                 {
409                     if(registrationInfo.Register == null) // if the register is null, then this is registration canceling entry
410                     {
411                         break;
412                     }
413                     Variable registerVariable;
414                     if(!variableStore.TryGetVariableFromReference(registrationInfo.Register, out registerVariable))
415                     {
416                         HandleError(ParsingError.MissingReference, registrationInfo.Register,
417                                     string.Format("Undefined register '{0}'.", registrationInfo.Register), true);
418                     }
419 
420                     var registerInterfaces = registerVariable.VariableType.GetInterfaces().Where(x => x.IsGenericType &&
421                                                                                 x.GetGenericTypeDefinition() == typeof(IPeripheralRegister<,>)
422                                                                                 && x.GetGenericArguments()[0].IsAssignableFrom(entryType)).ToArray();
423                     if(registerInterfaces.Length == 0)
424                     {
425                         HandleError(ParsingError.NoUsableRegisterInterface, registrationInfo.Register,
426                                     string.Format("Register '{0}' of type '{1}' does not provide an interface to register '{2}' of type '{3}'.",
427                                                   registrationInfo.Register.Value, registerVariable.VariableType, entry.VariableName, entryType), true);
428                     }
429 
430                     var possibleTypes = registerInterfaces.Select(x => x.GetGenericArguments()[1]).ToArray();
431                     var registrationPoint = registrationInfo.RegistrationPoint;
432 
433                     var usefulRegistrationPointTypes = new List<Type>();
434 
435                     var friendlyName = "Registration point";
436                     // reference and object values are type checked
437                     var referenceRegPoint = registrationPoint as ReferenceValue;
438                     var objectRegPoint = registrationPoint as ObjectValue;
439                     if(referenceRegPoint != null)
440                     {
441                         usefulRegistrationPointTypes.AddRange(ValidateReference(friendlyName, possibleTypes, referenceRegPoint));
442                     }
443                     else if(objectRegPoint != null)
444                     {
445                         usefulRegistrationPointTypes.AddRange(ValidateObjectValue(friendlyName, possibleTypes, objectRegPoint));
446                     }
447                     else
448                     {
449                         // for simple values we try to find a ctor
450                         var ctors = FindUsableRegistrationPoints(possibleTypes, registrationPoint);
451                         if(ctors.Count == 0)
452                         {
453                             // fall back to the null registration point if possible
454                             if(registrationPoint == null
455                                 && possibleTypes.Contains(typeof(NullRegistrationPoint)))
456                             {
457                                 usefulRegistrationPointTypes.Add(typeof(NullRegistrationPoint));
458                             }
459                             else
460                             {
461                                 HandleError(ParsingError.NoCtorForRegistrationPoint, registrationPoint ?? registrationInfo.Register, "Could not find any suitable constructor for this registration point.", true);
462                             }
463                         }
464                         else if(ctors.Count > 1)
465                         {
466                             HandleError(ParsingError.AmbiguousCtorForRegistrationPoint, registrationPoint ?? registrationInfo.Register,
467                                         "Ambiguous choice between constructors for registration point:" + Environment.NewLine +
468                                         ctors.Select(x => GetFriendlyConstructorName(x.Item1)).Aggregate((x, y) => x + Environment.NewLine + y), true);
469                         }
470                         else
471                         {
472                             registrationInfo.Constructor = ctors[0].Item1;
473                             registrationInfo.ConvertedValue = ctors[0].Item2;
474                             usefulRegistrationPointTypes.Add(ctors[0].Item1.ReflectedType);
475                         }
476                     }
477                     // our first criterium is registration point type (we seek the most derived), then we check for the registree (peripheral)
478                     if(!FindMostDerived(usefulRegistrationPointTypes))
479                     {
480                         HandleError(ParsingError.AmbiguousRegistrationPointType, registrationInfo.RegistrationPoint,
481                                     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,
482                                                   usefulRegistrationPointTypes[usefulRegistrationPointTypes.Count - 2],
483                                                   usefulRegistrationPointTypes[usefulRegistrationPointTypes.Count - 1]), false);
484                     }
485                     var usefulRegistreeTypes = registerInterfaces.Where(x => x.GenericTypeArguments[1] == usefulRegistrationPointTypes[0]).Select(x => x.GenericTypeArguments[0]).ToList();
486                     if(!FindMostDerived(usefulRegistreeTypes))
487                     {
488                         HandleError(ParsingError.AmbiguousRegistree, registrationInfo.RegistrationPoint,
489                                     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,
490                                                   usefulRegistreeTypes[usefulRegistreeTypes.Count - 2],
491                                                   usefulRegistreeTypes[usefulRegistreeTypes.Count - 1],
492                                                   usefulRegistrationPointTypes[0]), false);
493                     }
494                     registrationInfo.RegistrationInterface = typeof(IPeripheralRegister<,>).MakeGenericType(new[] { usefulRegistreeTypes[0], usefulRegistrationPointTypes[0] });
495                 }
496             }
497 
498             if(entry.Attributes == null)
499             {
500                 return;
501             }
502 
503             var ctorOrPropertyAttributes = entry.Attributes.OfType<ConstructorOrPropertyAttribute>();
504             CheckRepeatedCtorAttributes(ctorOrPropertyAttributes);
505             CheckRepeatedInitAttributes(entry.Attributes.OfType<InitAttribute>());
506 
507             foreach(var attribute in entry.Attributes)
508             {
509                 ValidateAttributePreMerge(entryType, attribute);
510             }
511             // checking overlapping irqs is here because ValidateAttributePreMerge will find sources for default irqs
512             // and this method assumes that such operation has already happened
513             CheckOverlappingIrqs(entry.Attributes.OfType<IrqAttribute>());
514 
515             entry.FlattenIrqAttributes();
516         }
517 
FindMostDerived(List<Type> types)518         private bool FindMostDerived(List<Type> types)
519         {
520             while(types.Count > 1)
521             {
522                 var type1 = types[types.Count - 2];
523                 var type2 = types[types.Count - 1];
524                 // note that if (*) and (**) are true, then type1 == type2
525                 if(type1.IsAssignableFrom(type2)) // (*)
526                 {
527                     types.Remove(type1);
528                 }
529                 else if(type2.IsAssignableFrom(type1)) // (**)
530                 {
531                     types.Remove(type2);
532                 }
533                 else
534                 {
535                     return false;
536                 }
537             }
538             return true;
539         }
540 
ProcessEntryPostMerge(Entry entry)541         private void ProcessEntryPostMerge(Entry entry)
542         {
543             // we have to find a constructor for this entry - if it is to be constructed (e.g. sysbus entry is not)
544             // we also have to find constructors for all of the object values within this entry
545 
546             if(entry.Type != null)
547             {
548                 entry.Constructor = FindConstructor(entry.Variable.VariableType,
549                                                     entry.Attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute), entry.Type);
550             }
551             else
552             {
553                 var constructorAttribute = entry.Attributes.OfType<ConstructorOrPropertyAttribute>().FirstOrDefault(x => !x.IsPropertyAttribute);
554                 if(constructorAttribute != null)
555                 {
556                     HandleError(ParsingError.CtorAttributesInNonCreatingEntry, constructorAttribute, "Constructor attribute within entry for variable that is not created.", false);
557                 }
558             }
559 
560             SyntaxTreeHelpers.VisitSyntaxTree<ObjectValue>(entry, objectValue =>
561             {
562                 objectValue.Constructor = FindConstructor(objectValue.ObjectValueType,
563                                                           objectValue.Attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute), objectValue);
564             });
565             if(entry.Attributes.Any(x => x is InitAttribute))
566             {
567                 ValidateInitable(entry);
568             }
569         }
570 
ValidateInitable(IInitable initable)571         private void ValidateInitable(IInitable initable)
572         {
573             string errorMessage;
574             if(!initHandler.Validate(initable, out errorMessage))
575             {
576                 HandleInitableError(errorMessage, initable);
577             }
578         }
579 
HandleInitableError(string message, IInitable initable)580         private void HandleInitableError(string message, IInitable initable)
581         {
582             HandleError(ParsingError.InitSectionValidationError, initable.Attributes.Single(x => x is InitAttribute), message, false);
583         }
584 
CreateFromEntry(Entry entry)585         private void CreateFromEntry(Entry entry)
586         {
587             if(entry.Type == null)
588             {
589                 return;
590             }
591             var constructor = entry.Constructor;
592             entry.Variable.Value = CreateAndHandleError(constructor, entry.Attributes, string.Format("'{0}'", entry.VariableName), entry.Type);
593         }
594 
CreateFromObjectValue(ObjectValue value)595         private object CreateFromObjectValue(ObjectValue value)
596         {
597             var constructor = value.Constructor;
598             var result = CreateAndHandleError(value.Constructor, value.Attributes, string.Format("object value of type '{0}'", value.ObjectValueType.Name), value);
599             if(value.Object != null)
600             {
601                 HandleInternalError(value);
602             }
603             value.Object = result;
604             objectValueUpdateQueue.Enqueue(value);
605             if(value.Attributes.Any(x => x is InitAttribute))
606             {
607                 objectValueInitQueue.Enqueue(value);
608             }
609             return result;
610         }
611 
CreateAndHandleError(ConstructorInfo constructor, IEnumerable<Syntax.Attribute> attributes, string friendlyName, IWithPosition responsibleSyntaxElement)612         private object CreateAndHandleError(ConstructorInfo constructor, IEnumerable<Syntax.Attribute> attributes, string friendlyName, IWithPosition responsibleSyntaxElement)
613         {
614             object result = null;
615             try
616             {
617                 result = constructor.Invoke(PrepareConstructorParameters(constructor, attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => !x.IsPropertyAttribute)));
618             }
619             catch(TargetInvocationException exception)
620             {
621                 var constructionException = exception.InnerException as ConstructionException;
622                 if(constructionException == null)
623                 {
624                     throw;
625                 }
626 
627                 var exceptionMessage = new StringBuilder();
628                 exceptionMessage.AppendLine(constructionException.Message);
629                 for(var innerException = constructionException.InnerException; innerException != null; innerException = innerException.InnerException)
630                 {
631                     exceptionMessage.AppendLine(innerException.Message);
632                 }
633                 var message = string.Format("Exception was thrown during construction of {0}:{1}{2}", friendlyName, Environment.NewLine, exceptionMessage);
634                 HandleError(ParsingError.ConstructionException, responsibleSyntaxElement, message, false);
635             }
636             return result;
637         }
638 
UpdatePropertiesAndInterruptsOnUpdateQueue()639         private void UpdatePropertiesAndInterruptsOnUpdateQueue()
640         {
641             while(objectValueUpdateQueue.Count > 0)
642             {
643                 var objectValue = objectValueUpdateQueue.Dequeue();
644                 SetPropertiesAndConnectInterrupts(objectValue.Object, objectValue.Attributes);
645             }
646         }
647 
PrepareConstructorParameters(ConstructorInfo constructor, IEnumerable<ConstructorOrPropertyAttribute> attributes)648         private object[] PrepareConstructorParameters(ConstructorInfo constructor, IEnumerable<ConstructorOrPropertyAttribute> attributes)
649         {
650             var parameters = constructor.GetParameters();
651             var parameterValues = new object[parameters.Length];
652             for(var i = 0; i < parameters.Length; i++)
653             {
654                 var parameter = parameters[i];
655                 var attribute = attributes.SingleOrDefault(x => ParameterNameMatches(x.Name, parameter, silent: true));
656                 if(attribute == null)
657                 {
658                     FillDefaultParameter(ref parameterValues[i], parameter);
659                     continue;
660                 }
661 
662                 if(TryConvertSimpleValue(parameter.ParameterType, attribute.Value, out parameterValues[i], silent: true).ResultType == ConversionResultType.ConversionSuccessful)
663                 {
664                     continue;
665                 }
666                 var referenceValue = attribute.Value as ReferenceValue;
667                 if(referenceValue != null)
668                 {
669                     parameterValues[i] = variableStore.GetVariableFromReference(referenceValue).Value;
670                     if(parameterValues[i] == null)
671                     {
672                         HandleInternalError(referenceValue); // should not be null at this point
673                     }
674                     continue;
675                 }
676                 var objectValue = attribute.Value as ObjectValue;
677                 if(objectValue != null)
678                 {
679                     parameterValues[i] = CreateFromObjectValue(objectValue);
680                     continue;
681                 }
682                 HandleInternalError(); // should not reach here
683             }
684             return parameterValues;
685         }
686 
FillDefaultParameter(ref object destination, ParameterInfo parameter)687         private void FillDefaultParameter(ref object destination, ParameterInfo parameter)
688         {
689             if(parameter.HasDefaultValue)
690             {
691                 destination = parameter.DefaultValue;
692             }
693             else
694             {
695                 if(!TryGetValueOfOurDefaultParameter(parameter.ParameterType, out destination))
696                 {
697                     HandleInternalError();
698                 }
699             }
700         }
701 
FindUsableRegistrationPoints(Type[] registrationPointTypes, Value value)702         private List<Tuple<ConstructorInfo, object>> FindUsableRegistrationPoints(Type[] registrationPointTypes, Value value)
703         {
704             var result = new List<Tuple<ConstructorInfo, object>>();
705             foreach(var type in registrationPointTypes)
706             {
707                 IEnumerable<ConstructorInfo> ctors = type.GetConstructors();
708 
709                 if(value == null)
710                 {
711                     ctors = ctors.Where(x => !x.GetParameters().TakeWhile(y => !y.HasDefaultValue).Any());
712                     result.AddRange(ctors.Select(x => Tuple.Create(x, (object)null)));
713                 }
714                 else
715                 {
716                     ctors = ctors.Where(x =>
717                     {
718                         var parameters = x.GetParameters();
719                         if(parameters.Length == 0)
720                         {
721                             return false;
722                         }
723                         if(parameters.Length == 1)
724                         {
725                             return true;
726                         }
727                         return parameters[1].HasDefaultValue; // if second is optional, all other are optional
728                     });
729                     foreach(var ctor in ctors)
730                     {
731                         var firstParamType = ctor.GetParameters()[0].ParameterType;
732                         object convertedValue;
733                         var conversionResult = TryConvertSimpleValue(firstParamType, value, out convertedValue);
734                         switch(conversionResult.ResultType)
735                         {
736                         case ConversionResultType.ConversionSuccessful:
737                             result.Add(Tuple.Create(ctor, convertedValue));
738                             break;
739                         case ConversionResultType.ConversionNotApplied:
740                             HandleInternalError(value); // should not reach here
741                             break;
742                         }
743                     }
744                 }
745             }
746             return result.Distinct().ToList();
747         }
748 
SetPropertiesAndConnectInterrupts(object objectToSetOn, IEnumerable<Syntax.Attribute> attributes)749         private void SetPropertiesAndConnectInterrupts(object objectToSetOn, IEnumerable<Syntax.Attribute> attributes)
750         {
751             var objectType = objectToSetOn.GetType();
752 
753             var propertyAttributes = attributes.OfType<ConstructorOrPropertyAttribute>().Where(x => x.IsPropertyAttribute);
754             foreach(var attribute in propertyAttributes)
755             {
756                 if(attribute.Value == null)
757                 {
758                     continue;
759                 }
760                 try
761                 {
762                     attribute.Property.GetSetMethod().Invoke(objectToSetOn, new[] { ConvertFromValue(attribute.Property.PropertyType, attribute.Value) });
763                 }
764                 catch(TargetInvocationException exception)
765                 {
766                     var recoverableException = exception.InnerException as RecoverableException;
767                     if(recoverableException == null)
768                     {
769                         throw;
770                     }
771                     HandleError(ParsingError.PropertySettingException, attribute, string.Format("Exception was thrown when setting property '{0}'", attribute.Name), false);
772                 }
773             }
774 
775             var irqAttributes = attributes.OfType<IrqAttribute>();
776             foreach(var multiplexedAttributes in irqAttributes)
777             {
778                 foreach(var attribute in multiplexedAttributes.Destinations)
779                 {
780                     if(attribute.DestinationPeripheral == null)
781                     {
782                         // irq -> none case, we can simply ignore it
783                         continue;
784                     }
785                     // at this moment all irq attributes are of simple type (i.e. a->b@c)
786                     var destinationReference = attribute.DestinationPeripheral.Reference;
787                     var destination = variableStore.GetVariableFromReference(destinationReference).Value;
788 
789                     IGPIO source;
790                     IGPIOReceiver destinationReceiver;
791 
792                     var irqEnd = multiplexedAttributes.Sources.Single().Ends.Single();
793                     if(irqEnd.PropertyName != null)
794                     {
795                         source = (IGPIO)GetGpioProperties(objectType).Single(x => x.Name == irqEnd.PropertyName).GetValue(objectToSetOn);
796 
797                         if(source == null)
798                         {
799                             HandleError(ParsingError.UninitializedSourceIrqObject, multiplexedAttributes,
800                                         $"{objectToSetOn} has uninitialized IRQ object {irqEnd.PropertyName}", false);
801                             continue;
802                         }
803                     }
804                     else
805                     {
806                         var connections = ((INumberedGPIOOutput)objectToSetOn).Connections;
807                         if(!connections.ContainsKey(irqEnd.Number))
808                         {
809                             HandleError(ParsingError.IrqSourcePinDoesNotExist, multiplexedAttributes,
810                                         $"{objectToSetOn} doesn't have IRQ {irqEnd.Number}.\nAvailable IRQs: {Misc.PrettyPrintCollection(connections.Keys)}.", false);
811                             continue;
812                         }
813                         source = connections[irqEnd.Number];
814 
815                         if(source == null)
816                         {
817                             HandleError(ParsingError.UninitializedSourceIrqObject, multiplexedAttributes,
818                                         $"{objectToSetOn} has uninitialized IRQ {irqEnd.Number}", false);
819                             continue;
820                         }
821                     }
822 
823                     var localIndex = attribute.DestinationPeripheral.LocalIndex;
824                     var index = attribute.Destinations.Single().Ends.Single().Number;
825                     if(localIndex.HasValue)
826                     {
827                         destinationReceiver = ((ILocalGPIOReceiver)destination).GetLocalReceiver(localIndex.Value);
828                     }
829                     else
830                     {
831                         destinationReceiver = (IGPIOReceiver)destination;
832                     }
833 
834                     var key = new IrqDestination(destinationReference.Value, localIndex, index);
835                     if(irqCombiners.TryGetValue(key, out var combinerConnection))
836                     {
837                         // Connect the first one to the old destination
838                         var combiner = combinerConnection.Combiner;
839                         if(combinerConnection.nextConnectionIndex == 0)
840                         {
841                             combiner.OutputLine.Connect(destinationReceiver, index);
842                         }
843                         destinationReceiver = combiner;
844                         index = combinerConnection.nextConnectionIndex++;
845                     }
846 
847                     source.Connect(destinationReceiver, index);
848                 }
849             }
850         }
851 
RegisterFromEntries(IEnumerable<Entry> sortedEntries)852         private List<Entry> RegisterFromEntries(IEnumerable<Entry> sortedEntries)
853         {
854             var result = new List<Entry>();
855             foreach(var entry in sortedEntries)
856             {
857                 if(!TryRegisterFromEntry(entry))
858                 {
859                     result.Add(entry);
860                 }
861             }
862             return result;
863         }
864 
AreAllParentsRegistered(Entry entry)865         private bool AreAllParentsRegistered(Entry entry)
866         {
867             foreach(var registrationInfo in entry.RegistrationInfos)
868             {
869                 if(registrationInfo.Register == null)
870                 {
871                     return true;
872                 }
873                 var register = variableStore.GetVariableFromReference(registrationInfo.Register).Value;
874                 var registerAsPeripheral = register as IPeripheral;
875                 if(registerAsPeripheral == null)
876                 {
877                     HandleError(ParsingError.CastException, registrationInfo.Register,
878                                 string.Format("Exception was thrown during registration of '{0}' in '{1}':{2}'{1}' does not implement IPeripheral.",
879                                               entry.VariableName, registrationInfo.Register.Value, Environment.NewLine), false);
880                 }
881                 if(!machine.IsRegistered((IPeripheral)register))
882                 {
883                     return false;
884                 }
885             }
886             return true;
887         }
888 
GetStateBits(string initiator)889         private IReadOnlyDictionary<string, int> GetStateBits(string initiator)
890         {
891             if(!string.IsNullOrEmpty(initiator))
892             {
893                 if(!variableStore.TryGetVariableInLocalScope(initiator, out var variable))
894                 {
895                     throw new RecoverableException($"Invalid initiator: {initiator}");
896                 }
897                 return (variable.Value as IPeripheralWithTransactionState)?.StateBits;
898             }
899 
900             var possibleInitiators = machine.GetPeripheralsOfType<IPeripheralWithTransactionState>();
901             if(!possibleInitiators.Any())
902             {
903                 return null;
904             }
905 
906             var theIntersectionOfTheirStateBitsets = possibleInitiators
907                 .Select(i => i.StateBits)
908                 .Aggregate((commonDict, nextDict) =>
909                     commonDict
910                         .Where(p => nextDict.TryGetValue(p.Key, out var value) && p.Value == value)
911                         .ToDictionary(p => p.Key, p => p.Value)
912                 );
913             return theIntersectionOfTheirStateBitsets;
914         }
915 
TryRegisterFromEntry(Entry entry)916         private bool TryRegisterFromEntry(Entry entry)
917         {
918             if(!AreAllParentsRegistered(entry))
919             {
920                 return false;
921             }
922             foreach(var registrationInfo in entry.RegistrationInfos)
923             {
924                 if(registrationInfo.Register == null)
925                 {
926                     // registration canceling entry (i.e. @none)
927                     // it may not coexist with other entries, so we return
928                     return true;
929                 }
930                 var register = variableStore.GetVariableFromReference(registrationInfo.Register).Value;
931                 var registrationPoints = new List<IRegistrationPoint>();
932                 if(registrationInfo.Constructor != null)
933                 {
934                     var constructorParameters = registrationInfo.Constructor.GetParameters();
935                     var constructorParameterValues = new object[constructorParameters.Length];
936                     int i;
937                     if(registrationInfo.ConvertedValue == null)
938                     {
939                         i = 0;
940                     }
941                     else
942                     {
943                         constructorParameterValues[0] = registrationInfo.ConvertedValue;
944                         i = 1;
945                     }
946                     for(; i < constructorParameters.Length; i++)
947                     {
948                         FillDefaultParameter(ref constructorParameterValues[i], constructorParameters[i]);
949                     }
950                     registrationPoints.Add((IRegistrationPoint)registrationInfo.Constructor.Invoke(constructorParameterValues));
951                 }
952                 else
953                 {
954                     var referenceRegPoint = registrationInfo.RegistrationPoint as ReferenceValue;
955                     var objectRegPoint = registrationInfo.RegistrationPoint as ObjectValue;
956                     if(referenceRegPoint != null)
957                     {
958                         registrationPoints.Add((IRegistrationPoint)variableStore.GetVariableFromReference(referenceRegPoint).Value);
959                     }
960                     else if(objectRegPoint != null)
961                     {
962                         var registrationPoint = (IRegistrationPoint)CreateFromObjectValue(objectRegPoint);
963                         if(registrationPoint is IConditionalRegistration cr && cr.Condition != null && !machine.IgnorePeripheralRegistrationConditions)
964                         {
965                             // Split it into simple registration points (one condition per) for each initiator
966                             var condition = AccessConditionParser.ParseCondition(cr.Condition);
967                             var conditionPerInitiator = AccessConditionParser.EvaluateWithStateBits(condition, GetStateBits);
968                             foreach(var initiatorName in conditionPerInitiator.Keys)
969                             {
970                                 IPeripheral initiator = null;
971                                 // The empty string is used as a placeholder initiator name, the conditions grouped under it
972                                 // should be global (that is have no initiator condition). The created conditional registrations
973                                 // will have Initiator == null.
974                                 if(!string.IsNullOrEmpty(initiatorName))
975                                 {
976                                     initiator = variableStore.GetVariableInLocalScope(initiatorName).Value as IPeripheral;
977                                     if(initiator == null)
978                                     {
979                                         throw new RecoverableException($"Initiator '{initiatorName}' is not a peripheral");
980                                     }
981                                 }
982                                 foreach(var initiatorCond in conditionPerInitiator[initiatorName])
983                                 {
984                                     registrationPoints.Add(cr.WithInitiatorAndStateMask(initiator, initiatorCond));
985                                 }
986                             }
987                         }
988                         else
989                         {
990                             // If IgnorePeripheralRegistrationConditions is set, the point is added as-is, without
991                             // transforming Condition into StateMask, so the condition is inert.
992                             registrationPoints.Add(registrationPoint);
993                         }
994                         UpdatePropertiesAndInterruptsOnUpdateQueue();
995                     }
996                     else
997                     {
998                         // it might be that this is real NullRegistrationPoint
999                         if(registrationInfo.RegistrationPoint != null)
1000                         {
1001                             HandleInternalError(registrationInfo.RegistrationPoint);
1002                         }
1003                         registrationPoints.Add(NullRegistrationPoint.Instance);
1004                     }
1005                 }
1006                 try
1007                 {
1008                     foreach(var registrationPoint in registrationPoints)
1009                     {
1010                         registrationInfo.RegistrationInterface.GetMethod("Register").Invoke(register, new[] { entry.Variable.Value, registrationPoint });
1011                     }
1012                 }
1013                 catch(TargetInvocationException exception)
1014                 {
1015                     var recoverableException = exception.InnerException as RecoverableException;
1016                     if(recoverableException == null)
1017                     {
1018                         throw;
1019                     }
1020                     HandleError(ParsingError.RegistrationException, registrationInfo.Register,
1021                                 string.Format("Exception was thrown during registration of '{0}' in '{1}':{2}{3}",
1022                                               entry.VariableName, registrationInfo.Register.Value, Environment.NewLine, recoverableException.Message), false);
1023                 }
1024             }
1025             try
1026             {
1027                 machine.SetLocalName((IPeripheral)entry.Variable.Value, entry.Alias != null ? entry.Alias.Value : entry.VariableName);
1028             }
1029             catch(RecoverableException exception)
1030             {
1031                 HandleError(ParsingError.NameSettingException, (IWithPosition)entry.Alias ?? entry.RegistrationInfos.First().Register,
1032                             string.Format("Exception was thrown during setting a name: {0}{1}", Environment.NewLine, exception.Message), false);
1033             }
1034             return true;
1035         }
1036 
ValidateAttributePreMerge(Type objectType, Syntax.Attribute syntaxAttribute)1037         private void ValidateAttributePreMerge(Type objectType, Syntax.Attribute syntaxAttribute)
1038         {
1039             var ctorOrPropertyAttribute = syntaxAttribute as ConstructorOrPropertyAttribute;
1040             // at this point we can only fully validate properties, because only after merge we will know which ctor to choose
1041             if(ctorOrPropertyAttribute != null)
1042             {
1043                 if(ctorOrPropertyAttribute.IsPropertyAttribute)
1044                 {
1045                     var name = ctorOrPropertyAttribute.Name;
1046                     var propertyInfo = objectType.GetProperty(name);
1047                     if(propertyInfo != null)
1048                     {
1049                         ValidateProperty(propertyInfo, ctorOrPropertyAttribute);
1050                     }
1051                     else
1052                     {
1053                         HandleError(ParsingError.PropertyDoesNotExist, syntaxAttribute,
1054                                     string.Format("Property '{0}' does not exist in type '{1}.", ctorOrPropertyAttribute.Name, objectType), false);
1055                     }
1056                     ctorOrPropertyAttribute.Property = propertyInfo;
1057                     return;
1058                 }
1059 
1060                 // for ctor attributes we only check object values and whether reference exists
1061                 var objectValue = ctorOrPropertyAttribute.Value as ObjectValue;
1062                 var referenceValue = ctorOrPropertyAttribute.Value as ReferenceValue;
1063                 if(referenceValue != null)
1064                 {
1065                     // we only check whether this reference exists, its type cannot be checked at this point, therefore friendly name does not matter
1066                     ValidateReference("", new[] { typeof(object) }, referenceValue);
1067                 }
1068                 if(objectValue != null)
1069                 {
1070                     ValidateObjectValue(string.Format("Constructor parameter {0}", ctorOrPropertyAttribute.Name), new[] { typeof(object) }, objectValue);
1071                 }
1072             }
1073 
1074             var irqAttribute = syntaxAttribute as IrqAttribute;
1075             if(irqAttribute != null)
1076             {
1077                 foreach(var attribute in irqAttribute.Destinations)
1078                 {
1079                     Variable irqDestinationVariable;
1080                     if(attribute.DestinationPeripheral != null)
1081                     {
1082                         if(!variableStore.TryGetVariableFromReference(attribute.DestinationPeripheral.Reference, out irqDestinationVariable))
1083                         {
1084                             HandleError(ParsingError.IrqDestinationDoesNotExist, attribute.DestinationPeripheral,
1085                                         string.Format("Irq destination '{0}' does not exist.", attribute.DestinationPeripheral.Reference.Value), true);
1086                         }
1087 
1088                         if(attribute.DestinationPeripheral.LocalIndex.HasValue && !typeof(ILocalGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType))
1089                         {
1090                             HandleError(ParsingError.NotLocalGpioReceiver, attribute.DestinationPeripheral,
1091                                         string.Format("Used local irq destination, while type '{0}' does not implement ILocalGPIOReceiver.", irqDestinationVariable.VariableType), true);
1092                         }
1093                     }
1094                     else
1095                     {
1096                         // irq -> none case
1097                         irqDestinationVariable = null;
1098                     }
1099 
1100                     if(irqAttribute.Sources == null)
1101                     {
1102                         var gpioProperties = GetGpioProperties(objectType).ToArray();
1103                         if(gpioProperties.Length == 0)
1104                         {
1105 
1106                             HandleError(ParsingError.IrqSourceDoesNotExist, irqAttribute,
1107                                         string.Format("Type '{0}' does not contain any property of type GPIO.", objectType), false);
1108                         }
1109                         if(gpioProperties.Length > 1)
1110                         {
1111                             // If we get more than one property, try to further narrow the list - search if there is one with DefaultInterruptAttribute
1112                             gpioProperties = gpioProperties.Where(p => p.GetCustomAttribute(typeof(DefaultInterruptAttribute)) != null).ToArray();
1113                         }
1114                         // Now, there are the following possibilities:
1115                         // (1) either there is only one GPIO in the peripheral model
1116                         // (2) there are multiple GPIOs but one is marked with DefaultInterruptAttribute
1117                         // (3) there are multiple GPIOs but none is marked with DefaultInterruptAttribute
1118                         // (4) there are multiple GPIOs and several are marked with DefaultInterruptAttribute
1119                         // The clause below covers options (3) and (4) - it impossible to determine default interrupt here.
1120                         if(gpioProperties.Length != 1)
1121                         {
1122                             HandleError(ParsingError.AmbiguousDefaultIrqSource, irqAttribute,
1123                                         "Ambiguous choice of default interrupt." +
1124                                         $"\nThere are the following properties of GPIO type available: {Misc.PrettyPrintCollection(GetGpioProperties(objectType), e => e.Name)}.",
1125                                         false);
1126                         }
1127                         // we can now fill the missing source so that we can treat default irqs as normal ones from this point on
1128                         irqAttribute.SetDefaultSource(gpioProperties[0].Name);
1129                     }
1130                     else
1131                     {
1132                         if(attribute.Destinations != null)
1133                         {
1134                             // for irq -> none arity is always correct
1135                             var leftArity = irqAttribute.Sources.Sum(x => x.Ends.Count());
1136                             var rightArity = attribute.Destinations.Sum(x => x.Ends.Count());
1137                             if(leftArity != rightArity)
1138                             {
1139                                 HandleError(ParsingError.WrongIrqArity, irqAttribute,
1140                                             string.Format("Irq arity does not match. It is {0} on the left side and {1} on the right side.", leftArity, rightArity), false);
1141                             }
1142                         }
1143 
1144                         foreach(var source in irqAttribute.Sources)
1145                         {
1146                             foreach(var end in source.Ends)
1147                             {
1148                                 if(end.PropertyName != null)
1149                                 {
1150                                     var gpioProperty = objectType.GetProperty(end.PropertyName);
1151                                     if(gpioProperty == null || gpioProperty.PropertyType != typeof(GPIO))
1152                                     {
1153                                         HandleError(ParsingError.IrqSourceDoesNotExist, source,
1154                                                     string.Format("Property '{0}' does not exist in '{1}' or is not of the GPIO type.", end.PropertyName, objectType), true);
1155                                     }
1156                                 }
1157                                 else
1158                                 {
1159                                     if(!typeof(INumberedGPIOOutput).IsAssignableFrom(objectType))
1160                                     {
1161                                         HandleError(ParsingError.IrqSourceIsNotNumberedGpioOutput, source,
1162                                                     string.Format("Type '{0}' is not a numbered gpio output, while numbered output was used.", objectType), true);
1163                                     }
1164                                 }
1165                             }
1166                         }
1167                     }
1168 
1169                     if(irqDestinationVariable != null &&
1170                         !(typeof(IGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType) || typeof(ILocalGPIOReceiver).IsAssignableFrom(irqDestinationVariable.VariableType)))
1171                     {
1172                         HandleError(ParsingError.IrqDestinationIsNotIrqReceiver, attribute.DestinationPeripheral,
1173                                     string.Format("Type '{0}' does not implement IGPIOReceiver or ILocalGPIOReceiver and cannot be a destination of interrupts.", irqDestinationVariable.VariableType), false);
1174                     }
1175                     return;
1176                 }
1177             }
1178         }
1179 
ValidateProperty(PropertyInfo propertyInfo, ConstructorOrPropertyAttribute attribute)1180         private void ValidateProperty(PropertyInfo propertyInfo, ConstructorOrPropertyAttribute attribute)
1181         {
1182             var value = attribute.Value;
1183             var propertyType = propertyInfo.PropertyType;
1184 
1185             if(propertyInfo.GetSetMethod() == null)
1186             {
1187                 HandleError(ParsingError.PropertyNotWritable, attribute, string.Format("Property {0} does not have a public setter.", propertyInfo.Name), false);
1188             }
1189 
1190             var propertyFriendlyName = string.Format("Property '{0}'", attribute.Name);
1191             // verification:
1192             // - X: none attributes (none values) are not checked
1193             // - simple values are simply converted
1194             // - references are type checked
1195             // - inline objects are recursively type checked
1196             if(attribute.Value == null)
1197             {
1198                 return;
1199             }
1200             var referenceValue = attribute.Value as ReferenceValue;
1201             var objectValue = attribute.Value as ObjectValue;
1202             if(referenceValue != null)
1203             {
1204                 ValidateReference(propertyFriendlyName, new[] { propertyInfo.PropertyType }, referenceValue);
1205             }
1206             else if(objectValue != null)
1207             {
1208                 ValidateObjectValue(propertyFriendlyName, new[] { propertyInfo.PropertyType }, objectValue);
1209             }
1210             else
1211             {
1212                 ConvertFromValue(propertyInfo.PropertyType, attribute.Value);
1213             }
1214         }
1215 
ValidateReference(string friendlyName, Type[] typesToAssign, ReferenceValue value)1216         private IEnumerable<Type> ValidateReference(string friendlyName, Type[] typesToAssign, ReferenceValue value)
1217         {
1218             Variable referenceVariable;
1219             if(!variableStore.TryGetVariableFromReference(value, out referenceVariable))
1220             {
1221                 HandleError(ParsingError.MissingReference, value, string.Format("Undefined reference '{0}'.", value.Value), true);
1222             }
1223 
1224             var result = typesToAssign.Where(x => x.IsAssignableFrom(referenceVariable.VariableType));
1225             if(!result.Any())
1226             {
1227                 string typeListing = GetTypeListing(typesToAssign);
1228                 HandleError(ParsingError.TypeMismatch, value,
1229                             string.Format("{0} of {3} is not assignable from reference '{1}' of type '{2}'.",
1230                                           friendlyName, value.Value, referenceVariable.VariableType, typeListing), true);
1231             }
1232             return result;
1233         }
1234 
ValidateObjectValue(string friendlyName, Type[] typesToAssign, ObjectValue value)1235         private IEnumerable<Type> ValidateObjectValue(string friendlyName, Type[] typesToAssign, ObjectValue value)
1236         {
1237             var objectValueType = ResolveTypeOrThrow(value.TypeName, value);
1238             value.ObjectValueType = objectValueType;
1239             var result = typesToAssign.Where(x => x.IsAssignableFrom(objectValueType));
1240             if(!result.Any())
1241             {
1242                 var typeListing = GetTypeListing(typesToAssign);
1243                 HandleError(ParsingError.TypeMismatch, value,
1244                             string.Format("{0} of {1} is not assignable from object of type '{2}'.", friendlyName, typeListing, objectValueType), false);
1245             }
1246             foreach(var attribute in value.Attributes)
1247             {
1248                 ValidateAttributePreMerge(objectValueType, attribute);
1249             }
1250             if(value.Attributes.Any(x => x is InitAttribute))
1251             {
1252                 ValidateInitable(value);
1253             }
1254             return result;
1255         }
1256 
CheckRepeatedCtorAttributes(IEnumerable<ConstructorOrPropertyAttribute> attributes)1257         private void CheckRepeatedCtorAttributes(IEnumerable<ConstructorOrPropertyAttribute> attributes)
1258         {
1259             var names = new HashSet<string>();
1260             foreach(var attribute in attributes)
1261             {
1262                 if(!names.Add(attribute.Name))
1263                 {
1264                     HandleError(ParsingError.PropertyOrCtorNameUsedMoreThanOnce, attribute,
1265                                 string.Format("{1} '{0}' is used more than once.", attribute.Name, attribute.IsPropertyAttribute ? "Property" : "Constructor argument"), false);
1266                 }
1267             }
1268         }
1269 
CheckOverlappingIrqs(IEnumerable<IrqAttribute> attributes)1270         private void CheckOverlappingIrqs(IEnumerable<IrqAttribute> attributes)
1271         {
1272             var sources = new HashSet<IrqEnd>();
1273             foreach(var attribute in attributes)
1274             {
1275                 foreach(var source in attribute.Sources)
1276                 {
1277                     foreach(var end in source.Ends)
1278                     {
1279                         if(!sources.Add(end))
1280                         {
1281                             // source position information can only be obtained if this is not a default irq source
1282                             // if it is - we use the whole attribute
1283                             HandleError(ParsingError.IrqSourceUsedMoreThanOnce, source.StartPosition != null ? (IWithPosition)source : attribute,
1284                                         string.Format("Interrupt '{0}' has already been used as a source in this entry.", end.ToShortString()), true);
1285                         }
1286                     }
1287                 }
1288             }
1289         }
1290 
CheckRepeatedInitAttributes(IEnumerable<InitAttribute> attributes)1291         private void CheckRepeatedInitAttributes(IEnumerable<InitAttribute> attributes)
1292         {
1293             var secondInitAttribute = attributes.Skip(1).FirstOrDefault();
1294             if(secondInitAttribute != null)
1295             {
1296                 HandleError(ParsingError.MoreThanOneInitAttribute, secondInitAttribute, "Entry can contain only one init attribute.", false);
1297             }
1298         }
1299 
1300         private ConversionResult TryConvertSimplestValue<T>(Value value, Type expectedType, Type comparedType, string typeName, ref object result) where T : Value, ISimplestValue
1301         {
1302             var tValue = value as T;
1303             if(tValue == null)
1304             {
1305                 return ConversionResult.ConversionNotApplied;
1306             }
1307             if(comparedType != expectedType)
1308             {
1309                 return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
1310             }
1311             result = tValue.ConvertedValue;
1312             return ConversionResult.Success;
1313         }
1314 
TryConvertRangeValue(Value value, Type expectedType, ref object result)1315         private ConversionResult TryConvertRangeValue(Value value, Type expectedType, ref object result)
1316         {
1317             if(value == null && expectedType == typeof(Range?))
1318             {
1319                 result = (Range?)null;
1320                 return ConversionResult.Success;
1321             }
1322 
1323             var tValue = value as RangeValue;
1324             if(tValue == null)
1325             {
1326                 return ConversionResult.ConversionNotApplied;
1327             }
1328 
1329             if(expectedType == typeof(Range?))
1330             {
1331                 result = (Range?)tValue.ConvertedValue;
1332                 return ConversionResult.Success;
1333             }
1334             else if(expectedType == typeof(Range))
1335             {
1336                 result = tValue.ConvertedValue;
1337                 return ConversionResult.Success;
1338             }
1339 
1340             return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
1341         }
1342 
TryConvertSimpleValue(Type expectedType, Value value, out object result, bool silent = false)1343         private ConversionResult TryConvertSimpleValue(Type expectedType, Value value, out object result, bool silent = false)
1344         {
1345             result = null;
1346 
1347             if(value is EmptyValue)
1348             {
1349                 result = expectedType.IsValueType ? Activator.CreateInstance(expectedType) : null;
1350                 return ConversionResult.Success;
1351             }
1352 
1353             var numericalValue = value as NumericalValue;
1354             var enumValue = value as EnumValue;
1355 
1356             var results = new[]
1357             {
1358                 TryConvertSimplestValue<StringValue>(value, expectedType, typeof(string), "string", ref result),
1359                 TryConvertSimplestValue<BoolValue>(value, expectedType, typeof(bool), "bool", ref result),
1360                 TryConvertRangeValue(value, expectedType, ref result)
1361             };
1362 
1363             var meaningfulResult = results.FirstOrDefault(x => x.ResultType != ConversionResultType.ConversionNotApplied);
1364             if(meaningfulResult != null)
1365             {
1366                 return meaningfulResult;
1367             }
1368 
1369             if(numericalValue != null)
1370             {
1371                 // numbers can be interpreted as enums either when they match a numerical value of one
1372                 // of the enum's entries or when the enum has AllowAnyNumericalValueAttribute defined
1373                 if(expectedType.IsEnum && SmartParser.Instance.TryParse(numericalValue.Value, expectedType, out result))
1374                 {
1375                     return ConversionResult.Success;
1376                 }
1377                 if(!NumericTypes.Contains(expectedType) || !SmartParser.Instance.TryParse(numericalValue.Value, expectedType, out result))
1378                 {
1379                     return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
1380                 }
1381                 return ConversionResult.Success;
1382             }
1383 
1384             if(enumValue != null)
1385             {
1386                 if(!expectedType.IsEnum)
1387                 {
1388                     return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.TypeMismatch, string.Format(TypeMismatchMessage, expectedType));
1389                 }
1390 
1391                 // Compare types first as enum's type may use an alias
1392                 if(!TypeNameMatches(enumValue.TypeName, expectedType, silent))
1393                 {
1394                     return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.EnumMismatch,
1395                                                 $"Enum type mismatch, expected '{expectedType.Name}' instead of '{enumValue.TypeName}'.");
1396                 }
1397 
1398                 var expectedNamespace = expectedType.Namespace.Split('.');
1399                 var givenNamespace = enumValue.ReversedNamespace;
1400 
1401                 // Compare namespaces
1402                 foreach(var names in givenNamespace.Zip(expectedNamespace.Reverse(), (first, second) => Tuple.Create(first, second)))
1403                 {
1404                     if(names.Item1 != names.Item2)
1405                     {
1406                         return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.EnumMismatch,
1407                                                     $"Enum namespace mismatch, expected '{names.Item2}' instead of '{names.Item1}'.");
1408                     }
1409                 }
1410 
1411                 if(!SmartParser.Instance.TryParse(enumValue.Value, expectedType, out result))
1412                 {
1413                     return new ConversionResult(ConversionResultType.ConversionUnsuccesful, ParsingError.EnumMismatch,
1414                                                 $"Unexpected enum value '{enumValue.Value}'.{Environment.NewLine}{Environment.NewLine}    Valid values:{Environment.NewLine}{GetValidEnumValues(expectedType)}");
1415                 }
1416                 return ConversionResult.Success;
1417             }
1418 
1419             return ConversionResult.ConversionNotApplied;
1420         }
1421 
ConvertFromValue(Type expectedType, Value value)1422         private object ConvertFromValue(Type expectedType, Value value)
1423         {
1424             object result;
1425             var simpleConversionResult = TryConvertSimpleValue(expectedType, value, out result);
1426             if(simpleConversionResult.ResultType == ConversionResultType.ConversionSuccessful)
1427             {
1428                 return result;
1429             }
1430             if(simpleConversionResult.ResultType == ConversionResultType.ConversionUnsuccesful)
1431             {
1432                 HandleError(simpleConversionResult.Error, value, simpleConversionResult.Message, true);
1433             }
1434 
1435             var objectValue = value as ObjectValue;
1436             if(objectValue != null)
1437             {
1438                 return CreateFromObjectValue(objectValue);
1439             }
1440 
1441             var referenceValue = value as ReferenceValue;
1442             if(referenceValue != null)
1443             {
1444                 return variableStore.GetVariableFromReference(referenceValue).Value;
1445             }
1446 
1447             HandleInternalError(value); // should not reach here
1448             return null;
1449         }
1450 
FindConstructor(Type type, IEnumerable<ConstructorOrPropertyAttribute> attributes, IWithPosition responsibleObject)1451         private ConstructorInfo FindConstructor(Type type, IEnumerable<ConstructorOrPropertyAttribute> attributes, IWithPosition responsibleObject)
1452         {
1453             var constructorSelectionReport = new LazyList<string>();
1454             var result = new List<ConstructorInfo>();
1455             var availableCtors = type.GetConstructors();
1456             // 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
1457             var attributeGroups = attributes.GroupBy(x => x.Value != null).ToDictionary(x => x.Key, x => (IEnumerable<ConstructorOrPropertyAttribute>)x);
1458             // we can simply treat x: none entries as non existiting (they are only important during merge phase)
1459             attributes = attributeGroups.ContainsKey(true) ? attributeGroups[true] : new ConstructorOrPropertyAttribute[0];
1460             attributes = attributes.ToArray();
1461 
1462             if(attributeGroups.ContainsKey(false))
1463             {
1464                 constructorSelectionReport.Add(() => "Following attributes were set to none (and therefore ignored):");
1465                 foreach(var attribute in attributeGroups[false])
1466                 {
1467                     constructorSelectionReport.Add(() => string.Format("  '{0}' at {1}", attribute.Name, GetFormattedPosition(attribute)));
1468                 }
1469             }
1470 
1471             foreach(var ctor in availableCtors)
1472             {
1473                 constructorSelectionReport.Add(() => "");
1474                 constructorSelectionReport.Add(() => string.Format("Considering ctor {0}.", GetFriendlyConstructorName(ctor)));
1475                 var unusedAttributes = new HashSet<ConstructorOrPropertyAttribute>(attributes);
1476 
1477                 foreach(var argument in ctor.GetParameters())
1478                 {
1479                     var correspondingAttributes = attributes.Where(x => ParameterNameMatches(x.Name, argument));
1480                     if(correspondingAttributes.Count() > 1)
1481                     {
1482                         HandleError(ParsingError.AliasedAndNormalArgumentName, responsibleObject,
1483                                     "Ambiguous choice between aliased and normal argument name:" + Environment.NewLine +
1484                                     String.Join(Environment.NewLine, correspondingAttributes.Select(x => x.Name + ": " + x.Value)), true);
1485                     }
1486                     var correspondingAttribute = correspondingAttributes.FirstOrDefault();
1487                     if(correspondingAttribute == null)
1488                     {
1489                         object defaultValue = null;
1490                         // let's check if we can fill this argument with default value
1491                         if(!argument.HasDefaultValue && !TryGetValueOfOurDefaultParameter(argument.ParameterType, out defaultValue))
1492                         {
1493                             constructorSelectionReport.Add(() => string.Format("  Could not find corresponding attribute for parameter '{0}' of type '{1}' and it is not a default parameter. Rejecting constructor.",
1494                                                                    argument.Name, argument.ParameterType));
1495                             goto next;
1496                         }
1497                         if(defaultValue == null)
1498                         {
1499                             defaultValue = argument.DefaultValue;
1500                         }
1501                         constructorSelectionReport.Add(() => string.Format("  Parameter '{0}' of type '{1}' filled with default value = '{2}'.", argument.Name, argument.ParameterType, defaultValue));
1502                         continue;
1503                     }
1504                     if(typeof(IMachine).IsAssignableFrom(argument.ParameterType))
1505                     {
1506                         constructorSelectionReport.Add(() => $"  Value provided for parameter {argument.Name} is of internal Machine type and it cannot be assigned by user. Rejecting contructor.");
1507                         goto next;
1508                     }
1509                     constructorSelectionReport.Add(() => string.Format("  For parameter '{0}' of type '{1}' found attribute at {3} with value {2}",
1510                                                            argument.Name, argument.ParameterType, correspondingAttribute.Value, GetFormattedPosition(correspondingAttribute)));
1511                     object convertedObject;
1512                     var simpleConversionResult = TryConvertSimpleValue(argument.ParameterType, correspondingAttribute.Value, out convertedObject);
1513                     if(simpleConversionResult.ResultType == ConversionResultType.ConversionUnsuccesful)
1514                     {
1515                         constructorSelectionReport.Add(() => "    There was an error when converting the value:");
1516                         constructorSelectionReport.Add(() => "    " + simpleConversionResult.Message);
1517                         goto next; // same as above
1518                     }
1519                     if(simpleConversionResult.ResultType == ConversionResultType.ConversionSuccessful)
1520                     {
1521                         constructorSelectionReport.Add(() => string.Format("    Value converted succesfully, it is = '{0}'", convertedObject));
1522                         unusedAttributes.Remove(correspondingAttribute);
1523                         continue;
1524                     }
1525                     var referenceValue = correspondingAttribute.Value as ReferenceValue;
1526                     if(referenceValue != null)
1527                     {
1528                         if(argument.ParameterType.IsAssignableFrom(variableStore.GetVariableFromReference(referenceValue).VariableType))
1529                         {
1530                             constructorSelectionReport.Add(() => "    Parameter is assignable from the reference value.");
1531                             unusedAttributes.Remove(correspondingAttribute);
1532                             continue;
1533                         }
1534                         else
1535                         {
1536                             constructorSelectionReport.Add(() => "    Parameter is not assignable from the reference value, constructor rejected.");
1537                             goto next;
1538                         }
1539                     }
1540                     var objectValue = correspondingAttribute.Value as ObjectValue;
1541                     if(objectValue != null)
1542                     {
1543                         if(argument.ParameterType.IsAssignableFrom(objectValue.ObjectValueType))
1544                         {
1545                             constructorSelectionReport.Add(() => "    Parameter is assignable from the object value.");
1546                             unusedAttributes.Remove(correspondingAttribute);
1547                             continue;
1548                         }
1549                         else
1550                         {
1551                             constructorSelectionReport.Add(() => "    Parameter is not assignable from the object value, constructor rejected.");
1552                             goto next;
1553                         }
1554                     }
1555                 }
1556 
1557                 if(unusedAttributes.Count == 0)
1558                 {
1559                     constructorSelectionReport.Add(() => "  No unused attributes, constructor accepted.");
1560                     result.Add(ctor);
1561                 }
1562                 else
1563                 {
1564                     constructorSelectionReport.Add(() => string.Format("  Constructor rejected, {0} unused attributes left:", unusedAttributes.Count));
1565                     foreach(var attribute in unusedAttributes)
1566                     {
1567                         constructorSelectionReport.Add(() => string.Format("    '{0}' with value '{1}' at {2}", attribute.Name, attribute.Value, GetFormattedPosition(attribute)));
1568                     }
1569                 }
1570             next:;
1571             }
1572 
1573             if(result.Count == 1)
1574             {
1575                 return result[0];
1576             }
1577 
1578             var constructorSelectionReportAsString = $"{Environment.NewLine}Constructor selection report:{Environment.NewLine}{string.Join(Environment.NewLine, constructorSelectionReport.ToList())}";
1579 
1580             if(result.Count == 0)
1581             {
1582                 HandleError(ParsingError.NoCtor, responsibleObject,
1583                             string.Format("Could not find suitable constructor for type '{0}'.{1}", type, constructorSelectionReportAsString), false);
1584             }
1585             else
1586             {
1587                 var prettyPrintedCtors = result.Select(x => x.GetParameters().Select(y => y.Name + ": " + y.ParameterType).Aggregate((y, z) => y + ", " + z))
1588                                                .Select(x => string.Format("({0})", x)).Aggregate((x, y) => x + Environment.NewLine + y);
1589                 HandleError(ParsingError.AmbiguousCtor, responsibleObject,
1590                             string.Format("Ambiguous choice between constructors for type '{0}':{1}{2}{3}", type, Environment.NewLine, prettyPrintedCtors, constructorSelectionReportAsString), false);
1591             }
1592 
1593             HandleInternalError();
1594             return result[0]; // will not reach here
1595         }
1596 
TryGetValueOfOurDefaultParameter(Type type, out object value)1597         private bool TryGetValueOfOurDefaultParameter(Type type, out object value)
1598         {
1599             value = null;
1600             if(typeof(IMachine).IsAssignableFrom(type))
1601             {
1602                 value = machine;
1603                 return true;
1604             }
1605             return false;
1606         }
1607 
ParameterNameMatches(string given, ParameterInfo info, bool silent = false)1608         private bool ParameterNameMatches(string given, ParameterInfo info, bool silent = false)
1609         {
1610             return NameOrAliasMatches(given, info.Name, "parameter", info.GetCustomAttribute<NameAliasAttribute>(), silent);
1611         }
1612 
TypeNameMatches(string given, Type type, bool silent = false)1613         private bool TypeNameMatches(string given, Type type, bool silent = false)
1614         {
1615             return NameOrAliasMatches(given, type.Name, "type", type.GetCustomAttribute<NameAliasAttribute>(), silent);
1616         }
1617 
NameOrAliasMatches(string given, string name, string typeClass, NameAliasAttribute alias, bool silent = false)1618         private bool NameOrAliasMatches(string given, string name, string typeClass, NameAliasAttribute alias, bool silent = false)
1619         {
1620             if(given == name)
1621             {
1622                 return true;
1623             }
1624 
1625             if(alias != null && alias.Name == given)
1626             {
1627                 if(!silent && alias.WarnOnUsage)
1628                 {
1629                     Logger.Log(LogLevel.Warning, "Using alias '{0}' for {1} '{2}'", alias.Name, typeClass, name);
1630                 }
1631 
1632                 return true;
1633             }
1634 
1635             return false;
1636         }
1637 
HandleInternalError(IWithPosition failingObject = null, [CallerMemberName] string callingMethod = R, [CallerFilePath] string callingFilePath = R, [CallerLineNumber] int callingFileLineNumber = 0)1638         private void HandleInternalError(IWithPosition failingObject = null,
1639             [CallerMemberName] string callingMethod = "",
1640             [CallerFilePath] string callingFilePath = "",
1641             [CallerLineNumber] int callingFileLineNumber = 0)
1642         {
1643             var message = string.Format("Internal error during processing in function '{0}' at {1}:{2}.", callingMethod, callingFilePath, callingFileLineNumber);
1644             if(failingObject != null)
1645             {
1646                 HandleError(ParsingError.InternalError, failingObject, message, false);
1647             }
1648             else
1649             {
1650                 throw new ParsingException(ParsingError.InternalError, message);
1651             }
1652         }
1653 
HandleError(ParsingError error, IWithPosition failingObject, string message, bool longMark)1654         private void HandleError(ParsingError error, IWithPosition failingObject, string message, bool longMark)
1655         {
1656             string source, fileName;
1657             if(!GetElementSourceAndPath(failingObject, out fileName, out source))
1658             {
1659                 HandleInternalError();
1660             }
1661 
1662             var lineNumber = failingObject.StartPosition.Line;
1663             var columnNumber = failingObject.StartPosition.Column;
1664             var messageBuilder = new StringBuilder();
1665             messageBuilder.AppendFormat("Error E{0:D2}: ", (int)error);
1666             messageBuilder.AppendLine(message);
1667             messageBuilder.AppendFormat("At {2}{0}:{1}:", lineNumber, columnNumber, fileName == "" ? "" : fileName + ':');
1668             messageBuilder.AppendLine();
1669             var sourceInLines = source.Replace("\r", string.Empty).Split(new[] { '\n' }, StringSplitOptions.None);
1670             var problematicLine = sourceInLines[lineNumber - 1];
1671             messageBuilder.AppendLine(problematicLine);
1672             messageBuilder.Append(' ', columnNumber - 1);
1673             messageBuilder.Append('^', longMark ? Math.Min(problematicLine.Length - (columnNumber - 1), failingObject.Length) : 1);
1674             throw new ParsingException(error, messageBuilder.ToString());
1675         }
1676 
ResolveTypeOrThrow(string typeName, IWithPosition syntaxElement)1677         private Type ResolveTypeOrThrow(string typeName, IWithPosition syntaxElement)
1678         {
1679             var extendedTypeName = typeName.StartsWith(DefaultNamespace, StringComparison.Ordinal) ? typeName : DefaultNamespace + typeName;
1680 
1681             var manager = TypeManager.Instance;
1682             var result = manager.TryGetTypeByName(typeName) ?? manager.TryGetTypeByName(extendedTypeName);
1683             if(result == null)
1684             {
1685                 HandleError(ParsingError.TypeNotResolved, syntaxElement, string.Format("Could not resolve type: '{0}'.", typeName), true);
1686             }
1687             return result;
1688         }
1689 
GetFormattedPosition(IWithPosition element)1690         private string GetFormattedPosition(IWithPosition element)
1691         {
1692             string path, unused;
1693             if(!GetElementSourceAndPath(element, out path, out unused))
1694             {
1695                 HandleInternalError();
1696             }
1697             return string.Format("{2}{0}:{1}", element.StartPosition.Line, element.StartPosition.Column, path == "" ? "" : path + ':');
1698         }
1699 
GetElementSourceAndPath(IWithPosition element, out string file, out string source)1700         private bool GetElementSourceAndPath(IWithPosition element, out string file, out string source)
1701         {
1702             var syntaxErrorPosition = element as WithPositionForSyntaxErrors;
1703             if(syntaxErrorPosition != null)
1704             {
1705                 file = syntaxErrorPosition.FileName;
1706                 source = syntaxErrorPosition.Source;
1707                 return true;
1708             }
1709 
1710             foreach(var description in processedDescriptions)
1711             {
1712                 if(SyntaxTreeHelpers.ScanFor(description, element))
1713                 {
1714                     file = description.FileName;
1715                     source = description.Source;
1716                     return true;
1717                 }
1718             }
1719             source = null;
1720             file = null;
1721             return false;
1722         }
1723 
GetTypeListing(Type[] typesToAssign)1724         private static string GetTypeListing(Type[] typesToAssign)
1725         {
1726 	        return typesToAssign.Length == 1 ? string.Format("type '{0}'", typesToAssign[0])
1727 												   : "possible types " + typesToAssign.Select(x => string.Format("'{0}'", x.Name)).Aggregate((x, y) => x + ", " + y);
1728         }
1729 
GetFriendlyConstructorName(ConstructorInfo ctor)1730         private static string GetFriendlyConstructorName(ConstructorInfo ctor)
1731         {
1732             var parameters = ctor.GetParameters();
1733             if(parameters.Length == 0)
1734             {
1735                 return string.Format("{0} with no parameters", ctor.DeclaringType);
1736             }
1737             return string.Format("{0} with the following parameters: [{1}]", ctor.DeclaringType, parameters.Select(x => x.ParameterType + (x.HasDefaultValue ? " (optional)" : ""))
1738                                  .Aggregate((x, y) => x + ", " + y));
1739         }
1740 
GetGpioProperties(Type type)1741         private static IEnumerable<PropertyInfo> GetGpioProperties(Type type)
1742         {
1743             return type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => typeof(GPIO).IsAssignableFrom(x.PropertyType));
1744         }
1745 
GetValidEnumValues(Type expectedType)1746         private static string GetValidEnumValues(Type expectedType)
1747         {
1748             var validValues = new StringBuilder();
1749             foreach(var field in Enum.GetValues(expectedType))
1750             {
1751                 validValues.AppendLine($"       {expectedType.Name}.{field},");
1752             }
1753             return validValues.ToString();
1754         }
1755 
1756         private readonly Machine machine;
1757         private readonly IUsingResolver usingResolver;
1758         private readonly IInitHandler initHandler;
1759         private readonly VariableStore variableStore;
1760         private readonly List<Description> processedDescriptions;
1761         private readonly Queue<ObjectValue> objectValueUpdateQueue;
1762         private readonly Queue<ObjectValue> objectValueInitQueue;
1763         private readonly Stack<string> usingsBeingProcessed;
1764         private readonly Dictionary<IrqDestination, IrqCombinerConnection> irqCombiners;
1765 
1766         private static readonly HashSet<Type> NumericTypes = new HashSet<Type>(new []
1767         {
1768             typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
1769             typeof(int), typeof(uint), typeof(long), typeof(ulong),
1770             typeof(decimal), typeof(float), typeof(double)
1771         }.Select(x => new[] { x, typeof(Nullable<>).MakeGenericType(x)}).SelectMany(x => x));
1772 
1773         private const string DefaultNamespace = "Antmicro.Renode.Peripherals.";
1774         private const string TypeMismatchMessage = "Type mismatch. Expected {0}.";
1775 
1776         private class WithPositionForSyntaxErrors : IWithPosition
1777         {
FromResult(IResult<T> result, string fileName, string source)1778             public static WithPositionForSyntaxErrors FromResult<T>(IResult<T> result, string fileName, string source)
1779             {
1780                 var position = new Position(result.Remainder.Position, result.Remainder.Line, result.Remainder.Column);
1781                 return new WithPositionForSyntaxErrors(1, position, fileName, source);
1782             }
1783 
1784             public int Length { get; private set; }
1785             public Position StartPosition { get; private set; }
1786             public string FileName { get; private set; }
1787             public string Source { get; private set; }
1788 
WithPositionForSyntaxErrors(int length, Position startPosition, string fileName, string source)1789             private WithPositionForSyntaxErrors(int length, Position startPosition, string fileName, string source)
1790             {
1791             	Length = length;
1792             	StartPosition = startPosition;
1793                 FileName = fileName;
1794                 Source = source;
1795             }
1796         }
1797 
1798         private class ReferenceValueStack
1799         {
ReferenceValueStack(ReferenceValueStack previous, ReferenceValue value, Entry entry)1800             public ReferenceValueStack(ReferenceValueStack previous, ReferenceValue value, Entry entry)
1801             {
1802                 Previous = previous;
1803                 Value = value;
1804                 Entry = entry;
1805             }
1806 
1807             public ReferenceValueStack Previous { get; private set; }
1808             public ReferenceValue Value { get; private set; }
1809             public Entry Entry { get; private set; }
1810         }
1811 
1812         private struct IrqDestination
1813         {
IrqDestinationAntmicro.Renode.PlatformDescription.CreationDriver.IrqDestination1814             public IrqDestination(string peripheralName, int? localIndex, int index)
1815             {
1816                 PeripheralName = peripheralName;
1817                 LocalIndex = localIndex;
1818                 Index = index;
1819             }
1820 
1821             public readonly string PeripheralName;
1822             public readonly int? LocalIndex;
1823             public readonly int Index;
1824         }
1825 
1826         private class IrqCombinerConnection
1827         {
IrqCombinerConnection(CombinedInput combiner)1828             public IrqCombinerConnection(CombinedInput combiner)
1829             {
1830                 Combiner = combiner;
1831                 nextConnectionIndex = 0;
1832             }
1833 
1834             public int nextConnectionIndex;
1835 
1836             public readonly CombinedInput Combiner;
1837         }
1838     }
1839 }
1840