Jo Shields fe777c5c82 Imported Upstream version 3.8.0
Former-commit-id: 6a76a29bd07d86e57c6c8da45c65ed5447d38a61
2014-09-04 09:07:35 +01:00

305 lines
11 KiB
C#

//
// ExpressionParserManual.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 System.Linq;
using Microsoft.Build.Exceptions;
namespace Microsoft.Build.Internal.Expressions
{
class ExpressionParserManual
{
// FIXME: we are going to not need ExpressionValidationType for this; always LaxString.
public ExpressionParserManual (string source, ExpressionValidationType validationType)
{
if (source == null)
throw new ArgumentNullException ("source");
this.source = source;
validation_type = validationType;
}
string source;
ExpressionValidationType validation_type;
public ExpressionList Parse ()
{
return Parse (0, source.Length);
}
ExpressionList Parse (int start, int end)
{
if (string.IsNullOrWhiteSpace (source))
return new ExpressionList ();
var ret = new ExpressionList ();
while (start < end) {
int bak = start;
ret.Add (ParseSingle (ref start, end));
if (bak == start)
throw new Exception ("Parser failed to progress token position: " + source);
}
return ret;
}
static readonly char [] token_starters = "$@%(),'\"".ToCharArray ();
Expression ParseSingle (ref int start, int end)
{
char token = source [start];
switch (token) {
case '$':
case '@':
case '%':
if (start == end || start + 1 == source.Length || source [start + 1] != '(') {
if (validation_type == 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
}
start += 2;
int last = FindMatchingCloseParen (start, end);
if (last < 0) {
if (validation_type == ExpressionValidationType.StrictBoolean)
throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
else {
start -= 2;
goto default; // treat as raw literal to the section end
}
}
Expression ret;
if (token == '$')
ret = EvaluatePropertyExpression (start, last);
else if (token == '%')
ret = EvaluateMetadataExpression (start, last);
else
ret = EvaluateItemExpression (start, last);
start = last + 1;
return ret;
case '\'':
case '"':
var quoteChar = source [start];
start++;
last = FindMatchingCloseQuote (quoteChar, start, end);
if (last < 0) {
if (validation_type == ExpressionValidationType.StrictBoolean)
throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
else {
start--;
goto default; // treat as raw literal to the section end
}
}
ret = new QuotedExpression () { QuoteChar = quoteChar, Contents = Parse (start, last) };
start = last + 1;
return ret;
// Below (until default) are important only for Condition evaluation
case '(':
if (validation_type == ExpressionValidationType.LaxString)
goto default;
start++;
last = FindMatchingCloseParen (start, end);
if (last < 0) {
if (validation_type == ExpressionValidationType.StrictBoolean)
throw new InvalidProjectFileException (string.Format ("expression did not have matching ')' since index {0} in \"{1}\"", start, source));
else {
start--;
goto default; // treat as raw literal to the section end
}
}
var contents = Parse (start, last).ToArray ();
if (contents.Length > 1)
throw new InvalidProjectFileException (string.Format ("unexpected continuous expression within (){0} in \"{1}\"", contents [1].Column > 0 ? " at " + contents [1].Column : null, source));
return contents.First ();
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 };
ret = new RawStringLiteral () { Value = val };
if (idx >= 0)
start = idx;
else
start = end;
return ret;
}
}
int FindMatchingCloseParen (int start, int end)
{
int n = 0;
for (int i = start; i < end; i++) {
if (source [i] == '(')
n++;
else if (source [i] == ')') {
if (n-- == 0)
return i;
}
}
return -1; // invalid
}
int FindMatchingCloseQuote (char quote, int start, int end)
{
int n = 0;
for (int i = start; i < end; i++) {
if (i < end + 1 && source [i] == '\\' && (source [i + 1] == quote || source [i + 1] == '\\'))
n += 2;
else if (source [i] == quote) {
if (n-- == 0)
return i;
}
}
return -1; // invalid
}
static readonly string spaces = " \t\r\n";
void SkipSpaces (ref int start)
{
while (start < source.Length && spaces.Contains (source [start]))
start++;
}
PropertyAccessExpression EvaluatePropertyExpression (int start, int end)
{
// member access
int dotAt = source.LastIndexOf ('.', end, end - start);
int colonsAt = source.LastIndexOf ("::", end, end - start, StringComparison.Ordinal);
if (dotAt < 0 && colonsAt < 0) {
// property access without member specification
int parenAt = source.IndexOf ('(', start, end - start);
string name = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
name = name.Trim ();
var access = new PropertyAccess () {
Name = new NameToken () { Name = name },
TargetType = PropertyTargetType.Object
};
if (parenAt > 0) { // method arguments
start = parenAt + 1;
access.Arguments = ParseFunctionArguments (ref start, end);
}
return new PropertyAccessExpression () { Access = access };
}
if (colonsAt < 0 || colonsAt < dotAt) {
// property access with member specification
int mstart = dotAt + 1;
int parenAt = source.IndexOf ('(', mstart, end - mstart);
string name = parenAt < 0 ? source.Substring (mstart, end - mstart) : source.Substring (mstart, parenAt - mstart);
name = name.Trim ();
var access = new PropertyAccess () {
Name = new NameToken () { Name = name },
TargetType = PropertyTargetType.Object,
Target = dotAt < 0 ? null : Parse (start, dotAt).FirstOrDefault ()
};
if (parenAt > 0) { // method arguments
start = parenAt + 1;
access.Arguments = ParseFunctionArguments (ref start, end);
}
return new PropertyAccessExpression () { Access = access };
} else {
// static type access
string type = source.Substring (start, colonsAt - 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));
type = type.Substring (1, type.Length - 2).Trim ();
start = colonsAt + 2;
int parenAt = source.IndexOf ('(', start, end - start);
string member = parenAt < 0 ? source.Substring (start, end - start) : source.Substring (start, parenAt - start);
member = member.Trim ();
if (member.Length == 0)
throw new InvalidProjectFileException ("Static member name is missing");
var access = new PropertyAccess () {
Name = new NameToken () { Name = member },
TargetType = PropertyTargetType.Type,
Target = new StringLiteral () { Value = new NameToken () { Name = type } }
};
if (parenAt > 0) { // method arguments
start = parenAt + 1;
access.Arguments = ParseFunctionArguments (ref start, end);
}
return new PropertyAccessExpression () { Access = access };
}
}
ExpressionList ParseFunctionArguments (ref int start, int end)
{
var args = new ExpressionList ();
do {
SkipSpaces (ref start);
if (start == source.Length)
throw new InvalidProjectFileException ("unterminated function call arguments.");
if (source [start] == ')')
break;
else if (args.Any ()) {
if (source [start] != ',')
throw new InvalidProjectFileException (string.Format ("invalid function call arguments specification. ',' is expected, got '{0}'", source [start]));
start++;
SkipSpaces (ref start);
}
args.Add (ParseSingle (ref start, end));
} while (true);
start++;
return args;
}
ItemAccessExpression EvaluateItemExpression (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 (idx + 2, end)
}
};
} else {
string name = source.Substring (start, end - start);
return new ItemAccessExpression () {
Application = new ItemApplication () { Name = new NameToken () { Name = name } }
};
}
}
MetadataAccessExpression EvaluateMetadataExpression (int start, int end)
{
int idx = source.IndexOf ('.', start, end - start);
string item = idx < 0 ? null : source.Substring (start, idx - start);
string meta = idx < 0 ? source.Substring (start, end - start) : source.Substring (idx + 1, end - idx - 1);
var access = new MetadataAccess () {
ItemType = item == null ? null : new NameToken () { Column = start, Name = item },
Metadata = new NameToken () { Column = idx < 0 ? start : idx + 1, Name = meta }
};
return new MetadataAccessExpression () { Access = access };
}
}
}