fe777c5c82
Former-commit-id: 6a76a29bd07d86e57c6c8da45c65ed5447d38a61
305 lines
11 KiB
C#
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 };
|
|
}
|
|
}
|
|
}
|
|
|