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);
            }
        }
		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;
			}
		}