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