2 // ExpressionTokenizer.cs
5 // Atsushi Enomoto (atsushi@xamarin.com)
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections.Generic;
30 using Microsoft.Build.Evaluation;
32 namespace Microsoft.Build.Internal.Expressions
34 enum ExpressionValidationType
46 class ExpressionTokenizer : yyParser.yyInput
48 public ExpressionTokenizer (string source, ExpressionValidationType validationType)
51 current_token_position = -1;
52 validation_type = validationType;
53 modes.Push (TokenizerMode.Default);
57 ExpressionValidationType validation_type;
61 int pos, current_token_position;
63 Stack<TokenizerMode> modes = new Stack<TokenizerMode> ();
65 TokenizerMode CurrentTokenizerMode {
66 get { return modes.Peek (); }
69 public bool advance ()
71 if (pos == source.Length)
77 while (pos < source.Length) {
78 if (spaces.IndexOf (source [pos]) >= 0)
83 if (pos == source.Length)
85 current_token_position = pos;
87 switch (source [pos++]) {
89 TokenForItemPropertyValue (".", Token.DOT);
92 TokenForItemPropertyValue (",", Token.COMMA);
95 TokenForItemPropertyValue ("[", Token.BRACE_OPEN);
98 TokenForItemPropertyValue ("]", Token.BRACE_CLOSE);
101 modes.Push (TokenizerMode.Default);
102 TokenForItemPropertyValue ("(", Token.PAREN_OPEN);
105 if (modes.Count > 0) {
107 current_token = Token.PAREN_CLOSE;
110 current_token = Token.NAME;
114 if (pos < source.Length && source [pos] == '>') {
115 current_token = Token.ARROW;
118 ErrorOnStrictBoolean ("-", "'-' is not followed by '>'.");
121 if (pos < source.Length && source [pos] == '=') {
122 current_token = Token.EQ;
125 ErrorOnStrictBoolean ("=", "'=' is not followed by '='.");
128 if (pos < source.Length && source [pos] == ':') {
129 current_token = Token.COLON2;
131 ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
135 if (pos < source.Length && source [pos] == '=') {
137 current_token = Token.NE;
139 TokenForItemPropertyValue ("!", Token.NOT);
142 if (pos < source.Length && source [pos] == '=') {
144 current_token = Token.GE;
146 current_token = Token.GT;
149 if (pos < source.Length && source [pos] == '=') {
151 current_token = Token.LE;
153 current_token = Token.LT;
156 if (pos < source.Length && source [pos] == '(') {
157 modes.Push (TokenizerMode.InsideItemOrProperty);
158 current_token = Token.PROP_OPEN;
161 ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('.");
164 if (pos < source.Length && source [pos] == '(') {
165 modes.Push (TokenizerMode.InsideItemOrProperty);
166 current_token = Token.ITEM_OPEN;
169 ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('.");
172 if (pos < source.Length && source [pos] == '(') {
173 modes.Push (TokenizerMode.InsideItemOrProperty);
174 current_token = Token.METADATA_OPEN;
177 ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
181 pos = source.IndexOf (source [pos - 1], pos);
183 ErrorOnStrictBoolean ("'", "unterminated string literal");
186 token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1);
187 current_token = Token.STRING_LITERAL;
191 pos = source.IndexOfAny (token_starter_chars, pos);
194 var val = source.Substring (current_token_position, pos - current_token_position);
195 if (val.Equals ("AND", StringComparison.OrdinalIgnoreCase))
196 current_token = Token.AND;
197 else if (val.Equals ("OR", StringComparison.OrdinalIgnoreCase))
198 current_token = Token.OR;
199 else if (val.Equals ("TRUE", StringComparison.OrdinalIgnoreCase))
200 current_token = Token.TRUE_LITERAL;
201 else if (val.Equals ("FALSE", StringComparison.OrdinalIgnoreCase))
202 current_token = Token.FALSE_LITERAL;
203 else if (val.Equals ("YES", StringComparison.OrdinalIgnoreCase))
204 current_token = Token.TRUE_LITERAL;
205 else if (val.Equals ("NO", StringComparison.OrdinalIgnoreCase))
206 current_token = Token.FALSE_LITERAL;
207 else if (val.Equals ("ON", StringComparison.OrdinalIgnoreCase))
208 current_token = Token.TRUE_LITERAL;
209 else if (val.Equals ("OFF", StringComparison.OrdinalIgnoreCase))
210 current_token = Token.FALSE_LITERAL;
212 current_token = Token.NAME;
213 token_value = ProjectCollection.Unescape (val);
220 string spaces = " \t\r\n";
222 static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray ();
224 void ReadStringLiteral (string source, char c)
226 while (pos < source.Length && source [pos] != c)
228 if (source [pos - 1] != c)
229 ErrorOnStrictBoolean (c.ToString (), string.Format ("missing string literal terminator [{0}]", c));
231 current_token = Token.NAME;
232 token_value = source.Substring (current_token_position + 1, pos - current_token_position - 2);
233 token_value = ProjectCollection.Unescape ((string) token_value);
237 void TokenForItemPropertyValue (string value, int token)
239 if (true)//CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty)
240 current_token = token;
242 current_token = Token.NAME;
247 void ErrorOnStrictBoolean (string value, string message)
249 if (validation_type == ExpressionValidationType.StrictBoolean) {
250 current_token = Token.ERROR;
253 current_token = Token.NAME;
260 return current_token;
263 public object value ()
265 if (current_token == Token.NAME || current_token == Token.STRING_LITERAL)
266 return new NameToken () { Name = (string) token_value, Column = current_token_position };
267 else if (error != null)
268 return new ErrorToken () { Message = error, Column = current_token_position };
270 return new Location () { Column = current_token_position };
274 class NameToken : Location
276 public string Name { get; set; }
278 public override string ToString ()
280 return string.Format ("[NameToken: Value={0}]", Name);
284 class ErrorToken : Location
286 public string Message { get; set; }
295 string ToLocationString ();
298 class Location : ILocation
300 //public int Line { get; set; }
301 public int Column { get; set; }
302 public string File { get; set; }
304 public string ToLocationString ()
306 return "at " + Column;