made several fixes in jay-based expression parser and use it for boolean evaluation.
authorAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Mon, 21 Oct 2013 10:54:58 +0000 (19:54 +0900)
committerAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Fri, 29 Nov 2013 09:20:50 +0000 (18:20 +0900)
For item/propery reference expansion, use manual parser.

mcs/class/Microsoft.Build/Makefile
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs

index e27ea36bf434a7558f8c3ce268420cf17e82fe49..78f4a4438d6c176bed6e21cb020fb857e7efcbd4 100644 (file)
@@ -32,7 +32,7 @@ EXTRA_DISTFILES = \
 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)
+       (cd Microsoft.Build.Internal; $(topdir)/../jay/jay -ctv < $(topdir)/../jay/skeleton.cs ExpressionParser.jay > ExpressionParser.cs)
 
 BUILT_SOURCES = $(EXPR_PARSER).cs
 
index 720c4d028f7ad552565ea0d64dd6d92241bb252a..d8d806812391859b930b1e5de6012c2b546ada48 100644 (file)
@@ -322,7 +322,7 @@ namespace Microsoft.Build.Evaluation
                
                bool ShouldInclude (string unexpandedValue)
                {
-                       return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this).EvaluateAsBoolean (unexpandedValue);
+                       return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this).EvaluateAsBoolean (ExpandString (unexpandedValue));
                }
 
                public string ExpandString (string unexpandedValue)
index 03a922638d9fbc471cbaa9bb569de831b133b1e0..b2a7214aed8a64d3d137d6c14a79646d014968cf 100644 (file)
@@ -77,6 +77,25 @@ namespace Microsoft.Build.Internal
                }
        }
        
+       enum Operator
+       {
+               EQ,
+               NE,
+               LT,
+               LE,
+               GT,
+               GE,
+               And,
+               Or
+       }
+       
+       partial class BinaryExpression : Expression
+       {
+               public Operator Operator { get; set; }
+               public Expression Left { get; set; }
+               public Expression Right { get; set; }
+       }
+       
        partial class BooleanLiteral : Expression
        {
                public bool Value { get; set; }
@@ -127,9 +146,9 @@ namespace Microsoft.Build.Internal
                public NameToken Item { get; set; }
        }
        
-       partial class StringLiteralExpression : Expression
+       partial class StringLiteral : Expression
        {
-               public ExpressionList Contents { get; set; }
+               public NameToken Value { get; set; }
        }
 
        partial class RawStringLiteral : Expression
index e8287fa04523925a145ab8053cf0e88070fe8147..c3a48a04825f4e83c74abee1bd671de8de0cb80a 100644 (file)
@@ -29,10 +29,14 @@ namespace Microsoft.Build.Internal
                
                public bool EvaluateAsBoolean (string source)
                {
-                       var el = new ExpressionParserManual (source, ExpressionValidationType.StrictBoolean).Parse ();
-                       if (el.Count () != 1)
-                               throw new InvalidProjectFileException ("Unexpected number of tokens");
-                       return el.First ().EvaluateAsBoolean (new EvaluationContext (this));
+                       try {
+                               var el = new ExpressionParser ().Parse (source, ExpressionValidationType.StrictBoolean);
+                               if (el.Count () != 1)
+                                       throw new InvalidProjectFileException ("Unexpected number of tokens");
+                               return el.First ().EvaluateAsBoolean (new EvaluationContext (this));
+                       } catch (yyParser.yyException ex) {
+                               throw new InvalidProjectFileException (string.Format ("failed to evaluate expression as boolean: '{0}'", source));
+                       }
                }
        }
        
@@ -97,6 +101,63 @@ namespace Microsoft.Build.Internal
                }
        }
        
+       partial class BinaryExpression : Expression
+       {
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       switch (Operator) {
+                       case Operator.EQ:
+                               return Left.EvaluateAsString (context).Equals (Right.EvaluateAsString (context));
+                       case Operator.NE:
+                               return !Left.EvaluateAsString (context).Equals (Right.EvaluateAsString (context));
+                       case Operator.And:
+                       case Operator.Or:
+                               // evaluate first, to detect possible syntax error on right expr.
+                               var lb = Left.EvaluateAsBoolean (context);
+                               var rb = Right.EvaluateAsBoolean (context);
+                               return Operator == Operator.And ? (lb && rb) : (lb || rb);
+                       }
+                       // comparison expressions - evaluate comparable first, then compare values.
+                       var left = Left.EvaluateAsObject (context);
+                       var right = Right.EvaluateAsObject (context);
+                       if (!(left is IComparable && right is IComparable))
+                               throw new InvalidProjectFileException ("expression cannot be evaluated as boolean");
+                       var result = ((IComparable) left).CompareTo (right);
+                       switch (Operator) {
+                       case Operator.GE:
+                               return result >= 0;
+                       case Operator.GT:
+                               return result > 0;
+                       case Operator.LE:
+                               return result <= 0;
+                       case Operator.LT:
+                               return result < 0;
+                       }
+                       throw new InvalidOperationException ();
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               static readonly Dictionary<Operator,string> strings = new Dictionary<Operator, string> () {
+                       {Operator.EQ, " == "},
+                       {Operator.NE, " != "},
+                       {Operator.LT, " < "},
+                       {Operator.LE, " <= "},
+                       {Operator.GT, " > "},
+                       {Operator.GE, " >= "},
+                       {Operator.And, " And "},
+                       {Operator.Or, " Or "},
+               };
+               
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       return Left.EvaluateAsString (context) + strings [Operator] + Right.EvaluateAsString (context);
+               }
+       }
+       
        partial class BooleanLiteral : Expression
        {
                public override string EvaluateAsString (EvaluationContext context)
@@ -206,7 +267,7 @@ namespace Microsoft.Build.Internal
                        throw new NotImplementedException ();
                }
        }
-       partial class StringLiteralExpression : Expression
+       partial class StringLiteral : Expression
        {
                public override bool EvaluateAsBoolean (EvaluationContext context)
                {
@@ -216,7 +277,7 @@ namespace Microsoft.Build.Internal
                
                public override string EvaluateAsString (EvaluationContext context)
                {
-                       return string.Concat (Contents.Select (e => e.EvaluateAsString (context)));
+                       return this.Value.Name;
                }
                
                public override object EvaluateAsObject (EvaluationContext context)
index c951d1a88f487744bb5186a6203ad2193630d7bc..ac0d726dfce4fc91b0b8d0874387cccb83405a49 100644 (file)
@@ -46,7 +46,7 @@ namespace Microsoft.Build.Internal
 {
        class ExpressionParser
        {
-               const int yacc_verbose_flag = 1;
+               const int yacc_verbose_flag = 0;
 
                object debug_obj = yacc_verbose_flag == 0 ? null : new yydebug.yyDebugSimple ();
                
@@ -55,10 +55,16 @@ namespace Microsoft.Build.Internal
                        var tokenizer = new ExpressionTokenizer (source, validationType);
                        return (ExpressionList) yyparse (tokenizer, debug_obj);
                }
+               
+               BinaryExpression Binary (Operator op, object left, object right)
+               {
+                       return new BinaryExpression () { Operator = op, Left = (Expression) left, Right = (Expression) right, Location = (ILocation) left };
+               }
 %}
 
 %token TRUE_LITERAL
 %token FALSE_LITERAL
+%token STRING_LITERAL
 %token EQ // ==
 %token NE // !=
 %token GT // >
@@ -70,8 +76,6 @@ namespace Microsoft.Build.Internal
 %token NOT //!
 %token DOT //.
 %token COMMA //,
-%token APOS // '
-%token QUOT // "
 %token PROP_OPEN // $(
 %token ITEM_OPEN // @(
 %token METADATA_OPEN // %(
@@ -93,29 +97,40 @@ ExpressionList
          { $$ = ((ExpressionList) $1).Add ((Expression) $2); }
        ;
 
-ExpressionExceptStringLiteralList
-       : /* empty */
-         { $$ = new ExpressionList (); }
-       | ExpressionExceptStringLiteralList ExpressionExceptStringLiteral
-         { $$ = ((ExpressionList) $1).Add ((Expression) $2); }
-       ;
-
 Expression
-       : ExpressionExceptStringLiteral
-       | StringLiteralExpression
+       : BinaryExpression
        ;
 
-ExpressionExceptStringLiteral
+BinaryExpression
+       : PrimaryExpression
+       | BinaryExpression EQ BinaryExpression
+         { $$ = Binary (Operator.EQ, $1, $3); }
+       | BinaryExpression NE BinaryExpression
+         { $$ = Binary (Operator.NE, $1, $3); }
+       | BinaryExpression GT BinaryExpression
+         { $$ = Binary (Operator.GT, $1, $3); }
+       | BinaryExpression GE BinaryExpression
+         { $$ = Binary (Operator.GE, $1, $3); }
+       | BinaryExpression LT BinaryExpression
+         { $$ = Binary (Operator.LT, $1, $3); }
+       | BinaryExpression LE BinaryExpression
+         { $$ = Binary (Operator.LE, $1, $3); }
+       | BinaryExpression AND BinaryExpression
+         { $$ = Binary (Operator.And, $1, $3); }
+       | BinaryExpression OR BinaryExpression
+         { $$ = Binary (Operator.Or, $1, $3); }
+       ;
+
+PrimaryExpression
        : BooleanLiteral
-       | BinaryExpression
+       | StringLiteral
        | UnaryExpression
        | PropertyAccessExpression
        | ItemAccessExpression
        | MetadataAccessExpression
        | RawStringLiteralOrFunction
        | ParenthesizedExpression
-       ;
-
+       
 BooleanLiteral
        : TRUE_LITERAL
          { $$ = new BooleanLiteral () { Value = true, Location = (ILocation) $1 }; }
@@ -123,17 +138,6 @@ BooleanLiteral
          { $$ = 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 }; }
@@ -176,11 +180,9 @@ MetadataAccess
          { $$ = new MetadataAccess () { Item = (NameToken) $1, Metadata = (NameToken) $3, Location = (ILocation) $1 }; }
        ;
 
-StringLiteralExpression
-       : APOS ExpressionExceptStringLiteralList APOS
-         { $$ = new StringLiteralExpression () { Contents = (ExpressionList) $2, Location = (ILocation) $1 }; }
-       | QUOT ExpressionExceptStringLiteralList QUOT
-         { $$ = new StringLiteralExpression () { Contents = (ExpressionList) $2, Location = (ILocation) $1 }; }
+StringLiteral
+       : STRING_LITERAL
+         { $$ = new StringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; }
        ;
 
 RawStringLiteralOrFunction
index 3f52e2450a1f9c40f77abe9d3004452113d60fc1..d981c054add8fe9cdd2925d4943fd6534ad3b030 100644 (file)
@@ -81,8 +81,7 @@ namespace Microsoft.Build.Internal
                                        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 };
-                                       var literal = new RawStringLiteral () { Value = val };
-                                       head.Add (new StringLiteralExpression () { Contents = new ExpressionList () { literal } });
+                                       head.Add (new StringLiteral () { Value = val });
                                        if (idx >= 0)
                                                start = idx;
                                        else
index 98a277a92a74bf6d1151bb19be9377b6013a31f6..94ad2c9940ed099e157a5079c1f913ad74933b71 100644 (file)
@@ -67,75 +67,54 @@ namespace Microsoft.Build.Internal
                                        current_token = Token.NAME;
                                }
                                break;
-                       case '"':
-                               current_token = Token.QUOT;
-                               break;
-                       case '\'':
-                               current_token = Token.APOS;
-                               break;
                        case '-':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == '>') {
+                               if (pos < source.Length && source [pos] == '>') {
                                        current_token = Token.ARROW;
                                        pos++;
                                } else
                                        ErrorOnStrictBoolean ("-", "'-' is not followed by '>'.");
                                break;
                        case '=':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == '=') {
+                               if (pos < source.Length && source [pos] == '=') {
                                        current_token = Token.EQ;
                                        pos++;
                                } else
                                        ErrorOnStrictBoolean ("=", "'=' is not followed by '='.");
                                break;
                        case ':':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == ':') {
+                               if (pos < source.Length && source [pos] == ':') {
                                        current_token = Token.COLON2;
-                                       pos++;
                                } else
                                        ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
+                               pos++;
                                break;
                        case '!':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
-                                       if (pos < source.Length && source [pos] == '=') {
-                                               pos++;
-                                               current_token = Token.NE;
-                                       } else
-                                               TokenForItemPropertyValue ("!", Token.NOT);
-                               }
-                               else
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.NE;
+                               } else
                                        TokenForItemPropertyValue ("!", Token.NOT);
                                break;
                        case '>':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
-                                       if (pos < source.Length && source [pos] == '=') {
-                                               pos++;
-                                               current_token = Token.GE;
-                                       }
-                                       else
-                                               current_token = Token.GT;
-                               }
-                               else
-                                       TokenForItemPropertyValue (">", Token.GT);
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.GE;
+                               } else
+                                       current_token = Token.GT;
                                break;
                        case '<':
-                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
-                                       if (pos < source.Length && source [pos] == '=') {
-                                               pos++;
-                                               current_token = Token.LE;
-                                       }
-                                       else
-                                               current_token = Token.LT;
-                               }
-                               else
-                                       TokenForItemPropertyValue ("<", Token.LT);
+                               if (pos < source.Length && source [pos] == '=') {
+                                       pos++;
+                                       current_token = Token.LE;
+                               } else
+                                       current_token = Token.LT;
                                break;
                        case '$':
                                if (pos < source.Length && source [pos] == '(') {
                                        modes.Push (TokenizerMode.InsideItemOrProperty);
                                        current_token = Token.PROP_OPEN;
                                        pos++;
-                               }
-                               else
+                               } else
                                        ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('.");
                                break;
                        case '@':
@@ -143,8 +122,7 @@ namespace Microsoft.Build.Internal
                                        modes.Push (TokenizerMode.InsideItemOrProperty);
                                        current_token = Token.ITEM_OPEN;
                                        pos++;
-                               }
-                               else
+                               } else
                                        ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('.");
                                break;
                        case '%':
@@ -152,10 +130,20 @@ namespace Microsoft.Build.Internal
                                        modes.Push (TokenizerMode.InsideItemOrProperty);
                                        current_token = Token.METADATA_OPEN;
                                        pos++;
-                               }
-                               else
+                               } else
                                        ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
                                break;
+                       case '"':
+                       case '\'':
+                               pos = source.IndexOf (source [pos - 1], pos);
+                               if (pos < 0) {
+                                       ErrorOnStrictBoolean ("'", "unterminated string literal");
+                                       pos = source.Length;
+                               }
+                               token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1);
+                               current_token = Token.STRING_LITERAL;
+                               pos++;
+                               break;
                        default:
                                pos = source.IndexOfAny (token_starter_chars, pos);
                                if (pos < 0)
@@ -229,7 +217,7 @@ namespace Microsoft.Build.Internal
                
                public object value ()
                {
-                       if (current_token == Token.NAME)
+                       if (current_token == Token.NAME || current_token == Token.STRING_LITERAL)
                                return new NameToken () { Name = (string) token_value, Column = current_token_position };
                        else if (error != null)
                                return new ErrorToken () { Message = error, Column = current_token_position };