Fix bogus expression tokenizer that failed at parenthesized expressions with And/Or.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / ExpressionTokenizer.cs
1 //
2 // ExpressionTokenizer.cs
3 //
4 // Author:
5 //   Atsushi Enomoto (atsushi@xamarin.com)
6 //
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28 using System;
29 using System.Collections.Generic;
30 using Microsoft.Build.Evaluation;
31
32 namespace Microsoft.Build.Internal
33 {
34         enum ExpressionValidationType
35         {
36                 LaxString,
37                 StrictBoolean,
38         }
39         
40         enum TokenizerMode
41         {
42                 Default,
43                 InsideItemOrProperty,
44         }
45         
46         class ExpressionTokenizer : yyParser.yyInput
47         {
48                 public ExpressionTokenizer (string source, ExpressionValidationType validationType)
49                 {
50                         this.source = source;
51                         current_token_position = -1;
52                         validation_type = validationType;
53                         modes.Push (TokenizerMode.Default);
54                 }
55                 
56                 string source;
57                 ExpressionValidationType validation_type;
58                 
59                 int current_token;
60                 string error;
61                 int pos, current_token_position;
62                 object token_value;
63                 Stack<TokenizerMode> modes = new Stack<TokenizerMode> ();
64
65                 TokenizerMode CurrentTokenizerMode {
66                         get { return modes.Peek (); }
67                 }
68
69                 public bool advance ()
70                 {
71                         if (pos == source.Length)
72                                 return false;
73
74                         error = null;
75                         token_value = null;
76
77                         while (pos < source.Length) {
78                                 if (spaces.IndexOf (source [pos]) >= 0)
79                                         pos++;
80                                 else
81                                         break;
82                         }
83                         if (pos == source.Length)
84                                 return false;
85                         current_token_position = pos;
86
87                         switch (source [pos++]) {
88                         case '.':
89                                 TokenForItemPropertyValue (".", Token.DOT);
90                                 break;
91                         case ',':
92                                 TokenForItemPropertyValue (",", Token.COMMA);
93                                 break;
94                         case '[':
95                                 TokenForItemPropertyValue ("[", Token.BRACE_OPEN);
96                                 break;
97                         case ']':
98                                 TokenForItemPropertyValue ("]", Token.BRACE_CLOSE);
99                                 break;
100                         case '(':
101                                 modes.Push (TokenizerMode.Default);
102                                 TokenForItemPropertyValue ("(", Token.PAREN_OPEN);
103                                 break;
104                         case ')':
105                                 if (modes.Count > 0) {
106                                         modes.Pop ();
107                                         current_token = Token.PAREN_CLOSE;
108                                 } else {
109                                         token_value = ")";
110                                         current_token = Token.NAME;
111                                 }
112                                 break;
113                         case '-':
114                                 if (pos < source.Length && source [pos] == '>') {
115                                         current_token = Token.ARROW;
116                                         pos++;
117                                 } else
118                                         ErrorOnStrictBoolean ("-", "'-' is not followed by '>'.");
119                                 break;
120                         case '=':
121                                 if (pos < source.Length && source [pos] == '=') {
122                                         current_token = Token.EQ;
123                                         pos++;
124                                 } else
125                                         ErrorOnStrictBoolean ("=", "'=' is not followed by '='.");
126                                 break;
127                         case ':':
128                                 if (pos < source.Length && source [pos] == ':') {
129                                         current_token = Token.COLON2;
130                                 } else
131                                         ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
132                                 pos++;
133                                 break;
134                         case '!':
135                                 if (pos < source.Length && source [pos] == '=') {
136                                         pos++;
137                                         current_token = Token.NE;
138                                 } else
139                                         TokenForItemPropertyValue ("!", Token.NOT);
140                                 break;
141                         case '>':
142                                 if (pos < source.Length && source [pos] == '=') {
143                                         pos++;
144                                         current_token = Token.GE;
145                                 } else
146                                         current_token = Token.GT;
147                                 break;
148                         case '<':
149                                 if (pos < source.Length && source [pos] == '=') {
150                                         pos++;
151                                         current_token = Token.LE;
152                                 } else
153                                         current_token = Token.LT;
154                                 break;
155                         case '$':
156                                 if (pos < source.Length && source [pos] == '(') {
157                                         modes.Push (TokenizerMode.InsideItemOrProperty);
158                                         current_token = Token.PROP_OPEN;
159                                         pos++;
160                                 } else
161                                         ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('.");
162                                 break;
163                         case '@':
164                                 if (pos < source.Length && source [pos] == '(') {
165                                         modes.Push (TokenizerMode.InsideItemOrProperty);
166                                         current_token = Token.ITEM_OPEN;
167                                         pos++;
168                                 } else
169                                         ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('.");
170                                 break;
171                         case '%':
172                                 if (pos < source.Length && source [pos] == '(') {
173                                         modes.Push (TokenizerMode.InsideItemOrProperty);
174                                         current_token = Token.METADATA_OPEN;
175                                         pos++;
176                                 } else
177                                         ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
178                                 break;
179                         case '"':
180                         case '\'':
181                                 pos = source.IndexOf (source [pos - 1], pos);
182                                 if (pos < 0) {
183                                         ErrorOnStrictBoolean ("'", "unterminated string literal");
184                                         pos = source.Length;
185                                 }
186                                 token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1);
187                                 current_token = Token.STRING_LITERAL;
188                                 pos++;
189                                 break;
190                         default:
191                                 pos = source.IndexOfAny (token_starter_chars, pos);
192                                 if (pos < 0)
193                                         pos = source.Length;
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 {
204                                         current_token = Token.NAME;
205                                         token_value = ProjectCollection.Unescape (val);
206                                         break;
207                                 }
208                                 break;
209                         }
210                         return true;
211                 }
212                 string spaces = " \t\r\n";
213
214                 static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray ();
215                 
216                 void ReadStringLiteral (string source, char c)
217                 {
218                         while (pos < source.Length && source [pos] != c)
219                                 pos++;
220                         if (source [pos - 1] != c)
221                                 ErrorOnStrictBoolean (c.ToString (), string.Format ("missing string literal terminator [{0}]", c));
222                         else {
223                                 current_token = Token.NAME;
224                                 token_value = source.Substring (current_token_position + 1, pos - current_token_position - 2);
225                                 token_value = ProjectCollection.Unescape ((string) token_value);
226                         }
227                 }
228                 
229                 void TokenForItemPropertyValue (string value, int token)
230                 {
231                         if (true)//CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty)
232                                 current_token = token;
233                         else {
234                                 current_token = Token.NAME;
235                                 token_value = value;
236                         }
237                 }
238                 
239                 void ErrorOnStrictBoolean (string value, string message)
240                 {
241                         if (validation_type == ExpressionValidationType.StrictBoolean) {
242                                 current_token = Token.ERROR;
243                                 error = message;
244                         } else {
245                                 current_token = Token.NAME;
246                                 token_value = value;
247                         }
248                 }
249                 
250                 public int token ()
251                 {
252                         return current_token;
253                 }
254                 
255                 public object value ()
256                 {
257                         if (current_token == Token.NAME || current_token == Token.STRING_LITERAL)
258                                 return new NameToken () { Name = (string) token_value, Column = current_token_position };
259                         else if (error != null)
260                                 return new ErrorToken () { Message = error, Column = current_token_position };
261                         else
262                                 return new Location () { Column = current_token_position };
263                 }
264         }       
265
266         class NameToken : Location
267         {
268                 public string Name { get; set; }
269                 
270                 public override string ToString ()
271                 {
272                         return string.Format ("[NameToken: Value={0}]", Name);
273                 }
274         }
275
276         class ErrorToken : Location
277         {
278                 public string Message { get; set; }
279         }
280
281         interface ILocation
282         {
283                 //int Line { get; }
284                 int Column { get; }
285                 //string File { get; }
286                 
287                 string ToLocationString ();
288         }
289
290         class Location : ILocation
291         {
292                 //public int Line { get; set; }
293                 public int Column { get; set; }
294                 //public string File { get; set; }
295                 
296                 public string ToLocationString ()
297                 {
298                         return "at " + Column;
299                 }
300         }
301 }