Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

310 lines
8.5 KiB
C#

//
// 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.Expressions
{
enum ExpressionValidationType
{
LaxString,
StrictBoolean,
}
enum TokenizerMode
{
Default,
InsideItemOrProperty,
}
class ExpressionTokenizer : yyParser.yyInput
{
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 ()
{
if (pos == source.Length)
return false;
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 '.':
TokenForItemPropertyValue (".", Token.DOT);
break;
case ',':
TokenForItemPropertyValue (",", Token.COMMA);
break;
case '[':
TokenForItemPropertyValue ("[", Token.BRACE_OPEN);
break;
case ']':
TokenForItemPropertyValue ("]", Token.BRACE_CLOSE);
break;
case '(':
modes.Push (TokenizerMode.Default);
TokenForItemPropertyValue ("(", Token.PAREN_OPEN);
break;
case ')':
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
ErrorOnStrictBoolean ("-", "'-' is not followed by '>'.");
break;
case '=':
if (pos < source.Length && source [pos] == '=') {
current_token = Token.EQ;
pos++;
} else
ErrorOnStrictBoolean ("=", "'=' is not followed by '='.");
break;
case ':':
if (pos < source.Length && source [pos] == ':') {
current_token = Token.COLON2;
} else
ErrorOnStrictBoolean (":", "':' is not followed by ':'.");
pos++;
break;
case '!':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.NE;
} else
TokenForItemPropertyValue ("!", Token.NOT);
break;
case '>':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.GE;
} else
current_token = Token.GT;
break;
case '<':
if (pos < source.Length && source [pos] == '=') {
pos++;
current_token = Token.LE;
} else
current_token = Token.LT;
break;
case '$':
if (pos < source.Length && source [pos] == '(') {
modes.Push (TokenizerMode.InsideItemOrProperty);
current_token = Token.PROP_OPEN;
pos++;
} else
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
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
ErrorOnStrictBoolean ("%", "metadata reference '%' is not followed by '('.");
break;
case '"':
case '\'':
pos = source.IndexOf (source [pos - 1], pos);
if (pos < 0) {
ErrorOnStrictBoolean ("'", "unterminated string literal");
pos = source.Length;
}
token_value = source.Substring (current_token_position + 1, pos - current_token_position - 1);
current_token = Token.STRING_LITERAL;
pos++;
break;
default:
pos = source.IndexOfAny (token_starter_chars, pos);
if (pos < 0)
pos = source.Length;
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.OR;
else if (val.Equals ("TRUE", 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);
break;
}
break;
}
return true;
}
string spaces = " \t\r\n";
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)
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 || 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 };
else
return new Location () { 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
{
public string Message { get; set; }
}
interface ILocation
{
//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;
}
}
}