Add full-scratch expression parser implementation.
authorAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Wed, 16 Oct 2013 07:16:10 +0000 (16:16 +0900)
committerAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Fri, 29 Nov 2013 09:20:36 +0000 (18:20 +0900)
mcs/class/Microsoft.Build/Makefile
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectCollection.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.cs [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.dll.sources
mcs/class/Microsoft.Build/Microsoft.Build_test.dll.sources
mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectTest.cs
mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs [new file with mode: 0644]

index fa3beb5963fa5e69d3cdc8880049eae9a8ec815e..5b7ca23a350c975b3cd28ccc8d809fcd604a5043 100644 (file)
@@ -23,11 +23,19 @@ LIB_MCS_FLAGS = \
 TEST_MCS_FLAGS = $(LIB_MCS_FLAGS)
 
 EXTRA_DISTFILES = \
+       Microsoft.Build.Internal/ExpressionParser.jay   \
        Test/FunctionalTestReferenceProject.csproj      \
        Test/FunctionalTestReferenceProject3.csproj     \
        Test/Microsoft.Build.Test.csproj        \
        Test/Microsoft.Build.csproj
 
+EXPR_PARSER = Microsoft.Build.Internal/ExpressionParser
+
+$(EXPR_PARSER).cs: $(EXPR_PARSER).jay $(topdir)/jay/skeleton.cs
+       (cd Microsoft.Build.Internal; $(topdir)/../jay/jay -ct < $(topdir)/../jay/skeleton.cs ExpressionParser.jay >> ExpressionParser.cs)
+
+BUILT_SOURCES = $(EXPR_PARSER).cs
+
 include ../../build/library.make
 
 export TESTING_MONO=a
index 580db30cd08ba6782cff0d02d54b286562f8aa54..99b3beed4bb314b12cea3f7361e124b2b64b31f8 100644 (file)
@@ -76,9 +76,9 @@ namespace Microsoft.Build.Evaluation
                        return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString);
                }
 
-               public static string Unescape (string scapedString)
+               public static string Unescape (string escapedString)
                {
-                       throw new NotImplementedException ();
+                       return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString);
                }
 
                public static ProjectCollection GlobalProjectCollection {
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs
new file mode 100644 (file)
index 0000000..4977121
--- /dev/null
@@ -0,0 +1,116 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Build.Internal
+{
+       
+       class Locatable
+       {
+               public ILocation Location { get; set; }         
+       }
+       
+       class ExpressionList : ILocation, IEnumerable<Expression>
+       {
+               public ExpressionList (Expression entry)
+               {
+                       Append (entry);
+               }
+               
+               //public int Line {
+               //      get { return list [0].Line; }
+               //}
+               public int Column {
+                       get { return list [0].Column; }
+               }
+               //public string File {
+               //      get { return list [0].File; }
+               //}
+                       
+               public IEnumerator<Expression> GetEnumerator ()
+               {
+                       return list.GetEnumerator ();
+               }
+               
+               IEnumerator IEnumerable.GetEnumerator ()
+               {
+                       return list.GetEnumerator ();
+               }
+               
+               List<Expression> list = new List<Expression> ();
+               
+               public ExpressionList Append (Expression expr)
+               {
+                       list.Add (expr);
+                       return this;
+               }
+       }
+
+       class Expression : Locatable, ILocation
+       {
+               //public int Line {
+               //      get { return Location.Line; }
+               //}
+               public int Column {
+                       get { return Location.Column; }
+               }
+               //public string File {
+               //      get { return Location.File; }
+               //}
+       }
+       
+       class BooleanLiteral : Expression
+       {
+               public bool Value { get; set; }
+       }
+
+       class NotExpression : Expression
+       {
+               public Expression Negated { get; set; }
+       }
+
+       class PropertyAccessExpression : Expression
+       {
+               public PropertyAccess Access { get; set; }
+       }
+       
+       class PropertyAccess : Locatable
+       {
+               public NameToken Name { get; set; }
+               public Expression Target { get; set; }
+       }
+
+       class ItemAccessExpression : Expression
+       {
+               public ItemApplication Application { get; set; }
+       }
+       
+       class ItemApplication : Locatable
+       {
+               public NameToken Name { get; set; }
+               public ExpressionList Expressions { get; set; }
+       }
+
+       class MetadataAccessExpression : Expression
+       {
+               public MetadataAccess Access { get; set; }
+       }
+       
+       class MetadataAccess : Locatable
+       {
+               public NameToken Metadata { get; set; }
+               public NameToken Item { get; set; }
+       }
+
+       class StringLiteral : Expression
+       {
+               public NameToken Value { get; set; }
+       }
+       
+       class FunctionCallExpression : Expression
+       {
+               public NameToken Name { get; set; }
+               public ExpressionList Arguments { get; set; }
+       }
+}
+
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.cs
new file mode 100644 (file)
index 0000000..ae267ea
--- /dev/null
@@ -0,0 +1,816 @@
+// created by jay 0.7 (c) 1998 Axel.Schreiner@informatik.uni-osnabrueck.de
+
+#line 2 "ExpressionParser.jay"
+
+using System;
+using System.Text;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Exceptions;
+using Microsoft.Build.Framework;
+
+/*
+
+Pseudo formal syntax for .NET 4.0 expression:
+
+Condition = Expression
+Include = Expression*
+
+ Expression
+       BooleanLiteral
+               TrueLiteral
+               FalseLiteral
+       BinaryExpression
+               Expression "==" Expression
+               Expression "!=" Expression
+               Expression ">" Expression
+               Expression ">=" Expression
+               Expression "<" Expression
+               Expression "<=" Expression
+               Expression "And" Expression
+               Expression "Or" Expression
+       UnaryExpression
+               "!" Expression
+       PropertyExpression
+               "$(" PropertyApplication ")"
+       ItemExpression
+               "@(" ItemApplication ")"
+       MetadataBatchingExpression
+               "%(" MetadataBatchingApplication ")"
+  StringLiteralOrFunction
+               StringLiteralOrFunctionName ( "(" FunctionArguments ")" )?
+
+.NET error messages are so detailed which is something like "you forgot '(' after '$' ?" - so
+it is likely that the MS tokenizer is hand-written.
+
+*/
+
+namespace Microsoft.Build.Internal
+{
+#if false
+       class ExpressionParser
+       {
+               public static string EvaluateExpression (string source, Project project, ITaskItem [] inputs)
+               {
+                       var head = new StringBuilder ();
+                       var tail = new StringBuilder ();
+                       int start = 0;
+                       int end = source.Length;
+                       while (start < end) {
+                               switch (source [start]) {
+                               case '$':
+                               case '@':
+                                       int last = source.LastIndexOf (')', end);
+                                       if (last < 0)
+                                               throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                       if (start + 1 == end || source [start] != '(')
+                                               throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
+                                       tail.Insert (0, source.Substring (last + 1, end - last));
+                                       start += 2;
+                                       end = last - 1;
+                                       if (source [start] == '$')
+                                               head.Append (EvaluatePropertyExpression (source, project, inputs, start, end));
+                                       else
+                                               head.Append (EvaluateItemExpression (source, project, inputs, start, end));
+                                       break;
+                               default:
+                                       head.Append (source.Substring (start, end - start));
+                                       start = end;
+                                       break;
+                               }
+                       }
+                       return head.ToString () + tail.ToString ();
+               }
+               
+               public static string EvaluatePropertyExpression (string source, Project project, ITaskItem [] inputs, int start, int end)
+               {
+                       int idx = source.IndexOf ("::", start, StringComparison.Ordinal);
+                       if (idx >= 0) {
+                               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);
+                               }
+                       } // the result could be context for further property access...
+                       
+                       idx = source.IndexOf ('.', start);
+                       if (idx > 0) {
+                               string name = source.Substring (start, idx - start);
+                               var prop = project.GetProperty (name);
+                               if (prop == null)
+                                       throw new InvalidProjectFileException (string.Format ("Property \"{0}\" was not found", name));
+                       }
+               }
+               
+               public static string EvaluateItemExpression (string source, Project project, ITaskItem [] inputs, int start, int end)
+               {
+                       // using property as context and evaluate
+                       int idx = source.IndexOf ("->", start, StringComparison.Ordinal);
+                       if (idx > 0) {
+                               string name = source.Substring (start, idx - start);
+                       }
+                       
+               }
+       }
+       
+       class ExpressionNode
+       {
+       }
+       
+       enum ExpressionNodeType
+       {
+               Item,
+               Property,
+               Transform,
+               Invocation
+       }
+
+#endif
+
+       class ExpressionParser
+       {
+               int yacc_verbose_flag = 1;
+#line default
+
+  /** error output stream.
+      It should be changeable.
+    */
+  public System.IO.TextWriter ErrorOutput = System.Console.Out;
+
+  /** simplified error message.
+      @see <a href="#yyerror(java.lang.String, java.lang.String[])">yyerror</a>
+    */
+  public void yyerror (string message) {
+    yyerror(message, null);
+  }
+#pragma warning disable 649
+  /* An EOF token */
+  public int eof_token;
+#pragma warning restore 649
+  /** (syntax) error message.
+      Can be overwritten to control message format.
+      @param message text to be displayed.
+      @param expected vector of acceptable tokens, if available.
+    */
+  public void yyerror (string message, string[] expected) {
+    if ((yacc_verbose_flag > 0) && (expected != null) && (expected.Length  > 0)) {
+      ErrorOutput.Write (message+", expecting");
+      for (int n = 0; n < expected.Length; ++ n)
+        ErrorOutput.Write (" "+expected[n]);
+        ErrorOutput.WriteLine ();
+    } else
+      ErrorOutput.WriteLine (message);
+  }
+
+  /** debugging support, requires the package jay.yydebug.
+      Set to null to suppress debugging messages.
+    */
+  internal yydebug.yyDebug debug;
+
+  protected const int yyFinal = 9;
+ // Put this array into a separate class so it is only initialized if debugging is actually used
+ // Use MarshalByRefObject to disable inlining
+ class YYRules : MarshalByRefObject {
+  public static readonly string [] yyRule = {
+    "$accept : Expression",
+    "ExpressionList : Expression",
+    "ExpressionList : ExpressionList Expression",
+    "Expression : BooleanLiteral",
+    "Expression : BinaryExpression",
+    "Expression : UnaryExpression",
+    "Expression : PropertyAccessExpression",
+    "Expression : ItemAccessExpression",
+    "Expression : MetadataAccessExpression",
+    "Expression : StringLiteralOrFunction",
+    "Expression : ParenthesizedExpression",
+    "BooleanLiteral : TRUE_LITERAL",
+    "BooleanLiteral : FALSE_LITERAL",
+    "BinaryExpression : Expression EQ Expression",
+    "BinaryExpression : Expression NE Expression",
+    "BinaryExpression : Expression GT Expression",
+    "BinaryExpression : Expression GE Expression",
+    "BinaryExpression : Expression LT Expression",
+    "BinaryExpression : Expression LE Expression",
+    "BinaryExpression : Expression AND Expression",
+    "BinaryExpression : Expression OR Expression",
+    "UnaryExpression : NOT Expression",
+    "PropertyAccessExpression : PROP_OPEN PropertyAccess PAREN_CLOSE",
+    "PropertyAccess : NAME",
+    "PropertyAccess : Expression DOT NAME",
+    "ItemAccessExpression : ITEM_OPEN ItemApplication PAREN_CLOSE",
+    "ItemApplication : NAME",
+    "ItemApplication : NAME ARROW ExpressionList",
+    "MetadataAccessExpression : METADATA_OPEN MetadataAccess PAREN_CLOSE",
+    "MetadataAccess : NAME",
+    "MetadataAccess : NAME DOT NAME",
+    "StringLiteralOrFunction : NAME",
+    "StringLiteralOrFunction : NAME PAREN_OPEN FunctionCallArguments PAREN_CLOSE",
+    "FunctionCallArguments : Expression",
+    "FunctionCallArguments : FunctionCallArguments COMMA Expression",
+    "ParenthesizedExpression : PAREN_OPEN Expression PAREN_CLOSE",
+  };
+ public static string getRule (int index) {
+    return yyRule [index];
+ }
+}
+  protected static readonly string [] yyNames = {    
+    "end-of-file",null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,"'!'",null,null,null,null,null,
+    null,"'('","')'",null,null,"','",null,"'.'",null,null,null,null,null,
+    null,null,null,null,null,null,null,null,"'<'",null,"'>'",null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,null,null,null,null,
+    null,null,null,null,null,null,null,null,null,null,"TRUE_LITERAL",
+    "FALSE_LITERAL","EQ","\"==\"","NE","\"!=\"","GT","GE","\">=\"","LT",
+    "LE","\"<=\"","AND","OR","NOT","DOT","COMMA","PROP_OPEN","\"$(\"",
+    "ITEM_OPEN","\"@(\"","METADATA_OPEN","\"%(\"","PAREN_OPEN",
+    "PAREN_CLOSE","COLON2","\"::\"","ARROW","\"->\"","NAME","ERROR",
+  };
+
+  /** index-checked interface to yyNames[].
+      @param token single character or %token value.
+      @return token name or [illegal] or [unknown].
+    */
+  public static string yyname (int token) {
+    if ((token < 0) || (token > yyNames.Length)) return "[illegal]";
+    string name;
+    if ((name = yyNames[token]) != null) return name;
+    return "[unknown]";
+  }
+
+#pragma warning disable 414
+  int yyExpectingState;
+#pragma warning restore 414
+  /** computes list of expected tokens on error by tracing the tables.
+      @param state for which to compute the list.
+      @return list of token names.
+    */
+  protected int [] yyExpectingTokens (int state){
+    int token, n, len = 0;
+    bool[] ok = new bool[yyNames.Length];
+    if ((n = yySindex[state]) != 0)
+      for (token = n < 0 ? -n : 0;
+           (token < yyNames.Length) && (n+token < yyTable.Length); ++ token)
+        if (yyCheck[n+token] == token && !ok[token] && yyNames[token] != null) {
+          ++ len;
+          ok[token] = true;
+        }
+    if ((n = yyRindex[state]) != 0)
+      for (token = n < 0 ? -n : 0;
+           (token < yyNames.Length) && (n+token < yyTable.Length); ++ token)
+        if (yyCheck[n+token] == token && !ok[token] && yyNames[token] != null) {
+          ++ len;
+          ok[token] = true;
+        }
+    int [] result = new int [len];
+    for (n = token = 0; n < len;  ++ token)
+      if (ok[token]) result[n++] = token;
+    return result;
+  }
+  protected string[] yyExpecting (int state) {
+    int [] tokens = yyExpectingTokens (state);
+    string [] result = new string[tokens.Length];
+    for (int n = 0; n < tokens.Length;  n++)
+      result[n++] = yyNames[tokens [n]];
+    return result;
+  }
+
+  /** the generated parser, with debugging messages.
+      Maintains a state and a value stack, currently with fixed maximum size.
+      @param yyLex scanner.
+      @param yydebug debug message writer implementing yyDebug, or null.
+      @return result of the last reduction, if any.
+      @throws yyException on irrecoverable parse error.
+    */
+  internal Object yyparse (yyParser.yyInput yyLex, Object yyd)
+                                {
+    this.debug = (yydebug.yyDebug)yyd;
+    return yyparse(yyLex);
+  }
+
+  /** initial size and increment of the state/value stack [default 256].
+      This is not final so that it can be overwritten outside of invocations
+      of yyparse().
+    */
+  protected int yyMax;
+
+  /** executed at the beginning of a reduce action.
+      Used as $$ = yyDefault($1), prior to the user-specified action, if any.
+      Can be overwritten to provide deep copy, etc.
+      @param first value for $1, or null.
+      @return first.
+    */
+  protected Object yyDefault (Object first) {
+    return first;
+  }
+
+       static int[] global_yyStates;
+       static object[] global_yyVals;
+#pragma warning disable 649
+       protected bool use_global_stacks;
+#pragma warning restore 649
+       object[] yyVals;                                        // value stack
+       object yyVal;                                           // value stack ptr
+       int yyToken;                                            // current input
+       int yyTop;
+
+  /** the generated parser.
+      Maintains a state and a value stack, currently with fixed maximum size.
+      @param yyLex scanner.
+      @return result of the last reduction, if any.
+      @throws yyException on irrecoverable parse error.
+    */
+  internal Object yyparse (yyParser.yyInput yyLex)
+  {
+    if (yyMax <= 0) yyMax = 256;               // initial size
+    int yyState = 0;                   // state stack ptr
+    int [] yyStates;                   // state stack 
+    yyVal = null;
+    yyToken = -1;
+    int yyErrorFlag = 0;                               // #tks to shift
+       if (use_global_stacks && global_yyStates != null) {
+               yyVals = global_yyVals;
+               yyStates = global_yyStates;
+   } else {
+               yyVals = new object [yyMax];
+               yyStates = new int [yyMax];
+               if (use_global_stacks) {
+                       global_yyVals = yyVals;
+                       global_yyStates = yyStates;
+               }
+       }
+
+    /*yyLoop:*/ for (yyTop = 0;; ++ yyTop) {
+      if (yyTop >= yyStates.Length) {                  // dynamically increase
+        global::System.Array.Resize (ref yyStates, yyStates.Length+yyMax);
+        global::System.Array.Resize (ref yyVals, yyVals.Length+yyMax);
+      }
+      yyStates[yyTop] = yyState;
+      yyVals[yyTop] = yyVal;
+      if (debug != null) debug.push(yyState, yyVal);
+
+      /*yyDiscarded:*/ while (true) {  // discarding a token does not change stack
+        int yyN;
+        if ((yyN = yyDefRed[yyState]) == 0) {  // else [default] reduce (yyN)
+          if (yyToken < 0) {
+            yyToken = yyLex.advance() ? yyLex.token() : 0;
+            if (debug != null)
+              debug.lex(yyState, yyToken, yyname(yyToken), yyLex.value());
+          }
+          if ((yyN = yySindex[yyState]) != 0 && ((yyN += yyToken) >= 0)
+              && (yyN < yyTable.Length) && (yyCheck[yyN] == yyToken)) {
+            if (debug != null)
+              debug.shift(yyState, yyTable[yyN], yyErrorFlag-1);
+            yyState = yyTable[yyN];            // shift to yyN
+            yyVal = yyLex.value();
+            yyToken = -1;
+            if (yyErrorFlag > 0) -- yyErrorFlag;
+            goto continue_yyLoop;
+          }
+          if ((yyN = yyRindex[yyState]) != 0 && (yyN += yyToken) >= 0
+              && yyN < yyTable.Length && yyCheck[yyN] == yyToken)
+            yyN = yyTable[yyN];                        // reduce (yyN)
+          else
+            switch (yyErrorFlag) {
+  
+            case 0:
+              yyExpectingState = yyState;
+              // yyerror(String.Format ("syntax error, got token `{0}'", yyname (yyToken)), yyExpecting(yyState));
+              if (debug != null) debug.error("syntax error");
+              if (yyToken == 0 /*eof*/ || yyToken == eof_token) throw new yyParser.yyUnexpectedEof ();
+              goto case 1;
+            case 1: case 2:
+              yyErrorFlag = 3;
+              do {
+                if ((yyN = yySindex[yyStates[yyTop]]) != 0
+                    && (yyN += Token.yyErrorCode) >= 0 && yyN < yyTable.Length
+                    && yyCheck[yyN] == Token.yyErrorCode) {
+                  if (debug != null)
+                    debug.shift(yyStates[yyTop], yyTable[yyN], 3);
+                  yyState = yyTable[yyN];
+                  yyVal = yyLex.value();
+                  goto continue_yyLoop;
+                }
+                if (debug != null) debug.pop(yyStates[yyTop]);
+              } while (-- yyTop >= 0);
+              if (debug != null) debug.reject();
+              throw new yyParser.yyException("irrecoverable syntax error");
+  
+            case 3:
+              if (yyToken == 0) {
+                if (debug != null) debug.reject();
+                throw new yyParser.yyException("irrecoverable syntax error at end-of-file");
+              }
+              if (debug != null)
+                debug.discard(yyState, yyToken, yyname(yyToken),
+                                                       yyLex.value());
+              yyToken = -1;
+              goto continue_yyDiscarded;               // leave stack alone
+            }
+        }
+        int yyV = yyTop + 1-yyLen[yyN];
+        if (debug != null)
+          debug.reduce(yyState, yyStates[yyV-1], yyN, YYRules.getRule (yyN), yyLen[yyN]);
+        yyVal = yyV > yyTop ? null : yyVals[yyV]; // yyVal = yyDefault(yyV > yyTop ? null : yyVals[yyV]);
+        switch (yyN) {
+case 1:
+#line 168 "ExpressionParser.jay"
+  { yyVal = new ExpressionList ((Expression) yyVals[0+yyTop]); }
+  break;
+case 2:
+#line 170 "ExpressionParser.jay"
+  { yyVal = ((ExpressionList) yyVals[-1+yyTop]).Append ((Expression) yyVals[0+yyTop]); }
+  break;
+case 11:
+#line 185 "ExpressionParser.jay"
+  { yyVal = new BooleanLiteral () { Value = true, Location = (ILocation) yyVals[0+yyTop] }; }
+  break;
+case 12:
+#line 187 "ExpressionParser.jay"
+  { yyVal = new BooleanLiteral () { Value = false, Location = (ILocation) yyVals[0+yyTop] }; }
+  break;
+case 21:
+#line 203 "ExpressionParser.jay"
+  { yyVal = new NotExpression () { Negated = (Expression) yyVals[0+yyTop], Location = (ILocation) yyVals[-1+yyTop] }; }
+  break;
+case 22:
+#line 208 "ExpressionParser.jay"
+  { yyVal = new PropertyAccessExpression () { Access = (PropertyAccess) yyVals[-1+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 23:
+#line 213 "ExpressionParser.jay"
+  { yyVal = new PropertyAccess () { Name = (NameToken) yyVals[0+yyTop], Location = (NameToken) yyVals[0+yyTop] }; }
+  break;
+case 24:
+#line 215 "ExpressionParser.jay"
+  { yyVal = new PropertyAccess () { Name = (NameToken) yyVals[0+yyTop], Target = (Expression) yyVals[-2+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 25:
+#line 220 "ExpressionParser.jay"
+  { yyVal = new ItemAccessExpression () { Application = (ItemApplication) yyVals[-1+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 27:
+#line 227 "ExpressionParser.jay"
+  { yyVal = new ItemApplication () { Name = (NameToken) yyVals[-2+yyTop], Expressions = (ExpressionList) yyVals[0+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 28:
+#line 232 "ExpressionParser.jay"
+  { yyVal = new MetadataAccessExpression () { Access = (MetadataAccess) yyVals[-1+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 29:
+#line 238 "ExpressionParser.jay"
+  { yyVal = new MetadataAccess () { Metadata = (NameToken) yyVals[0+yyTop], Location = (ILocation) yyVals[0+yyTop] }; }
+  break;
+case 30:
+#line 240 "ExpressionParser.jay"
+  { yyVal = new MetadataAccess () { Item = (NameToken) yyVals[-2+yyTop], Metadata = (NameToken) yyVals[0+yyTop], Location = (ILocation) yyVals[-2+yyTop] }; }
+  break;
+case 31:
+#line 245 "ExpressionParser.jay"
+  { yyVal = new StringLiteral () { Value = (NameToken) yyVals[0+yyTop], Location = (ILocation) yyVals[0+yyTop] }; }
+  break;
+case 32:
+#line 247 "ExpressionParser.jay"
+  { yyVal = new FunctionCallExpression () { Name = (NameToken) yyVals[-3+yyTop], Arguments = (ExpressionList) yyVals[-1+yyTop], Location = (ILocation) yyVals[-3+yyTop] }; }
+  break;
+case 33:
+#line 252 "ExpressionParser.jay"
+  { yyVal = new ExpressionList ((Expression) yyVals[0+yyTop]); }
+  break;
+case 34:
+#line 254 "ExpressionParser.jay"
+  { yyVal = ((ExpressionList) yyVals[-2+yyTop]).Append ((Expression) yyVals[0+yyTop]); }
+  break;
+case 35:
+#line 259 "ExpressionParser.jay"
+  { yyVal = (Expression) yyVals[-1+yyTop]; }
+  break;
+#line default
+        }
+        yyTop -= yyLen[yyN];
+        yyState = yyStates[yyTop];
+        int yyM = yyLhs[yyN];
+        if (yyState == 0 && yyM == 0) {
+          if (debug != null) debug.shift(0, yyFinal);
+          yyState = yyFinal;
+          if (yyToken < 0) {
+            yyToken = yyLex.advance() ? yyLex.token() : 0;
+            if (debug != null)
+               debug.lex(yyState, yyToken,yyname(yyToken), yyLex.value());
+          }
+          if (yyToken == 0) {
+            if (debug != null) debug.accept(yyVal);
+            return yyVal;
+          }
+          goto continue_yyLoop;
+        }
+        if (((yyN = yyGindex[yyM]) != 0) && ((yyN += yyState) >= 0)
+            && (yyN < yyTable.Length) && (yyCheck[yyN] == yyState))
+          yyState = yyTable[yyN];
+        else
+          yyState = yyDgoto[yyM];
+        if (debug != null) debug.shift(yyStates[yyTop], yyState);
+        goto continue_yyLoop;
+      continue_yyDiscarded: ;  // implements the named-loop continue: 'continue yyDiscarded'
+      }
+    continue_yyLoop: ;         // implements the named-loop continue: 'continue yyLoop'
+    }
+  }
+
+/*
+ All more than 3 lines long rules are wrapped into a method
+*/
+#line default
+   static readonly short [] yyLhs  = {              -1,
+    1,    1,    0,    0,    0,    0,    0,    0,    0,    0,
+    2,    2,    3,    3,    3,    3,    3,    3,    3,    3,
+    4,    5,   10,   10,    6,   11,   11,    7,   12,   12,
+    8,    8,   13,   13,    9,
+  };
+   static readonly short [] yyLen = {           2,
+    1,    2,    1,    1,    1,    1,    1,    1,    1,    1,
+    1,    1,    3,    3,    3,    3,    3,    3,    3,    3,
+    2,    3,    1,    3,    3,    1,    3,    3,    1,    3,
+    1,    4,    1,    3,    3,
+  };
+   static readonly short [] yyDefRed = {            0,
+   11,   12,    0,    0,    0,    0,    0,   31,    0,    3,
+    4,    5,    6,    7,    8,    9,   10,   21,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,   22,    0,   25,    0,
+   28,   35,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,   20,   24,    0,    0,   30,    0,   32,    0,    0,
+  };
+  protected static readonly short [] yyDgoto  = {             9,
+   55,   10,   11,   12,   13,   14,   15,   16,   17,   21,
+   23,   25,   44,
+  };
+  protected static readonly short [] yySindex = {         -180,
+    0,    0, -180, -156, -286, -284, -180,    0, -203,    0,
+    0,    0,    0,    0,    0,    0,    0,    0, -276, -251,
+ -274, -267, -243, -231, -238, -126, -180, -180, -180, -180,
+ -180, -180, -180, -180, -180, -241,    0, -180,    0, -239,
+    0,    0, -203, -270, -153, -118, -141, -113, -109, -246,
+ -220,    0,    0, -203, -180,    0, -180,    0, -203, -203,
+  };
+  protected static readonly short [] yyRindex = {            0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0, -177,    0,
+    0, -226,    0, -219,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0, -259,    0,  142,  131,  105,   79,   53,   27,
+    1,    0,    0, -232, -212,    0,    0,    0, -206, -253,
+  };
+  protected static readonly short [] yyGindex = {            2,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,
+  };
+  protected static readonly short [] yyTable = {            22,
+   19,   24,   57,   27,   18,   20,   37,   28,   26,   29,
+   58,   30,   31,   33,   32,   33,   38,   34,   35,   34,
+   36,   33,   34,   35,    1,    1,   18,   34,   43,   45,
+   46,   47,   48,   49,   50,   51,   52,   39,    1,   54,
+   40,    1,   41,    1,   53,    1,   56,    1,    1,   35,
+    2,    2,   17,    1,   26,   28,   59,   29,   60,   30,
+   31,   29,   32,   33,    2,   34,   35,    2,   27,    2,
+    0,    2,    0,    2,    2,    0,    1,    2,   16,    2,
+    0,   31,    0,   31,    0,   31,   31,    0,   31,   31,
+    3,   31,   31,    4,   31,    5,    0,    6,    0,    7,
+    1,    2,    0,   23,   15,    8,    0,   29,    0,   30,
+   31,    0,   32,   33,    3,   34,   35,    4,    0,    5,
+    0,    6,   31,    7,   32,   33,    0,   34,   35,   19,
+   14,    0,   28,    0,   29,    0,   30,   31,    0,   32,
+   33,   13,   34,   35,   30,   31,    0,   32,   33,    0,
+   34,   35,   32,   33,   42,   34,   35,   33,    0,   34,
+   35,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+    0,    0,    0,    0,    0,    0,    0,   19,   19,   19,
+    0,   19,    0,   19,   19,    0,   19,   19,    0,   19,
+    0,   19,   19,   19,   19,    0,   19,    0,   19,    0,
+   19,   19,    0,   18,   18,   18,   19,   18,    0,   18,
+   18,    0,   18,   18,    0,    0,    0,   18,   18,   18,
+   18,    0,   18,    0,   18,    0,   18,   18,    0,   17,
+   17,   17,   18,   17,    0,   17,   17,    0,   17,    0,
+    0,    0,    0,   17,   17,   17,   17,    0,   17,    0,
+   17,    0,   17,   17,    0,   16,   16,   16,   17,   16,
+    0,   16,   16,    0,    0,    0,    0,    0,    0,   16,
+   16,   16,   16,    0,   16,    0,   16,    0,   16,   16,
+    0,   15,   15,   15,   16,   15,    0,   15,    0,    0,
+    0,    0,    0,    0,    0,   15,   15,   15,   15,    0,
+   15,    0,   15,    0,   15,   15,    0,   14,   14,   14,
+   15,   14,    0,    0,    0,    0,    0,    0,   13,   13,
+   13,   14,   14,   14,   14,    0,   14,    0,   14,    0,
+   14,   14,   13,   13,   13,   13,   14,   13,    0,   13,
+    0,   13,   13,    0,    0,    0,    0,   13,
+  };
+  protected static readonly short [] yyCheck = {           286,
+    0,  286,  273,  280,    3,    4,  281,  259,    7,  261,
+  281,  263,  264,  273,  266,  267,  284,  269,  270,  273,
+  272,  281,  269,  270,  257,  258,    0,  281,   27,   28,
+   29,   30,   31,   32,   33,   34,   35,  281,  271,   38,
+  272,  274,  281,  276,  286,  278,  286,  280,  281,  270,
+  257,  258,    0,  286,  281,  259,   55,  261,   57,  263,
+  264,  281,  266,  267,  271,  269,  270,  274,  281,  276,
+   -1,  278,   -1,  280,  281,   -1,  257,  258,    0,  286,
+   -1,  259,   -1,  261,   -1,  263,  264,   -1,  266,  267,
+  271,  269,  270,  274,  272,  276,   -1,  278,   -1,  280,
+  257,  258,   -1,  281,    0,  286,   -1,  261,   -1,  263,
+  264,   -1,  266,  267,  271,  269,  270,  274,   -1,  276,
+   -1,  278,  264,  280,  266,  267,   -1,  269,  270,  286,
+    0,   -1,  259,   -1,  261,   -1,  263,  264,   -1,  266,
+  267,    0,  269,  270,  263,  264,   -1,  266,  267,   -1,
+  269,  270,  266,  267,  281,  269,  270,  267,   -1,  269,
+  270,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,   -1,   -1,  257,  258,  259,
+   -1,  261,   -1,  263,  264,   -1,  266,  267,   -1,  269,
+   -1,  271,  272,  273,  274,   -1,  276,   -1,  278,   -1,
+  280,  281,   -1,  257,  258,  259,  286,  261,   -1,  263,
+  264,   -1,  266,  267,   -1,   -1,   -1,  271,  272,  273,
+  274,   -1,  276,   -1,  278,   -1,  280,  281,   -1,  257,
+  258,  259,  286,  261,   -1,  263,  264,   -1,  266,   -1,
+   -1,   -1,   -1,  271,  272,  273,  274,   -1,  276,   -1,
+  278,   -1,  280,  281,   -1,  257,  258,  259,  286,  261,
+   -1,  263,  264,   -1,   -1,   -1,   -1,   -1,   -1,  271,
+  272,  273,  274,   -1,  276,   -1,  278,   -1,  280,  281,
+   -1,  257,  258,  259,  286,  261,   -1,  263,   -1,   -1,
+   -1,   -1,   -1,   -1,   -1,  271,  272,  273,  274,   -1,
+  276,   -1,  278,   -1,  280,  281,   -1,  257,  258,  259,
+  286,  261,   -1,   -1,   -1,   -1,   -1,   -1,  257,  258,
+  259,  271,  272,  273,  274,   -1,  276,   -1,  278,   -1,
+  280,  281,  271,  272,  273,  274,  286,  276,   -1,  278,
+   -1,  280,  281,   -1,   -1,   -1,   -1,  286,
+  };
+
+#line 263 "ExpressionParser.jay"
+
+       }
+#line default
+namespace yydebug {
+        using System;
+        internal interface yyDebug {
+                void push (int state, Object value);
+                void lex (int state, int token, string name, Object value);
+                void shift (int from, int to, int errorFlag);
+                void pop (int state);
+                void discard (int state, int token, string name, Object value);
+                void reduce (int from, int to, int rule, string text, int len);
+                void shift (int from, int to);
+                void accept (Object value);
+                void error (string message);
+                void reject ();
+        }
+        
+        class yyDebugSimple : yyDebug {
+                void println (string s){
+                        Console.Error.WriteLine (s);
+                }
+                
+                public void push (int state, Object value) {
+                        println ("push\tstate "+state+"\tvalue "+value);
+                }
+                
+                public void lex (int state, int token, string name, Object value) {
+                        println("lex\tstate "+state+"\treading "+name+"\tvalue "+value);
+                }
+                
+                public void shift (int from, int to, int errorFlag) {
+                        switch (errorFlag) {
+                        default:                               // normally
+                                println("shift\tfrom state "+from+" to "+to);
+                                break;
+                        case 0: case 1: case 2:                // in error recovery
+                                println("shift\tfrom state "+from+" to "+to
+                                            +"\t"+errorFlag+" left to recover");
+                                break;
+                        case 3:                                // normally
+                                println("shift\tfrom state "+from+" to "+to+"\ton error");
+                                break;
+                        }
+                }
+                
+                public void pop (int state) {
+                        println("pop\tstate "+state+"\ton error");
+                }
+                
+                public void discard (int state, int token, string name, Object value) {
+                        println("discard\tstate "+state+"\ttoken "+name+"\tvalue "+value);
+                }
+                
+                public void reduce (int from, int to, int rule, string text, int len) {
+                        println("reduce\tstate "+from+"\tuncover "+to
+                                    +"\trule ("+rule+") "+text);
+                }
+                
+                public void shift (int from, int to) {
+                        println("goto\tfrom state "+from+" to "+to);
+                }
+                
+                public void accept (Object value) {
+                        println("accept\tvalue "+value);
+                }
+                
+                public void error (string message) {
+                        println("error\t"+message);
+                }
+                
+                public void reject () {
+                        println("reject");
+                }
+                
+        }
+}
+// %token constants
+ class Token {
+  public const int TRUE_LITERAL = 257;
+  public const int FALSE_LITERAL = 258;
+  public const int EQ = 259;
+  public const int NE = 261;
+  public const int GT = 263;
+  public const int GE = 264;
+  public const int LT = 266;
+  public const int LE = 267;
+  public const int AND = 269;
+  public const int OR = 270;
+  public const int NOT = 271;
+  public const int DOT = 272;
+  public const int COMMA = 273;
+  public const int PROP_OPEN = 274;
+  public const int ITEM_OPEN = 276;
+  public const int METADATA_OPEN = 278;
+  public const int PAREN_OPEN = 280;
+  public const int PAREN_CLOSE = 281;
+  public const int COLON2 = 282;
+  public const int ARROW = 284;
+  public const int NAME = 286;
+  public const int ERROR = 287;
+  public const int yyErrorCode = 256;
+ }
+ namespace yyParser {
+  using System;
+  /** thrown for irrecoverable syntax errors and stack overflow.
+    */
+  internal class yyException : System.Exception {
+    public yyException (string message) : base (message) {
+    }
+  }
+  internal class yyUnexpectedEof : yyException {
+    public yyUnexpectedEof (string message) : base (message) {
+    }
+    public yyUnexpectedEof () : base ("") {
+    }
+  }
+
+  /** must be implemented by a scanner object to supply input to the parser.
+    */
+  internal interface yyInput {
+    /** move on to next token.
+        @return false if positioned beyond tokens.
+        @throws IOException on input error.
+      */
+    bool advance (); // throws java.io.IOException;
+    /** classifies current token.
+        Should not be called if advance() returned false.
+        @return current %token or single character.
+      */
+    int token ();
+    /** associated with current token.
+        Should not be called if advance() returned false.
+        @return value for token().
+      */
+    Object value ();
+  }
+ }
+} // close outermost namespace, that MUST HAVE BEEN opened in the prolog
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay
new file mode 100644 (file)
index 0000000..93b215c
--- /dev/null
@@ -0,0 +1,264 @@
+%{
+
+using System;
+using System.Text;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Exceptions;
+using Microsoft.Build.Framework;
+
+/*
+
+Pseudo formal syntax for .NET 4.0 expression:
+
+Condition = Expression
+Include = Expression*
+
+ Expression
+       BooleanLiteral
+               TrueLiteral
+               FalseLiteral
+       BinaryExpression
+               Expression "==" Expression
+               Expression "!=" Expression
+               Expression ">" Expression
+               Expression ">=" Expression
+               Expression "<" Expression
+               Expression "<=" Expression
+               Expression "And" Expression
+               Expression "Or" Expression
+       UnaryExpression
+               "!" Expression
+       PropertyExpression
+               "$(" PropertyApplication ")"
+       ItemExpression
+               "@(" ItemApplication ")"
+       MetadataBatchingExpression
+               "%(" MetadataBatchingApplication ")"
+  StringLiteralOrFunction
+               StringLiteralOrFunctionName ( "(" FunctionArguments ")" )?
+
+.NET error messages are so detailed which is something like "you forgot '(' after '$' ?" - so
+it is likely that the MS tokenizer is hand-written.
+
+*/
+
+namespace Microsoft.Build.Internal
+{
+#if false
+       class ExpressionParser
+       {
+               public static string EvaluateExpression (string source, Project project, ITaskItem [] inputs)
+               {
+                       var head = new StringBuilder ();
+                       var tail = new StringBuilder ();
+                       int start = 0;
+                       int end = source.Length;
+                       while (start < end) {
+                               switch (source [start]) {
+                               case '$':
+                               case '@':
+                                       int last = source.LastIndexOf (')', end);
+                                       if (last < 0)
+                                               throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                       if (start + 1 == end || source [start] != '(')
+                                               throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
+                                       tail.Insert (0, source.Substring (last + 1, end - last));
+                                       start += 2;
+                                       end = last - 1;
+                                       if (source [start] == '$')
+                                               head.Append (EvaluatePropertyExpression (source, project, inputs, start, end));
+                                       else
+                                               head.Append (EvaluateItemExpression (source, project, inputs, start, end));
+                                       break;
+                               default:
+                                       head.Append (source.Substring (start, end - start));
+                                       start = end;
+                                       break;
+                               }
+                       }
+                       return head.ToString () + tail.ToString ();
+               }
+               
+               public static string EvaluatePropertyExpression (string source, Project project, ITaskItem [] inputs, int start, int end)
+               {
+                       int idx = source.IndexOf ("::", start, StringComparison.Ordinal);
+                       if (idx >= 0) {
+                               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);
+                               }
+                       } // the result could be context for further property access...
+                       
+                       idx = source.IndexOf ('.', start);
+                       if (idx > 0) {
+                               string name = source.Substring (start, idx - start);
+                               var prop = project.GetProperty (name);
+                               if (prop == null)
+                                       throw new InvalidProjectFileException (string.Format ("Property \"{0}\" was not found", name));
+                       }
+               }
+               
+               public static string EvaluateItemExpression (string source, Project project, ITaskItem [] inputs, int start, int end)
+               {
+                       // using property as context and evaluate
+                       int idx = source.IndexOf ("->", start, StringComparison.Ordinal);
+                       if (idx > 0) {
+                               string name = source.Substring (start, idx - start);
+                       }
+                       
+               }
+       }
+       
+       class ExpressionNode
+       {
+       }
+       
+       enum ExpressionNodeType
+       {
+               Item,
+               Property,
+               Transform,
+               Invocation
+       }
+
+#endif
+
+       class ExpressionParser
+       {
+               int yacc_verbose_flag = 1;
+%}
+
+%left TRUE_LITERAL
+%left FALSE_LITERAL
+%left EQ "=="
+%left NE "!="
+%left GT ">"
+%left GE ">="
+%left LT "<"
+%left LE "<="
+%left AND
+%left OR
+%left NOT "!"
+%left DOT "."
+%left COMMA ","
+%left PROP_OPEN "$("
+%left ITEM_OPEN "@("
+%left METADATA_OPEN "%("
+%left PAREN_OPEN "("
+%left PAREN_CLOSE ")"
+%left COLON2 "::"
+%left ARROW "->"
+%left NAME
+%left ERROR
+
+%start Expression
+
+%%
+
+ExpressionList
+       : Expression
+         { $$ = new ExpressionList ((Expression) $1); }
+       | ExpressionList Expression
+         { $$ = ((ExpressionList) $1).Append ((Expression) $2); }
+       ;
+Expression
+       : BooleanLiteral
+       | BinaryExpression
+       | UnaryExpression
+       | PropertyAccessExpression
+       | ItemAccessExpression
+       | MetadataAccessExpression
+       | StringLiteralOrFunction
+       | ParenthesizedExpression
+       ;
+
+BooleanLiteral
+       : TRUE_LITERAL
+         { $$ = new BooleanLiteral () { Value = true, Location = (ILocation) $1 }; }
+       | FALSE_LITERAL
+         { $$ = new BooleanLiteral () { Value = false, Location = (ILocation) $1 }; }
+       ;
+
+BinaryExpression
+       : Expression EQ Expression
+       | Expression NE Expression
+       | Expression GT Expression
+       | Expression GE Expression
+       | Expression LT Expression
+       | Expression LE Expression
+       | Expression AND Expression
+       | Expression OR Expression
+       ;
+
+UnaryExpression
+       : NOT Expression
+         { $$ = new NotExpression () { Negated = (Expression) $2, Location = (ILocation) $1 }; }
+       ;
+
+PropertyAccessExpression
+       : PROP_OPEN PropertyAccess PAREN_CLOSE
+         { $$ = new PropertyAccessExpression () { Access = (PropertyAccess) $2, Location = (ILocation) $1 }; }
+       ;
+
+PropertyAccess
+       : NAME
+         { $$ = new PropertyAccess () { Name = (NameToken) $1, Location = (NameToken) $1 }; }
+       | Expression DOT NAME
+         { $$ = new PropertyAccess () { Name = (NameToken) $3, Target = (Expression) $1, Location = (ILocation) $1 }; }
+       ;
+
+ItemAccessExpression
+       : ITEM_OPEN ItemApplication PAREN_CLOSE
+         { $$ = new ItemAccessExpression () { Application = (ItemApplication) $2, Location = (ILocation) $1 }; }
+       ;
+
+// looking a bit messy, but gives different location
+ItemApplication
+       : NAME
+       | NAME ARROW ExpressionList
+         { $$ = new ItemApplication () { Name = (NameToken) $1, Expressions = (ExpressionList) $3, Location = (ILocation) $1 }; }
+       ;
+
+MetadataAccessExpression
+       : METADATA_OPEN MetadataAccess PAREN_CLOSE
+         { $$ = new MetadataAccessExpression () { Access = (MetadataAccess) $2, Location = (ILocation) $1 }; }
+       ;
+
+// looking a bit messy, but gives different location
+MetadataAccess
+       : NAME
+         { $$ = new MetadataAccess () { Metadata = (NameToken) $1, Location = (ILocation) $1 }; }
+       | NAME DOT NAME
+         { $$ = new MetadataAccess () { Item = (NameToken) $1, Metadata = (NameToken) $3, Location = (ILocation) $1 }; }
+       ;
+
+StringLiteralOrFunction
+       : NAME
+         { $$ = new StringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; }
+       | NAME PAREN_OPEN FunctionCallArguments PAREN_CLOSE
+         { $$ = new FunctionCallExpression () { Name = (NameToken) $1, Arguments = (ExpressionList) $3, Location = (ILocation) $1 }; }
+       ;
+
+FunctionCallArguments
+       : Expression
+         { $$ = new ExpressionList ((Expression) $1); }
+       | FunctionCallArguments COMMA Expression
+         { $$ = ((ExpressionList) $1).Append ((Expression) $3); }
+       ;
+
+ParenthesizedExpression
+       : PAREN_OPEN Expression PAREN_CLOSE
+         { $$ = (Expression) $2; }
+       ;
+
+%%
+
+       }
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs
new file mode 100644 (file)
index 0000000..ae3b176
--- /dev/null
@@ -0,0 +1,205 @@
+using System;
+using Microsoft.Build.Evaluation;
+
+namespace Microsoft.Build.Internal
+{
+       class ExpressionTokenizer : yyParser.yyInput
+       {
+               public ExpressionTokenizer (string source)
+               {
+                       this.source = source;
+                       current_token_position = -1;
+               }
+               
+               string source;
+               
+               int current_token;
+               string error;
+               int pos, current_token_position;
+               object token_value;
+
+               public bool advance ()
+               {
+                       if (pos == source.Length)
+                               return false;
+
+                       error = null;
+                       token_value = null;
+                       current_token_position = pos;
+
+                       switch (source [pos++]) {
+                       case '.':
+                               current_token = Token.DOT;
+                               break;
+                       case ',':
+                               current_token = Token.COMMA;
+                               break;
+                       case '(':
+                               current_token = Token.PAREN_OPEN;
+                               break;
+                       case ')':
+                               current_token = Token.PAREN_CLOSE;
+                               break;
+                       case '-':
+                               if (pos < source.Length && source [pos] == '>') {
+                                       current_token = Token.ARROW;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "'-' is not followed by '>'.";
+                               }
+                               break;
+                       case '=':
+                               if (pos < source.Length && source [pos] == '=') {
+                                       current_token = Token.EQ;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "'=' is not followed by '='.";
+                               }
+                               break;
+                       case ':':
+                               if (pos < source.Length && source [pos] == ':') {
+                                       current_token = Token.COLON2;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "':' is not followed by ':'.";
+                               }
+                               break;
+                       case '!':
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.NE;
+                               }
+                               else
+                                       current_token = Token.NOT;
+                               break;
+                       case '>':
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.GE;
+                               }
+                               else
+                                       current_token = Token.GT;
+                               break;
+                       case '<':
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.LE;
+                               }
+                               else
+                                       current_token = Token.LT;
+                               break;
+                       case '$':
+                               if (pos < source.Length && source [pos] == '(') {
+                                       current_token = Token.PROP_OPEN;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "property reference '$' is not followed by '('.";
+                               }
+                               break;
+                       case '@':
+                               if (pos < source.Length && source [pos] == '(') {
+                                       current_token = Token.ITEM_OPEN;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "item reference '@' is not followed by '('.";
+                               }
+                               break;
+                       case '%':
+                               if (pos < source.Length && source [pos] == '(') {
+                                       current_token = Token.METADATA_OPEN;
+                                       pos++;
+                               } else {
+                                       current_token = Token.ERROR;
+                                       error = "metadata reference '%' is not followed by '('.";
+                               }
+                               break;
+                       case '"':
+                               ReadStringLiteral (source, '"');
+                               break;
+                       case '\'':
+                               ReadStringLiteral (source, '\'');
+                               break;
+                       default:
+                               pos = source.IndexOfAny (token_starter_chars, pos);
+                               if (pos < 0)
+                                       pos = source.Length;
+                               var val = source.Substring (current_token_position, pos - current_token_position - 1);
+                               if (val.Equals ("AND", StringComparison.OrdinalIgnoreCase))
+                                       current_token = Token.AND;
+                               else if (val.Equals ("OR", StringComparison.OrdinalIgnoreCase))
+                                       current_token = Token.OR;
+                               else if (val.Equals ("TRUE", StringComparison.OrdinalIgnoreCase))
+                                       current_token = Token.TRUE_LITERAL;
+                               else if (val.Equals ("FALSE", StringComparison.OrdinalIgnoreCase))
+                                       current_token = Token.FALSE_LITERAL;
+                               else {
+                                       current_token = Token.NAME;
+                                       token_value = ProjectCollection.Unescape (val);
+                                       break;
+                               }
+                               break;
+                       }
+                       return true;
+               }
+
+               static readonly char [] token_starter_chars = ".,)-=:!><$@%\"' ".ToCharArray ();
+               
+               void ReadStringLiteral (string source, char c)
+               {
+                       while (pos < source.Length && source [pos] != c)
+                               pos++;
+                       if (source [pos - 1] != c) {
+                               current_token = Token.ERROR;
+                               error = string.Format ("missing string literal terminator [{0}]", c);
+                       } else {
+                               current_token = Token.NAME;
+                               token_value = source.Substring (current_token_position + 1, pos - current_token_position - 2);
+                               token_value = ProjectCollection.Unescape ((string) token_value);
+                       }
+               }
+               
+               public int token ()
+               {
+                       return current_token;
+               }
+               
+               public object value ()
+               {
+                       if (current_token == Token.NAME)
+                               return new NameToken () { Name = (string) token_value, Column = current_token_position };
+                       else if (error != null)
+                               return new ErrorToken () { Message = error, Column = current_token_position };
+                       else
+                               return new Location () { Column = current_token_position };
+               }
+       }       
+
+       class NameToken : Location
+       {
+               public string Name { get; set; }
+       }
+
+       class ErrorToken : Location
+       {
+               public string Message { get; set; }
+       }
+
+       interface ILocation
+       {
+               //int Line { get; }
+               int Column { get; }
+               //string File { get; }
+       }
+
+       class Location : ILocation
+       {
+               //public int Line { get; set; }
+               public int Column { get; set; }
+               //public string File { get; set; }
+       }
+}
index 98812e5ea18295dcf13217783d785c6c1be4d706..e2c58186bf2e5405409be8caffcd273a2cbd2109 100644 (file)
@@ -72,6 +72,8 @@ Microsoft.Build.Execution/ProjectTargetInstanceChild.cs
 Microsoft.Build.Execution/TargetResult.cs                           
 Microsoft.Build.Execution/TargetResultCode.cs
 Microsoft.Build.Internal/CollectionFromEnumerable.cs
+Microsoft.Build.Internal/ExpressionConstructs.cs
+Microsoft.Build.Internal/ExpressionTokenizer.cs
 Microsoft.Build.Internal/FilteredEnumerable.cs
 Microsoft.Build.Internal/ReverseEnumerable.cs
 Microsoft.Build.Logging/ColorResetter.cs
index 8c23302c4c6edb99927ae8f04ab6165dfe0f9892..ae09422fc30ccaeb8f6030030488a314f1406469 100644 (file)
@@ -10,6 +10,7 @@ Microsoft.Build.Execution/BuildParametersTest.cs
 Microsoft.Build.Execution/BuildManagerTest.cs
 Microsoft.Build.Execution/ProjectInstanceTest.cs
 Microsoft.Build.Internal/CollectionFromEnumerableTest.cs
+Microsoft.Build.Internal/ExpressionParserTest.cs
 Microsoft.Build.Logging/ConsoleLoggerTest.cs
 Microsoft.Build.Logging/LoggerDescriptionTest.cs
 
index b3260b2b3a51bead453ee8213ce173371b4cb4f5..f32793c41856abb51c9bdb320ed0eaa763f5b860 100644 (file)
@@ -88,8 +88,7 @@ namespace MonoTests.Microsoft.Build.Evaluation
     <Baz Condition=""$(Void)=="">$(FOO)</Baz>
   </PropertyGroup>
 </Project>";
-                       var path = "file://localhost/foo.xml";
-                       var reader = XmlReader.Create (new StringReader (xml), null, path);
+                       var reader = XmlReader.Create (new StringReader (xml));
                        var root = ProjectRootElement.Create (reader);
                        new Project (root);
                }
@@ -105,8 +104,7 @@ namespace MonoTests.Microsoft.Build.Evaluation
     <Baz Condition=""$(Void)==''"">$(FOO)</Baz>
   </PropertyGroup>
 </Project>";
-                       var path = "file://localhost/foo.xml";
-                       var reader = XmlReader.Create (new StringReader (xml), null, path);
+                       var reader = XmlReader.Create (new StringReader (xml));
                        var root = ProjectRootElement.Create (reader);
                        var proj = new Project (root);
                        Assert.AreEqual ("xyz", proj.ExpandString ("x$(BAR)z"), "#1");
@@ -121,7 +119,6 @@ namespace MonoTests.Microsoft.Build.Evaluation
             string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
   <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' />
 </Project>";
-            var path = "file://localhost/foo.xml";
             var xml = XmlReader.Create (new StringReader (project_xml));
             var root = ProjectRootElement.Create (xml);
             var proj = new Project (root);
@@ -135,12 +132,28 @@ namespace MonoTests.Microsoft.Build.Evaluation
             string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
   <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' />
 </Project>";
-            var path = "file://localhost/foo.xml";
             var xml = XmlReader.Create (new StringReader (project_xml));
             var root = ProjectRootElement.Create (xml);
             var proj = new Project (root);
                        Assert.IsFalse (proj.Build ("Build", new ILogger [] {new ConsoleLogger ()})); // missing mandatory properties
                }
+               
+               [Test]
+               public void EvaluateIncludeAsEmptyThenIgnored ()
+               {
+            string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <ItemGroup>
+    <Foo Include='' />
+    <Bar />
+  </ItemGroup>
+</Project>";
+            var xml = XmlReader.Create (new StringReader (project_xml));
+            var root = ProjectRootElement.Create (xml);
+            var proj = new Project (root);
+            // note that Foo is ignored.
+                       Assert.AreEqual (1, proj.ItemsIgnoringCondition.Count, "#1");
+                       Assert.IsNotNull ("Bar", proj.ItemsIgnoringCondition.First ().ItemType, "#2");
+               }
        }
 }
 
diff --git a/mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs b/mcs/class/Microsoft.Build/Test/Microsoft.Build.Internal/ExpressionParserTest.cs
new file mode 100644 (file)
index 0000000..4b092e3
--- /dev/null
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
+using NUnit.Framework;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Exceptions;
+using System.Collections.Generic;
+
+namespace MonoTests.Microsoft.Build.Internal
+{
+       [TestFixture]
+       public class ExpressionParserTest
+       {
+                       string [] invalid_as_boolean = {
+                               "$",
+                               "@",
+                               "%",
+                               "%-1",
+                               "$(",
+                               "%(",
+                               "$)",
+                               "%)",
+                               "%24",
+                               "()",
+                               "{}",
+                               "A", // must be evaluated as a boolean
+                               "1", // ditto (no default conversion to bool)
+                               "$ (foo) == ''",
+                               "@ (foo) == ''",
+                               "$(Foo) == And", // reserved keyword 'and'
+                               "$(Foo) == Or", // reserved keyword 'or'
+                               "$(Foo) == $(Bar) == $(Baz)", // unexpected '=='
+                               "$([System.String]::Format('Tr'))$([System.String]::Format('ue'))", // only one expression is accepted
+                       };
+                       string [] valid = {
+                               "'%24' == 0",
+                               "true",
+                               "fAlSe",
+                       };
+                       string [] depends = {
+                               // valid only if evaluated to boolean
+                               "$(foo)",
+                               "@(foo)",
+                       };
+                       
+               [Test]
+               public void EvaluateAsBoolean ()
+               {
+                       foreach (var expr in invalid_as_boolean.Concat (valid)) {
+                               string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <ItemGroup>
+    <Foo Condition=""{0}"" />
+  </ItemGroup>
+</Project>";
+                               var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
+                               var root = ProjectRootElement.Create (xml);
+                               try {
+                                       new Project (root);
+                                       if (invalid_as_boolean.Contains (expr))
+                                               Assert.Fail ("Parsing Condition '{0}' should fail", expr);
+                               } catch (InvalidProjectFileException) {
+                                       if (valid.Contains (expr))
+                                               throw;
+                                       continue;
+                               }
+                       }
+               }
+               
+               [Test]
+               public void EvaluateAsString ()
+               {
+                       foreach (var expr in invalid_as_boolean.Concat (valid)) {
+                               string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <ItemGroup>
+    <Foo Condition=""{0}"" />
+  </ItemGroup>
+</Project>";
+                               var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
+                               var root = ProjectRootElement.Create (xml);
+                               // everything should pass
+                               new Project (root);
+                       }
+               }
+               
+               [Test]
+               public void EvaluateReferencesWithProperties ()
+               {
+                       foreach (var expr in depends) {
+                               string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <ItemGroup>
+    <Foo Condition=""{0}"" />
+  </ItemGroup>
+</Project>";
+                               var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
+                               var root = ProjectRootElement.Create (xml);
+                               var props = new Dictionary<string,string> ();
+                               props ["foo"] = "true";
+                               new Project (root, props, null);
+                       }
+               }
+               
+               [Test]
+               public void EvaluateReferencesWithoutProperties ()
+               {
+                       foreach (var expr in depends) {
+                               string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+  <ItemGroup>
+    <Foo Condition=""{0}"" />
+  </ItemGroup>
+</Project>";
+                               var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
+                               var root = ProjectRootElement.Create (xml);
+                               try {
+                                       new Project (root);
+                                       Assert.Fail ("Parsing Condition '{0}' should fail", expr);
+                               } catch (InvalidProjectFileException) {
+                                       continue;
+                               }
+                       }
+               }               
+       }
+}
+