1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System.Globalization;
5 using System.Collections;
6 using System.Xml;
7 using System;
8 using Microsoft.Build.BuildEngine.Shared;
9 
10 using Microsoft.Build.Framework;
11 
12 namespace Microsoft.Build.BuildEngine
13 {
14     [Flags]
15     internal enum ParserOptions
16     {
17         None                = 0x0,
18         AllowProperties     = 0x1,
19         AllowItemLists      = 0x2,
20         AllowPropertiesAndItemLists = AllowProperties | AllowItemLists,
21         AllowItemMetadata   = 0x4,
22         AllowPropertiesAndItemMetadata = AllowProperties | AllowItemMetadata,
23         AllowAll            = AllowProperties | AllowItemLists | AllowItemMetadata
24     };
25 
26     /// <summary>
27     /// This class implements the grammar for complex conditionals.
28     ///
29     /// The usage is:
30     ///    Parser p = new Parser(CultureInfo);
31     ///    ExpressionTree t = p.Parse(expression, XmlNode);
32     ///
33     /// The expression tree can then be evaluated and re-evaluated as needed.
34     /// </summary>
35     internal sealed class Parser
36     {
37         private Scanner lexer;
38         private XmlAttribute conditionAttribute;
39         private ParserOptions options;
40         internal int errorPosition = 0; // useful for unit tests
41 
42 #region REMOVE_COMPAT_WARNING
43 
44         private bool warnedForExpression = false;
45 
46         private BuildEventContext logBuildEventContext;
47         /// <summary>
48         ///  Location contextual information which are attached to logging events to
49         ///  say where they are in relation to the process, engine, project, target,task which is executing
50         /// </summary>
51         internal BuildEventContext LogBuildEventContext
52         {
53             get
54             {
55                 return logBuildEventContext;
56             }
57             set
58             {
59                 logBuildEventContext = value;
60             }
61         }
62         private EngineLoggingServices loggingServices;
63         /// <summary>
64         /// Engine Logging Service reference where events will be logged to
65         /// </summary>
66         internal EngineLoggingServices LoggingServices
67         {
68             set
69             {
70                 this.loggingServices = value;
71             }
72 
73             get
74             {
75                 return this.loggingServices;
76             }
77         }
78 #endregion
79 
Parser()80         internal Parser()
81         {
82             // nothing to see here, move along.
83         }
84 
85         //
86         // Main entry point for parser.
87         // You pass in the expression you want to parse, and you get an
88         // ExpressionTree out the back end.
89         //
Parse(string expression, XmlAttribute conditionAttributeRef, ParserOptions optionSettings)90         internal GenericExpressionNode Parse(string expression, XmlAttribute conditionAttributeRef, ParserOptions optionSettings)
91         {
92             // We currently have no support (and no scenarios) for disallowing property references
93             // in Conditions.
94             ErrorUtilities.VerifyThrow(0 != (optionSettings & ParserOptions.AllowProperties),
95                 "Properties should always be allowed.");
96 
97             this.conditionAttribute = conditionAttributeRef;
98             this.options = optionSettings;
99 
100             lexer = new Scanner(expression, options);
101             if (!lexer.Advance())
102             {
103                 errorPosition = lexer.GetErrorPosition();
104                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, lexer.GetErrorResource(), expression, errorPosition, lexer.UnexpectedlyFound);
105             }
106             GenericExpressionNode node = Expr(expression);
107             if (!lexer.IsNext(Token.TokenType.EndOfInput))
108             {
109                 errorPosition = lexer.GetErrorPosition();
110                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
111             }
112             return node;
113         }
114 
115         //
116         // Top node of grammar
117         //    See grammar for how the following methods relate to each
118         //    other.
119         //
Expr(string expression)120         private GenericExpressionNode Expr(string expression)
121         {
122             GenericExpressionNode node = BooleanTerm(expression);
123             if (!lexer.IsNext(Token.TokenType.EndOfInput))
124             {
125                 node = ExprPrime(expression, node);
126             }
127 
128 
129             #region REMOVE_COMPAT_WARNING
130             // Check for potential change in behavior
131             if (LoggingServices != null && !warnedForExpression &&
132                 node.PotentialAndOrConflict())
133             {
134                 // We only want to warn once even if there multiple () sub expressions
135                 warnedForExpression = true;
136                 // Try to figure out where this expression was located
137                 string projectFile = String.Empty;
138                 int lineNumber   = 0;
139                 int columnNumber = 0;
140                 if (this.conditionAttribute != null)
141                 {
142                     projectFile = XmlUtilities.GetXmlNodeFile(this.conditionAttribute, String.Empty /* no project file if XML is purely in-memory */);
143                     XmlSearcher.GetLineColumnByNode(this.conditionAttribute, out lineNumber, out columnNumber);
144                 }
145                 // Log a warning regarding the fact the expression may have been evaluated
146                 // incorrectly in earlier version of MSBuild
147                 LoggingServices.LogWarning(logBuildEventContext,new BuildEventFileInfo(projectFile, lineNumber, columnNumber), "ConditionMaybeEvaluatedIncorrectly", expression);
148             }
149             #endregion
150 
151             return node;
152         }
153 
ExprPrime(string expression, GenericExpressionNode lhs)154         private GenericExpressionNode ExprPrime(string expression, GenericExpressionNode lhs)
155         {
156             if (Same(expression, Token.TokenType.EndOfInput))
157             {
158                 return lhs;
159             }
160             else if (Same(expression, Token.TokenType.Or))
161             {
162                 OperatorExpressionNode orNode = new OrExpressionNode();
163                 GenericExpressionNode rhs = BooleanTerm(expression);
164                 orNode.LeftChild = lhs;
165                 orNode.RightChild = rhs;
166                 return ExprPrime( expression, orNode );
167             }
168             else
169             {
170                 // I think this is ok.  ExprPrime always shows up at
171                 // the rightmost side of the grammar rhs, the EndOfInput case
172                 // takes care of things
173                 return lhs;
174             }
175         }
176 
BooleanTerm(string expression)177         private GenericExpressionNode BooleanTerm(string expression)
178         {
179             GenericExpressionNode node = RelationalExpr(expression);
180             if (null == node)
181             {
182                 errorPosition = lexer.GetErrorPosition();
183                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
184             }
185 
186             if (!lexer.IsNext(Token.TokenType.EndOfInput))
187             {
188                 node = BooleanTermPrime(expression, node);
189             }
190             return node;
191         }
192 
BooleanTermPrime(string expression, GenericExpressionNode lhs)193         private GenericExpressionNode BooleanTermPrime(string expression, GenericExpressionNode lhs)
194         {
195             if (lexer.IsNext(Token.TokenType.EndOfInput))
196             {
197                 return lhs;
198             }
199             else if (Same(expression, Token.TokenType.And))
200             {
201                 GenericExpressionNode rhs = RelationalExpr(expression);
202                 if (null == rhs)
203                 {
204                     errorPosition = lexer.GetErrorPosition();
205                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
206                 }
207 
208                 OperatorExpressionNode andNode = new AndExpressionNode();
209                 andNode.LeftChild = lhs;
210                 andNode.RightChild = rhs;
211                 return BooleanTermPrime(expression, andNode);
212             }
213             else
214             {
215                 // Should this be error case?
216                 return lhs;
217             }
218         }
219 
RelationalExpr(string expression)220         private GenericExpressionNode RelationalExpr(string expression)
221         {
222          {
223                 GenericExpressionNode lhs = Factor(expression);
224                 if (null == lhs)
225                 {
226                     errorPosition = lexer.GetErrorPosition();
227                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
228                 }
229 
230                 OperatorExpressionNode node = RelationalOperation(expression);
231                 if (node == null)
232                 {
233                     return lhs;
234                 }
235                 GenericExpressionNode rhs = Factor(expression);
236                 node.LeftChild = lhs;
237                 node.RightChild = rhs;
238                 return node;
239             }
240         }
241 
242 
RelationalOperation(string expression)243         private OperatorExpressionNode RelationalOperation(string expression)
244         {
245             OperatorExpressionNode node = null;
246             if (Same(expression, Token.TokenType.LessThan))
247             {
248                 node = new LessThanExpressionNode();
249             }
250             else if (Same(expression, Token.TokenType.GreaterThan))
251             {
252                 node = new GreaterThanExpressionNode();
253             }
254             else if (Same(expression, Token.TokenType.LessThanOrEqualTo))
255             {
256                 node = new LessThanOrEqualExpressionNode();
257             }
258             else if (Same(expression, Token.TokenType.GreaterThanOrEqualTo))
259             {
260                 node = new GreaterThanOrEqualExpressionNode();
261             }
262             else if (Same(expression, Token.TokenType.EqualTo))
263             {
264                 node = new EqualExpressionNode();
265             }
266             else if (Same(expression, Token.TokenType.NotEqualTo))
267             {
268                 node = new NotEqualExpressionNode();
269             }
270             return node;
271         }
272 
Factor(string expression)273         private GenericExpressionNode Factor(string expression)
274         {
275             // Checks for TokenTypes String, Numeric, Property, ItemMetadata, and ItemList.
276             GenericExpressionNode arg = this.Arg(expression);
277 
278             // If it's one of those, return it.
279             if (arg != null)
280             {
281                 return arg;
282             }
283 
284             // If it's not one of those, check for other TokenTypes.
285             Token current = lexer.CurrentToken;
286             if (Same(expression, Token.TokenType.Function))
287             {
288                 if (!Same(expression, Token.TokenType.LeftParenthesis))
289                 {
290                     errorPosition = lexer.GetErrorPosition();
291                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", lexer.IsNextString(), errorPosition);
292                     return null;
293                 }
294                 ArrayList arglist = new ArrayList();
295                 Arglist(expression, arglist);
296                 if (!Same(expression, Token.TokenType.RightParenthesis))
297                 {
298                     errorPosition = lexer.GetErrorPosition();
299                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
300                     return null;
301                 }
302                 return new FunctionCallExpressionNode( current.String, arglist);
303             }
304             else if (Same(expression, Token.TokenType.LeftParenthesis))
305             {
306                 GenericExpressionNode child = Expr(expression);
307                 if (Same(expression, Token.TokenType.RightParenthesis))
308                     return child;
309                 else
310                 {
311                     errorPosition = lexer.GetErrorPosition();
312                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
313                 }
314             }
315             else if (Same(expression, Token.TokenType.Not))
316             {
317                 OperatorExpressionNode notNode = new NotExpressionNode();
318                 GenericExpressionNode expr = Factor(expression);
319                 if (expr == null)
320                 {
321                     errorPosition = lexer.GetErrorPosition();
322                     ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
323                 }
324                 notNode.LeftChild = expr;
325                 return notNode;
326             }
327             else
328             {
329                 errorPosition = lexer.GetErrorPosition();
330                 ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, "UnexpectedTokenInCondition", expression, lexer.IsNextString(), errorPosition);
331             }
332             return null;
333         }
334 
Arglist(string expression, ArrayList arglist)335         private void Arglist(string expression, ArrayList arglist)
336         {
337             if (!lexer.IsNext(Token.TokenType.RightParenthesis))
338                 Args(expression, arglist);
339         }
340 
Args(string expression, ArrayList arglist)341         private void Args(string expression, ArrayList arglist)
342         {
343             GenericExpressionNode arg = Arg(expression);
344             arglist.Add(arg);
345             if (Same(expression, Token.TokenType.Comma))
346             {
347                 Args(expression, arglist);
348             }
349         }
350 
Arg(string expression)351         private GenericExpressionNode Arg(string expression)
352         {
353             Token current = lexer.CurrentToken;
354             if (Same(expression, Token.TokenType.String))
355             {
356                 return new StringExpressionNode(current.String);
357             }
358             else if (Same(expression, Token.TokenType.Numeric))
359             {
360                 return new NumericExpressionNode(current.String);
361             }
362             else if (Same(expression, Token.TokenType.Property))
363             {
364                 return new StringExpressionNode(current.String);
365             }
366             else if (Same(expression, Token.TokenType.ItemMetadata))
367             {
368                 return new StringExpressionNode(current.String);
369             }
370             else if (Same(expression, Token.TokenType.ItemList))
371             {
372                 return new StringExpressionNode(current.String);
373             }
374             else
375             {
376                 return null;
377             }
378         }
379 
Same(string expression, Token.TokenType token)380         private bool Same(string expression, Token.TokenType token)
381         {
382             if (lexer.IsNext(token))
383             {
384                 if (!lexer.Advance())
385                 {
386                     errorPosition = lexer.GetErrorPosition();
387                     if (null != lexer.UnexpectedlyFound)
388                     {
389                         ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, lexer.GetErrorResource(), expression, errorPosition, lexer.UnexpectedlyFound);
390                     }
391                     else
392                     {
393                         ProjectErrorUtilities.VerifyThrowInvalidProject(false, this.conditionAttribute, lexer.GetErrorResource(), expression, errorPosition);
394                     }
395                 }
396                 return true;
397             }
398             else
399                 return false;
400         }
401 
402     }
403 }
404