1 //
2 // Copyright (c) 2010-2024 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.Linq;
10 using System.Text;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Exceptions;
13 using Sprache;
14 
15 namespace Antmicro.Renode.PlatformDescription
16 {
17     public class AccessConditionParser
18     {
ParseCondition(string condition)19         public static DnfFormula ParseCondition(string condition)
20         {
21             return DnfFormula.FromDnfTree(ParseExpression(condition).ToDnf());
22         }
23 
EvaluateWithStateBits(DnfFormula formula, Func<string, IReadOnlyDictionary<string, int>> getStateBits)24         public static Dictionary<string, List<StateMask>> EvaluateWithStateBits(DnfFormula formula, Func<string, IReadOnlyDictionary<string, int>> getStateBits)
25         {
26             var result = new Dictionary<string, List<StateMask>>();
27 
28             // A term can have multiple conditions but up to one initiator.
29             foreach(var term in formula.Terms)
30             {
31                 var initiator = term.Conditions.OfType<InitiatorConditionNode>().SingleOrDefault()?.Initiator;
32 
33                 // Empty string is used as a key if an initiator wasn't specified.
34                 var initiatorKey = initiator ?? string.Empty;
35                 if(!result.ContainsKey(initiatorKey))
36                 {
37                     result[initiatorKey] = new List<StateMask>();
38                 }
39 
40                 // If `initiator` is null then `stateBits` will contain common state bits for all IPeripheralWithTransactionState.
41                 //
42                 // It can be null if:
43                 // 1. `initiator` does not implement IPeripheralWithTransactionState,
44                 // 2. `initiator` is null and there's no peripheral implementing IPeripheralWithTransactionState.
45                 var stateBits = getStateBits(initiator);
46 
47                 var conditions = term.Conditions.Where(c => !(c is InitiatorConditionNode));
48                 if(conditions.Any())
49                 {
50                     if(stateBits == null || !stateBits.Any())
51                     {
52                         var message = new StringBuilder("Conditions provided (")
53                             .Append(string.Join(" && ", conditions))
54                             .Append(") but ")
55                             .Append(initiator == null
56                                     ? $"there are no peripherals implementing {nameof(Peripherals.IPeripheralWithTransactionState)} or they have no common state bits"
57                                     : $"the initiator '{initiator}' doesn't implement {nameof(Peripherals.IPeripheralWithTransactionState)} or has no state bits"
58                             ).ToString();
59                         throw new RecoverableException(message);
60                     }
61                 }
62 
63                 var termStateMask = new StateMask();
64                 foreach(var condition in conditions)
65                 {
66                     var initiatorString = initiator == null
67                         ? $"peripherals implementing {nameof(Peripherals.IPeripheralWithTransactionState)} (initiator not specified)"
68                         : $"the initiator '{initiator}'";
69 
70                     if(!stateBits.TryGetValue(condition.Condition, out var bitPosition))
71                     {
72                         var supportedBitsString = "'" + string.Join("', '", stateBits.Select(pair => pair.Key)) + "'";
73                         throw new RecoverableException($"Provided condition is unsupported by {initiatorString}: {condition.Condition}; supported conditions: {supportedBitsString}");
74                     }
75 
76                     if(termStateMask.HasMaskBit(bitPosition))
77                     {
78                         throw new RecoverableException($"Conditions conflict detected for {initiatorString}: {condition.Condition}");
79                     }
80 
81                     // The given StateMask bit should be unset if the condition is negated, otherwise set.
82                     var bitShouldBeSet = !condition.Negated;
83                     termStateMask = termStateMask.WithBitValue(bitPosition, bitShouldBeSet);
84                 }
85                 result[initiatorKey].Add(termStateMask);
86             }
87             return result;
88         }
89 
ParseExpression(string expr)90         private static AstNode ParseExpression(string expr)
91         {
92             return OrTerm.Parse(expr);
93         }
94 
95         private static Parser<IEnumerable<char>> OrOp => Parse.String("||").Token();
96 
97         private static Parser<AstNode> OrTerm =>
98             Parse.ChainOperator(OrOp, AndTerm, (op, l, r) => new OrNode(l, r));
99 
100         private static Parser<IEnumerable<char>> AndOp => Parse.String("&&").Token();
101 
102         private static Parser<AstNode> AndTerm =>
103             Parse.ChainOperator(AndOp, NegateTerm, (op, l, r) => new AndNode(l, r));
104 
105         private static Parser<AstNode> NegateTerm =>
106             NegatedFactor
107             .Or(Factor);
108 
109         private static Parser<AstNode> NegatedFactor =>
110             from _ in Parse.Char('!').Token()
111             from expr in Factor
112             select new NotNode(expr);
113 
114         private static Parser<AstNode> Factor =>
115             SubExpression
116             .Or(InitiatorCondition)
117             .Or(Condition);
118 
119         private static Parser<AstNode> SubExpression =>
120             from lparen in Parse.Char('(').Token()
121             from expr in OrTerm
122             from rparen in Parse.Char(')').Token()
123             select expr;
124 
125         private static Parser<string> GenericToken => Parse.Regex(@"\w+").Token();
126 
127         private static Parser<AstNode> Condition =>
128             GenericToken
129             .Select(name => new ConditionNode(name));
130 
131         private static Parser<IEnumerable<char>> InitiatorKeyword => Parse.String("initiator").Token();
132 
133         private static Parser<IEnumerable<char>> EqualsOp => Parse.String("==").Token();
134 
135         private static Parser<AstNode> InitiatorCondition =>
136             from _ in InitiatorKeyword
137             from __ in EqualsOp
138             from initiator in GenericToken
139             select new InitiatorConditionNode(initiator);
140 
141         public abstract class AstNode
142         {
ToDnf()143             public abstract AstNode ToDnf();
144         }
145 
146         public class AndNode : AstNode
147         {
AndNode(AstNode left, AstNode right)148             public AndNode(AstNode left, AstNode right)
149             {
150                 Left = left;
151                 Right = right;
152             }
153 
ToDnf()154             public override AstNode ToDnf()
155             {
156                 var leftDnf = Left.ToDnf();
157                 var rightDnf = Right.ToDnf();
158                 if(leftDnf is OrNode leftOr)
159                 {
160                     // (l.l || l.r) && r to (l.l && r) || (l.r && r)
161                     // Recursively call ToDnf on it to handle cases like `(a || b) && (c || d)`
162                     return new OrNode(new AndNode(leftOr.Left, rightDnf), new AndNode(leftOr.Right, rightDnf)).ToDnf();
163                 }
164                 if(rightDnf is OrNode rightOr)
165                 {
166                     // l && (r.l || r.r) to (l && r.l) || (l && r.r)
167                     return new OrNode(new AndNode(leftDnf, rightOr.Left), new AndNode(leftDnf, rightOr.Right)).ToDnf();
168                 }
169                 return new AndNode(leftDnf, rightDnf);
170             }
171 
ToString()172             public override string ToString() => $"({Left} && {Right})";
173 
174             public readonly AstNode Left;
175             public readonly AstNode Right;
176         }
177 
178         public class OrNode : AstNode
179         {
OrNode(AstNode left, AstNode right)180             public OrNode(AstNode left, AstNode right)
181             {
182                 Left = left;
183                 Right = right;
184             }
185 
ToDnf()186             public override AstNode ToDnf()
187             {
188                 return new OrNode(Left.ToDnf(), Right.ToDnf());
189             }
190 
ToString()191             public override string ToString() => $"({Left} || {Right})";
192 
193             public readonly AstNode Left;
194             public readonly AstNode Right;
195         }
196 
197         public class NotNode : AstNode
198         {
NotNode(AstNode operand)199             public NotNode(AstNode operand)
200             {
201                 Operand = operand;
202             }
203 
ToDnf()204             public override AstNode ToDnf()
205             {
206                 if(Operand is AndNode and)
207                 {
208                     // !(l && r) to (!l) || (!r)
209                     return new OrNode(new NotNode(and.Left).ToDnf(), new NotNode(and.Right).ToDnf());
210                 }
211                 if(Operand is OrNode or)
212                 {
213                     // !(p || q) to (!p) && (!q)
214                     return new AndNode(new NotNode(or.Left).ToDnf(), new NotNode(or.Right).ToDnf());
215                 }
216                 if(Operand is NotNode not)
217                 {
218                     return not.Operand.ToDnf();
219                 }
220                 return this;
221             }
222 
ToString()223             public override string ToString() => $"!{Operand}";
224 
225             public readonly AstNode Operand;
226         }
227 
228         public class ConditionNode : AstNode
229         {
ConditionNode(string condition, bool negated = false)230             public ConditionNode(string condition, bool negated = false)
231             {
232                 Condition = condition;
233                 Negated = negated;
234             }
235 
ToDnf()236             public override AstNode ToDnf() => this;
237 
ToString()238             public override string ToString() => $"{(Negated ? "!" : "")}{Condition}";
239             public ConditionNode Negation => new ConditionNode(Condition, !Negated);
240 
241             public readonly string Condition;
242             public readonly bool Negated;
243         }
244 
245         public class InitiatorConditionNode : ConditionNode
246         {
InitiatorConditionNode(string initiator)247             public InitiatorConditionNode(string initiator) : base($"initiator == {initiator}")
248             {
249                 Initiator = initiator;
250             }
251 
252             public readonly string Initiator;
253         }
254 
255         public class DnfTerm
256         {
DnfTerm(IReadOnlyList<ConditionNode> conditions)257             public DnfTerm(IReadOnlyList<ConditionNode> conditions)
258             {
259                 Conditions = conditions;
260             }
261 
ToString()262             public override string ToString() => $"({string.Join(" && ", Conditions)})";
263 
264             public readonly IReadOnlyList<ConditionNode> Conditions;
265         }
266 
267         public class DnfFormula
268         {
DnfFormula(IReadOnlyList<DnfTerm> terms)269             public DnfFormula(IReadOnlyList<DnfTerm> terms)
270             {
271                 Terms = terms;
272             }
273 
FromDnfTree(AstNode root)274             public static DnfFormula FromDnfTree(AstNode root)
275             {
276                 var terms = new List<DnfTerm>();
277                 GatherDnfTerms(root, terms);
278                 return new DnfFormula(terms);
279             }
280 
GatherDnfTerms(AstNode node, List<DnfTerm> terms)281             private static void GatherDnfTerms(AstNode node, List<DnfTerm> terms)
282             {
283                 if(node is OrNode orNode)
284                 {
285                     GatherDnfTerms(orNode.Left, terms);
286                     GatherDnfTerms(orNode.Right, terms);
287                 }
288                 else if(node is AndNode || node is ConditionNode || node is NotNode)
289                 {
290                     var conditions = new List<ConditionNode>();
291                     GatherConditions(node, conditions);
292                     terms.Add(new DnfTerm(conditions));
293                 }
294                 else
295                 {
296                     throw new InvalidOperationException($"Unexpected node type: {node.GetType().FullName}");
297                 }
298             }
299 
GatherConditions(AstNode node, List<ConditionNode> conditions)300             private static void GatherConditions(AstNode node, List<ConditionNode> conditions)
301             {
302                 if(node is ConditionNode conditionNode)
303                 {
304                     conditions.Add(conditionNode);
305                 }
306                 else if(node is NotNode notNode)
307                 {
308                     conditions.Add(((ConditionNode)notNode.Operand).Negation);
309                 }
310                 else if(node is AndNode andNode)
311                 {
312                     GatherConditions(andNode.Left, conditions);
313                     GatherConditions(andNode.Right, conditions);
314                 }
315                 else
316                 {
317                     throw new InvalidOperationException($"Unexpected node type: {node.GetType().FullName}");
318                 }
319             }
320 
ToString()321             public override string ToString() => $"{string.Join(" || ", Terms)}";
322 
323             public readonly IReadOnlyList<DnfTerm> Terms;
324         }
325     }
326 }
327