Changed TryPeek to handle race condition with Dequeue.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / ExpressionParserManual.cs
1 //
2 // ExpressionParserManual.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 System.Linq;
31 using Microsoft.Build.Exceptions;
32
33 namespace Microsoft.Build.Internal.Expressions
34 {
35         class ExpressionParserManual
36         {
37                 // FIXME: we are going to not need ExpressionValidationType for this; always LaxString.
38                 public ExpressionParserManual (string source, ExpressionValidationType validationType)
39                 {
40                         if (source == null)
41                                 throw new ArgumentNullException ("source");
42                         this.source = source;
43                         validation_type = validationType;
44                 }
45                 
46                 string source;
47                 ExpressionValidationType validation_type;
48                 
49                 public ExpressionList Parse ()
50                 {
51                         return Parse (0, source.Length);
52                 }
53                 
54                 ExpressionList Parse (int start, int end)
55                 {
56                         if (string.IsNullOrWhiteSpace (source))
57                                 return new ExpressionList ();
58
59                         var ret = new ExpressionList ();
60                         while (start < end) {
61                                 int bak = start;
62                                 ret.Add (ParseSingle (ref start, end));
63                                 SkipSpaces (ref start);
64                                 if (bak == start)
65                                         throw new Exception ("Parser failed to progress token position: " + source);
66                         }
67                         return ret;
68                 }
69                 
70                 static readonly char [] token_starters = "$@%(),".ToCharArray ();
71                 
72                 Expression ParseSingle (ref int start, int end)
73                 {
74                         char token = source [start];
75                         switch (token) {
76                         case '$':
77                         case '@':
78                         case '%':
79                                 if (start == end || start + 1 == source.Length || source [start + 1] != '(') {
80                                         if (validation_type == ExpressionValidationType.StrictBoolean)
81                                                 throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
82                                         else
83                                                 goto default; // treat as raw literal to the section end
84                                 }
85                                 start += 2;
86                                 int last = FindMatchingCloseParen (start, end);
87                                 if (last < 0) {
88                                         if (validation_type == ExpressionValidationType.StrictBoolean)
89                                                 throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
90                                         else {
91                                                 start -= 2;
92                                                 goto default; // treat as raw literal to the section end
93                                         }
94                                 }
95                                 Expression ret;
96                                 if (token == '$')
97                                         ret = EvaluatePropertyExpression (start, last);
98                                 else if (token == '%')
99                                         ret = EvaluateMetadataExpression (start, last);
100                                 else
101                                         ret = EvaluateItemExpression (start, last);
102                                 start = last + 1;
103                                 return ret;
104                                 
105                         // Below (until default) are important only for Condition evaluation
106                         case '(':
107                                 if (validation_type == ExpressionValidationType.LaxString)
108                                         goto default;
109                                 start++;
110                                 last = FindMatchingCloseParen (start, end);
111                                 if (last < 0) {
112                                         if (validation_type == ExpressionValidationType.StrictBoolean)
113                                                 throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
114                                         else {
115                                                 start--;
116                                                 goto default; // treat as raw literal to the section end
117                                         }
118                                 }
119                                 var contents = Parse (start, last).ToArray ();
120                                 if (contents.Length > 1)
121                                         throw new InvalidProjectFileException (string.Format ("unexpected continuous expression within (){0} in \"{1}\"", contents [1].Column > 0 ? " at " + contents [1].Column : null, source));
122                                 return contents.First ();
123
124                         default:
125                                 int idx = source.IndexOfAny (token_starters, start + 1);
126                                 string name = idx < 0 ? source.Substring (start, end - start) : source.Substring (start, idx - start);
127                                 var val = new NameToken () { Name = name };
128                                 ret = new RawStringLiteral () { Value = val };
129                                 if (idx >= 0)
130                                         start = idx;
131                                 else
132                                         start = end;
133
134                                 return ret;
135                         }
136                 }
137                 
138                 int FindMatchingCloseParen (int start, int end)
139                 {
140                         int n = 0;
141                         for (int i = start; i < end; i++) {
142                                 if (source [i] == '(')
143                                         n++;
144                                 else if (source [i] == ')') {
145                                         if (n-- == 0)
146                                                 return i;
147                                 }
148                         }
149                         return -1; // invalid
150                 }
151                 
152                 static readonly string spaces = " \t\r\n";
153                 
154                 void SkipSpaces (ref int start)
155                 {
156                         while (start < source.Length && spaces.Contains (source [start]))
157                                 start++;
158                 }
159                 
160                 PropertyAccessExpression EvaluatePropertyExpression (int start, int end)
161                 {
162                         // member access
163                         int dotAt = source.LastIndexOf ('.', end, end - start);
164                         int colonsAt = source.LastIndexOf ("::", end, end - start, StringComparison.Ordinal);
165                         if (dotAt < 0 && colonsAt < 0) {
166                                 // property access without member specification
167                                 int parenAt = source.IndexOf ('(', start, end - start);
168                                 string name = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
169                                 var access = new PropertyAccess () {
170                                         Name = new NameToken () { Name = name },
171                                         TargetType = PropertyTargetType.Object
172                                         };
173                                 if (parenAt > 0) { // method arguments
174                                         start = parenAt + 1;
175                                         access.Arguments = ParseFunctionArguments (ref start, end);
176                                 }
177                                 return new PropertyAccessExpression () { Access = access };
178                         }
179                         if (colonsAt < 0 || colonsAt < dotAt) {
180                                 // property access with member specification
181                                 int mstart = dotAt + 1;
182                                 int parenAt = source.IndexOf ('(', mstart, end - mstart);
183                                 string name = parenAt < 0 ? source.Substring (mstart, end - mstart) : source.Substring (mstart, parenAt - mstart);
184                                 var access = new PropertyAccess () {
185                                         Name = new NameToken () { Name = name },
186                                         TargetType = PropertyTargetType.Object,
187                                         Target = dotAt < 0 ? null : Parse (start, dotAt).FirstOrDefault () 
188                                 };
189                                 if (parenAt > 0) { // method arguments
190                                         start = parenAt + 1;
191                                         access.Arguments = ParseFunctionArguments (ref start, end);
192                                 }
193                                 return new PropertyAccessExpression () { Access = access };
194                         } else {
195                                 // static type access
196                                 string type = source.Substring (start, colonsAt - start);
197                                 if (type.Length < 2 || type [0] != '[' || type [type.Length - 1] != ']')
198                                         throw new InvalidProjectFileException (string.Format ("Static function call misses appropriate type name surrounded by '[' and ']' at {0} in \"{1}\"", start, source));
199                                 type = type.Substring (1, type.Length - 2);
200                                 start = colonsAt + 2;
201                                 int parenAt = source.IndexOf ('(', start, end - start);
202                                 string member = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
203                                 if (member.Length == 0)
204                                         throw new InvalidProjectFileException ("Static member name is missing");
205                                 var access = new PropertyAccess () {
206                                         Name = new NameToken () { Name = member },
207                                         TargetType = PropertyTargetType.Type,
208                                         Target = new StringLiteral () { Value = new NameToken () { Name = type } }
209                                 };
210                                 if (parenAt > 0) { // method arguments
211                                         start = parenAt + 1;
212                                         access.Arguments = ParseFunctionArguments (ref start, end);
213                                 }
214                                 return new PropertyAccessExpression () { Access = access };
215                         }
216                 }
217                 
218                 ExpressionList ParseFunctionArguments (ref int start, int end)
219                 {
220                         var args = new ExpressionList ();
221                         do {
222                                 SkipSpaces (ref start);
223                                 if (start == source.Length)
224                                         throw new InvalidProjectFileException ("unterminated function call arguments.");
225                                 if (source [start] == ')')
226                                         break;
227                                 else if (args.Any ()) {
228                                         if (source [start] != ',')
229                                                 throw new InvalidProjectFileException (string.Format ("invalid function call arguments specification. ',' is expected, got '{0}'", source [start]));
230                                         start++;
231                                 }
232                                 args.Add (ParseSingle (ref start, end));
233                         } while (true);
234                         start++;
235                         return args;
236                 }
237                 
238                 ItemAccessExpression EvaluateItemExpression (int start, int end)
239                 {
240                         // using property as context and evaluate
241                         int idx = source.IndexOf ("->", start, StringComparison.Ordinal);
242                         if (idx > 0) {
243                                 string name = source.Substring (start, idx - start);
244                                 return new ItemAccessExpression () {
245                                         Application = new ItemApplication () {
246                                                 Name = new NameToken () { Name = name },
247                                                 Expressions = Parse (idx + 2, end)
248                                                 }
249                                         };
250                         } else {
251                                 string name = source.Substring (start, end - start);
252                                 return new ItemAccessExpression () {
253                                         Application = new ItemApplication () { Name = new NameToken () { Name = name } }
254                                         };
255                         }
256                 }
257                 
258                 MetadataAccessExpression EvaluateMetadataExpression (int start, int end)
259                 {
260                         int idx = source.IndexOf ('.', start, end - start);
261                         string item = idx < 0 ? null : source.Substring (start, idx - start);
262                         string meta = idx < 0 ? source.Substring (start, end - start) : source.Substring (idx + 1, end - idx - 1);
263                         var access = new MetadataAccess () {
264                                         ItemType = item == null ? null : new NameToken () { Column = start, Name = item },
265                                         Metadata = new NameToken () { Column = idx < 0 ? start : idx + 1, Name = meta }
266                                         };
267                         return new MetadataAccessExpression () { Access = access };
268                 }
269         }
270 }
271