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