2 // ExpressionParserManual.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;
31 using Microsoft.Build.Exceptions;
33 namespace Microsoft.Build.Internal.Expressions
35 class ExpressionParserManual
37 // FIXME: we are going to not need ExpressionValidationType for this; always LaxString.
38 public ExpressionParserManual (string source, ExpressionValidationType validationType)
41 throw new ArgumentNullException ("source");
43 validation_type = validationType;
47 ExpressionValidationType validation_type;
49 public ExpressionList Parse ()
51 return Parse (0, source.Length);
54 ExpressionList Parse (int start, int end)
56 if (string.IsNullOrWhiteSpace (source))
57 return new ExpressionList ();
59 var ret = new ExpressionList ();
62 ret.Add (ParseSingle (ref start, end));
63 SkipSpaces (ref start);
65 throw new Exception ("Parser failed to progress token position: " + source);
70 static readonly char [] token_starters = "$@%(),".ToCharArray ();
72 Expression ParseSingle (ref int start, int end)
74 char token = source [start];
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));
83 goto default; // treat as raw literal to the section end
86 int last = FindMatchingCloseParen (start, end);
88 if (validation_type == ExpressionValidationType.StrictBoolean)
89 throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
92 goto default; // treat as raw literal to the section end
97 ret = EvaluatePropertyExpression (start, last);
98 else if (token == '%')
99 ret = EvaluateMetadataExpression (start, last);
101 ret = EvaluateItemExpression (start, last);
105 // Below (until default) are important only for Condition evaluation
107 if (validation_type == ExpressionValidationType.LaxString)
110 last = FindMatchingCloseParen (start, end);
112 if (validation_type == ExpressionValidationType.StrictBoolean)
113 throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
116 goto default; // treat as raw literal to the section end
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 ();
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 };
138 int FindMatchingCloseParen (int start, int end)
141 for (int i = start; i < end; i++) {
142 if (source [i] == '(')
144 else if (source [i] == ')') {
149 return -1; // invalid
152 static readonly string spaces = " \t\r\n";
154 void SkipSpaces (ref int start)
156 while (start < source.Length && spaces.Contains (source [start]))
160 PropertyAccessExpression EvaluatePropertyExpression (int start, int end)
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
173 if (parenAt > 0) { // method arguments
175 access.Arguments = ParseFunctionArguments (ref start, end);
177 return new PropertyAccessExpression () { Access = access };
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 ()
189 if (parenAt > 0) { // method arguments
191 access.Arguments = ParseFunctionArguments (ref start, end);
193 return new PropertyAccessExpression () { Access = access };
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 } }
210 if (parenAt > 0) { // method arguments
212 access.Arguments = ParseFunctionArguments (ref start, end);
214 return new PropertyAccessExpression () { Access = access };
218 ExpressionList ParseFunctionArguments (ref int start, int end)
220 var args = new ExpressionList ();
222 SkipSpaces (ref start);
223 if (start == source.Length)
224 throw new InvalidProjectFileException ("unterminated function call arguments.");
225 if (source [start] == ')')
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]));
232 args.Add (ParseSingle (ref start, end));
238 ItemAccessExpression EvaluateItemExpression (int start, int end)
240 // using property as context and evaluate
241 int idx = source.IndexOf ("->", start, StringComparison.Ordinal);
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)
251 string name = source.Substring (start, end - start);
252 return new ItemAccessExpression () {
253 Application = new ItemApplication () { Name = new NameToken () { Name = name } }
258 MetadataAccessExpression EvaluateMetadataExpression (int start, int end)
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 }
267 return new MetadataAccessExpression () { Access = access };