+//
+// ExpressionTokenizer.cs
+//
+// Author:
+// Atsushi Enomoto (atsushi@xamarin.com)
+//
+// Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
using System;
+using System.Collections.Generic;
using Microsoft.Build.Evaluation;
-namespace Microsoft.Build.Internal
+namespace Microsoft.Build.Internal.Expressions
{
+ 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 ()
{
error = null;
token_value = null;
+
+ while (pos < source.Length) {
+ if (spaces.IndexOf (source [pos]) >= 0)
+ pos++;
+ else
+ break;
+ }
+ if (pos == source.Length)
+ return false;
current_token_position = pos;
switch (source [pos++]) {
case '.':
- current_token = Token.DOT;
+ TokenForItemPropertyValue (".", Token.DOT);
break;
case ',':
- current_token = Token.COMMA;
+ TokenForItemPropertyValue (",", Token.COMMA);
+ break;
+ case '[':
+ TokenForItemPropertyValue ("[", Token.BRACE_OPEN);
+ break;
+ case ']':
+ TokenForItemPropertyValue ("]", Token.BRACE_CLOSE);
break;
case '(':
- current_token = Token.PAREN_OPEN;
+ modes.Push (TokenizerMode.Default);
+ 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 '-':
if (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] == '=') {
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] == ':') {
current_token = Token.COLON2;
- pos++;
- } else {
- current_token = Token.ERROR;
- error = "':' is not followed by ':'.";
- }
+ } else
+ ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
+ pos++;
break;
case '!':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.NE;
- }
- else
- current_token = Token.NOT;
+ } else
+ TokenForItemPropertyValue ("!", Token.NOT);
break;
case '>':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.GE;
- }
- else
+ } else
current_token = Token.GT;
break;
case '<':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.LE;
- }
- else
+ } else
current_token = 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 '('.";
- }
+ } else
+ ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
break;
case '"':
- ReadStringLiteral (source, '"');
- break;
case '\'':
- ReadStringLiteral (source, '\'');
+ pos = source.IndexOf (source [pos - 1], pos);
+ if (pos < 0) {
+ ErrorOnStrictBoolean ("'", "unterminated string literal");
+ pos = source.Length;
+ }
+ token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1);
+ current_token = Token.STRING_LITERAL;
+ pos++;
break;
default:
pos = source.IndexOfAny (token_starter_chars, pos);
if (pos < 0)
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))
current_token = Token.TRUE_LITERAL;
else if (val.Equals ("FALSE", StringComparison.OrdinalIgnoreCase))
current_token = Token.FALSE_LITERAL;
+ else if (val.Equals ("YES", StringComparison.OrdinalIgnoreCase))
+ current_token = Token.TRUE_LITERAL;
+ else if (val.Equals ("NO", StringComparison.OrdinalIgnoreCase))
+ current_token = Token.FALSE_LITERAL;
+ else if (val.Equals ("ON", StringComparison.OrdinalIgnoreCase))
+ current_token = Token.TRUE_LITERAL;
+ else if (val.Equals ("OFF", StringComparison.OrdinalIgnoreCase))
+ current_token = Token.FALSE_LITERAL;
else {
current_token = Token.NAME;
token_value = ProjectCollection.Unescape (val);
}
return true;
}
+ string spaces = " \t\r\n";
- static readonly char [] token_starter_chars = ".,)-=:!><$@%\"' ".ToCharArray ();
+ static readonly char [] token_starter_chars = ".,[]()-=:!><$@%\"' ".ToCharArray ();
void ReadStringLiteral (string source, char c)
{
while (pos < source.Length && source [pos] != c)
pos++;
- if (source [pos - 1] != c) {
- current_token = Token.ERROR;
- error = string.Format ("missing string literal terminator [{0}]", c);
- } else {
+ 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 (true)//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;
public object value ()
{
- if (current_token == Token.NAME)
+ if (current_token == Token.NAME || current_token == Token.STRING_LITERAL)
return new NameToken () { Name = (string) token_value, Column = current_token_position };
else if (error != null)
return new ErrorToken () { Message = error, Column = current_token_position };
class NameToken : Location
{
public string Name { get; set; }
+
+ public override string ToString ()
+ {
+ return string.Format ("[NameToken: Value={0}]", Name);
+ }
}
class ErrorToken : Location
{
//int Line { get; }
int Column { get; }
- //string File { 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 File { get; set; }
+
+ public string ToLocationString ()
+ {
+ return "at " + Column;
+ }
}
}