/*
 * This file is part of ADQLLibrary.
 * 
 * ADQLLibrary is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ADQLLibrary is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institute (ARI)
 */

/*
* This JavaCC file implements the BNF definition of ADQL v2.0
* (IVOA Recommendation 30 Oct 2008 - http://www.ivoa.net/Documents/cover/ADQL-20081030.html).
* 
* To generate the parser with this file use JavaCC. This .jj file has been
* successfully tested with JavaCC 6.0.
* 
* The generated parser checks the syntax of the given ADQL query and generates
* an object representation but no coherence with any database is done.
* If the syntax is not conform to the ADQL definition an error message is
* printed else it will be the message "Correct syntax".
*
*  Author:  Gr&eacute;gory Mantelet (CDS;ARI)
*  Version: 1.5-1 (07/2019)
*/

							/* ########### */
							/* # OPTIONS # */
							/* ########### */
options {
	STATIC = false;
	IGNORE_CASE = true;
	DEBUG_PARSER = true;
}

							/* ########## */
							/* # PARSER # */
							/* ########## */
PARSER_BEGIN(ADQLParser)

package adql.parser;

import java.util.Stack;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Collection;

import java.io.FileReader;
import java.io.IOException;

import adql.db.exception.UnresolvedIdentifiersException;

import adql.parser.IdentifierItems.IdentifierItem;

import adql.parser.ADQLQueryFactory.JoinType;

import adql.query.*;
import adql.query.from.*;
import adql.query.constraint.*;

import adql.query.operand.*;

import adql.query.operand.function.*;

import adql.query.operand.function.geometry.*;
import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;

import adql.translator.PostgreSQLTranslator;
import adql.translator.TranslationException;

/**
* Parses an ADQL query thanks to the {@link ADQLParser#Query()} function.
* 
* <p>
*   This parser is able, thanks to a {@link QueryChecker} object, to check each
*   {@link ADQLQuery} just after its generation. It could be used to check the
*   consistency between the ADQL query to parse and the "database" on which the
*   query must be executed. By default, there is no {@link QueryChecker}. Thus
*   you must extend {@link QueryChecker} to check semantically all generated
*   ADQLQuery objects.
* </p>
* 
* <p>
*   To create an object representation of the given ADQL query, this parser uses
*   a {@link ADQLQueryFactory} object. So if you want customize some object
*   (ie. CONTAINS) of this representation you just have to extend the
*   corresponding default object (ie. ContainsFunction) and to extend the
*   corresponding function of {@link ADQLQueryFactory}
*   (ie. createContains(...)).
* </p>
*
* <p>Here are the key functions to use:</p>
* <ul>
* 	<li>{@link #parseQuery(java.lang.String)} (or any of its alternatives)
* 		to parse an input ADQL query String and get its corresponding ADQL tree
*   </li>
*   <li>{@link #tryQuickFix(java.lang.String)} to try fixing the most common
* 		issues with ADQL queries (e.g. Unicode confusable characters,
* 		unescaped ADQL identifiers, SQL reserved keywords, ...)</li>
* </ul>
* 
* <p><b><u>WARNING:</u>
*   To modify this class it's strongly encouraged to modify the .jj file in the
*   section between <i>PARSER_BEGIN</i> and <i>PARSER_END</i> and to re-compile
*   it with JavaCC.
* </b></p>
*
* @see QueryChecker
* @see ADQLQueryFactory
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 1.5-1 (07/2019)
*/
public class ADQLParser {
	
	/** Tools to build the object representation of the ADQL query. */
	private ADQLQueryFactory queryFactory = new ADQLQueryFactory();
	
	/** The stack of queries (because there may be some sub-queries). */
	private Stack<ADQLQuery> stackQuery = new Stack<ADQLQuery>();
	
	/** The object representation of the ADQL query to parse.
	 * (ONLY USED DURING THE PARSING, else it is always <i>null</i>). */
	private ADQLQuery query = null;
	
	/** Checks each {@link ADQLQuery} (sub-query or not) just after their
	 * generation. */
	private QueryChecker queryChecker = null;
	
	/** The first token of a table/column name. This token is extracted by
	 * {@link #Identifier()}. */
	private Token currentIdentifierToken = null;
	
	/**
	* Builds an ADQL parser without a query to parse.
	*/
	public ADQLParser(){
		this(new java.io.ByteArrayInputStream("".getBytes()));
		setDebug(false);
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a
	* {@link QueryChecker} and a {@link ADQLQueryFactory}.
	*
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(QueryChecker checker, ADQLQueryFactory factory) {
		this();
		
		queryChecker = checker;
			
		if (factory != null)
			queryFactory = factory;
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a
	* {@link QueryChecker}.
	*
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(QueryChecker checker) {
		this(checker, null);
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a
	* {@link ADQLQueryFactory}.
	*
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(ADQLQueryFactory factory) {
		this((QueryChecker)null, factory);
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, QueryChecker checker, ADQLQueryFactory factory) {
		this(stream);
		setDebug(false);
		
		setDebug(false);
		
		queryChecker = checker;
		
		if (factory != null)
			queryFactory = factory;
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.InputStream stream, QueryChecker checker) {
		this(stream, checker, null);
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, ADQLQueryFactory factory) {
		this(stream, (QueryChecker)null, factory);
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, QueryChecker checker, ADQLQueryFactory factory) {
		this(stream, encoding);
		setDebug(false);
		
		queryChecker = checker;
		
		if (factory != null)
			queryFactory = factory;
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, QueryChecker checker) {
		this(stream, encoding, checker, null);
	}
	
	/**
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, ADQLQueryFactory factory) {
		this(stream, encoding, null, factory);
	}
	
	/**
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.Reader reader, QueryChecker checker, ADQLQueryFactory factory) {
		this(reader);
		setDebug(false);
		
		setDebug(false);
		
		queryChecker = checker;

		if (factory != null)
			queryFactory = factory;
	}
	
	/**
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.Reader reader, QueryChecker checker) {
		this(reader, checker, null);
	}
	
	/**
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.Reader reader, ADQLQueryFactory factory) {
		this(reader, null, factory);
	}
	
	/**
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param checker	The object to use to check each {@link ADQLQuery }.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(ADQLParserTokenManager tm, QueryChecker checker, ADQLQueryFactory factory) {
		this(tm);
		setDebug(false);
		
		setDebug(false);
		
		queryChecker = checker;

		if (factory != null)
			queryFactory = factory;
	}
	
	/**
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(ADQLParserTokenManager tm, QueryChecker checker) {
		this(tm, checker, null);
	}
	
	/**
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(ADQLParserTokenManager tm, ADQLQueryFactory factory) {
		this(tm, null, factory);
	}

	/* ADDITIONAL GETTERS & SETTERS */
	
	public final void setDebug(boolean debug){
		if (debug) enable_tracing();
		else       disable_tracing();
	}
	
	public final QueryChecker getQueryChecker(){
		return queryChecker;
	}
	
	public final void setQueryChecker(QueryChecker checker){
		queryChecker = checker;
	}
	
	public final ADQLQueryFactory getQueryFactory(){
		return queryFactory;
	}
	
	public final void setQueryFactory(ADQLQueryFactory factory){
		queryFactory = (factory!=null)?factory:(new ADQLQueryFactory());
	}

	/* EXCEPTION HELPER FUNCTION */
	
	private final ParseException generateParseException(Exception ex){
		if (!(ex instanceof ParseException)){
			ParseException pex = new ParseException("["+ex.getClass().getName()+"] "+ex.getMessage());
			pex.setStackTrace(ex.getStackTrace());
			return pex;
		}else
			return (ParseException)ex;
	}

	/* QUERY PARSING FUNCTIONS */

	/**
	 * Tell whether the given string is a valid ADQL regular identifier.
	 *
	 * <p>
	 * 	According to the ADQL-2.0's BNF, a regular identifier (i.e. not delimited
	 * 	; not between double quotes) must be a letter followed by a letter, digit
	 * 	or underscore. So, the following regular expression:
	 * </p>
	 * <pre>[a-zA-Z]+[a-zA-Z0-9_]*</pre>
	 *
	 * <p>This is what this function tests on the given string.</p>
	 *
	 * @param idCandidate	The string to test.
	 *
	 * @return	<code>true</code> if the given string is a valid regular
	 *        	identifier,
	 *        	<code>false</code> otherwise.
	 *
	 * @see #testRegularIdentifier(adql.parser.Token)
	 *
	 * @since 1.5
	 */
	public final boolean isRegularIdentifier(final String idCandidate) {
		return idCandidate.matches("[a-zA-Z]+[a-zA-Z0-9_]*");
	}

	/**
	 * Test the given token as an ADQL's regular identifier.
	 *
	 * <p>
	 * 	This function uses {@link #isRegularIdentifier(java.lang.String)} to
	 * 	test the given token's image. If the test fails, a
	 * 	{@link adql.parser.ParseException} is thrown.
	 * </p>
	 *
	 * @param token	The token to test.
	 *
	 * @throws ParseException	If the given token is not a valid ADQL regular
	 *                       	identifier.
	 *
	 * @see #isRegularIdentifier(java.lang.String)
	 *
	 * @since 1.5
	 */
	public final void testRegularIdentifier(final Token token) throws ParseException {
		if (!isRegularIdentifier(token.image))
			throw new ParseException("Invalid ADQL regular identifier: \""+token.image+"\"! If it aims to be a column/table name/alias, you should write it between double quotes.", new TextPosition(token));
	}
	
	/**
	* Parses the query given at the creation of this parser or in the
	* <i>ReInit</i> functions.
	*
	* @return 	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#Query()
	*/
	public final ADQLQuery parseQuery() throws ParseException {
		stackQuery.clear();
		query = null;
		try { 
			return Query();
		}catch(TokenMgrError tme) {
			throw new ParseException(tme);
		}
	}
	
	/**
	* Parses the query given in parameter.
	*
	* @param q	The ADQL query to parse.
	* 
	* @return	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#ReInit(java.io.InputStream)
	* @see ADQLParser#setDebug(boolean)
	* @see ADQLParser#Query()
	*/
	public final ADQLQuery parseQuery(String q) throws ParseException {
		stackQuery.clear();
		query = null;
		ReInit(new java.io.ByteArrayInputStream(q.getBytes()));
		try { 
			return Query();
		}catch(TokenMgrError tme) {
			throw new ParseException(tme);
		}
	}
	
	/**
	* Parses the query contained in the stream given in parameter.
	*
	* @param stream		The stream which contains the ADQL query to parse.
	* 
	* @return	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#ReInit(java.io.InputStream)
	* @see ADQLParser#setDebug(boolean)
	* @see ADQLParser#Query()
	*/
	public final ADQLQuery parseQuery(java.io.InputStream stream) throws ParseException {
		stackQuery.clear();
		query = null;
		ReInit(stream);
		try { 
			return Query();
		}catch(TokenMgrError tme) {
			throw new ParseException(tme);
		}
	}

	/* CORRECTION SUGGESTION */

	/**
	 * Try fixing tokens/terms of the input ADQL query.
	 *
	 * <p>
	 * 	<b>This function does not try to fix syntactical or semantical errors.</b>
	 *  It just try to fix the most common issues in ADQL queries, such as:
	 * </p>
	 * <ul>
	 * 	<li>some Unicode characters confusable with ASCII characters (like a
	 * 		space, a dash, ...) ; this function replace them by their ASCII
	 * 		alternative,</li>
	 * 	<li>any of the following are double quoted:
	 * 		<ul>
	 * 			<li>non regular ADQL identifiers
	 * 				(e.g. <code>_RAJ2000</code>),</li>
	 * 			<li>ADQL function names used as identifiers
	 * 				(e.g. <code>distance</code>)</li>
	 * 			<li>and SQL reserved keywords
	 * 				(e.g. <code>public</code>).</li>
	 * 		</ul>
	 * 	</li>
	 * </ul>
	 *
	 * <p><i><b>Note 1:</b>
	 * 	The given stream is NOT closed by this function even if the EOF is
	 * 	reached. It is the responsibility of the caller to close it.
	 * </i></p>
	 *
	 * <p><i><b>Note 2:</b>
	 * 	This function does not use any instance variable of this parser
	 * 	(especially the InputStream or Reader provided at initialisation or
	 * 	ReInit).
	 * </i></p>
	 *
	 * @param input	Stream containing the input ADQL query to fix.
	 *
	 * @return	The suggested correction of the input ADQL query.
	 *
	 * @throws java.io.IOException	If there is any error while reading from the
	 *                            	given input stream.
	 * @throws ParseException	If any unrecognised character is encountered,
	 *                       	or if anything else prevented the tokenization
	 *                       	   of some characters/words/terms.
	 *
	 * @see #tryQuickFix(java.lang.String)
	 *
	 * @since 1.5
	 */
	public final String tryQuickFix(final java.io.InputStream input) throws java.io.IOException, ParseException {
		// Fetch everything into a single string:
		StringBuffer buf = new StringBuffer();
		byte[] cBuf = new byte[1024];
		int nbChar;
		while((nbChar = input.read(cBuf)) > -1){
			buf.append(new String(cBuf, 0, nbChar));
		}
		
		// Convert the buffer into a String and now try to fix it:
		return tryQuickFix(buf.toString());
	}

	/**
	 * Try fixing tokens/terms of the given ADQL query.
	 *
	 * <p>
	 * 	<b>This function does not try to fix syntactical or semantical errors.</b>
	 *  It just try to fix the most common issues in ADQL queries, such as:
	 * </p>
	 * <ul>
	 * 	<li>some Unicode characters confusable with ASCII characters (like a
	 * 		space, a dash, ...) ; this function replace them by their ASCII
	 * 		alternative,</li>
	 * 	<li>any of the following are double quoted:
	 * 		<ul>
	 * 			<li>non regular ADQL identifiers
	 * 				(e.g. <code>_RAJ2000</code>),</li>
	 * 			<li>ADQL function names used as identifiers
	 * 				(e.g. <code>distance</code>)</li>
	 * 			<li>and SQL reserved keywords
	 * 				(e.g. <code>public</code>).</li>
	 * 		</ul>
	 * 	</li>
	 * </ul>
	 *
	 * <p><i><b>Note:</b>
	 * 	This function does not use any instance variable of this parser
	 * 	(especially the InputStream or Reader provided at initialisation or
	 * 	ReInit).
	 * </i></p>
	 *
	 * @param adqlQuery	The input ADQL query to fix.
	 *
	 * @return	The suggested correction of the given ADQL query.
	 *
	 * @throws ParseException	If any unrecognised character is encountered,
	 *                       	or if anything else prevented the tokenization
	 *                       	   of some characters/words/terms.
	 *
	 * @since 1.5
	 */
	public String tryQuickFix(String adqlQuery) throws ParseException {
		StringBuffer suggestedQuery = new StringBuffer();

		// 1. Replace all Unicode confusable characters:
		adqlQuery = replaceUnicodeConfusables(adqlQuery);

		/* 1.bis. Normalise new lines and tabulations
		 *        (to simplify the column counting): */
		adqlQuery = adqlQuery.replaceAll("(\r\n|\r|\n)", System.getProperty("line.separator")).replaceAll("\t", "    ");

		// 2. Analyse the query token by token:
		ADQLParserTokenManager parser = new ADQLParserTokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(adqlQuery.getBytes())));
		
		final String[] lines = adqlQuery.split(System.getProperty("line.separator"));

		try{
			String suggestedToken;
			int lastLine = 1, lastCol = 1;

			Token token = null, nextToken = parser.getNextToken();
			// for all tokens until the EOF or EOQ:
			do{
				// get the next token:
				token = nextToken;
				nextToken = (isEnd(token) ? null : parser.getNextToken());

				// 3. Double quote any suspect token:
				if (mustEscape(token, nextToken)){
					suggestedToken = "\"" + token.image + "\"";
				}else
					suggestedToken = token.image;

				/* 4. Append all space characters (and comments) before the
				 *    token: */
				/* same line, just get the space characters between the last
				 * token and the one to append: */
				if (lastLine == token.beginLine){
					if (token.kind == ADQLParserConstants.EOF)
						suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1));
					else
						suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1, token.beginColumn - (isEnd(token) ? 0 : 1)));
					lastCol = token.endColumn + 1;
				}
				// not the same line...
				else{
				    /* append all remaining space characters until the position
				     * of the token to append: */
					do{
						suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1)).append('\n');
						lastLine++;
						lastCol = 1;
					}while(lastLine < token.beginLine);
					/* if there are still space characters before the token,
					 * append them as well: */
					if (lastCol < token.beginColumn)
						suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1, token.beginColumn - 1));
					// finally, set the correct column position:
					lastCol = token.endColumn + 1;
				}

				// 5. Append the suggested token:
				suggestedQuery.append(suggestedToken);

			}while(!isEnd(token));

		}catch(TokenMgrError err){
		    // wrap such errors and propagate them:
			throw new ParseException(err);
		}

		return suggestedQuery.toString();
	}

	/**
	 * All of the most common Unicode confusable characters and their
	 * ASCII/UTF-8 alternative.
	 *
	 * <p>
	 * 	Keys of this map represent the ASCII character while the values are the
	 * 	regular expression for all possible Unicode alternatives.
	 * </p>
	 *
	 * <p><i><b>Note:</b>
	 * 	All of them have been listed using
	 * 	<a href="https://unicode.org/cldr/utility/confusables.jsp">Unicode Utilities: Confusables</a>.
	 * </i></p>
	 *
	 * @since 1.5
	 */
	protected final static java.util.Map<String, String> REGEX_UNICODE_CONFUSABLES = new java.util.HashMap<String, String>(10);
	/** Regular expression matching all Unicode alternatives for <code>-</code>.
	 * @since 1.5 */
	protected final static String REGEX_DASH         = "[\u002D\u02D7\u06D4\u2010\u2011\u2012\u2013\u2043\u2212\u2796\u2CBA\uFE58\u2014\u2015\u207B\u208B\u0096\u058A\uFE63\uFF0D]";
	/** Regular expression matching all Unicode alternatives for <code>_</code>.
	 * @since 1.5 */
	protected final static String REGEX_UNDERSCORE   = "[\u005F\u07FA\uFE4D\uFE4E\uFE4F]";
	/** Regular expression matching all Unicode alternatives for <code>'</code>.
	 * @since 1.5 */
	protected final static String REGEX_QUOTE        = "[\u0027\u0060\u00B4\u02B9\u02BB\u02BC\u02BD\u02BE\u02C8\u02CA\u02CB\u02F4\u0374\u0384\u055A\u055D\u05D9\u05F3\u07F4\u07F5\u144A\u16CC\u1FBD\u1FBF\u1FEF\u1FFD\u1FFE\u2018\u2019\u201B\u2032\u2035\uA78C\uFF07\uFF40]";
	/** Regular expression matching all Unicode alternatives for <code>"</code>.
	 * @since 1.5 */
	protected final static String REGEX_DOUBLE_QUOTE = "[\u02BA\u02DD\u02EE\u02F6\u05F2\u05F4\u1CD3\u201C\u201D\u201F\u2033\u2036\u3003\uFF02]";
	/** Regular expression matching all Unicode alternatives for <code>.</code>.
	 * @since 1.5 */
	protected final static String REGEX_STOP         = "[\u002E\u0660\u06F0\u0701\u0702\u2024\uA4F8\uA60E]";
	/** Regular expression matching all Unicode alternatives for <code>+</code>.
	 * @since 1.5 */
	protected final static String REGEX_PLUS         = "[\u002B\u16ED\u2795]";
	/** Regular expression matching all Unicode alternatives for <code> </code>.
	 * @since 1.5 */
	protected final static String REGEX_SPACE        = "[\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F]";
	/** Regular expression matching all Unicode alternatives for <code>&lt;</code>.
	 * @since 1.5 */
	protected final static String REGEX_LESS_THAN    = "[\u003C\u02C2\u1438\u16B2\u2039\u276E]";
	/** Regular expression matching all Unicode alternatives for <code>&gt;</code>.
	 * @since 1.5 */
	protected final static String REGEX_GREATER_THAN = "[\u003E\u02C3\u1433\u203A\u276F]";
	/** Regular expression matching all Unicode alternatives for <code>=</code>.
	 * @since 1.5 */
	protected final static String REGEX_EQUAL        = "[\u003D\u1400\u2E40\u30A0\uA4FF]";
	static {
		REGEX_UNICODE_CONFUSABLES.put("-", REGEX_DASH);
		REGEX_UNICODE_CONFUSABLES.put("_", REGEX_UNDERSCORE);
		REGEX_UNICODE_CONFUSABLES.put("'", REGEX_QUOTE);
		REGEX_UNICODE_CONFUSABLES.put("\"", REGEX_DOUBLE_QUOTE);
		REGEX_UNICODE_CONFUSABLES.put(".", REGEX_STOP);
		REGEX_UNICODE_CONFUSABLES.put("+", REGEX_PLUS);
		REGEX_UNICODE_CONFUSABLES.put(" ", REGEX_SPACE);
		REGEX_UNICODE_CONFUSABLES.put("<", REGEX_LESS_THAN);
		REGEX_UNICODE_CONFUSABLES.put(">", REGEX_GREATER_THAN);
		REGEX_UNICODE_CONFUSABLES.put("=", REGEX_EQUAL);
	}

	/**
	 * Replace all Unicode characters that can be confused with other ASCI/UTF-8
	 * characters (e.g. different spaces, dashes, ...) in their ASCII version.
	 *
	 * @param adqlQuery	The ADQL query string in which Unicode confusable
	 *                 	characters must be replaced.
	 *
	 * @return	The same query without the most common Unicode confusable
	 *        	characters.
	 *
	 * @since 1.5
	 */
	protected String replaceUnicodeConfusables(final String adqlQuery){
		String newAdqlQuery = adqlQuery;
		for(java.util.Map.Entry<String, String> confusable : REGEX_UNICODE_CONFUSABLES.entrySet())
			newAdqlQuery = newAdqlQuery.replaceAll(confusable.getValue(), confusable.getKey());
		return newAdqlQuery;
	}

	/**
	 * Tell whether the given token represents the end of an ADQL query.
	 *
	 * @param token	Token to analyze.
	 *
	 * @return	<code>true</code> if the given token represents a query end,
	 *        	<code>false</code> otherwise.
	 *
	 * @since 1.5
	 */
	protected boolean isEnd(final Token token){
		return token.kind == ADQLParserConstants.EOF || token.kind == ADQLParserConstants.EOQ;
	}

	/**
	 * Tell whether the given token must be double quoted.
	 *
	 * <p>
	 * 	This function considers all the following as terms to double quote:
	 * </p>
	 * <ul>
	 * 	<li>SQL reserved keywords</li>,
	 * 	<li>unrecognised regular identifiers (e.g. neither a delimited nor a
	 * 		valid ADQL regular identifier)</li>
	 * 	<li>and ADQL function name without a parameters list.</li>
	 * </ul>
	 *
	 * @param token		The token to analyze.
	 * @param nextToken	The following token. (useful to detect the start of a
	 *                 	function's parameters list)
	 *
	 * @return	<code>true</code> if the given token must be double quoted,
	 *        	<code>false</code> to keep it as provided.
	 *
	 * @since 1.5
	 */
	protected boolean mustEscape(final Token token, final Token nextToken){
		switch(token.kind){
			case ADQLParserConstants.SQL_RESERVED_WORD:
				return true;
			case ADQLParserConstants.REGULAR_IDENTIFIER_CANDIDATE:
				return !isRegularIdentifier(token.image);
			default:
				return isFunctionName(token) && (nextToken == null || nextToken.kind != ADQLParserConstants.LEFT_PAR);
		}
	}

	/**
	 * Tell whether the given token matches to an ADQL function name.
	 *
	 * @param token	The token to analyze.
	 *
	 * @return	<code>true</code> if the given token is an ADQL function name,
	 *        	<code>false</code> otherwise.
	 *
	 * @since 1.5
	 */
	protected boolean isFunctionName(final Token token){
		switch(token.kind){
			case ADQLParserConstants.COUNT:
			case ADQLParserConstants.EXISTS:
			case ADQLParserConstants.AVG:
			case ADQLParserConstants.MAX:
			case ADQLParserConstants.MIN:
			case ADQLParserConstants.SUM:
			case ADQLParserConstants.BOX:
			case ADQLParserConstants.CENTROID:
			case ADQLParserConstants.CIRCLE:
			case ADQLParserConstants.POINT:
			case ADQLParserConstants.POLYGON:
			case ADQLParserConstants.REGION:
			case ADQLParserConstants.CONTAINS:
			case ADQLParserConstants.INTERSECTS:
			case ADQLParserConstants.AREA:
			case ADQLParserConstants.COORD1:
			case ADQLParserConstants.COORD2:
			case ADQLParserConstants.COORDSYS:
			case ADQLParserConstants.DISTANCE:
			case ADQLParserConstants.ABS:
			case ADQLParserConstants.CEILING:
			case ADQLParserConstants.DEGREES:
			case ADQLParserConstants.EXP:
			case ADQLParserConstants.FLOOR:
			case ADQLParserConstants.LOG:
			case ADQLParserConstants.LOG10:
			case ADQLParserConstants.MOD:
			case ADQLParserConstants.PI:
			case ADQLParserConstants.POWER:
			case ADQLParserConstants.RADIANS:
			case ADQLParserConstants.RAND:
			case ADQLParserConstants.ROUND:
			case ADQLParserConstants.SQRT:
			case ADQLParserConstants.TRUNCATE:
			case ADQLParserConstants.ACOS:
			case ADQLParserConstants.ASIN:
			case ADQLParserConstants.ATAN:
			case ADQLParserConstants.ATAN2:
			case ADQLParserConstants.COS:
			case ADQLParserConstants.COT:
			case ADQLParserConstants.SIN:
			case ADQLParserConstants.TAN:
			case ADQLParserConstants.USING:
				return true;
			default:
				return false;
		}
	}

	/* MAIN PROGRAM */

	/**
	* Gets the specified ADQL query and parses the given ADQL query. The SQL
	* translation is then printed if the syntax is correct.
	* 
	* <p>
	*     <b>ONLY the syntax is checked: the query is NOT EXECUTED !</b>
	* </p>
	*
	* @param args
	
	* @throws Exception
	*/
	public static final void main(String[] args) throws Exception {
		final String USAGE = "Usage:\n    adqlParser.jar [-d] [-v] [-e] [-a|-s] [-f] [<FILE>|<URL>]\n\nNOTE: If no file or URL is given, the ADQL query is expected in the standard\n      input. This query must end with a ';' or <Ctrl+D>!\n\nParameters:\n    -v or --verbose : Print the main steps of the parsing\n    -d or --debug   : Print stack traces when a grave error occurs\n    -e or --explain : Explain the ADQL parsing (or Expand the parsing tree)\n    -a or --adql    : Display the understood ADQL query\n    -s or --sql     : Ask the SQL translation of the given ADQL query\n                      (SQL compatible with PostgreSQL)\n    -f or --try-fix : Try fixing the most common ADQL query issues before\n                      attempting to parse the query.\n\nReturn:\n    By default: nothing if the query is correct. Otherwise a message explaining\n                why the query is not correct is displayed.\n    With the -s option, the SQL translation of the given ADQL query will be\n    returned.\n    With the -a option, the ADQL query is returned as it has been understood.\n\nExit status:\n    0  OK !\n    1  Parameter error (missing or incorrect parameter)\n    2  File error (incorrect file/url, reading error, ...)\n    3  Parsing error (syntactic or semantic error)\n    4  Translation error (a problem has occurred during the translation of the\n       given ADQL query in SQL).";

		ADQLParser parser;

		final String urlRegex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";

		String file = null, metaFile = null;
		short mode = -1;
		boolean verbose=false, debug=false, explain=false, tryFix=false;

		// Parameters reading:
		for(int i=0; i<args.length; i++){
			if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--debug"))
				debug = true;
			else if (args[i].equalsIgnoreCase("-v") || args[i].equalsIgnoreCase("--verbose"))
				verbose = true;
			else if (args[i].equalsIgnoreCase("-e") || args[i].equalsIgnoreCase("--explain"))
				explain = true;
			else if (args[i].equalsIgnoreCase("-a") || args[i].equalsIgnoreCase("--adql")){
				if (mode != -1){
					System.err.println("((!)) Too much parameter: you must choose between -s, -c, -a or nothing ((!))\n"+USAGE);
					System.exit(1);
				}else
					mode = 1;
			}else if (args[i].equalsIgnoreCase("-s") || args[i].equalsIgnoreCase("--sql")){
				if (mode != -1){
					System.err.println("((!)) Too much parameter: you must choose between -s, -c, -a or nothing ((!))\n"+USAGE);
					System.exit(1);
				}else
					mode = 2;
			}else if (args[i].equalsIgnoreCase("-f") || args[i].equalsIgnoreCase("--try-fix"))
				tryFix = true;
			else if (args[i].equalsIgnoreCase("-h") || args[i].equalsIgnoreCase("--help")){
				System.out.println(USAGE);
				System.exit(0);
			}else if (args[i].startsWith("-")){
				System.err.println("((!)) Unknown parameter: \""+args[i]+"\" ((!))\u005cn"+USAGE);
				System.exit(1);
			}else
				file = args[i].trim();
		}

		try{

			// Try fixing the query, if asked:
			if (tryFix) {
				if (verbose)
					System.out.println("((i)) Trying to automatically fix the query...");

				String query;
				java.io.InputStream in = null;
				try {
					// get the input stream...
					if (file == null || file.length() == 0)
						in = System.in;
					else if (file.matches(urlRegex))
						in = (new java.net.URL(file)).openStream();
					else
						in = new java.io.FileInputStream(file);
					
					// ...and try fixing the query:
					query = (new ADQLParser()).tryQuickFix(in);
				} finally {
					// close the stream (if opened):
					if (in != null)
						in.close();
					in = null;
				}
				
				if (verbose)
					System.out.println("((i)) SUGGESTED QUERY:\n" + query);

				// Initialise the parser with this fixed query:
				parser = new ADQLParser(new java.io.ByteArrayInputStream(query.getBytes()));	
			}
			// Otherwise, take the query as provided:
			else {
				// Initialise the parser with the specified input:
				if (file == null || file.length() == 0)
					parser = new ADQLParser(System.in);
				else if (file.matches(urlRegex))
					parser = new ADQLParser((new java.net.URL(file)).openStream());
				else
					parser = new ADQLParser(new java.io.FileInputStream(file));
			}

			// Enable/Disable the debugging in function of the parameters:
			parser.setDebug(explain);

			// Query parsing:
			try{
				if (verbose)	System.out.print("((i)) Parsing ADQL query...");
				ADQLQuery q = parser.parseQuery();
				if (verbose)	System.out.println("((i)) CORRECT ADQL QUERY ((i))");
				if (mode==2){
					PostgreSQLTranslator translator = new PostgreSQLTranslator();
					if (verbose)	System.out.print("((i)) Translating in SQL...");
					String sql = translator.translate(q);
					if (verbose)	System.out.println("ok");
					System.out.println(sql);
				}else if (mode==1){
					System.out.println(q.toADQL());
				}
			}catch(UnresolvedIdentifiersException uie){
				System.err.println("((X)) "+uie.getNbErrors()+" unresolved identifiers:");
				for(ParseException pe : uie)
					System.err.println("\t - at "+pe.getPosition()+": "+uie.getMessage());
				if (debug)		uie.printStackTrace(System.err);
				System.exit(3);
			}catch(ParseException pe){
				System.err.println("((X)) Syntax error: "+pe.getMessage()+" ((X))");
				if (debug)		pe.printStackTrace(System.err);
				System.exit(3);
			}catch(TranslationException te){
				if (verbose)	System.out.println("error");
				System.err.println("((X)) Translation error: "+te.getMessage()+" ((X))");
				if (debug)		te.printStackTrace(System.err);
				System.exit(4);
			}

		}catch(IOException ioe){
			System.err.println("\n((X)) Error while reading the file \""+file+"\": "+ioe.getMessage()+" ((X))");
			if (debug)		ioe.printStackTrace(System.err);
			System.exit(2);
		}
    	
    }
}

PARSER_END(ADQLParser)


							/* ########### */
							/* # GRAMMAR # */
							/* ########### */
/* ******************** */
/* Characters to ignore */
/* ******************** */
SKIP : { < " " | "\t" | "\n" | "\r" | "\r\n" > }

/* ************************************************************************** */
/* Reserved SQL words                                                         */
/*                                                                            */
/* NOTE:                                                                      */
/*   This list is the one provided by the ADQL-2.0 standard after removal of  */
/*   all ADQL used words (e.g. SELECT, AS, LIKE, AVG, ABS, COS, POINT).       */
/*   (see ParseException.initialise(Token, int[][], String[]) for more        */
/*   details)                                                                 */
/* ************************************************************************** */

TOKEN : {
	< SQL_RESERVED_WORD: ("ABSOLUTE"|"ACTION"|"ADD"|"ALLOCATE"|"ALTER"|"ANY"|"ARE"|"ASSERTION"|"AT"|"AUTHORIZATION"|"BEGIN"|"BIT"|"BIT_LENGTH"|"BOTH"|"CASCADE"|"CASCADED"|"CASE"|"CAST"|"CATALOG"|"CHAR"|"CHARACTER"|"CHAR_LENGTH"|"CHARACTER_LENGTH"|"CHECK"|"CLOSE"|"COALESCE"|"COLLATE"|"COLLATION"|"COLUMN"|"COMMIT"|"CONNECT"|"CONNECTION"|"CONSTRAINT"|"CONSTRAINTS"|"CONTINUE"|"CONVERT"|"CORRESPONDING"|"CREATE"|"CURRENT"|"CURRENT_DATE"|"CURRENT_TIME"|"CURRENT_TIMESTAMP"|"CURRENT_USER"|"CURSOR"|"DATE"|"DAY"|"DEALLOCATE"|"DECIMAL"|"DECLARE"|"DEFAULT"|"DEFERRABLE"|"DEFERRED"|"DELETE"|"DESCRIBE"|"DESCRIPTOR"|"DIAGNOSTICS"|"DISCONNECT"|"DOMAIN"|"DOUBLE"|"DROP"|"ELSE"|"END"|"END-EXEC"|"ESCAPE"|"EXCEPT"|"EXCEPTION"|"EXEC"|"EXECUTE"|"EXTERNAL"|"EXTRACT"|"FALSE"|"FETCH"|"FIRST"|"FLOAT"|"FOR"|"FOREIGN"|"FOUND"|"GET"|"GLOBAL"|"GO"|"GOTO"|"GRANT"|"HOUR"|"IDENTITY"|"IMMEDIATE"|"INDICATOR"|"INITIALLY"|"INPUT"|"INSENSITIVE"|"INSERT"|"INT"|"INTEGER"|"INTERSECT"|"INTERVAL"|"INTO"|"ISOLATION"|"KEY"|"LANGUAGE"|"LAST"|"LEADING"|"LEVEL"|"LOCAL"|"LOWER"|"MATCH"|"MINUTE"|"MODULE"|"MONTH"|"NAMES"|"NATIONAL"|"NCHAR"|"NEXT"|"NO"|"NULLIF"|"NUMERIC"|"OCTET_LENGTH"|"OF"|"ONLY"|"OPEN"|"OPTION"|"OUTPUT"|"OVERLAPS"|"PAD"|"PARTIAL"|"POSITION"|"PRECISION"|"PREPARE"|"PRESERVE"|"PRIMARY"|"PRIOR"|"PRIVILEGES"|"PROCEDURE"|"PUBLIC"|"READ"|"REAL"|"REFERENCES"|"RELATIVE"|"RESTRICT"|"REVOKE"|"ROLLBACK"|"ROWS"|"SCHEMA"|"SCROLL"|"SECOND"|"SECTION"|"SESSION"|"SESSION_USER"|"SET"|"SIZE"|"SMALLINT"|"SOME"|"SPACE"|"SQL"|"SQLCODE"|"SQLERROR"|"SQLSTATE"|"SUBSTRING"|"SYSTEM_USER"|"TABLE"|"TEMPORARY"|"THEN"|"TIME"|"TIMESTAMP"|"TIMEZONE_HOUR"|"TIMEZONE_MINUTE"|"TO"|"TRAILING"|"TRANSACTION"|"TRANSLATE"|"TRANSLATION"|"TRIM"|"TRUE"|"UNION"|"UNIQUE"|"UNKNOWN"|"UPDATE"|"UPPER"|"USAGE"|"USER"|"VALUE"|"VALUES"|"VARCHAR"|"VARYING"|"VIEW"|"WHEN"|"WHENEVER"|"WITH"|"WORK"|"WRITE"|"YEAR"|"ZONE") >
}

/* *********** */
/* Punctuation */
/* *********** */
TOKEN : {
	< LEFT_PAR: "(" >
|	< RIGHT_PAR: ")" > 
|	< DOT: "." >
|	< COMMA: "," >
|	< EOQ: ";">
|	< CONCAT: "||" >
}

/* ******************** */
/* Arithmetic operators */
/* ******************** */
TOKEN : {
	< PLUS: "+" >
|	< MINUS: "-" >
|	< ASTERISK: "*" >
|	< DIVIDE: "/" >
}

/* ******************** */
/* Comparison operators */
/* ******************** */
TOKEN : {
	< EQUAL: "=" >
|	< NOT_EQUAL: "<>" | "!=" >
|	< LESS_THAN: "<" >
|	< LESS_EQUAL_THAN: "<=" >
|	< GREATER_THAN: ">" >
|	< GREATER_EQUAL_THAN: ">=" >
}

/* *************** */
/* SELECT's tokens */
/* *************** */
TOKEN : {
	< SELECT: "SELECT" >
|	< QUANTIFIER: "DISTINCT" | "ALL" >
|	< TOP: "TOP" >
}

/* ************* */
/* FROM's tokens */
/* ************* */
TOKEN : {
	< FROM: "FROM" >
|	< AS: "AS" >
|	< NATURAL: "NATURAL" >
|	< INNER: "INNER" >
|	< OUTER: "OUTER" >
|	< RIGHT: "RIGHT" >
|	< LEFT: "LEFT" >
|	< FULL: "FULL" >
|	< JOIN: "JOIN" >
|	< ON: "ON" >
|	< USING: "USING" >
}

/* ************** */
/* WHERE's tokens */
/* ************** */
TOKEN : {
	< WHERE: "WHERE" >
|	< AND: "AND" >
|	< OR: "OR" >
|	< NOT: "NOT" >
|	< IS: "IS" >
|	< NULL: "NULL" >
|	< BETWEEN: "BETWEEN" >
|	< LIKE: "LIKE" >
|	< IN: "IN" >
|	< EXISTS: "EXISTS" >
}

/* ********************* */
/* Other clauses' tokens */
/* ********************* */
TOKEN : {
	< BY: "BY" >
|	< GROUP: "GROUP" >
|	< HAVING: "HAVING" >
|	< ORDER: "ORDER" >
|	< ASC: "ASC" >
|	< DESC: "DESC" >
}

/* ************* */
/* SQL functions */
/* ************* */
TOKEN : {
	< AVG: "AVG" >
|	< MAX: "MAX" >
|	< MIN: "MIN" >
|	< SUM: "SUM" >
|	< COUNT: "COUNT" >
}

/* ************** */
/* ADQL functions */
/* ************** */
TOKEN : {
	< BOX: "BOX" >
|	< CENTROID: "CENTROID" >
|	< CIRCLE: "CIRCLE" >
|	< POINT: "POINT" >
|	< POLYGON: "POLYGON" >
|	< REGION: "REGION" >

|	< CONTAINS: "CONTAINS" >
|	< INTERSECTS: "INTERSECTS" >
|	< AREA: "AREA" >
|	< COORD1: "COORD1" >
|	< COORD2: "COORD2" >
|	< COORDSYS: "COORDSYS" >
|	< DISTANCE: "DISTANCE" >
}

/* ********************** */
/* Mathematical functions */
/* ********************** */
TOKEN : {
	< ABS: "ABS" >
|	< CEILING: "CEILING" >
|	< DEGREES: "DEGREES" >
|	< EXP: "EXP" >
|	< FLOOR: "FLOOR" >
|	< LOG: "LOG" >
|	< LOG10: "LOG10" >
|	< MOD: "MOD" >
|	< PI: "PI" >
|	< POWER: "POWER" >
|	< RADIANS: "RADIANS" >
|	< RAND: "RAND" >
|	< ROUND: "ROUND" >
|	< SQRT: "SQRT" >
|	< TRUNCATE: "TRUNCATE" >
}

/* ************************* */
/* Trigonometrical functions */
/* ************************* */
TOKEN : {
	< ACOS: "ACOS" >
|	< ASIN: "ASIN" >
|	< ATAN: "ATAN" >
|	< ATAN2: "ATAN2" >
|	< COS: "COS" >
|	< COT: "COT" >
|	< SIN: "SIN" >
|	< TAN: "TAN" >
}

/* ******* */
/* Comment */
/* ******* */
SKIP : { < <MINUS><MINUS> (~["\n","\r"])* ("\n"|"\r"|"\r\n")? > }

/* ****** */
/* String */
/* ****** */
<DEFAULT> MORE : { "'" : WithinString }
<WithinString> MORE : { < ~["'"] | ("''") > }
<WithinString> TOKEN : { < STRING_LITERAL: "'" >: DEFAULT }

/* *************** */
/* Primary numbers */
/* *************** */
TOKEN : {
	< SCIENTIFIC_NUMBER: (<UNSIGNED_FLOAT>|<UNSIGNED_INTEGER>) "E" (<PLUS>|<MINUS>)? <UNSIGNED_INTEGER> >
|	< UNSIGNED_FLOAT: (<UNSIGNED_INTEGER> <DOT> (<UNSIGNED_INTEGER>)?) | (<DOT> <UNSIGNED_INTEGER>) >
|	< UNSIGNED_INTEGER: (<DIGIT>)+ >
|	< #DIGIT: ["0"-"9"] >
}

/* ************************************************* */
/* Identifier (column, tables, ...) */
/* ************************************************* */
<DEFAULT> MORE : { "\"" : WithinDelimitedId }
<WithinDelimitedId> MORE : { < ~["\""] | ("\"\"") > }
<WithinDelimitedId> TOKEN : { < DELIMITED_IDENTIFIER: "\"" >: DEFAULT }

TOKEN : {
	< REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) >
|	< #Letter: ["a"-"z","A"-"Z","_","?","!","$","@","^","#","`","~","[","]","{","}"] >
}

							/* ########## */
							/* # SYNTAX # */
							/* ########## */
							
/* ******************* */
/* GENERAL ADQL SYNTAX */
/* ******************* */
/**
* Parses the ADQL query given at the parser creation or in the {@link ADQLParser#ReInit(java.io.InputStream)}
* or in the <i>parseQuery</i> functions.
*
* @return					The object representation of the query.
* @throws ParseException	If the query syntax is incorrect.
*/
ADQLQuery Query(): {ADQLQuery q = null;}{
	q=QueryExpression() (<EOF> | <EOQ>)
	{
		// check the query:
		if (queryChecker != null)
			queryChecker.check(q);
			
		return q;
	}
}

ADQLQuery QueryExpression(): {TextPosition endPos = null;} {
	{
		try{
			// create the query:
			query = queryFactory.createQuery();
			stackQuery.push(query);
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
	Select()
	From()     {endPos = query.getFrom().getPosition();}
	[Where()   {endPos = query.getWhere().getPosition();}]
	[GroupBy() {endPos = query.getGroupBy().getPosition();}]
	[Having()  {endPos = query.getHaving().getPosition();}]
	[OrderBy() {endPos = query.getOrderBy().getPosition();}]
	{
		// set the position of the query:
		query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos));
		
		// get the previous query (!= null if the current query is a sub-query):
		ADQLQuery previousQuery = stackQuery.pop();
		if (stackQuery.isEmpty())
			query = null;
		else
			query = stackQuery.peek();
			
		return previousQuery;
	}
}

ADQLQuery SubQueryExpression(): {ADQLQuery q = null; Token start, end;} {
	start=<LEFT_PAR> q=QueryExpression() end=<RIGHT_PAR>
	{
		q.setPosition(new TextPosition(start, end));
		return q;
	}
}

void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; Token start,t = null;} {
	start=<SELECT>
	[t=<QUANTIFIER> {select.setDistinctColumns(t.image.equalsIgnoreCase("DISTINCT"));}]
	[<TOP> t=<UNSIGNED_INTEGER>
	 {
	  try{
	  	select.setLimit(Integer.parseInt(t.image));
	  }catch(NumberFormatException nfe){
	  	throw new ParseException("[l."+t.beginLine+";c."+t.beginColumn+"] The TOP limit (\""+t.image+"\") isn't a regular unsigned integer !");
	  }
	 }
	]
	
	item=SelectItem() {select.add(item);}
	(<COMMA> item=SelectItem() {select.add(item);})*
	{
		TextPosition lastItemPos = query.getSelect().get(query.getSelect().size()-1).getPosition();
		select.setPosition(new TextPosition(start.beginLine, start.beginColumn, lastItemPos.endLine, lastItemPos.endColumn));
	}
}

SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
	(
		( starToken=<ASTERISK>
		  {
		    item = new SelectAllColumns(query);
		    item.setPosition(new TextPosition(starToken));
		    return item;
		  }
		)
	|LOOKAHEAD(7)
		(
			id=Identifier() <DOT> { identifiers.append(id); }
			(
				id=Identifier() <DOT> { identifiers.append(id); }
				(
					id=Identifier() <DOT> { identifiers.append(id); }
				)?
			)?
			starToken=<ASTERISK>
			{
				try{
					item = new SelectAllColumns( queryFactory.createTable(identifiers, null) );
					TextPosition firstPos = identifiers.get(0).position;
					item.setPosition(new TextPosition(firstPos.beginLine, firstPos.beginColumn, starToken.endLine, (starToken.endColumn < 0) ? -1 : (starToken.endColumn + 1)));
					return item;
				}catch(Exception ex) {
					throw generateParseException(ex);
				}
			}
		)
		
	| 
		(op=ValueExpression()[[<AS>] label=Identifier()])
	)
	
	{
		try{
			item = queryFactory.createSelectItem(op, (label==null)?null:label.identifier);
			if (label != null){
				item.setCaseSensitive(label.caseSensitivity);
				item.setPosition(new TextPosition(op.getPosition(), label.position));
			}else
				item.setPosition(new TextPosition(op.getPosition()));
			return item;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

void From():{FromContent content = null, content2 = null;}{
	try{
		<FROM> content=TableRef()
		(<COMMA> content2=TableRef()
		 {
		   TextPosition startPos = content.getPosition(), endPos = content2.getPosition();
		   content = queryFactory.createJoin(JoinType.CROSS, content, content2);
		   content.setPosition(new TextPosition(startPos, endPos));
		 }
		)*
		{ query.setFrom(content); }
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

void Where(): {ClauseConstraints where = query.getWhere(); ADQLConstraint condition; Token start;} {
	start=<WHERE> ConditionsList(where)
	{
	  TextPosition endPosition = where.getPosition();
	  where.setPosition(new TextPosition(start.beginLine, start.beginColumn, endPosition.endLine, endPosition.endColumn));
	}
}

void GroupBy(): {ClauseADQL<ADQLColumn> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} {
	start=<GROUP> <BY> colRef=Column() { groupBy.add(colRef); }
	( <COMMA> colRef=Column() { groupBy.add(colRef); } )*
	{ groupBy.setPosition(new TextPosition(start.beginLine, start.beginColumn, colRef.getPosition().endLine, colRef.getPosition().endColumn)); }
}

void Having(): {ClauseConstraints having = query.getHaving(); Token start;} {
	start=<HAVING> ConditionsList(having)
	{
	  TextPosition endPosition = having.getPosition();
	  having.setPosition(new TextPosition(start.beginLine, start.beginColumn, endPosition.endLine, endPosition.endColumn));
	}
}

void OrderBy(): {ClauseADQL<ADQLOrder> orderBy = query.getOrderBy(); ADQLOrder order = null; Token start;} {
	start=<ORDER> <BY> order=OrderItem() {orderBy.add(order);}
	( <COMMA> order=OrderItem() {orderBy.add(order);} )*
	{ orderBy.setPosition(new TextPosition(start, token)); }
}

/* *************************** */
/* COLUMN AND TABLE REFERENCES */
/* *************************** */
IdentifierItem Identifier(): {Token t;} {
	(
		t=<REGULAR_IDENTIFIER_CANDIDATE>
		{
		  	testRegularIdentifier(t);
			return new IdentifierItem(t, false);
		}
	|
		t=<DELIMITED_IDENTIFIER>
		{ return new IdentifierItem(t, true); }
	)
}

/**
 * Extracts the name of a table with its possible catalog and schema prefixes.
 * 
 * @return A {@link IdentifierItems} which contains at most three items: catalogName, schemaName and tableName.
 */
IdentifierItems TableName(): {IdentifierItems identifiers=new IdentifierItems(true); IdentifierItem id=null;} {
	(
		id=Identifier() {identifiers.append(id);}						// catalog
		(LOOKAHEAD(1) <DOT> id=Identifier() {identifiers.append(id);})?	// schema
		(LOOKAHEAD(1) <DOT> id=Identifier() {identifiers.append(id);})?	// table
	)
	{ return identifiers; }
}

/**
 * Extracts the name of a column with its possible catalog, schema and table prefixes.
 * 
 * @return A {@link IdentifierItems} which contains at most four items: catalogName, schemaName, tableName and columnName.
 */
IdentifierItems ColumnName(): {IdentifierItem id; IdentifierItems table=null, identifiers=new IdentifierItems(false);} {
	( id=Identifier() (LOOKAHEAD(1) <DOT> table=TableName())? )
	{
		identifiers.append(id);
		if (table != null){
			for(int i=0; i<table.size(); i++)
				identifiers.append(table.get(i));
		}
		return identifiers;
	}
}

ADQLColumn Column(): {IdentifierItems identifiers;} {
	identifiers = ColumnName()
	{
		try{
			return queryFactory.createColumn(identifiers);
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

ADQLOrder OrderItem(): {IdentifierItem identifier = null; Token ind = null, desc = null;}{
	(identifier=Identifier() | ind=<UNSIGNED_INTEGER>) (<ASC> | desc=<DESC>)?
	{
		try{
			ADQLOrder order = null;
			if (identifier != null){
				order = queryFactory.createOrder(identifier, desc!=null);
				order.setPosition(identifier.position);
			}else{
				order = queryFactory.createOrder(Integer.parseInt(ind.image), desc!=null);
				order.setPosition(new TextPosition(ind));
			}
			return order;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

FromContent SimpleTableRef(): {IdentifierItem alias = null; IdentifierItems identifiers = null; ADQLQuery subQuery = null; FromContent content = null; Token start,end;} {
	try{
		(
			identifiers=TableName() [[<AS>] alias=Identifier()]
			{
			  content = queryFactory.createTable(identifiers, alias);
			  if (alias == null)
			  	content.setPosition(new TextPosition(identifiers.get(0).position, identifiers.get(identifiers.size()-1).position));
			  else
			  	content.setPosition(new TextPosition(identifiers.get(0).position, alias.position));
			  return content;
			}
		|LOOKAHEAD(2)
			subQuery=SubQueryExpression() [<AS>] alias=Identifier()
			{
			  content = queryFactory.createTable(subQuery, alias);
			  if (alias == null)
			  	content.setPosition(new TextPosition(subQuery.getPosition()));
			  else
			  	content.setPosition(new TextPosition(subQuery.getPosition(), alias.position));
			  return content;
			}
		|
			start=<LEFT_PAR> content=JoinedTable() end=<RIGHT_PAR>
			{
			  content.setPosition(new TextPosition(start, end));
			  return content;
			}
		)
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

FromContent TableRef(): { FromContent content; } {
	content=SimpleTableRef()
	( LOOKAHEAD(2) content=JoinSpecification(content) )*
	{ return content; }
}

FromContent JoinedTable(): { FromContent content; } {
	content=SimpleTableRef()
	( content=JoinSpecification(content) )+
	{ return content; }
}


ADQLJoin JoinSpecification(FromContent leftTable): { boolean natural = false; JoinType type = JoinType.INNER;  ClauseConstraints condition = new ClauseConstraints("ON"); ArrayList<ADQLColumn> lstColumns=new ArrayList<ADQLColumn>(); IdentifierItem id; FromContent rightTable; ADQLJoin join; Token lastPar;} {
	try{
		(
			<NATURAL> {natural=true;} [<INNER> | ((<LEFT> {type = JoinType.OUTER_LEFT;}|<RIGHT> {type = JoinType.OUTER_RIGHT;}|<FULL> {type = JoinType.OUTER_FULL;}) [<OUTER>])] <JOIN> rightTable=SimpleTableRef()
			{
			  join = queryFactory.createJoin(type, leftTable, rightTable);
			  join.setPosition(new TextPosition(leftTable.getPosition(), rightTable.getPosition()));
			  return join;
			}
		|
			[<INNER> | ((<LEFT> {type = JoinType.OUTER_LEFT;}|<RIGHT> {type = JoinType.OUTER_RIGHT;}|<FULL> {type = JoinType.OUTER_FULL;}) [<OUTER>])] <JOIN> rightTable=SimpleTableRef()
			(
				<ON> ConditionsList(condition)
				{
				  join = queryFactory.createJoin(type, leftTable, rightTable, condition);
				  join.setPosition(new TextPosition(leftTable.getPosition(), condition.getPosition()));
				  return join;
				}
			|
				<USING> <LEFT_PAR> id=Identifier()
						{ lstColumns.add( queryFactory.createColumn(id) ); }
						(
							<COMMA> id=Identifier()
							{ lstColumns.add( queryFactory.createColumn(id) ); }
						)* lastPar=<RIGHT_PAR>
				{
				  join = queryFactory.createJoin(type, leftTable, rightTable, lstColumns);
				  join.setPosition(new TextPosition(leftTable.getPosition().beginLine, leftTable.getPosition().beginColumn, lastPar.endLine, (lastPar.endColumn < 0) ? -1 : (lastPar.endColumn + 1)));
				  return join;
				}
			)
		)
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

/* ****** */
/* STRING */
/* ****** */
StringConstant String(): {Token t, start=null; String str=""; StringConstant cst;} {
	(t=<STRING_LITERAL>
	 {
	   	str += t.image.substring(1, t.image.length()-1).replaceAll("''", "'");
	   	if (start == null)
	   		start = t;
	 }
	)+
	{
		try{
		  cst = queryFactory.createStringConstant(str);
		  cst.setPosition(new TextPosition(start, t));
		  return cst;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

/* ************* */
/* NUMERIC TYPES */
/* ************* */
NumericConstant UnsignedNumeric(): {Token t; NumericConstant cst;} {
	(t=<SCIENTIFIC_NUMBER>
	| t=<UNSIGNED_FLOAT>
	| t=<UNSIGNED_INTEGER>)
	{		try{
		  cst = queryFactory.createNumericConstant(t.image);
		  cst.setPosition(new TextPosition(t));
		  return cst;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

NumericConstant UnsignedFloat(): {Token t; NumericConstant cst;} {
	(t=<UNSIGNED_INTEGER>
	| t=<UNSIGNED_FLOAT>)
	{
		try{
			cst = queryFactory.createNumericConstant(t.image);
		  	cst.setPosition(new TextPosition(t));
			return cst;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

NumericConstant SignedInteger(): {Token sign=null, number; NumericConstant cst;} {
	((sign=<PLUS>|sign=<MINUS>)? number=<UNSIGNED_INTEGER>)
	{
		try{
		  	if (sign == null){		  		cst = queryFactory.createNumericConstant(number.image);
		  		cst.setPosition(new TextPosition(number));
		 	}else{		 		cst = queryFactory.createNumericConstant(sign.image+number.image);
		  		cst.setPosition(new TextPosition(sign, number));
		 	}
		 	return cst;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

/* *********** */
/* EXPRESSIONS */
/* *********** */
ADQLOperand NumericValueExpressionPrimary(): {ADQLColumn column; ADQLOperand op; Token left, right;} {
	try{
		(// unsigned_value_specification
		  op=UnsignedNumeric() {return op;}
		// column_reference
		| column=Column() {column.setExpectedType('N'); return column;}
		// set_function_specification
		| op=SqlFunction() {return op;}
		// LEFT_PAR value_expression RIGHT_PAR
		| (left=<LEFT_PAR> op=NumericExpression() right=<RIGHT_PAR>) { WrappedOperand wop = queryFactory.createWrappedOperand(op); wop.setPosition(new TextPosition(left, right)); return wop; })
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

ADQLOperand StringValueExpressionPrimary(): {StringConstant expr; ADQLColumn column; ADQLOperand op; Token left, right;} {
	try{
		(// string
		  expr=String() {return expr;}
		// unsigned numeric
		| op=UnsignedNumeric() {return op;}
		// set_function_specification
		| op=SqlFunction() {return op;}
		// column_reference
		| column=Column() {column.setExpectedType('*'); return column;}
		// LEFT_PAR value_expression RIGHT_PAR
		| (left=<LEFT_PAR> (op=ValueExpression()) right=<RIGHT_PAR>) { WrappedOperand wop = queryFactory.createWrappedOperand(op); wop.setPosition(new TextPosition(left, right)); return wop; })
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; } {
	try{
		(LOOKAHEAD((<PLUS>|<MINUS>) | (Factor() (<PLUS>|<MINUS>|<ASTERISK>|<DIVIDE>))) valueExpr=NumericExpression()
		| LOOKAHEAD(<COORDSYS> | (StringFactor() <CONCAT>)) valueExpr=StringExpression()
		| LOOKAHEAD(<LEFT_PAR>) left=<LEFT_PAR> valueExpr=ValueExpression() right=<RIGHT_PAR> { valueExpr = queryFactory.createWrappedOperand(valueExpr); ((WrappedOperand)valueExpr).setPosition(new TextPosition(left, right)); }
		| LOOKAHEAD(<REGULAR_IDENTIFIER_CANDIDATE> <LEFT_PAR>) valueExpr=UserDefinedFunction()
		| LOOKAHEAD(2) valueExpr=GeometryValueFunction()
		| LOOKAHEAD(Column()) valueExpr=Column()
		| LOOKAHEAD(String()) valueExpr=StringFactor()
		| LOOKAHEAD(3) valueExpr=Factor()

		/* At this position in this switch, all possibilities (including
		 * Column()) have already been tested and failed.
		 * 
		 * So, this final choice actually aims to throw an error set with the
		 * current token and with an error message implying that a column name
		 * was expected (which is generally the case in an ADQL query).
		 *
		 * Note: This choice will generally be reached if an unexpected ADQL/SQL
		 *       word is ending the query. */
		| valueExpr=Column() )
		{return valueExpr;}
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} {
	(leftOp=NumericTerm() ((sign=<PLUS> | sign=<MINUS>) rightOp=NumericExpression())?)
	{
	if (sign == null)
		return leftOp;
	else{
		try{
			Operation operation = queryFactory.createOperation(leftOp, OperationType.getOperator(sign.image), rightOp);
			operation.setPosition(new TextPosition(leftOp.getPosition(), rightOp.getPosition()));
			return operation;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
	}
}

ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} {
	(leftOp=Factor() ((sign=<ASTERISK> | sign=<DIVIDE>) rightOp=NumericTerm())?)
	{
	if (sign == null)
		return leftOp;
	else{
		try{
			Operation operation = queryFactory.createOperation(leftOp, OperationType.getOperator(sign.image), rightOp);
			operation.setPosition(new TextPosition(leftOp.getPosition(), rightOp.getPosition()));
			return operation;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
	}
}

ADQLOperand Factor(): {boolean negative = false; Token minusSign = null; ADQLOperand op;} {
	(
		(<PLUS> | (minusSign=<MINUS> {negative = true;}))?
		(LOOKAHEAD(2) op=NumericFunction() | op=NumericValueExpressionPrimary())
	)
	
	{
		if (negative){
			try{
			  	TextPosition position = op.getPosition();
				op = queryFactory.createNegativeOperand(op);
				NegativeOperand negativeOp = (NegativeOperand)op;
				if (minusSign != null)
					negativeOp.setPosition(new TextPosition(minusSign.beginLine, minusSign.beginColumn, position.endLine, position.endColumn));
				else
					negativeOp.setPosition(position);
			}catch(Exception ex){
				throw generateParseException(ex);
			}
		}
		
		return op;
	}
}

ADQLOperand StringExpression(): {ADQLOperand leftOp; ADQLOperand rightOp = null;} {
	leftOp=StringFactor()
	(
		<CONCAT>
		rightOp=StringFactor()
		{
			if (!(leftOp instanceof Concatenation)){
				try{
					ADQLOperand temp = leftOp;
					leftOp = queryFactory.createConcatenation();
					((Concatenation)leftOp).add(temp);
				}catch(Exception ex){
					throw generateParseException(ex);
				}
			}
			((Concatenation)leftOp).add(rightOp);
		}
	)*
	{
		if (leftOp instanceof Concatenation){
			Concatenation concat = (Concatenation)leftOp;
			concat.setPosition(new TextPosition(concat.get(0).getPosition(), concat.get(concat.size()-1).getPosition()));
		}
	  return leftOp;
	}
}

ADQLOperand StringFactor(): {ADQLOperand op;} {
	(op=ExtractCoordSys()
	| LOOKAHEAD(2) op=UserDefinedFunction() { ((UserDefinedFunction)op).setExpectedType('S'); }
	| op=StringValueExpressionPrimary())
	{return op;}
}

GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; GeometryFunction gf = null;} {
	(col=Column() | gf=GeometryValueFunction())
	{
		if (col != null){
		  	col.setExpectedType('G');
			return new GeometryValue<GeometryFunction>(col);
		}else
			return new GeometryValue<GeometryFunction>(gf);
	}
}

/* ********************************** */
/* BOOLEAN EXPRESSIONS (WHERE clause) */
/* ********************************** */
ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint constraint = null; Token op = null; boolean notOp = false;} {
	try{
		[op=<NOT> {notOp = true;}]
		constraint=Constraint()
		{
			if (notOp){
			  	TextPosition oldPos = constraint.getPosition();
				constraint = queryFactory.createNot(constraint);
				((NotConstraint)constraint).setPosition(new TextPosition(op.beginLine, op.beginColumn, oldPos.endLine, oldPos.endColumn));
			}
			notOp = false;
			
			if (clause instanceof ADQLConstraint)
				clause.add(constraint);
			else
				clause.add(constraint);
		}
		(
			(op=<AND> | op=<OR>)
			[<NOT> {notOp = true;}]
			constraint=Constraint()
			{
				if (notOp){
			  		TextPosition oldPos = constraint.getPosition();
					constraint = queryFactory.createNot(constraint);
					((NotConstraint)constraint).setPosition(new TextPosition(op.beginLine, op.beginColumn, oldPos.endLine, oldPos.endColumn));
				}
				notOp = false;
				
				if (clause instanceof ADQLConstraint)
					clause.add(op.image, constraint);
				else
					clause.add(op.image, constraint);
			}
		)*
	}catch(Exception ex){
		throw generateParseException(ex);
	}
	{
	  	if (!clause.isEmpty()){
	  		TextPosition start = clause.get(0).getPosition();
	  		TextPosition end = clause.get(clause.size()-1).getPosition();
			clause.setPosition(new TextPosition(start, end));
		}
		return clause;
	}
}

ADQLConstraint Constraint(): {ADQLConstraint constraint =  null; Token start, end;} {
	(LOOKAHEAD(<EXISTS> | ValueExpression()) constraint=Predicate()
	| (
		start=<LEFT_PAR>
		{
			try{
				constraint = queryFactory.createGroupOfConstraints();
			}catch(Exception ex){
				throw generateParseException(ex);
			}
		}
		ConditionsList((ConstraintsGroup)constraint)
		end=<RIGHT_PAR>
		{ ((ConstraintsGroup)constraint).setPosition(new TextPosition(start, end)); }
	))
	{return constraint;}
}

ADQLConstraint Predicate(): {ADQLQuery q=null; ADQLColumn column=null; ADQLOperand strExpr1=null, strExpr2=null; ADQLOperand op; Token start, notToken = null, end; ADQLConstraint constraint = null;} {
	try{
		// exists_predicate
		(
		  (start=<EXISTS> q=SubQueryExpression()
			{
			  Exists e = queryFactory.createExists(q);
			  e.setPosition(new TextPosition(start.beginLine, start.beginColumn, q.getPosition().endLine, q.getPosition().endColumn));
			  return e;
			}
		  )
		// null_predicate
		| LOOKAHEAD(Column() <IS>)(column=Column() <IS> [notToken=<NOT>] end=<NULL>
		    {
		      IsNull in = queryFactory.createIsNull((notToken!=null), column);
		      in.setPosition(new TextPosition(column.getPosition().beginLine, column.getPosition().beginColumn, end.endLine, (end.endColumn < 0) ? -1 : (end.endColumn + 1)));
		      return in;
		    }
		   )
		// like_predicate
		| LOOKAHEAD(StringExpression() [<NOT>] <LIKE>) (strExpr1=StringExpression() [notToken=<NOT>] <LIKE> strExpr2=StringExpression()
		    {
		      Comparison comp = queryFactory.createComparison(strExpr1, (notToken==null)?ComparisonOperator.LIKE:ComparisonOperator.NOTLIKE, strExpr2);
		      comp.setPosition(new TextPosition(strExpr1.getPosition(), strExpr2.getPosition()));
		      return comp;
		    }
		  )
		| (op=ValueExpression()
			(// comparison_predicate
			(constraint=ComparisonEnd(op))
			// between predicate
			| LOOKAHEAD(2) constraint=BetweenEnd(op)
			// in_predicate
			| constraint=InEnd(op)
			)
		  )
		)
	}catch(Exception ex){
		throw generateParseException(ex);
	}
	{return constraint;}
}

Comparison ComparisonEnd(ADQLOperand leftOp): {Token comp; ADQLOperand rightOp;} {
	((comp=<EQUAL> | comp=<NOT_EQUAL> | comp=<LESS_THAN> | comp=<LESS_EQUAL_THAN> | comp=<GREATER_THAN> | comp=<GREATER_EQUAL_THAN>) rightOp=ValueExpression())
	{
		try{
		  	Comparison comparison = queryFactory.createComparison(leftOp, ComparisonOperator.getOperator(comp.image), rightOp);
		  	comparison.setPosition(new TextPosition(leftOp.getPosition(), rightOp.getPosition()));
			return comparison;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

Between BetweenEnd(ADQLOperand leftOp): {Token start,notToken=null; ADQLOperand min, max;} {
	[notToken=<NOT>] start=<BETWEEN> min=ValueExpression() <AND> max=ValueExpression()
	{
		try{
		  	Between bet = queryFactory.createBetween((notToken!=null), leftOp, min, max);
		  	if (notToken != null) start = notToken;
		  	bet.setPosition(new TextPosition(start.beginLine, start.beginColumn, max.getPosition().endLine, max.getPosition().endColumn));
			return bet;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

In InEnd(ADQLOperand leftOp): {Token not=null, start; ADQLQuery q = null; ADQLOperand item; Vector<ADQLOperand> items = new Vector<ADQLOperand>();} {
	[not=<NOT>] start=<IN>
	(LOOKAHEAD(2) q=SubQueryExpression()
	| (<LEFT_PAR> item=ValueExpression() {items.add(item);} (<COMMA> item=ValueExpression() {items.add(item);})* <RIGHT_PAR>))
	{
		try{
		  	In in;
		  	start = (not!=null) ? not : start;
			if (q != null){
				in = queryFactory.createIn(leftOp, q, not!=null);
				in.setPosition(new TextPosition(start.beginLine, start.beginColumn, q.getPosition().endLine, q.getPosition().endColumn));
			}else{
				ADQLOperand[] list = new ADQLOperand[items.size()];
				int i=0;
				for(ADQLOperand op : items)
					list[i++] = op;
				in = queryFactory.createIn(leftOp, list, not!=null);
				in.setPosition(new TextPosition(start.beginLine, start.beginColumn, list[list.length-1].getPosition().endLine, list[list.length-1].getPosition().endColumn));
			}
			return in;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}


/* ************* */
/* SQL FUNCTIONS */
/* ************* */
SQLFunction SqlFunction(): {Token fct, all=null, distinct=null, end; ADQLOperand op=null; SQLFunction funct = null;}{
	try{
		(
			(fct=<COUNT> <LEFT_PAR> [distinct=<QUANTIFIER>] (all=<ASTERISK> | op=ValueExpression()) end=<RIGHT_PAR>
			{
			  funct = queryFactory.createSQLFunction((all!=null)?SQLFunctionType.COUNT_ALL:SQLFunctionType.COUNT, op, distinct != null && distinct.image.equalsIgnoreCase("distinct"));
			  funct.setPosition(new TextPosition(fct, end));
			})
		|
			((fct=<AVG> | fct=<MAX> | fct=<MIN> | fct=<SUM>) <LEFT_PAR> [distinct=<QUANTIFIER>] op=ValueExpression() end=<RIGHT_PAR> 
			{
			  funct = queryFactory.createSQLFunction(SQLFunctionType.valueOf(fct.image.toUpperCase()), op, distinct != null && distinct.image.equalsIgnoreCase("distinct"));
			  funct.setPosition(new TextPosition(fct, end));
			})
		)
	}catch(Exception ex){
		throw generateParseException(ex);
	}
	{ return funct; }
}


/* ************** */
/* ADQL FUNCTIONS */
/* ************** */
ADQLOperand[] Coordinates(): {ADQLOperand[] ops = new ADQLOperand[2];} {
	ops[0]=NumericExpression() <COMMA> ops[1]=NumericExpression()
	{return ops;}
}

GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<GeometryFunction> gvf1, gvf2; GeometryValue<PointFunction> gvp1, gvp2; GeometryFunction gf = null; PointFunction p1=null, p2=null; ADQLColumn col1 = null, col2 = null;} {
	try{
		// predicate_geometry_function
		(
			((fct=<CONTAINS> | fct=<INTERSECTS>) <LEFT_PAR> gvf1=GeometryExpression() <COMMA> gvf2=GeometryExpression() end=<RIGHT_PAR>
			{
				if (fct.image.equalsIgnoreCase("contains"))
					gf = queryFactory.createContains(gvf1, gvf2);
				else
					gf = queryFactory.createIntersects(gvf1, gvf2);
			})
		// non_predicate_geometry_function
		|	(fct=<AREA> <LEFT_PAR> gvf1=GeometryExpression() end=<RIGHT_PAR>) {gf = queryFactory.createArea(gvf1);}
		|	(fct=<COORD1> <LEFT_PAR> (p1=Point() {gf = queryFactory.createCoord1(p1);} | col1=Column() {col1.setExpectedType('G'); gf = queryFactory.createCoord1(col1);}) end=<RIGHT_PAR>)
		|	(fct=<COORD2> <LEFT_PAR> (p1=Point() {gf = queryFactory.createCoord2(p1);} | col1=Column() {col1.setExpectedType('G'); gf = queryFactory.createCoord2(col1);}) end=<RIGHT_PAR>)
		|	(fct=<DISTANCE>
				<LEFT_PAR>
				(p1=Point()|col1=Column()) 
				{
					if (p1 != null)
						gvp1 = new GeometryValue<PointFunction>(p1);
					else{
						col1.setExpectedType('G');
						gvp1 = new GeometryValue<PointFunction>(col1);
					}
				}
				<COMMA>
				(p2=Point()|col2=Column())
				{
					if (p2 != null)
						gvp2 = new GeometryValue<PointFunction>(p2);
					else{
						col2.setExpectedType('G');
						gvp2 = new GeometryValue<PointFunction>(col2);
					}
				} 
				end=<RIGHT_PAR>
				{gf = queryFactory.createDistance(gvp1, gvp2);}
			)
		)
	}catch(Exception ex){
		throw generateParseException(ex);
	}
	
	{
	  gf.setPosition(new TextPosition(fct, end));
	  return gf;
	}
}

ADQLOperand CoordinateSystem(): { ADQLOperand coordSys=null;}{
	coordSys=StringExpression()
	{ return coordSys; }
}

GeometryFunction GeometryValueFunction(): {Token fct=null, end=null; ADQLOperand coordSys; ADQLOperand width, height; ADQLOperand[] coords, tmp; Vector<ADQLOperand> vCoords; ADQLOperand op=null; GeometryValue<GeometryFunction> gvf = null; GeometryFunction gf = null;} {
	try{
		// BOX:
		((fct=<BOX> <LEFT_PAR> coordSys=CoordinateSystem() // coord_sys
				<COMMA> coords=Coordinates() // coordinates
				<COMMA> width=NumericExpression() <COMMA> height=NumericExpression() end=<RIGHT_PAR>)
		 {gf = queryFactory.createBox(coordSys, coords[0], coords[1], width, height);}
		 
		// CENTROID:
		| (fct=<CENTROID> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>) {gf = queryFactory.createCentroid(gvf);}
		
		// CIRCLE:
		| (fct=<CIRCLE> <LEFT_PAR> coordSys=CoordinateSystem() // coord_sys
				<COMMA> coords=Coordinates() // coordinates
				<COMMA> width=NumericExpression() end=<RIGHT_PAR>) // radius
		 {gf = queryFactory.createCircle(coordSys, coords[0], coords[1], width);}
		
		// POINT: 
		| gf=Point()
		
		// POLYGON:
		| (fct=<POLYGON> <LEFT_PAR> coordSys=CoordinateSystem() // coord_sys
				{ vCoords = new Vector<ADQLOperand>(); } // coordinates
				<COMMA> tmp=Coordinates() {vCoords.add(tmp[0]); vCoords.add(tmp[1]);}
				<COMMA> tmp=Coordinates() {vCoords.add(tmp[0]); vCoords.add(tmp[1]);}
				<COMMA> tmp=Coordinates() {vCoords.add(tmp[0]); vCoords.add(tmp[1]);}
				(<COMMA> tmp=Coordinates() {vCoords.add(tmp[0]); vCoords.add(tmp[1]);})*
				end=<RIGHT_PAR>)
		  { gf = queryFactory.createPolygon(coordSys, vCoords); }
		  
		// REGION:
		| (fct=<REGION> <LEFT_PAR> op=StringExpression() end=<RIGHT_PAR>) {gf = queryFactory.createRegion(op);})
	}catch(Exception ex){
		throw generateParseException(ex);
	}
	
	{
	  if (fct != null && end != null) // = !(gf instanceof Point)
	  	gf.setPosition(new TextPosition(fct, end));
	  return gf;
	}
}

PointFunction Point(): {Token start, end; ADQLOperand coordSys; ADQLOperand[] coords;} {
	start=<POINT> <LEFT_PAR> coordSys=CoordinateSystem() // coord_sys
			<COMMA> coords=Coordinates() end=<RIGHT_PAR> // coordinates
	{
		try{
			PointFunction pf = queryFactory.createPoint(coordSys, coords[0], coords[1]);
			pf.setPosition(new TextPosition(start, end));
			return pf;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

GeometryFunction ExtractCoordSys(): {Token start, end; GeometryValue<GeometryFunction> gvf;} {
	start=<COORDSYS> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>
	{
		try{
			GeometryFunction gf = queryFactory.createExtractCoordSys(gvf);
			gf.setPosition(new TextPosition(start, end));
			return gf;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

/* ***************** */
/* NUMERIC FUNCTIONS */
/* ***************** */
ADQLFunction NumericFunction(): {ADQLFunction fct;} {
	(fct=MathFunction()
	| fct=TrigFunction()
	| fct=GeometryFunction()
	| fct=UserDefinedFunction() { ((UserDefinedFunction)fct).setExpectedType('N'); })
	{return fct;}
}

MathFunction MathFunction(): {Token fct=null, end; ADQLOperand param1=null, param2=null; NumericConstant integerValue = null;} {
	try{
		((fct=<ABS> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<CEILING> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<DEGREES> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<EXP> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<FLOOR> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<LOG> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<LOG10> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<MOD> <LEFT_PAR> param1=NumericExpression() <COMMA> param2=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<PI> <LEFT_PAR> end=<RIGHT_PAR>)
		| (fct=<POWER> <LEFT_PAR> param1=NumericExpression() <COMMA> param2=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<RADIANS> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<RAND> <LEFT_PAR> (param1=NumericExpression())? end=<RIGHT_PAR>)
		| (fct=<ROUND> <LEFT_PAR> param1=NumericExpression() (<COMMA> param2=SignedInteger())? end=<RIGHT_PAR>)
		| (fct=<SQRT> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
		| (fct=<TRUNCATE> <LEFT_PAR> param1=NumericExpression() (<COMMA> param2=SignedInteger())? end=<RIGHT_PAR>))
		{
			MathFunction mf = queryFactory.createMathFunction(MathFunctionType.valueOf(fct.image.toUpperCase()), param1, param2);
			mf.setPosition(new TextPosition(fct, end));
			return mf;
		}
	}catch(Exception ex){
		throw generateParseException(ex);
	}
}

MathFunction TrigFunction(): {Token fct=null, end; ADQLOperand param1=null, param2=null;} {
	((fct=<ACOS> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<ASIN> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<ATAN> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<ATAN2> <LEFT_PAR> param1=NumericExpression() <COMMA> param2=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<COS> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<COT> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<SIN> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>)
	| (fct=<TAN> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>))
	{
		try{
			MathFunction mf = queryFactory.createMathFunction(MathFunctionType.valueOf(fct.image.toUpperCase()), param1, param2);
			mf.setPosition(new TextPosition(fct, end));
			return mf;
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

UserDefinedFunction UserDefinedFunction(): {Token fct, end; Vector<ADQLOperand> params = new Vector<ADQLOperand>(); ADQLOperand op;} {
	fct=<REGULAR_IDENTIFIER_CANDIDATE> <LEFT_PAR> (op=ValueExpression() {params.add(op);} (<COMMA> op=ValueExpression() {params.add(op);})*)? end=<RIGHT_PAR>
	{
		// Ensure the given function name is valid: 
		if (!isRegularIdentifier(fct.image))
			throw new ParseException("Invalid (User Defined) Function name: \""+fct.image+"\"!", new TextPosition(fct));
		
		//System.out.println("INFO [ADQLParser]: \""+fct.image+"\" (from line "+fct.beginLine+" and column "+fct.beginColumn+" to line "+token.endLine+" and column "+(token.endColumn+1)+") is considered as an user defined function !");
		
		try{
			//  Build the parameters list:
			ADQLOperand[] parameters = new ADQLOperand[params.size()];
			for(int i=0; i<params.size(); i++)
				parameters[i] = params.get(i);
			
			// Create the UDF function:
			UserDefinedFunction udf = queryFactory.createUserDefinedFunction(fct.image, parameters);
			udf.setPosition(new TextPosition(fct, end));
			return udf;
			
		}catch(UnsupportedOperationException uoe){
		  	/* This catch clause is just for backward compatibility:
		  	 * if the createUserDefinedFunction(...) is overridden and
		  	 * the function can not be identified a such exception may be thrown). */
			throw new ParseException(uoe.getMessage(), new TextPosition(fct, token));
		}catch(Exception ex){
			throw generateParseException(ex);
		}
	}
}

