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
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;
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)
// 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)
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 ()
}
public ICollection<ProjectItem> AllEvaluatedItems {
- get { throw new NotImplementedException (); }
+ get { return all_evaluated_items; }
}
public ICollection<ProjectProperty> AllEvaluatedProperties {
}
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 {
using System;
using System.Linq;
using Microsoft.Build.Construction;
+using Microsoft.Build.Internal;
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; }
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
: base (project, propertyType, xml.Name)
{
this.xml = xml;
+ UpdateEvaluatedValue ();
}
ProjectPropertyElement xml;
: base (project, PropertyType.Environment, name)
{
this.value = value;
+ UpdateEvaluatedValue ();
}
readonly string value;
: base (project, PropertyType.Global, name)
{
this.value = value;
+ UpdateEvaluatedValue ();
}
readonly string value;
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 ()
{
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; }
//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; }
}
public ExpressionList Expressions { get; set; }
}
- class MetadataAccessExpression : Expression
+ partial class MetadataAccessExpression : Expression
{
public MetadataAccess Access { get; set; }
}
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; }
--- /dev/null
+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 ();
+ }
+ }
+}
+
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
;
{ $$ = 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
--- /dev/null
+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 ();
+ }
+ }
+}
+
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 ()
{
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))
}
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 ();
{
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;
//int Line { get; }
int Column { get; }
//string File { get; }
+
+ string ToLocationString ();
}
class Location : ILocation
//public int Line { get; set; }
public int Column { get; set; }
//public string File { get; set; }
+
+ public string ToLocationString ()
+ {
+ return "at " + Column;
+ }
}
}
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
}
[Test]
- [Category ("NotWorking")]
+ //[Category ("NotWorking")]
public void ExpandString ()
{
string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>