Skip to content
adqlGrammar.jj 74.4 KiB
Newer Older
/*
 * 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)
*/

							/* ########### */
							/* # OPTIONS # */
							/* ########### */
options {
	STATIC = false;
	IGNORE_CASE = 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)
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()));
	* 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) {
	* 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) {
		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);
		
		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) {
		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) {
		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) {

	/* 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) {
	}
	
	/**
	* 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) {
	}
	
	/**
	* 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) {

	/* 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:
			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))");