Altered expression parser to hand-written one (which was kind of anticipated) for...
authorAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Wed, 16 Oct 2013 18:29:05 +0000 (03:29 +0900)
committerAtsushi Eno <atsushieno@veritas-vos-liberabit.com>
Fri, 29 Nov 2013 09:20:37 +0000 (18:20 +0900)
mcs/class/Microsoft.Build/Makefile
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/Project.cs
mcs/class/Microsoft.Build/Microsoft.Build.Evaluation/ProjectProperty.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionConstructs.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParser.jay
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs [new file with mode: 0644]
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionTokenizer.cs
mcs/class/Microsoft.Build/Microsoft.Build.dll.sources
mcs/class/Microsoft.Build/Test/Microsoft.Build.Evaluation/ProjectTest.cs

index 5b7ca23a350c975b3cd28ccc8d809fcd604a5043..e27ea36bf434a7558f8c3ce268420cf17e82fe49 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 -ct < $(topdir)/../jay/skeleton.cs ExpressionParser.jay > ExpressionParser.cs)
 
 BUILT_SOURCES = $(EXPR_PARSER).cs
 
index e947dd7187290567d8379c05f8226c74bb3e8018..c0aa7bb4280808155e3ac0ee09fa886334d33790 100644 (file)
@@ -144,6 +144,7 @@ namespace Microsoft.Build.Evaluation
                Dictionary<string, ProjectItemDefinition> item_definitions;
                List<ResolvedImport> raw_imports;
                List<ProjectItem> raw_items;
+               List<ProjectItem> all_evaluated_items;
                List<string> item_types;
                List<ProjectProperty> properties;
                Dictionary<string, ProjectTargetInstance> targets;
@@ -167,21 +168,35 @@ namespace Microsoft.Build.Evaluation
                                this.properties.Add (new EnvironmentProjectProperty (this, (string)p.Key, (string)p.Value));
                        foreach (var p in GlobalProperties)
                                this.properties.Add (new GlobalProjectProperty (this, p.Key, p.Value));
+
+                       all_evaluated_items = new List<ProjectItem> ();
                        foreach (var child in Xml.Children) {
-                               if (child is ProjectPropertyGroupElement)
-                                       foreach (var p in ((ProjectPropertyGroupElement) child).Properties)
+                               var pge = child as ProjectPropertyGroupElement;
+                               if (pge != null)
+                                       foreach (var p in pge.Properties)
                                                this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal));
-                               else if (child is ProjectItemGroupElement)
-                                       foreach (var p in ((ProjectItemGroupElement) child).Items)
-                                               this.raw_items.Add (new ProjectItem (this, p));
-                               else if (child is ProjectItemDefinitionGroupElement)
-                                       foreach (var p in ((ProjectItemDefinitionGroupElement) child).ItemDefinitions) {
-                                               ProjectItemDefinition existing;
-                                               if (!item_definitions.TryGetValue (p.ItemType, out existing))
-                                                       item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType)));
-                                               existing.AddItems (p);
+                               var ige = child as ProjectItemGroupElement;
+                               if (ige != null) {
+                                       foreach (var p in ige.Items) {
+                                               var item = new ProjectItem (this, p);
+                                               this.raw_items.Add (item);
+                                               if (ShouldInclude (ige.Condition))
+                                                       all_evaluated_items.Add (item);
+                                       }
+                               }
+                               var def = child as ProjectItemDefinitionGroupElement;
+                               if (def != null) {
+                                       foreach (var p in def.ItemDefinitions) {
+                                               if (ShouldInclude (p.Condition)) {
+                                                       ProjectItemDefinition existing;
+                                                       if (!item_definitions.TryGetValue (p.ItemType, out existing))
+                                                               item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType)));
+                                                       existing.AddItems (p);
+                                               }
                                        }
+                               }
                        }
+                       all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
                }
 
                public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
@@ -286,10 +301,15 @@ namespace Microsoft.Build.Evaluation
                        // FIXME: maybe fill other properties to the result.
                        return ret;
                }
+               
+               bool ShouldInclude (string unexpandedValue)
+               {
+                       return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this).EvaluateAsBoolean (unexpandedValue);
+               }
 
                public string ExpandString (string unexpandedValue)
                {
-                       throw new NotImplementedException ();
+                       return new ExpressionEvaluator (this).Evaluate (unexpandedValue);
                }
 
                public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
@@ -347,7 +367,7 @@ namespace Microsoft.Build.Evaluation
 
                public ProjectProperty GetProperty (string name)
                {
-                       return properties.FirstOrDefault (p => p.Name == name);
+                       return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
                }
 
                public void MarkDirty ()
@@ -427,7 +447,7 @@ namespace Microsoft.Build.Evaluation
                }
 
                public ICollection<ProjectItem> AllEvaluatedItems {
-                       get { throw new NotImplementedException (); }
+                       get { return all_evaluated_items; }
                }
 
                public ICollection<ProjectProperty> AllEvaluatedProperties {
@@ -481,7 +501,20 @@ namespace Microsoft.Build.Evaluation
                }
 
                public ICollection<ProjectItem> Items {
-                       get { throw new NotImplementedException (); }
+                       get {
+                               var ret = new List<ProjectItem> ();
+                               foreach (var child in Xml.Children) {
+                                       var ige = child as ProjectItemGroupElement;
+                                       if (ige != null) {
+                                               foreach (var p in ige.Items) {
+                                                       var item = new ProjectItem (this, p);
+                                                       if (ShouldInclude (ige.Condition))
+                                                               ret.Add (item);
+                                               }
+                                       }
+                               }
+                               return ret;
+                       }
                }
 
                public ICollection<ProjectItem> ItemsIgnoringCondition {
index 5b0a093e00d0a33025a1d3c8f06caf664818a242..7d9f547a09a0aed0b32147ea90cc6596a744881c 100644 (file)
@@ -29,6 +29,7 @@
 using System;
 using System.Linq;
 using Microsoft.Build.Construction;
+using Microsoft.Build.Internal;
 
 namespace Microsoft.Build.Evaluation
 {
@@ -43,10 +44,9 @@ namespace Microsoft.Build.Evaluation
                        Project = project;
                }
 
+               string evaluated_value; // see UpdateEvaluatedValue().
                public string EvaluatedValue {
-                       get {
-                               throw new NotImplementedException ();
-                       }
+                       get { return evaluated_value; }
                }
 
                public abstract bool IsEnvironmentProperty { get; }
@@ -68,6 +68,11 @@ namespace Microsoft.Build.Evaluation
                public abstract string UnevaluatedValue { get; set; }
 
                public abstract ProjectPropertyElement Xml { get; }
+               
+               internal void UpdateEvaluatedValue ()
+               {
+                       evaluated_value = Project.ExpandString (UnevaluatedValue);
+               }
        }
 
        // copy from MS.Build.Engine/BuildProperty.cs
@@ -123,6 +128,7 @@ namespace Microsoft.Build.Evaluation
                        : base (project, propertyType, xml.Name)
                {
                        this.xml = xml;
+                       UpdateEvaluatedValue ();
                }
                
                ProjectPropertyElement xml;
@@ -142,6 +148,7 @@ namespace Microsoft.Build.Evaluation
                        : base (project, PropertyType.Environment, name)
                {
                        this.value = value;
+                       UpdateEvaluatedValue ();
                }
                
                readonly string value;
@@ -161,6 +168,7 @@ namespace Microsoft.Build.Evaluation
                        : base (project, PropertyType.Global, name)
                {
                        this.value = value;
+                       UpdateEvaluatedValue ();
                }
                
                readonly string value;
index 4977121c5e86eb72a71a6e211db20f3d89c2c0a3..03a922638d9fbc471cbaa9bb569de831b133b1e0 100644 (file)
@@ -10,22 +10,30 @@ namespace Microsoft.Build.Internal
                public ILocation Location { get; set; }         
        }
        
-       class ExpressionList : ILocation, IEnumerable<Expression>
+       partial class ExpressionList : ILocation, IEnumerable<Expression>
        {
+               public ExpressionList ()
+               {
+               }
+               
                public ExpressionList (Expression entry)
                {
-                       Append (entry);
+                       Add (entry);
                }
                
                //public int Line {
-               //      get { return list [0].Line; }
+               //      get { return list.Count == 0 ? 0 : list [0].Line; }
                //}
                public int Column {
-                       get { return list [0].Column; }
+                       get { return list.Count == 0 ? 0 : list [0].Column; }
                }
                //public string File {
-               //      get { return list [0].File; }
+               //      get { return list.Count == 0 ? null : list [0].File; }
                //}
+               public string ToLocationString ()
+               {
+                       return list.Count == 0 ? null : list [0].Location.ToLocationString ();
+               }
                        
                public IEnumerator<Expression> GetEnumerator ()
                {
@@ -39,14 +47,20 @@ namespace Microsoft.Build.Internal
                
                List<Expression> list = new List<Expression> ();
                
-               public ExpressionList Append (Expression expr)
+               public ExpressionList Add (Expression expr)
                {
                        list.Add (expr);
                        return this;
                }
+               
+               public ExpressionList Insert (int pos, Expression expr)
+               {
+                       list.Insert (pos, expr);
+                       return this;
+               }
        }
 
-       class Expression : Locatable, ILocation
+       abstract partial class Expression : Locatable, ILocation
        {
                //public int Line {
                //      get { return Location.Line; }
@@ -57,30 +71,41 @@ namespace Microsoft.Build.Internal
                //public string File {
                //      get { return Location.File; }
                //}
+               public string ToLocationString ()
+               {
+                       return Location.ToLocationString ();
+               }
        }
        
-       class BooleanLiteral : Expression
+       partial class BooleanLiteral : Expression
        {
                public bool Value { get; set; }
        }
 
-       class NotExpression : Expression
+       partial class NotExpression : Expression
        {
                public Expression Negated { get; set; }
        }
 
-       class PropertyAccessExpression : Expression
+       partial class PropertyAccessExpression : Expression
        {
                public PropertyAccess Access { get; set; }
        }
        
+       enum PropertyTargetType
+       {
+               Object,
+               Type,
+       }
+       
        class PropertyAccess : Locatable
        {
                public NameToken Name { get; set; }
                public Expression Target { get; set; }
+               public PropertyTargetType TargetType { get; set; }
        }
 
-       class ItemAccessExpression : Expression
+       partial class ItemAccessExpression : Expression
        {
                public ItemApplication Application { get; set; }
        }
@@ -91,7 +116,7 @@ namespace Microsoft.Build.Internal
                public ExpressionList Expressions { get; set; }
        }
 
-       class MetadataAccessExpression : Expression
+       partial class MetadataAccessExpression : Expression
        {
                public MetadataAccess Access { get; set; }
        }
@@ -101,13 +126,18 @@ namespace Microsoft.Build.Internal
                public NameToken Metadata { get; set; }
                public NameToken Item { get; set; }
        }
+       
+       partial class StringLiteralExpression : Expression
+       {
+               public ExpressionList Contents { get; set; }
+       }
 
-       class StringLiteral : Expression
+       partial class RawStringLiteral : Expression
        {
                public NameToken Value { get; set; }
        }
        
-       class FunctionCallExpression : Expression
+       partial class FunctionCallExpression : Expression
        {
                public NameToken Name { get; set; }
                public ExpressionList Arguments { get; set; }
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs
new file mode 100644 (file)
index 0000000..80a79dd
--- /dev/null
@@ -0,0 +1,251 @@
+using System;
+using System.Linq;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Exceptions;
+using System.Collections.Generic;
+
+namespace Microsoft.Build.Internal
+{
+       class ExpressionEvaluator
+       {
+               public ExpressionEvaluator (Project project)
+               {
+                       this.Project = project;
+               }
+               
+               public Project Project { get; private set; }
+               
+               public string Evaluate (string source)
+               {
+                       return Evaluate (source, new ExpressionParserManual ().Parse (source, ExpressionValidationType.LaxString));
+                       //return Evaluate (new ExpressionParser ().Parse (source, validationType));
+               }
+               
+               public string Evaluate (string source, ExpressionList exprList)
+               {
+                       if (exprList == null)
+                               throw new ArgumentNullException ("exprList");
+                       return string.Concat (exprList.Select (e => e.EvaluateAsString (new EvaluationContext (this))));
+               }
+               
+               public bool EvaluateAsBoolean (string source)
+               {
+                       var el = new ExpressionParserManual ().Parse (source, ExpressionValidationType.StrictBoolean);
+                       if (el.Count () != 1)
+                               throw new InvalidProjectFileException ("Unexpected number of tokens");
+                       return el.First ().EvaluateAsBoolean (new EvaluationContext (this));
+               }
+       }
+       
+       class EvaluationContext
+       {
+               public EvaluationContext (ExpressionEvaluator evaluator)
+               {
+                       Evaluator = evaluator;
+               }
+               
+               public ExpressionEvaluator Evaluator { get; private set; }
+               public Project Project {
+                       get { return Evaluator.Project; }
+               }
+               public ProjectItem ContextItem { get; set; }            
+               
+               List<ProjectItem> items = new List<ProjectItem> ();
+               List<ProjectProperty> props = new List<ProjectProperty> ();
+               
+               public string EvaluateItem (ProjectItem item)
+               {
+                       if (items.Contains (item))
+                               throw new InvalidProjectFileException (string.Format ("Recursive reference to item '{0}' with include '{1}' was found", item.ItemType, item.UnevaluatedInclude));
+                       try {
+                               items.Add (item);
+                               // FIXME: needs verification if string evaluation is appropriate.
+                               return Evaluator.Evaluate (item.UnevaluatedInclude);
+                       } finally {
+                               items.Remove (item);
+                       }
+               }
+               
+               public string EvaluateProperty (ProjectProperty prop)
+               {
+                       if (props.Contains (prop))
+                               throw new InvalidProjectFileException (string.Format ("Recursive reference to property '{0}' was found", prop.Name));
+                       try {
+                               props.Add (prop);
+                               // FIXME: needs verification if string evaluation is appropriate.
+                               return Evaluator.Evaluate (prop.UnevaluatedValue);
+                       } finally {
+                               props.Remove (prop);
+                       }
+               }
+       }
+       
+       abstract partial class Expression
+       {
+               public abstract string EvaluateAsString (EvaluationContext context);
+               public abstract bool EvaluateAsBoolean (EvaluationContext context);
+               public abstract object EvaluateAsObject (EvaluationContext context);
+       }
+       
+       partial class BooleanLiteral : Expression
+       {
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       return Value ? "True" : "False";
+               }
+               
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       return Value;
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       return Value;
+               }
+       }
+
+       partial class NotExpression : Expression
+       {
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       // no negation for string
+                       return "!" + Negated.EvaluateAsString (context);
+               }
+               
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       return !Negated.EvaluateAsBoolean (context);
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       return EvaluateAsString (context);
+               }
+       }
+
+       partial class PropertyAccessExpression : Expression
+       {
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new InvalidProjectFileException ("Project item access cannot be evaluated as boolean");
+               }
+               
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       var ret = EvaluateAsObject (context);
+                       return ret == null ? null : ret.ToString ();
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       if (Access.Target == null) {
+                               var prop = context.Project.GetProperty (Access.Name.Name);
+                               if (prop == null)
+                                       return null;
+                               return context.EvaluateProperty (prop);
+                       } else {
+                               var obj = EvaluateAsObject (context);
+                               if (obj == null)
+                                       return null;
+                               var prop = obj.GetType ().GetProperty (Access.Name.Name);
+                               if (prop == null)
+                                       throw new InvalidProjectFileException (string.Format ("access to undefined property '{0}' at {1}", Access.Name, Location));
+                               return prop.GetValue (obj, null);
+                       }
+               }
+       }
+
+       partial class ItemAccessExpression : Expression
+       {
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new InvalidProjectFileException ("Project item access cannot be evaluated as boolean");
+               }
+               
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       var items = context.Project.GetItems (Application.Name.Name);
+                       if (Application.Expressions == null)
+                               return string.Join (";", items.Select (item => context.EvaluateItem (item)).Select (inc => !string.IsNullOrWhiteSpace (inc)));
+                       else
+                               return string.Join (";", items.Select (item => string.Concat (Application.Expressions.Select (e => e.EvaluateAsString (context)))).ToArray ());
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       return EvaluateAsString (context);
+               }
+       }
+
+       partial class MetadataAccessExpression : Expression
+       {
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+       }
+       partial class StringLiteralExpression : Expression
+       {
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       return string.Concat (Contents.Select (e => e.EvaluateAsString (context)));
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       return EvaluateAsString (context);
+               }
+       }
+       partial class RawStringLiteral : Expression
+       {
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       return Value.Name;
+               }
+               
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new InvalidProjectFileException ("raw string literal cannot be evaluated as boolean");
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       return EvaluateAsString (context);
+               }
+       }
+       
+       partial class FunctionCallExpression : Expression
+       {
+               public override string EvaluateAsString (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               public override bool EvaluateAsBoolean (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+               
+               public override object EvaluateAsObject (EvaluationContext context)
+               {
+                       throw new NotImplementedException ();
+               }
+       }
+}
+
index 93b215c3d300f34883cd959f74bb4f2d2962ce21..c951d1a88f487744bb5186a6203ad2193630d7bc 100644 (file)
@@ -44,139 +44,75 @@ 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));
-                       }
-               }
+               const int yacc_verbose_flag = 1;
+
+               object debug_obj = yacc_verbose_flag == 0 ? null : new yydebug.yyDebugSimple ();
                
-               public static string EvaluateItemExpression (string source, Project project, ITaskItem [] inputs, int start, int end)
+               public ExpressionList Parse (string source, ExpressionValidationType validationType)
                {
-                       // using property as context and evaluate
-                       int idx = source.IndexOf ("->", start, StringComparison.Ordinal);
-                       if (idx > 0) {
-                               string name = source.Substring (start, idx - start);
-                       }
-                       
+                       var tokenizer = new ExpressionTokenizer (source, validationType);
+                       return (ExpressionList) yyparse (tokenizer, debug_obj);
                }
-       }
-       
-       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
+%token TRUE_LITERAL
+%token FALSE_LITERAL
+%token EQ // ==
+%token NE // !=
+%token GT // >
+%token GE // >=
+%token LT // <
+%token LE // <=
+%token AND // AND
+%token OR // OR
+%token NOT //!
+%token DOT //.
+%token COMMA //,
+%token APOS // '
+%token QUOT // "
+%token PROP_OPEN // $(
+%token ITEM_OPEN // @(
+%token METADATA_OPEN // %(
+%token PAREN_OPEN // (
+%token PAREN_CLOSE // )
+%token COLON2 // ::
+%token ARROW // ->
+%token NAME
+%token ERROR
+
+%start ExpressionList
 
 %%
 
 ExpressionList
-       : Expression
-         { $$ = new ExpressionList ((Expression) $1); }
+       : /* empty */
+         { $$ = new ExpressionList (); }
        | ExpressionList Expression
-         { $$ = ((ExpressionList) $1).Append ((Expression) $2); }
+         { $$ = ((ExpressionList) $1).Add ((Expression) $2); }
+       ;
+
+ExpressionExceptStringLiteralList
+       : /* empty */
+         { $$ = new ExpressionList (); }
+       | ExpressionExceptStringLiteralList ExpressionExceptStringLiteral
+         { $$ = ((ExpressionList) $1).Add ((Expression) $2); }
        ;
+
 Expression
+       : ExpressionExceptStringLiteral
+       | StringLiteralExpression
+       ;
+
+ExpressionExceptStringLiteral
        : BooleanLiteral
        | BinaryExpression
        | UnaryExpression
        | PropertyAccessExpression
        | ItemAccessExpression
        | MetadataAccessExpression
-       | StringLiteralOrFunction
+       | RawStringLiteralOrFunction
        | ParenthesizedExpression
        ;
 
@@ -240,18 +176,27 @@ MetadataAccess
          { $$ = new MetadataAccess () { Item = (NameToken) $1, Metadata = (NameToken) $3, Location = (ILocation) $1 }; }
        ;
 
-StringLiteralOrFunction
+StringLiteralExpression
+       : APOS ExpressionExceptStringLiteralList APOS
+         { $$ = new StringLiteralExpression () { Contents = (ExpressionList) $2, Location = (ILocation) $1 }; }
+       | QUOT ExpressionExceptStringLiteralList QUOT
+         { $$ = new StringLiteralExpression () { Contents = (ExpressionList) $2, Location = (ILocation) $1 }; }
+       ;
+
+RawStringLiteralOrFunction
        : NAME
-         { $$ = new StringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; }
+         { $$ = new RawStringLiteral () { Value = (NameToken) $1, Location = (ILocation) $1 }; }
+       | NAME PAREN_OPEN PAREN_CLOSE
+         { $$ = new FunctionCallExpression () { Name = (NameToken) $1, Arguments = new ExpressionList (), 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); }
+         { $$ = new ExpressionList (); }
        | FunctionCallArguments COMMA Expression
-         { $$ = ((ExpressionList) $1).Append ((Expression) $3); }
+         { $$ = ((ExpressionList) $1).Add ((Expression) $3); }
        ;
 
 ParenthesizedExpression
diff --git a/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs b/mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionParserManual.cs
new file mode 100644 (file)
index 0000000..7bdadcf
--- /dev/null
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Build.Exceptions;
+
+namespace Microsoft.Build.Internal
+{
+       class ExpressionParserManual
+       {
+               public ExpressionList Parse (string source, ExpressionValidationType validationType)
+               {
+                       return Parse (source, validationType, 0, source.Length);
+               }
+               
+               static readonly char [] token_starters = "$@%('\"".ToCharArray ();
+               
+               ExpressionList Parse (string source, ExpressionValidationType validationType, int start, int end)
+               {
+                       if (string.IsNullOrWhiteSpace (source))
+                               return new ExpressionList ();
+
+                       var head = new List<Expression> ();
+                       var tail = new List<Expression> ();
+                       while (start < end) {
+                               switch (source [start]) {
+                               case '$':
+                               case '@':
+                               case '%':
+                                       int last = source.LastIndexOf (')', end - 1, end - start);
+                                       if (last < 0) {
+                                               if (validationType == ExpressionValidationType.StrictBoolean)
+                                                       throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
+                                               else
+                                                       goto default; // treat as raw literal to the section end
+                                       }
+                                       if (start + 1 == end || source [start + 1] != '(') {
+                                               if (validationType == ExpressionValidationType.StrictBoolean)
+                                                       throw new InvalidProjectFileException (string.Format ("missing '(' after '{0}' at {1} in \"{2}\"", source [start], start, source));
+                                               else
+                                                       goto default; // treat as raw literal to the section end
+                                       }
+                                       string tailValue = source.Substring (last + 1, end - last - 1);
+                                       if (tailValue.Length > 0)
+                                               tail.Add (new StringLiteralExpression () { Contents = new ExpressionList () { new RawStringLiteral () { Value = new NameToken () { Name = tailValue } } } });
+                                       start += 2;
+                                       end = last;
+                                       if (source [start - 2] == '$')
+                                               head.Add (EvaluatePropertyExpression (source, validationType, start, end));
+                                       else if (source [start - 2] == '%')
+                                               head.Add (EvaluateMetadataExpression (source, validationType, start, end));
+                                       else
+                                               head.Add (EvaluateItemExpression (source, validationType, start, end));
+                                       start = end;
+                                       break;
+                               default:
+                                       int idx = source.IndexOfAny (token_starters, start + 1);
+                                       string name = idx < 0 ? source.Substring (start, end - start) : source.Substring (start, idx - start);
+                                       var val = new NameToken () { Name = name };
+                                       var literal = new RawStringLiteral () { Value = val };
+                                       head.Add (new StringLiteralExpression () { Contents = new ExpressionList () { literal } });
+                                       if (idx >= 0)
+                                               start = idx;
+                                       else
+                                               start = end;
+                                       break;
+                               }
+                       }
+                       var ret = new ExpressionList ();
+                       foreach (var e in head.Concat (((IEnumerable<Expression>) tail).Reverse ()))
+                               ret.Add (e);
+                       return ret;
+               }
+               
+               PropertyAccessExpression EvaluatePropertyExpression (string source, ExpressionValidationType validationType, int start, int end)
+               {
+                       // member access
+                       int idx = source.LastIndexOf ('.', start);
+                       if (idx >= 0) {
+                               string name = (idx > 0) ? source.Substring (idx, end - idx) : source.Substring (start, end);
+                               return new PropertyAccessExpression () {
+                                       Access = new PropertyAccess () {
+                                               Name = new NameToken () { Name = name },
+                                               TargetType = PropertyTargetType.Object,
+                                               Target = idx < 0 ? null : Parse (source, validationType, start, idx).FirstOrDefault () 
+                                               }
+                                       };
+                       } else {
+                               // static type access
+                               idx = source.IndexOf ("::", start, StringComparison.Ordinal);
+                               if (idx >= 0) {
+                                       throw new NotImplementedException ();
+                               
+                                       string type = source.Substring (start, idx - start);
+                                       if (type.Length < 2 || type [0] != '[' || type [type.Length - 1] != ']')
+                                               throw new InvalidProjectFileException (string.Format ("Static function call misses appropriate type name surrounded by '[' and ']' at {0} in \"{1}\"", start, source));
+                                       int start2 = idx + 2;
+                                       int idx2 = source.IndexOf ('(', idx + 2, end - start2);
+                                       if (idx2 < 0) {
+                                               // access to static property
+                                               string member = source.Substring (start2, end - start2);
+                                       } else {
+                                               // access to static method
+                                               string member = source.Substring (start2, idx2 - start2);
+                                       }
+                               } else {
+                                       // property access without member specification
+                                       return new PropertyAccessExpression () {
+                                               Access = new PropertyAccess () {
+                                                       Name = new NameToken () { Name = source.Substring (start, end - start) },
+                                                       TargetType = PropertyTargetType.Object
+                                                       }
+                                               };
+                               }
+                       }
+               }
+               
+               ItemAccessExpression EvaluateItemExpression (string source, ExpressionValidationType validationType, 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);
+                               return new ItemAccessExpression () {
+                                       Application = new ItemApplication () {
+                                               Name = new NameToken () { Name = name },
+                                               Expressions = Parse (source, validationType, idx, end - idx)
+                                               }
+                                       };
+                       } else {
+                               string name = source.Substring (start, end - start);
+                               return new ItemAccessExpression () {
+                                       Application = new ItemApplication () { Name = new NameToken () { Name = name } }
+                                       };
+                       }
+                       
+                       throw new NotImplementedException ();
+               }
+               
+               MetadataAccessExpression EvaluateMetadataExpression (string source, ExpressionValidationType validatioType, int start, int end)
+               {
+                       throw new NotImplementedException ();
+               }
+       }
+}
+
index ae3b176d4a9fed640e0dccc699d4bb1fc44452b1..98a277a92a74bf6d1151bb19be9377b6013a31f6 100644 (file)
@@ -1,22 +1,43 @@
 using System;
+using System.Collections.Generic;
 using Microsoft.Build.Evaluation;
 
 namespace Microsoft.Build.Internal
 {
+       enum ExpressionValidationType
+       {
+               LaxString,
+               StrictBoolean,
+       }
+       
+       enum TokenizerMode
+       {
+               Default,
+               InsideItemOrProperty,
+       }
+       
        class ExpressionTokenizer : yyParser.yyInput
        {
-               public ExpressionTokenizer (string source)
+               public ExpressionTokenizer (string source, ExpressionValidationType validationType)
                {
                        this.source = source;
                        current_token_position = -1;
+                       validation_type = validationType;
+                       modes.Push (TokenizerMode.Default);
                }
                
                string source;
+               ExpressionValidationType validation_type;
                
                int current_token;
                string error;
                int pos, current_token_position;
                object token_value;
+               Stack<TokenizerMode> modes = new Stack<TokenizerMode> ();
+
+               TokenizerMode CurrentTokenizerMode {
+                       get { return modes.Peek (); }
+               }
 
                public bool advance ()
                {
@@ -29,106 +50,117 @@ namespace Microsoft.Build.Internal
 
                        switch (source [pos++]) {
                        case '.':
-                               current_token = Token.DOT;
+                               TokenForItemPropertyValue (".", Token.DOT);
                                break;
                        case ',':
-                               current_token = Token.COMMA;
+                               TokenForItemPropertyValue (",", Token.COMMA);
                                break;
                        case '(':
-                               current_token = Token.PAREN_OPEN;
+                               TokenForItemPropertyValue ("(", Token.PAREN_OPEN);
                                break;
                        case ')':
-                               current_token = Token.PAREN_CLOSE;
+                               if (modes.Count > 0) {
+                                       modes.Pop ();
+                                       current_token = Token.PAREN_CLOSE;
+                               } else {
+                                       token_value = ")";
+                                       current_token = Token.NAME;
+                               }
+                               break;
+                       case '"':
+                               current_token = Token.QUOT;
+                               break;
+                       case '\'':
+                               current_token = Token.APOS;
                                break;
                        case '-':
-                               if (pos < source.Length && source [pos] == '>') {
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == '>') {
                                        current_token = Token.ARROW;
                                        pos++;
-                               } else {
-                                       current_token = Token.ERROR;
-                                       error = "'-' is not followed by '>'.";
-                               }
+                               } else
+                                       ErrorOnStrictBoolean ("-", "'-' is not followed by '>'.");
                                break;
                        case '=':
-                               if (pos < source.Length && source [pos] == '=') {
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == '=') {
                                        current_token = Token.EQ;
                                        pos++;
-                               } else {
-                                       current_token = Token.ERROR;
-                                       error = "'=' is not followed by '='.";
-                               }
+                               } else
+                                       ErrorOnStrictBoolean ("=", "'=' is not followed by '='.");
                                break;
                        case ':':
-                               if (pos < source.Length && source [pos] == ':') {
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty && pos < source.Length && source [pos] == ':') {
                                        current_token = Token.COLON2;
                                        pos++;
-                               } else {
-                                       current_token = Token.ERROR;
-                                       error = "':' is not followed by ':'.";
-                               }
+                               } else
+                                       ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
                                break;
                        case '!':
-                               if (pos < source.Length && source [pos] == '=') {
-                                       pos++;
-                                       current_token = Token.NE;
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
+                                       if (pos < source.Length && source [pos] == '=') {
+                                               pos++;
+                                               current_token = Token.NE;
+                                       } else
+                                               TokenForItemPropertyValue ("!", Token.NOT);
                                }
                                else
-                                       current_token = Token.NOT;
+                                       TokenForItemPropertyValue ("!", Token.NOT);
                                break;
                        case '>':
-                               if (pos < source.Length && source [pos] == '=') {
-                                       pos++;
-                                       current_token = Token.GE;
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
+                                       if (pos < source.Length && source [pos] == '=') {
+                                               pos++;
+                                               current_token = Token.GE;
+                                       }
+                                       else
+                                               current_token = Token.GT;
                                }
                                else
-                                       current_token = Token.GT;
+                                       TokenForItemPropertyValue (">", Token.GT);
                                break;
                        case '<':
-                               if (pos < source.Length && source [pos] == '=') {
-                                       pos++;
-                                       current_token = Token.LE;
+                               if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty) {
+                                       if (pos < source.Length && source [pos] == '=') {
+                                               pos++;
+                                               current_token = Token.LE;
+                                       }
+                                       else
+                                               current_token = Token.LT;
                                }
                                else
-                                       current_token = Token.LT;
+                                       TokenForItemPropertyValue ("<", Token.LT);
                                break;
                        case '$':
                                if (pos < source.Length && source [pos] == '(') {
+                                       modes.Push (TokenizerMode.InsideItemOrProperty);
                                        current_token = Token.PROP_OPEN;
                                        pos++;
-                               } else {
-                                       current_token = Token.ERROR;
-                                       error = "property reference '$' is not followed by '('.";
                                }
+                               else
+                                       ErrorOnStrictBoolean ("$", "property reference '$' is not followed by '('.");
                                break;
                        case '@':
                                if (pos < source.Length && source [pos] == '(') {
+                                       modes.Push (TokenizerMode.InsideItemOrProperty);
                                        current_token = Token.ITEM_OPEN;
                                        pos++;
-                               } else {
-                                       current_token = Token.ERROR;
-                                       error = "item reference '@' is not followed by '('.";
                                }
+                               else
+                                       ErrorOnStrictBoolean ("@", "item reference '@' is not followed by '('.");
                                break;
                        case '%':
                                if (pos < source.Length && source [pos] == '(') {
+                                       modes.Push (TokenizerMode.InsideItemOrProperty);
                                        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, '\'');
+                               else
+                                       ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
                                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);
+                               var val = source.Substring (current_token_position, pos - current_token_position);
                                if (val.Equals ("AND", StringComparison.OrdinalIgnoreCase))
                                        current_token = Token.AND;
                                else if (val.Equals ("OR", StringComparison.OrdinalIgnoreCase))
@@ -144,8 +176,15 @@ namespace Microsoft.Build.Internal
                                }
                                break;
                        }
+                       while (pos < source.Length) {
+                               if (spaces.IndexOf (source [pos]) > 0)
+                                       pos++;
+                               else
+                                       break;
+                       }
                        return true;
                }
+               string spaces = " \t\r\n";
 
                static readonly char [] token_starter_chars = ".,)-=:!><$@%\"' ".ToCharArray ();
                
@@ -153,16 +192,36 @@ namespace Microsoft.Build.Internal
                {
                        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 {
+                       if (source [pos - 1] != c)
+                               ErrorOnStrictBoolean (c.ToString (), 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);
                        }
                }
                
+               void TokenForItemPropertyValue (string value, int token)
+               {
+                       if (CurrentTokenizerMode == TokenizerMode.InsideItemOrProperty)
+                               current_token = token;
+                       else {
+                               current_token = Token.NAME;
+                               token_value = value;
+                       }
+               }
+               
+               void ErrorOnStrictBoolean (string value, string message)
+               {
+                       if (validation_type == ExpressionValidationType.StrictBoolean) {
+                               current_token = Token.ERROR;
+                               error = message;
+                       } else {
+                               current_token = Token.NAME;
+                               token_value = value;
+                       }
+               }
+               
                public int token ()
                {
                        return current_token;
@@ -194,6 +253,8 @@ namespace Microsoft.Build.Internal
                //int Line { get; }
                int Column { get; }
                //string File { get; }
+               
+               string ToLocationString ();
        }
 
        class Location : ILocation
@@ -201,5 +262,10 @@ namespace Microsoft.Build.Internal
                //public int Line { get; set; }
                public int Column { get; set; }
                //public string File { get; set; }
+               
+               public string ToLocationString ()
+               {
+                       return "at " + Column;
+               }
        }
 }
index e2c58186bf2e5405409be8caffcd273a2cbd2109..77990df09d90dfd613f8d017f97d7f2ed6eca206 100644 (file)
@@ -73,6 +73,8 @@ Microsoft.Build.Execution/TargetResult.cs
 Microsoft.Build.Execution/TargetResultCode.cs
 Microsoft.Build.Internal/CollectionFromEnumerable.cs
 Microsoft.Build.Internal/ExpressionConstructs.cs
+Microsoft.Build.Internal/ExpressionEvaluator.cs
+Microsoft.Build.Internal/ExpressionParserManual.cs
 Microsoft.Build.Internal/ExpressionTokenizer.cs
 Microsoft.Build.Internal/FilteredEnumerable.cs
 Microsoft.Build.Internal/ReverseEnumerable.cs
index f32793c41856abb51c9bdb320ed0eaa763f5b860..116d3d81b07884328d8b07fae8c88877aa18d5ad 100644 (file)
@@ -94,7 +94,7 @@ namespace MonoTests.Microsoft.Build.Evaluation
                }
                
                [Test]
-               [Category ("NotWorking")]
+               //[Category ("NotWorking")]
                public void ExpandString ()
                {
                        string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>