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