diff options
Diffstat (limited to 'mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs')
-rw-r--r-- | mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs new file mode 100644 index 0000000000..9760ad2acb --- /dev/null +++ b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs @@ -0,0 +1,309 @@ +// +// ExpressionTokenizer.cs +// +// Author: +// Atsushi Enomoto (atsushi@xamarin.com) +// +// Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Collections.Generic; +using Microsoft.Build.Evaluation; + +namespace Microsoft.Build.Internal.Expressions +{ + enum ExpressionValidationType + { + LaxString, + StrictBoolean, + } + + enum TokenizerMode + { + Default, + InsideItemOrProperty, + } + + class ExpressionTokenizer : yyParser.yyInput + { + public ExpressionTokenizer (string source, ExpressionValidationType validationType) + { + this.source = source; + current_token_position = -1; + validation_type = validationType; + modes.Push (TokenizerMode.Default); + } + + string source; + ExpressionValidationType validation_type; + + int current_token; + string error; + int pos, current_token_position; + object token_value; + Stack<TokenizerMode> modes = new Stack<TokenizerMode> (); + + TokenizerMode CurrentTokenizerMode { + get { return modes.Peek (); } + } + + public bool advance () + { + if (pos == source.Length) + return false; + + error = null; + token_value = null; + + while (pos < source.Length) { + if (spaces.IndexOf (source [pos]) >= 0) + pos++; + else + break; + } + if (pos == source.Length) + return false; + current_token_position = pos; + + switch (source [pos++]) { + case '.': + TokenForItemPropertyValue (".", Token.DOT); + break; + case ',': + TokenForItemPropertyValue (",", Token.COMMA); + break; + case '[': + TokenForItemPropertyValue ("[", Token.BRACE_OPEN); + break; + case ']': + TokenForItemPropertyValue ("]", Token.BRACE_CLOSE); + break; + case '(': + modes.Push (TokenizerMode.Default); + TokenForItemPropertyValue ("(", Token.PAREN_OPEN); + break; + case ')': + if (modes.Count > 0) { + modes.Pop (); + current_token = Token.PAREN_CLOSE; + } else { + token_value = ")"; + current_token = Token.NAME; + } + break; + case '-': + if (pos < source.Length && source [pos] == '>') { + current_token = Token.ARROW; + pos++; + } else + ErrorOnStrictBoolean ("-", "'-' is not followed by '>'."); + break; + case '=': + if (pos < source.Length && source [pos] == '=') { + current_token = Token.EQ; + pos++; + } else + ErrorOnStrictBoolean ("=", "'=' is not followed by '='."); + break; + case ':': + if (pos < source.Length && source [pos] == ':') { + current_token = Token.COLON2; + } else + ErrorOnStrictBoolean (":", "':' is not followed by ':'."); + pos++; + break; + case '!': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.NE; + } else + TokenForItemPropertyValue ("!", Token.NOT); + break; + case '>': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.GE; + } else + current_token = Token.GT; + break; + case '<': + if (pos < source.Length && source [pos] == '=') { + pos++; + current_token = Token.LE; + } else + current_token = Token.LT; + break; + case '$': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.PROP_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('."); + break; + case '@': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.ITEM_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('."); + break; + case '%': + if (pos < source.Length && source [pos] == '(') { + modes.Push (TokenizerMode.InsideItemOrProperty); + current_token = Token.METADATA_OPEN; + pos++; + } else + ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('."); + break; + case '"': + case '\'': + pos = source.IndexOf (source [pos - 1], pos); + if (pos < 0) { + ErrorOnStrictBoolean ("'", "unterminated string literal"); + pos = source.Length; + } + token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1); + current_token = Token.STRING_LITERAL; + pos++; + break; + default: + pos = source.IndexOfAny (token_starter_chars, pos); + if (pos < 0) + pos = source.Length; + var val = source.Substring (current_token_position, pos - current_token_position); + if (val.Equals ("AND", StringComparison.OrdinalIgnoreCase)) + current_token = Token.AND; + else if (val.Equals ("OR", StringComparison.OrdinalIgnoreCase)) + current_token = Token.OR; + else if (val.Equals ("TRUE", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("FALSE", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else if (val.Equals ("YES", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("NO", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else if (val.Equals ("ON", StringComparison.OrdinalIgnoreCase)) + current_token = Token.TRUE_LITERAL; + else if (val.Equals ("OFF", StringComparison.OrdinalIgnoreCase)) + current_token = Token.FALSE_LITERAL; + else { + current_token = Token.NAME; + token_value = ProjectCollection.Unescape (val); + break; + } + break; + } + return true; + } + string spaces = " \t\r\n"; + + static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray (); + + void ReadStringLiteral (string source, char c) + { + while (pos < source.Length && source [pos] != c) + pos++; + if (source [pos - 1] != c) + ErrorOnStrictBoolean (c.ToString (), string.Format ("missing string literal terminator [{0}]", c)); + else { + current_token = Token.NAME; + token_value = source.Substring (current_token_position + 1, pos - current_token_position - 2); + token_value = ProjectCollection.Unescape ((string) token_value); + } + } + + void TokenForItemPropertyValue (string value, int token) + { + if (true)//CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) + current_token = token; + else { + current_token = Token.NAME; + token_value = value; + } + } + + void ErrorOnStrictBoolean (string value, string message) + { + if (validation_type == ExpressionValidationType.StrictBoolean) { + current_token = Token.ERROR; + error = message; + } else { + current_token = Token.NAME; + token_value = value; + } + } + + public int token () + { + return current_token; + } + + public object value () + { + if (current_token == Token.NAME || current_token == Token.STRING_LITERAL) + return new NameToken () { Name = (string) token_value, Column = current_token_position }; + else if (error != null) + return new ErrorToken () { Message = error, Column = current_token_position }; + else + return new Location () { Column = current_token_position }; + } + } + + class NameToken : Location + { + public string Name { get; set; } + + public override string ToString () + { + return string.Format ("[NameToken: Value={0}]", Name); + } + } + + class ErrorToken : Location + { + public string Message { get; set; } + } + + interface ILocation + { + //int Line { get; } + int Column { get; } + string File { get; } + + string ToLocationString (); + } + + class Location : ILocation + { + //public int Line { get; set; } + public int Column { get; set; } + public string File { get; set; } + + public string ToLocationString () + { + return "at " + Column; + } + } +} |