Merge pull request #1404 from woodsb02/mono-route
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / ExpressionParserManual.cs
index 4748b81eb03aa57ea51717a1e95f55923b3ac75b..73bac64445e11fa5b98bf225bc1d56867c3fb7c8 100644 (file)
@@ -1,15 +1,44 @@
+//
+// ExpressionParserManual.cs
+//
+// Author:
+//   Atsushi Enomoto (atsushi@xamarin.com)
+//
+// Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using Microsoft.Build.Exceptions;
 
-namespace Microsoft.Build.Internal
+namespace Microsoft.Build.Internal.Expressions
 {
        class ExpressionParserManual
        {
                // FIXME: we are going to not need ExpressionValidationType for this; always LaxString.
                public ExpressionParserManual (string source, ExpressionValidationType validationType)
                {
+                       if (source == null)
+                               throw new ArgumentNullException ("source");
                        this.source = source;
                        validation_type = validationType;
                }
@@ -22,85 +51,105 @@ namespace Microsoft.Build.Internal
                        return Parse (0, source.Length);
                }
                
-               static readonly char [] token_starters = "$@%('\"".ToCharArray ();
-               
                ExpressionList Parse (int start, int end)
                {
                        if (string.IsNullOrWhiteSpace (source))
                                return new ExpressionList ();
 
-                       var head = new List<Expression> ();
-                       var tail = new List<Expression> ();
+                       var ret = new ExpressionList ();
                        while (start < end) {
-                               char token = source [start];
-                               switch (token) {
-                               case '$':
-                               case '@':
-                               case '%':
-                                       if (start == end || start + 1 == source.Length || source [start + 1] != '(') {
-                                               if (validation_type == ExpressionValidationType.StrictBoolean)
-                                                       throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
-                                               else
-                                                       goto default; // treat as raw literal to the section end
+                               int bak = start;
+                               ret.Add (ParseSingle (ref start, end));
+                               if (bak == start)
+                                       throw new Exception ("Parser failed to progress token position: " + source);
+                       }
+                       return ret;
+               }
+               
+               static readonly char [] token_starters = "$@%(),'\"".ToCharArray ();
+               
+               Expression ParseSingle (ref int start, int end)
+               {
+                       char token = source [start];
+                       switch (token) {
+                       case '$':
+                       case '@':
+                       case '%':
+                               if (start == end || start + 1 == source.Length || source [start + 1] != '(') {
+                                       if (validation_type == ExpressionValidationType.StrictBoolean)
+                                               throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
+                                       else
+                                               goto default; // treat as raw literal to the section end
+                               }
+                               start += 2;
+                               int last = FindMatchingCloseParen (start, end);
+                               if (last < 0) {
+                                       if (validation_type == ExpressionValidationType.StrictBoolean)
+                                               throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                       else {
+                                               start -= 2;
+                                               goto default; // treat as raw literal to the section end
                                        }
-                                       start += 2;
-                                       int last = FindMatchingCloseParen (start, end);
-                                       if (last < 0) {
-                                               if (validation_type == ExpressionValidationType.StrictBoolean)
-                                                       throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
-                                               else {
-                                                       start -= 2;
-                                                       goto default; // treat as raw literal to the section end
-                                               }
+                               }
+                               Expression ret;
+                               if (token == '$')
+                                       ret = EvaluatePropertyExpression (start, last);
+                               else if (token == '%')
+                                       ret = EvaluateMetadataExpression (start, last);
+                               else
+                                       ret = EvaluateItemExpression (start, last);
+                               start = last + 1;
+                               return ret;
+                       
+                       case '\'':
+                       case '"':
+                               var quoteChar = source [start];
+                               start++;
+                               last = FindMatchingCloseQuote (quoteChar, start, end);
+                               if (last < 0) {
+                                       if (validation_type == ExpressionValidationType.StrictBoolean)
+                                               throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                       else {
+                                               start--;
+                                               goto default; // treat as raw literal to the section end
                                        }
-                                       if (token == '$')
-                                               head.Add (EvaluatePropertyExpression (start, last));
-                                       else if (token == '%')
-                                               head.Add (EvaluateMetadataExpression (start, last));
-                                       else
-                                               head.Add (EvaluateItemExpression (start, last));
-                                       start = last + 1;
-                                       break;
-                                       
-                               // Below (until default) are important only for Condition evaluation
-                               case '(':
-                                       if (validation_type == ExpressionValidationType.LaxString)
-                                               goto default;
-                                       start++;
-                                       last = FindMatchingCloseParen (start, end);
-                                       if (last < 0)
-                                               if (validation_type == ExpressionValidationType.StrictBoolean)
-                                                       throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
-                                               else {
-                                                       start--;
-                                                       goto default; // treat as raw literal to the section end
-                                               }
-                                       var contents = Parse (start, last).ToArray ();
-                                       if (contents.Length > 1)
-                                               throw new InvalidProjectFileException (string.Format ("unexpected continuous expression within (){0} in \"{1}\"", contents [1].Column > 0 ? " at " + contents [1].Column : null, source));
-                                       head.Add (contents.First ());
-                                       break;
+                               }
+                               ret = new QuotedExpression () { QuoteChar = quoteChar, Contents = Parse (start, last) };
+                               start = last + 1;
+                               return ret;
+                       // Below (until default) are important only for Condition evaluation
+                       case '(':
+                               if (validation_type == ExpressionValidationType.LaxString)
+                                       goto default;
+                               start++;
+                               last = FindMatchingCloseParen (start, end);
+                               if (last < 0) {
+                                       if (validation_type == ExpressionValidationType.StrictBoolean)
+                                               throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                       else {
+                                               start--;
+                                               goto default; // treat as raw literal to the section end
+                                       }
+                               }
+                               var contents = Parse (start, last).ToArray ();
+                               if (contents.Length > 1)
+                                       throw new InvalidProjectFileException (string.Format ("unexpected continuous expression within (){0} in \"{1}\"", contents [1].Column > 0 ? " at " + contents [1].Column : null, source));
+                               return contents.First ();
 
-                               default:
-                                       int idx = source.IndexOfAny (token_starters, start + 1);
-                                       string name = idx < 0 ? source.Substring (start, end - start) : source.Substring (start, idx - start);
-                                       var val = new NameToken () { Name = name };
-                                       head.Add (new StringLiteral () { Value = val });
-                                       if (idx >= 0)
-                                               start = idx;
-                                       else
-                                               start = end;
+                       default:
+                               int idx = source.IndexOfAny (token_starters, start + 1);
+                               string name = idx < 0 ? source.Substring (start, end - start) : source.Substring (start, idx - start);
+                               var val = new NameToken () { Name = name };
+                               ret = new RawStringLiteral () { Value = val };
+                               if (idx >= 0)
+                                       start = idx;
+                               else
+                                       start = end;
 
-                                       break;
-                               }
-                               SkipSpaces (ref start);
+                               return ret;
                        }
-                       var ret = new ExpressionList ();
-                       foreach (var e in head.Concat (((IEnumerable<Expression>) tail).Reverse ()))
-                               ret.Add (e);
-                       return ret;
                }
-               
+
                int FindMatchingCloseParen (int start, int end)
                {
                        int n = 0;
@@ -114,7 +163,21 @@ namespace Microsoft.Build.Internal
                        }
                        return -1; // invalid
                }
-               
+
+               int FindMatchingCloseQuote (char quote, int start, int end)
+               {
+                       int n = 0;
+                       for (int i = start; i < end; i++) {
+                               if (i < end + 1 && source [i] == '\\' && (source [i + 1] == quote || source [i + 1] == '\\'))
+                                       n += 2;
+                               else if (source [i] == quote) {
+                                       if (n-- == 0)
+                                               return i;
+                               }
+                       }
+                       return -1; // invalid
+               }
+
                static readonly string spaces = " \t\r\n";
                
                void SkipSpaces (ref int start)
@@ -126,46 +189,85 @@ namespace Microsoft.Build.Internal
                PropertyAccessExpression EvaluatePropertyExpression (int start, int end)
                {
                        // member access
-                       int idx = source.LastIndexOf ('.', start);
-                       if (idx >= 0) {
-                               string name = (idx > 0) ? source.Substring (idx, end - idx) : source.Substring (start, end);
-                               return new PropertyAccessExpression () {
-                                       Access = new PropertyAccess () {
-                                               Name = new NameToken () { Name = name },
-                                               TargetType = PropertyTargetType.Object,
-                                               Target = idx < 0 ? null : Parse (start, idx).FirstOrDefault () 
-                                               }
+                       int dotAt = source.LastIndexOf ('.', end, end - start);
+                       int colonsAt = source.LastIndexOf ("::", end, end - start, StringComparison.Ordinal);
+                       if (dotAt < 0 && colonsAt < 0) {
+                               // property access without member specification
+                               int parenAt = source.IndexOf ('(', start, end - start);
+                               string name = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
+                               name = name.Trim ();
+                               var access = new PropertyAccess () {
+                                       Name = new NameToken () { Name = name },
+                                       TargetType = PropertyTargetType.Object
                                        };
+                               if (parenAt > 0) { // method arguments
+                                       start = parenAt + 1;
+                                       access.Arguments = ParseFunctionArguments (ref start, end);
+                               }
+                               return new PropertyAccessExpression () { Access = access };
+                       }
+                       if (colonsAt < 0 || colonsAt < dotAt) {
+                               // property access with member specification
+                               int mstart = dotAt + 1;
+                               int parenAt = source.IndexOf ('(', mstart, end - mstart);
+                               string name = parenAt < 0 ? source.Substring (mstart, end - mstart) : source.Substring (mstart, parenAt - mstart);
+                               name = name.Trim ();
+                               var access = new PropertyAccess () {
+                                       Name = new NameToken () { Name = name },
+                                       TargetType = PropertyTargetType.Object,
+                                       Target = dotAt < 0 ? null : Parse (start, dotAt).FirstOrDefault () 
+                               };
+                               if (parenAt > 0) { // method arguments
+                                       start = parenAt + 1;
+                                       access.Arguments = ParseFunctionArguments (ref start, end);
+                               }
+                               return new PropertyAccessExpression () { Access = access };
                        } else {
                                // static type access
-                               idx = source.IndexOf ("::", start, StringComparison.Ordinal);
-                               if (idx >= 0) {
-                                       throw new NotImplementedException ();
-                               
-                                       string type = source.Substring (start, idx - start);
-                                       if (type.Length < 2 || type [0] != '[' || type [type.Length - 1] != ']')
-                                               throw new InvalidProjectFileException (string.Format ("Static function call misses appropriate type name surrounded by '[' and ']' at {0} in \"{1}\"", start, source));
-                                       int start2 = idx + 2;
-                                       int idx2 = source.IndexOf ('(', idx + 2, end - start2);
-                                       if (idx2 < 0) {
-                                               // access to static property
-                                               string member = source.Substring (start2, end - start2);
-                                       } else {
-                                               // access to static method
-                                               string member = source.Substring (start2, idx2 - start2);
-                                       }
-                               } else {
-                                       // property access without member specification
-                                       return new PropertyAccessExpression () {
-                                               Access = new PropertyAccess () {
-                                                       Name = new NameToken () { Name = source.Substring (start, end - start) },
-                                                       TargetType = PropertyTargetType.Object
-                                                       }
-                                               };
+                               string type = source.Substring (start, colonsAt - start);
+                               if (type.Length < 2 || type [0] != '[' || type [type.Length - 1] != ']')
+                                       throw new InvalidProjectFileException (string.Format ("Static function call misses appropriate type name surrounded by '[' and ']' at {0} in \"{1}\"", start, source));
+                               type = type.Substring (1, type.Length - 2).Trim ();
+                               start = colonsAt + 2;
+                               int parenAt = source.IndexOf ('(', start, end - start);
+                               string member = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
+                               member = member.Trim ();
+                               if (member.Length == 0)
+                                       throw new InvalidProjectFileException ("Static member name is missing");
+                               var access = new PropertyAccess () {
+                                       Name = new NameToken () { Name = member },
+                                       TargetType = PropertyTargetType.Type,
+                                       Target = new StringLiteral () { Value = new NameToken () { Name = type } }
+                               };
+                               if (parenAt > 0) { // method arguments
+                                       start = parenAt + 1;
+                                       access.Arguments = ParseFunctionArguments (ref start, end);
                                }
+                               return new PropertyAccessExpression () { Access = access };
                        }
                }
                
+               ExpressionList ParseFunctionArguments (ref int start, int end)
+               {
+                       var args = new ExpressionList ();
+                       do {
+                               SkipSpaces (ref start);
+                               if (start == source.Length)
+                                       throw new InvalidProjectFileException ("unterminated function call arguments.");
+                               if (source [start] == ')')
+                                       break;
+                               else if (args.Any ()) {
+                                       if (source [start] != ',')
+                                               throw new InvalidProjectFileException (string.Format ("invalid function call arguments specification. ',' is expected, got '{0}'", source [start]));
+                                       start++;
+                                       SkipSpaces (ref start);
+                               }
+                               args.Add (ParseSingle (ref start, end));
+                       } while (true);
+                       start++;
+                       return args;
+               }
+               
                ItemAccessExpression EvaluateItemExpression (int start, int end)
                {
                        // using property as context and evaluate
@@ -175,7 +277,7 @@ namespace Microsoft.Build.Internal
                                return new ItemAccessExpression () {
                                        Application = new ItemApplication () {
                                                Name = new NameToken () { Name = name },
-                                               Expressions = Parse (idx, end - idx)
+                                               Expressions = Parse (idx + 2, end)
                                                }
                                        };
                        } else {
@@ -184,13 +286,18 @@ namespace Microsoft.Build.Internal
                                        Application = new ItemApplication () { Name = new NameToken () { Name = name } }
                                        };
                        }
-                       
-                       throw new NotImplementedException ();
                }
                
                MetadataAccessExpression EvaluateMetadataExpression (int start, int end)
                {
-                       throw new NotImplementedException ();
+                       int idx = source.IndexOf ('.', start, end - start);
+                       string item = idx < 0 ? null : source.Substring (start, idx - start);
+                       string meta = idx < 0 ? source.Substring (start, end - start) : source.Substring (idx + 1, end - idx - 1);
+                       var access = new MetadataAccess () {
+                                       ItemType = item == null ? null : new NameToken () { Column = start, Name = item },
+                                       Metadata = new NameToken () { Column = idx < 0 ? start : idx + 1, Name = meta }
+                                       };
+                       return new MetadataAccessExpression () { Access = access };
                }
        }
 }