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.Collections.Generic; 8 using System.IO; 9 using System.Linq; 10 using Antmicro.Renode.PlatformDescription.Syntax; 11 12 namespace Antmicro.Renode.PlatformDescription 13 { 14 partial class CreationDriver 15 { 16 private class UsingsGraph 17 { UsingsGraph(CreationDriver creationDriver, string rootFilePath, string rootFileSource)18 public UsingsGraph(CreationDriver creationDriver, string rootFilePath, string rootFileSource) 19 { 20 this.rootFilePath = rootFilePath; 21 this.rootFileSource = rootFileSource; 22 this.creationDriver = creationDriver; 23 usingsMap = new Dictionary<string, GraphNode>(); 24 } 25 UsingsFileVisitor(Description description, string prefix)26 public delegate void UsingsFileVisitor(Description description, string prefix); 27 TraverseDepthFirst(UsingsFileVisitor visitor)28 public void TraverseDepthFirst(UsingsFileVisitor visitor) 29 { 30 // This function does a depth first search on the usings graph (with GraphNode nodes) and runs visitor 31 // on each using file. 32 var nodesToProcess = new Stack<GraphNode>(); 33 34 var graphNodeFinished = new Dictionary<GraphNode, bool>(); 35 var fileCurrentlyProcessed = new Dictionary<string, bool>(); 36 37 GraphNode rootNode = GetOrCreateGraphNode(creationDriver, rootFilePath, rootFileSource, "", null); 38 fileCurrentlyProcessed[rootNode.FileId] = true; 39 graphNodeFinished[rootNode] = false; 40 41 nodesToProcess.Push(rootNode); 42 while(nodesToProcess.Count != 0) 43 { 44 var currentFile = nodesToProcess.Peek(); 45 46 var finished = true; 47 48 // Iteration is in reverse to process the children in the same order as they are written in the platform 49 // description - usings later in the description are pushed to the stack first, so they are popped last. 50 foreach(var usingEntry in currentFile.ParsedDescription.Usings.Reverse()) 51 { 52 var filePath = Path.GetFullPath(creationDriver.usingResolver.Resolve(usingEntry.Path, currentFile.Path)); 53 if(!File.Exists(filePath)) 54 { 55 creationDriver.HandleError(ParsingError.UsingFileNotFound, usingEntry.Path, 56 string.Format("Using '{0}' resolved as '{1}' does not exist.", usingEntry.Path, filePath), true); 57 } 58 59 var prefix = currentFile.Prefix + usingEntry.Prefix; 60 GraphNode node = GetOrCreateGraphNode(creationDriver, filePath, "", prefix, currentFile); 61 62 if(!graphNodeFinished.ContainsKey(node)) 63 { 64 graphNodeFinished[node] = false; 65 } 66 67 if(!fileCurrentlyProcessed.ContainsKey(node.FileId)) 68 { 69 fileCurrentlyProcessed[node.FileId] = false; 70 } 71 72 // Detect cycles. 73 if(fileCurrentlyProcessed[node.FileId] || node.Path == currentFile.Path) 74 { 75 creationDriver.HandleError(ParsingError.RecurringUsing, usingEntry, 76 string.Format("There is a cycle in using file dependency."), 77 false); 78 } 79 80 if(graphNodeFinished[node]) 81 { 82 continue; 83 } 84 85 fileCurrentlyProcessed[node.FileId] = true; 86 nodesToProcess.Push(node); 87 finished = false; 88 } 89 90 if(finished) 91 { 92 visitor(currentFile.ParsedDescription, currentFile.Prefix); 93 graphNodeFinished[currentFile] = true; 94 currentFile = nodesToProcess.Pop(); 95 fileCurrentlyProcessed[currentFile.FileId] = false; 96 } 97 } 98 } 99 GetOrCreateGraphNode(CreationDriver creationDriver, string filePath, string source, string prefix, GraphNode parent)100 private GraphNode GetOrCreateGraphNode(CreationDriver creationDriver, string filePath, string source, string prefix, GraphNode parent) 101 { 102 if(!usingsMap.TryGetValue(GraphNode.MakeGraphId(parent?.GraphId ?? "", prefix, filePath), out var node)) 103 { 104 node = CreateGraphNode(creationDriver, filePath, source, prefix, parent); 105 } 106 return node; 107 } 108 CreateGraphNode(CreationDriver creationDriver, string filePath, string source, string prefix, GraphNode parent)109 private GraphNode CreateGraphNode(CreationDriver creationDriver, string filePath, string source, string prefix, GraphNode parent) 110 { 111 if(source == "") 112 { 113 source = File.ReadAllText(filePath); 114 } 115 var description = creationDriver.ParseDescription(source, filePath); 116 117 // processedDescriptions must be populated as soon as possible for HandleError to properly report error 118 // locations. 119 if(!creationDriver.processedDescriptions.Contains(description)) 120 { 121 creationDriver.processedDescriptions.Add(description); 122 } 123 124 ValidateDescriptionUsings(description); 125 var node = new GraphNode(filePath, source, prefix, description, parent); 126 usingsMap[node.GraphId] = node; 127 return node; 128 } 129 ValidateDescriptionUsings(Description description)130 private void ValidateDescriptionUsings(Description description) 131 { 132 var used = new HashSet<string>(); 133 foreach(var usingEntry in description.Usings) 134 { 135 if(!used.Add(usingEntry.Prefix + usingEntry.Path)) 136 { 137 creationDriver.HandleError(ParsingError.DuplicateUsing, usingEntry, "Duplicate using entry.", false); 138 } 139 } 140 } 141 142 private class GraphNode 143 { GraphNode(string path, string source, string prefix, Description parsedDescription, GraphNode parent)144 public GraphNode(string path, string source, string prefix, Description parsedDescription, GraphNode parent) 145 { 146 Path = path; 147 Source = source; 148 Prefix = prefix; 149 Parent = parent; 150 GraphId = MakeGraphId(parent?.GraphId ?? "", prefix, path); 151 FileId = prefix + path; 152 ParsedDescription = parsedDescription; 153 SetupScopeInDescription(ParsedDescription, Prefix); 154 } 155 MakeGraphId(string parentGraphId, string prefix, string path)156 public static string MakeGraphId(string parentGraphId, string prefix, string path) 157 { 158 // Path can be empty, as is the case when loading a platform description from string. 159 // This is not a problem and doesn't require special handling, since there is at most one using like 160 // that in the usings hierarchy (the root). 161 return parentGraphId + prefix + path; 162 } 163 164 public string GraphId { get; } 165 public string FileId { get; } 166 public string Path { get; } 167 public string Source { get; } 168 public string Prefix { get; } 169 public Description ParsedDescription { get; } 170 public GraphNode Parent { get; set; } 171 SetupScopeInDescription(Description description, string prefix)172 private void SetupScopeInDescription(Description description, string prefix) 173 { 174 // This prefixes all variables with an appropriate prefix and sets the correct scope for the Description. 175 SyntaxTreeHelpers.VisitSyntaxTree<IPrefixable>(description, x => x.Prefix(prefix)); 176 SyntaxTreeHelpers.VisitSyntaxTree<ReferenceValue>(description, x => x.Scope = description.FileName); 177 } 178 }; 179 180 private readonly CreationDriver creationDriver; 181 private readonly string rootFilePath; 182 private readonly string rootFileSource; 183 private readonly Dictionary<string, GraphNode> usingsMap; 184 } 185 } 186 } 187