2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="CqlParserHelpers.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
// @backupOwner Microsoft
2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
namespace System.Data.Common.EntitySql
{
using System ;
using System.Collections.Generic ;
using System.Data.Common.EntitySql.AST ;
using System.Data.Entity ;
using System.Globalization ;
/// <summary>
/// Represents the Cql Parser engine. Also, implements helpers and util routines.
/// </summary>
internal sealed partial class CqlParser
{
private Node _parsedTree ;
private CqlLexer _lexer ;
private string _query ;
private ParserOptions _parserOptions ;
private const string _internalYaccSyntaxErrorMessage = "syntax error" ;
/// <summary>
/// Contains inclusive count of method expressions.
/// </summary>
private uint _methodExprCounter ;
private Stack < uint > _methodExprCounterStack ;
private string _version = YYMAJOR . ToString ( NumberFormatInfo . InvariantInfo ) + '.' + YYMINOR . ToString ( NumberFormatInfo . InvariantInfo ) ;
internal CqlParser ( ParserOptions parserOptions , bool debug )
{
// The common practice is to make the null check at the public surface,
// however this method is a convergence zone from multiple public entry points and it makes sense to
// check for null once, here.
EntityUtil . CheckArgumentNull ( parserOptions , "parserOptions" ) ;
_parserOptions = parserOptions ;
yydebug = debug ;
}
/// <summary>
/// Main entry point for parsing cql.
/// </summary>
/// <param name="query">query text</param>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic rules are violated and the query cannot be accepted</exception>
/// <returns>Abstract Syntax Tree</returns>
internal Node Parse ( string query )
{
// The common practice is to make the null check at the public surface,
// however this method is a convergence zone from multiple public entry points and it makes sense to
// check for null once, here.
EntityUtil . CheckArgumentNull ( query , "query" ) ;
if ( String . IsNullOrEmpty ( query ) | | query . Trim ( ) . Length = = 0 )
{
throw EntityUtil . Argument ( System . Data . Entity . Strings . InvalidEmptyQueryTextArgument ) ;
}
_query = query ;
_parsedTree = null ;
_methodExprCounter = 0 ;
_methodExprCounterStack = new Stack < uint > ( ) ;
internalParseEntryPoint ( ) ;
return _parsedTree ;
}
/// <summary>
/// Returns query string
/// </summary>
internal string Query
{
get { return _query ; }
}
#if ENTITYSQL_PARSER_YYDEBUG
/// <summary>
/// Enables/Disables yacc debugging.
/// </summary>
internal bool EnableDebug
{
get { return yydebug ; }
set { yydebug = value ; }
}
#endif
/// <summary>
/// Returns ParserOptions used
/// </summary>
/// <remarks>Once parse has been invoked, ParserOptions are frozen and cannot be changed. otherwise a EntityException exception will be thrown</remarks>
internal ParserOptions ParserOptions
{
get { return _parserOptions ; }
}
/// <summary>
/// Internal entry point
/// </summary>
private void internalParseEntryPoint ( )
{
_lexer = new CqlLexer ( Query , ParserOptions ) ;
#if ENTITYSQL_PARSER_YYDEBUG
CqlLexer . Token tk = lexer . yylex ( ) ;
while ( null ! = tk )
{
Console . WriteLine ( "{0} := {1}" , tk . TokenId , lexer . yytext ( ) ) ;
tk = lexer . yylex ( ) ;
}
#endif
yyparse ( ) ;
}
//
// Conversion/Cast/Helpers
//
private static Node AstNode ( object o ) { return ( ( Node ) o ) ; }
private static int AstNodePos ( object o ) { return ( ( Node ) o ) . ErrCtx . InputPosition ; }
private static CqlLexer . TerminalToken Terminal ( object o ) { return ( ( CqlLexer . TerminalToken ) o ) ; }
private static int TerminalPos ( object o ) { return ( ( CqlLexer . TerminalToken ) o ) . IPos ; }
private static NodeList < T > ToNodeList < T > ( object o ) where T : Node { return ( ( NodeList < T > ) o ) ; }
private short yylex ( )
{
CqlLexer . Token token = null ;
token = _lexer . yylex ( ) ;
if ( null = = token )
{
return 0 ;
}
_lexer . AdvanceIPos ( ) ;
yylval = token . Value ;
return token . TokenId ;
}
private void yyerror_stackoverflow ( )
{
yyerror ( System . Data . Entity . Strings . StackOverflowInParser ) ;
}
private void yyerror ( string s )
{
if ( s . Equals ( _internalYaccSyntaxErrorMessage , StringComparison . Ordinal ) )
{
int errorPosition = _lexer . IPos ;
string syntaxContextInfo = null ;
string term = _lexer . YYText ;
if ( ! String . IsNullOrEmpty ( term ) )
{
syntaxContextInfo = System . Data . Entity . Strings . LocalizedTerm ;
ErrorContext errCtx = null ;
Node astNode = yylval as Node ;
if ( null ! = astNode & & ( null ! = astNode . ErrCtx ) & & ( ! String . IsNullOrEmpty ( astNode . ErrCtx . ErrorContextInfo ) ) )
{
errCtx = astNode . ErrCtx ;
errorPosition = Math . Min ( errorPosition , errorPosition - term . Length ) ;
}
if ( ( yylval is CqlLexer . TerminalToken ) & & CqlLexer . IsReservedKeyword ( term ) & & ! ( astNode is Identifier ) )
{
syntaxContextInfo = System . Data . Entity . Strings . LocalizedKeyword ;
term = term . ToUpperInvariant ( ) ;
errorPosition = Math . Min ( errorPosition , errorPosition - term . Length ) ;
}
else if ( null ! = errCtx )
{
syntaxContextInfo = EntityRes . GetString ( errCtx . ErrorContextInfo ) ;
}
syntaxContextInfo = String . Format ( CultureInfo . CurrentCulture , "{0} '{1}'" , syntaxContextInfo , term ) ;
}
throw EntityUtil . EntitySqlError ( _query ,
System . Data . Entity . Strings . GenericSyntaxError ,
errorPosition ,
syntaxContextInfo ,
false /* loadErrorContextInfoFromResource */ ) ;
}
throw EntityUtil . EntitySqlError ( _query , s , _lexer . IPos ) ;
}
//
// Error tracking helpers
//
private void SetErrCtx ( Node astExpr , CqlLexer . TerminalToken tokenValue , string info )
{
SetErrCtx ( astExpr , tokenValue . IPos , info ) ;
}
private void SetErrCtx ( Node astExpr , int inputPos , string info )
{
astExpr . ErrCtx . InputPosition = inputPos ;
astExpr . ErrCtx . ErrorContextInfo = info ;
astExpr . ErrCtx . CommandText = _query ;
}
private void StartMethodExprCounting ( )
{
// Save the current counter value.
_methodExprCounterStack . Push ( _methodExprCounter ) ;
// Reset the counter for the current level.
_methodExprCounter = 0 ;
}
private void IncrementMethodExprCount ( )
{
+ + _methodExprCounter ;
}
private uint EndMethodExprCounting ( )
{
// Save number of method expressions on the current level.
uint count = _methodExprCounter ;
// Restore upper level counter and adjust it with the number of method expressions on the current level.
_methodExprCounter + = _methodExprCounterStack . Pop ( ) ;
return count ;
}
}
}