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