Merge pull request #778 from cmorris98/master
[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.Expressions
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 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;
211                                 else {
212                                         current_token = Token.NAME;
213                                         token_value = ProjectCollection.Unescape (val);
214                                         break;
215                                 }
216                                 break;
217                         }
218                         return true;
219                 }
220                 string spaces = " \t\r\n";
221
222                 static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray ();
223                 
224                 void ReadStringLiteral (string source, char c)
225                 {
226                         while (pos < source.Length && source [pos] != c)
227                                 pos++;
228                         if (source [pos - 1] != c)
229                                 ErrorOnStrictBoolean (c.ToString (), string.Format ("missing string literal terminator [{0}]", c));
230                         else {
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);
234                         }
235                 }
236                 
237                 void TokenForItemPropertyValue (string value, int token)
238                 {
239                         if (true)//CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty)
240                                 current_token = token;
241                         else {
242                                 current_token = Token.NAME;
243                                 token_value = value;
244                         }
245                 }
246                 
247                 void ErrorOnStrictBoolean (string value, string message)
248                 {
249                         if (validation_type == ExpressionValidationType.StrictBoolean) {
250                                 current_token = Token.ERROR;
251                                 error = message;
252                         } else {
253                                 current_token = Token.NAME;
254                                 token_value = value;
255                         }
256                 }
257                 
258                 public int token ()
259                 {
260                         return current_token;
261                 }
262                 
263                 public object value ()
264                 {
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 };
269                         else
270                                 return new Location () { Column = current_token_position };
271                 }
272         }       
273
274         class NameToken : Location
275         {
276                 public string Name { get; set; }
277                 
278                 public override string ToString ()
279                 {
280                         return string.Format ("[NameToken: Value={0}]", Name);
281                 }
282         }
283
284         class ErrorToken : Location
285         {
286                 public string Message { get; set; }
287         }
288
289         interface ILocation
290         {
291                 //int Line { get; }
292                 int Column { get; }
293                 string File { get; }
294                 
295                 string ToLocationString ();
296         }
297
298         class Location : ILocation
299         {
300                 //public int Line { get; set; }
301                 public int Column { get; set; }
302                 public string File { get; set; }
303                 
304                 public string ToLocationString ()
305                 {
306                         return "at " + Column;
307                 }
308         }
309 }