/*
// $Id: //open/dev/farrago/src/org/eigenbase/lurql/parser/LurqlParser.jj#1 $
// Package org.eigenbase is a class library of data management components.
// Copyright (C) 2005-2009 The Eigenbase Project
// Copyright (C) 2009-2009 SQLstream, Inc.
// Copyright (C) 2005-2009 LucidEra, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version approved by The Eigenbase Project.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
options {
    STATIC = false;
    IGNORE_CASE = true;
    UNICODE_INPUT = true;
}

PARSER_BEGIN(LurqlParser)

package org.eigenbase.lurql.parser;

import org.eigenbase.lurql.*;

import java.util.*;
import java.util.regex.*;

import org.eigenbase.sql.parser.*;

public class LurqlParser
{
}

PARSER_END(LurqlParser)

LurqlQuery LurqlQuery() :
{
    List selectList;
    LurqlQueryNode root;
}
{
    <SELECT> selectList = SelectList()
    <FROM> root = Root()
    <SEMICOLON>
    {
        return new LurqlQuery(selectList, root);
    }
}

List SelectList() :
{
    List list;
}
{
    (
        <STAR>
        {
            list = Collections.singletonList("*");
        }
        | list = IdentifierList()
    )
    {
        return list;
    }
}

List IdentifierList() :
{
    List list = new ArrayList();
    String id;
}
{
    id = Identifier()
    {
        list.add(id);
    }
    (
        <COMMA> id = Identifier()
        {
            list.add(id);
        }
    ) *
    {
        return list;
    }
}

LurqlQueryNode Root() :
{
    LurqlQueryNode term;
}
{
    (term = SimpleRoot() | term = CompoundRoot())
    {
        return term;
    }
}

List WhereClause() :
{
    List filterList = new ArrayList();
    LurqlFilter filter;
}
{
    <WHERE>
    filter = Filter()
    {
        filterList.add(filter);
    }
    (
        <AND> filter = Filter()
        {
            filterList.add(filter);
        }
    ) *
    {
        return filterList;
    }
}

LurqlFilter Filter() :
{
    LurqlFilter filter;
    boolean negated = false;
}
{
    [ <NOT> { negated = true; } ]
    filter = LeafFilter()
    {
        if (negated) {
            filter.setNegated();
        }
        return filter;
    }
}

LurqlFilter LeafFilter() :
{
    String attributeName;
    Object value;
    Set values;
    String sqlQuery;
    LurqlDynamicParam param;
    LurqlPathSpec existsPathSpec;
    List existsSelectList = null;
}
{
    attributeName = Identifier()
    (
        <EQUALS> value = FilterValue()
        {
            values = Collections.singleton(value);
            return new LurqlFilter(attributeName, values);
        }
        |
        <MATCHES> value = FilterValue()
        {
            values = Collections.singleton(value);
            return new LurqlFilter(attributeName, values, true);
        }
        |
        <IN>
        (
            <LPAREN>
            {
                values = new LinkedHashSet();
            }
            FilterValueList(values)
            <RPAREN>
            {
                return new LurqlFilter(attributeName, values);
            }
            |
            sqlQuery = SqlQuery()
            {
                return new LurqlFilter(attributeName, sqlQuery);
            }
            |
            param = DynamicParameter()
            {
                return new LurqlFilter(attributeName, param);
            }
        )
    )
    |
    <EXISTS>
    [ existsSelectList = SelectList() <IN> ]
    existsPathSpec = PathSpec()
    {
        if (existsSelectList == null) {
            existsSelectList = Collections.singletonList("*");
        }
        return new LurqlFilter(
            new LurqlExists(existsSelectList, existsPathSpec));
    }
}

LurqlRoot SimpleRoot() :
{
    String className = null;
    String aliasName = null;
    String objectId = null;
    List whereClause = Collections.EMPTY_LIST;
    LurqlPathSpec pathSpec = null;
}
{
    <CLASS> className = Identifier()
    [
        <AS> aliasName = Identifier()
    ]
    [ 
        whereClause = WhereClause()
    ]
    [
        <THEN> pathSpec = PathSpec()
    ]
    {
        return new LurqlRoot(
            aliasName, className, whereClause,pathSpec);
    }
}

LurqlPathSpec CompoundRoot() :
{
    List roots = new ArrayList();
    LurqlQueryNode root;
    LurqlPathSpec gatherThen = null;
}
{
    <LPAREN> root = Root()
    {
        roots.add(root);
    }
    (
        <UNION> root = Root()
        {
            roots.add(root);
        }
    ) *
    <RPAREN>
    [
        <GATHER> <THEN> gatherThen = PathSpec()
    ]
    {
        return new LurqlPathSpec(roots, gatherThen, false);
    }
}

LurqlPathSpec PathSpec() :
{
    List branches = new ArrayList();
    LurqlPathBranch branch;
    LurqlPathSpec gatherThen = null;
    boolean gatherParent = false;
}
{
    <LPAREN> branch = PathBranch()
    {
        branches.add(branch);
    }
    (
        <UNION> branch = PathBranch()
        {
            branches.add(branch);
        }
    ) *
    <RPAREN>
    [
        <GATHER> [ <WITH> <PARENT> { gatherParent = true; } ]
        <THEN>
        gatherThen = PathSpec()
    ]
    {
        return new LurqlPathSpec(branches, gatherThen, gatherParent);
    }
}

LurqlPathBranch PathBranch() :
{
    LurqlPathBranch branch;
}
{
    (
        branch = Follow()
        | branch = Recurse()
    )
    {
        return branch;
    }
}

LurqlFollow Follow() :
{
    String aliasName = null;
    Map associationFilters = new LinkedHashMap();
    List whereClause = Collections.EMPTY_LIST;
    LurqlPathSpec thenSpec = null;
}
{
    <FOLLOW>
    (
        AssociationFilter(associationFilters)
    ) *
    [
        <AS> aliasName = Identifier()
    ]
    [ 
        whereClause = WhereClause()
    ]
    [
        <THEN> thenSpec = PathSpec()
    ]
    {
        return new LurqlFollow(
            aliasName, associationFilters, whereClause, thenSpec);
    }
}

void AssociationFilter(Map map) :
{
    String key;
    String value;
}
{
    (
        <ORIGIN>
        (
            <END>
            {
                key = LurqlFollow.AF_ORIGIN_END;
            }
            | <CLASS>
            {
                key = LurqlFollow.AF_ORIGIN_CLASS;
            }
        )
        value = Identifier())
    {
        map.put(key, value);
    }
    |
    (
        <DESTINATION>
        (
            <END>
            {
                key = LurqlFollow.AF_DESTINATION_END;
            }
            | <CLASS>
            {
                key = LurqlFollow.AF_DESTINATION_CLASS;
            }
        )
        value = Identifier())
    {
        map.put(key, value);
    }
    | <COMPOSITE>
    {
        map.put(LurqlFollow.AF_COMPOSITE, null);
    }
    | <NONCOMPOSITE>
    {
        map.put(LurqlFollow.AF_NONCOMPOSITE, null);
    }
    |
    (
        <ASSOCIATION> value = Identifier()
    )
    {
        map.put(LurqlFollow.AF_ASSOCIATION, value);
    }
    | <FORWARD>
    {
        map.put(LurqlFollow.AF_FORWARD, null);
    }
    | <BACKWARD>
    {
        map.put(LurqlFollow.AF_BACKWARD, null);
    }
}

LurqlRecurse Recurse() :
{
    LurqlPathSpec pathSpec;
    LurqlPathSpec thenSpec = null;
    // TODO jvs 4-Aug-2006:  acceptance of path-spec-with-gather without
    // enclosing parens is for backwards compatibility; eliminate it after
    // a while
}
{
    <RECURSIVELY>
    (
        LOOKAHEAD(2) (pathSpec = PathSpec())
        | (<LPAREN> pathSpec = PathSpec() <RPAREN>)
    )
    [
        <THEN> thenSpec = PathSpec()
    ]
    {
        return new LurqlRecurse(pathSpec, thenSpec);
    }
}

String Identifier() :
{
}
{
    <IDENTIFIER>
    {
        return getToken(0).image;
    }
    |
    <D_QUOTED_STRING>
    {
        return SqlParserUtil.strip(getToken(0).image, "\"");
    }
}

String StringLiteral() :
{
}
{
    <S_QUOTED_STRING>
    {
        return SqlParserUtil.parseString(getToken(0).image);
    }
}

Object NullLiteral() :
{
}
{
    <NULL>
    {
        return LurqlFilter.NULL_VALUE;
    }
}

void FilterValueList(Collection collection) :
{
    Object s;
}
{
    s = FilterValue()
    {
        collection.add(s);
    }
    (
        <COMMA>
        s = FilterValue()
        {
            collection.add(s);
        }
    ) *
}

String SqlQuery() :
{
}
{
    <SQL_QUERY>
    {
        String s = getToken(0).image;
        return s.substring(1, s.length() - 1);
    }
}

LurqlDynamicParam DynamicParameter() :
{
    String id;
}
{
    <QMARK> id = Identifier()
    {
        return new LurqlDynamicParam(id);
    }
}

Object FilterValue() :
{
    Object obj;
}
{
    (
        obj = DynamicParameter()
        | obj = StringLiteral()
        | obj = NullLiteral()
    )
    {
        return obj;
    }
}

TOKEN :
{
    < SELECT: "select" >
    | < FROM: "from" >
    | < STAR: "*" >
    | < COMMA: "," >
    | < EQUALS: "=" >
    | < SEMICOLON: ";" >
    | < SQUOTE: "'" >
    | < DQUOTE: "\"" >
    | < LPAREN: "(" >
    | < RPAREN: ")" >
    | < CLASS: "class" >
    | < AS: "as" >
    | < LSQUARE: "[" >
    | < RSQUARE: "]" >
    | < QMARK: "?" >
    | < UNION: "union" >
    | < EXISTS: "exists" >
    | < GATHER: "gather" >
    | < THEN: "then" >
    | < FOLLOW: "follow" >
    | < ORIGIN: "origin" >
    | < DESTINATION: "destination" >
    | < END: "end" >
    | < COMPOSITE: "composite" >
    | < NONCOMPOSITE: "noncomposite" >
    | < ASSOCIATION: "association" >
    | < FORWARD: "forward" >
    | < BACKWARD: "backward" >
    | < RECURSIVELY: "recursively" >
    | < WHERE: "where" >
    | < AND: "and" >
    | < WITH: "with" >
    | < PARENT: "parent" >
    | < IN: "in" >
    | < MATCHES: "matches" >
    | < NULL: "null" >
    | < NOT: "not" >
    | < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>)* >
    | < S_QUOTED_STRING: <SQUOTE> ( (~["'","\n","\r"]) | ("''"))* <SQUOTE> >
    | < D_QUOTED_STRING: <DQUOTE> ( (~["\"","\n","\r"]) | ("\"\""))* <DQUOTE> >
    | < SQL_QUERY: <LSQUARE> ( (~["]"]) )* <RSQUARE> >
    | < #LETTER:
    [
        "\u0024",
        "\u0041"-"\u005a",
        "\u005f",
        "\u0061"-"\u007a",
        "\u00c0"-"\u00d6",
        "\u00d8"-"\u00f6",
        "\u00f8"-"\u00ff",
        "\u0100"-"\u1fff",
        "\u3040"-"\u318f",
        "\u3300"-"\u337f",
        "\u3400"-"\u3d2d",
        "\u4e00"-"\u9fff",
        "\uf900"-"\ufaff"
    ] >
    | < #DIGIT:
    [
        "\u0030"-"\u0039",
        "\u0660"-"\u0669",
        "\u06f0"-"\u06f9",
        "\u0966"-"\u096f",
        "\u09e6"-"\u09ef",
        "\u0a66"-"\u0a6f",
        "\u0ae6"-"\u0aef",
        "\u0b66"-"\u0b6f",
        "\u0be7"-"\u0bef",
        "\u0c66"-"\u0c6f",
        "\u0ce6"-"\u0cef",
        "\u0d66"-"\u0d6f",
        "\u0e50"-"\u0e59",
        "\u0ed0"-"\u0ed9",
        "\u1040"-"\u1049"
    ] >
}

SKIP :
{
    " "
    | "\t"
    | "\n"
    | "\r"
    | "\f"
}

// End LurqlParser.jj
