For item/propery reference expansion, use manual parser.
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
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)
}
}
+ 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; }
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
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));
+ }
}
}
}
}
+ 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)
throw new NotImplementedException ();
}
}
- partial class StringLiteralExpression : Expression
+ partial class StringLiteral : Expression
{
public override bool EvaluateAsBoolean (EvaluationContext context)
{
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)
{
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 ();
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 // >
%token NOT //!
%token DOT //.
%token COMMA //,
-%token APOS // '
-%token QUOT // "
%token PROP_OPEN // $(
%token ITEM_OPEN // @(
%token METADATA_OPEN // %(
{ $$ = ((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 }; }
{ $$ = 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 }; }
{ $$ = 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
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
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 '@':
modes.Push (TokenizerMode.InsideItemOrProperty);
current_token = Token.ITEM_OPEN;
pos++;
- }
- else
+ } else
ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('.");
break;
case '%':
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)
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 };