/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Grammar file for Pig tree parser (for schema alias validation).
 *
 * NOTE: THIS FILE IS BASED ON QueryParser.g, SO IF YOU CHANGE THAT FILE, YOU WILL
 *       PROBABLY NEED TO MAKE CORRESPONDING CHANGES TO THIS FILE AS WELL.
 */

tree grammar LogicalPlanGenerator;

options {
    tokenVocab=QueryParser;
    ASTLabelType=CommonTree;
    output=AST;
    backtrack=true;
}

scope GScope {
    LogicalRelationalOperator currentOp; // Current relational operator that's being built.
}

@header {
package org.apache.pig.parser;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pig.impl.PigContext;
import org.apache.pig.impl.builtin.GFAny;
import org.apache.pig.impl.logicalLayer.FrontendException;
import org.apache.pig.impl.streaming.StreamingCommand;
import org.apache.pig.impl.streaming.StreamingCommand.HandleSpec;
import org.apache.pig.impl.util.MultiMap;
import org.apache.pig.impl.util.NumValCarrier;
import org.apache.pig.impl.plan.PlanValidationException;
import org.apache.pig.newplan.Operator;
import org.apache.pig.newplan.logical.expression.AddExpression;
import org.apache.pig.newplan.logical.expression.AndExpression;
import org.apache.pig.newplan.logical.expression.BinCondExpression;
import org.apache.pig.newplan.logical.expression.CastExpression;
import org.apache.pig.newplan.logical.expression.ConstantExpression;
import org.apache.pig.newplan.logical.expression.DereferenceExpression;
import org.apache.pig.newplan.logical.expression.DivideExpression;
import org.apache.pig.newplan.logical.expression.EqualExpression;
import org.apache.pig.newplan.logical.expression.GreaterThanEqualExpression;
import org.apache.pig.newplan.logical.expression.GreaterThanExpression;
import org.apache.pig.newplan.logical.expression.IsNullExpression;
import org.apache.pig.newplan.logical.expression.LessThanEqualExpression;
import org.apache.pig.newplan.logical.expression.LessThanExpression;
import org.apache.pig.newplan.logical.expression.LogicalExpression;
import org.apache.pig.newplan.logical.expression.LogicalExpressionPlan;
import org.apache.pig.newplan.logical.expression.MapLookupExpression;
import org.apache.pig.newplan.logical.expression.ModExpression;
import org.apache.pig.newplan.logical.expression.MultiplyExpression;
import org.apache.pig.newplan.logical.expression.NegativeExpression;
import org.apache.pig.newplan.logical.expression.NotEqualExpression;
import org.apache.pig.newplan.logical.expression.NotExpression;
import org.apache.pig.newplan.logical.expression.OrExpression;
import org.apache.pig.newplan.logical.expression.ProjectExpression;
import org.apache.pig.newplan.logical.expression.RegexExpression;
import org.apache.pig.newplan.logical.expression.ScalarExpression;
import org.apache.pig.newplan.logical.expression.SubtractExpression;
import org.apache.pig.newplan.logical.expression.UserFuncExpression;
import org.apache.pig.newplan.logical.relational.LOCogroup;
import org.apache.pig.newplan.logical.relational.LOCube;
import org.apache.pig.newplan.logical.relational.LOFilter;
import org.apache.pig.newplan.logical.relational.LOForEach;
import org.apache.pig.newplan.logical.relational.LOGenerate;
import org.apache.pig.newplan.logical.relational.LOLimit;
import org.apache.pig.newplan.logical.relational.LOJoin;
import org.apache.pig.newplan.logical.relational.LOSort;
import org.apache.pig.newplan.logical.relational.LORank;
import org.apache.pig.newplan.logical.relational.LOSplitOutput;
import org.apache.pig.newplan.logical.relational.LogicalPlan;
import org.apache.pig.newplan.logical.relational.LogicalRelationalOperator;
import org.apache.pig.newplan.logical.relational.LogicalSchema;
import org.apache.pig.newplan.logical.relational.LogicalSchema.LogicalFieldSchema;
import org.apache.pig.newplan.logical.relational.LOCogroup.GROUPTYPE;
import org.apache.pig.newplan.logical.relational.LOJoin.JOINTYPE;
import org.apache.pig.FuncSpec;
import org.apache.pig.builtin.PigStreaming;
import org.apache.pig.data.DataBag;
import org.apache.pig.data.DataType;
import org.apache.pig.data.Tuple;
import java.util.Arrays;
import java.util.Collections;
import java.math.BigInteger;
import java.math.BigDecimal;
}

@members {
private static Log log = LogFactory.getLog( LogicalPlanGenerator.class );

private LogicalPlanBuilder builder = null;

private boolean inForeachPlan = false;

private boolean inNestedCommand = false;

public LogicalPlan getLogicalPlan() {
    return builder.getPlan();
}

public Map<String, Operator> getOperators() {
    return builder.getOperators();
}

public String getLastRel() {
    return builder.getLastRel();
}

@Override
protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow)
throws RecognitionException {
    throw new MismatchedTokenException( ttype, input );
}

@Override
public Object recoverFromMismatchedSet(IntStream input, RecognitionException e, BitSet follow)
throws RecognitionException {
    throw e;
}

public LogicalPlanGenerator(TreeNodeStream input, LogicalPlanBuilder builder) {
    this(input, new RecognizerSharedState());
    this.builder = builder;
}

public LogicalPlanGenerator(TreeNodeStream input, PigContext pigContext, String scope,
    Map<String, String> fileNameMap) {
    this( input );
    builder = new LogicalPlanBuilder( pigContext, scope, fileNameMap, input );
}

} // End of @members

@rulecatch {
catch(RecognitionException re) {
    throw re;
}
}

query : ^( QUERY statement* )
;

statement
scope {
   // Parsing context
    String alias; // The alias of the current operator, either given or generated by the parser.
    Integer parallel; // Parallelism
    String inputAlias; // The alias of the input operator
    int inputIndex;
}
@init {
    $statement::inputIndex = 0;
}
 : general_statement
 | split_statement
 | realias_statement
 | assert_statement
 | register_statement
;

split_statement : split_clause
;

realias_statement : realias_clause
;

assert_statement : assert_clause
;

register_statement
: ^( REGISTER QUOTEDSTRING (USING IDENTIFIER AS IDENTIFIER)? )
  {
    // registers are handled by QueryParserDriver and are not actually part of the logical plan
    // so we just ignore them here
  }
;

general_statement
: ^( STATEMENT ( alias { $statement::alias = $alias.name; } )? oa = op_clause parallel_clause? )
  {
      Operator op = builder.lookupOperator( $oa.alias );
      builder.setParallel( (LogicalRelationalOperator)op, $statement::parallel );
  }
;

realias_clause
: ^(REALIAS alias IDENTIFIER)
    {
	    Operator op = builder.lookupOperator( $IDENTIFIER.text );
	    if (op==null) {
	        throw new UndefinedAliasException(input,
	            new SourceLocation( (PigParserNode)$IDENTIFIER ), $IDENTIFIER.text);
	    }
	    builder.putOperator( $alias.name, (LogicalRelationalOperator)op );
    }
;

parallel_clause
 : ^( PARALLEL INTEGER )
   {
       $statement::parallel = Integer.parseInt( $INTEGER.text );
   }
;

alias returns[String name]: IDENTIFIER { $name = $IDENTIFIER.text; }
;

op_clause returns[String alias] :
            define_clause
          | load_clause { $alias = $load_clause.alias; }
          | group_clause { $alias = $group_clause.alias; }
          | store_clause { $alias = $store_clause.alias; }
          | filter_clause { $alias = $filter_clause.alias; }
          | distinct_clause { $alias = $distinct_clause.alias; }
          | limit_clause { $alias = $limit_clause.alias; }
          | sample_clause { $alias = $sample_clause.alias; }
          | order_clause { $alias = $order_clause.alias; }
          | rank_clause { $alias = $rank_clause.alias; }
          | cross_clause { $alias = $cross_clause.alias; }
          | join_clause { $alias = $join_clause.alias; }
          | union_clause { $alias = $union_clause.alias; }
          | stream_clause { $alias = $stream_clause.alias; }
          | mr_clause { $alias = $mr_clause.alias; }
          | foreach_clause { $alias = $foreach_clause.alias; }
          | cube_clause { $alias = $cube_clause.alias; }
          | assert_clause { $alias = $assert_clause.alias; }
;

define_clause
 : ^( DEFINE alias cmd[$alias.name] )
   {
       builder.defineCommand( $alias.name, $cmd.command );
   }
 | ^( DEFINE alias func_clause[FunctionType.UNKNOWNFUNC] )
   {
       builder.defineFunction( $alias.name, $func_clause.funcSpec );
   }
;

cmd[String alias] returns[StreamingCommand command]
@init {
    List<String> shipPaths = new ArrayList<String>();
    List<String> cachePaths = new ArrayList<String>();
    SourceLocation loc = new SourceLocation( (PigParserNode)$cmd.start );
}
 : ^( EXECCOMMAND ( ship_clause[shipPaths] | cache_clause[cachePaths] | input_clause | output_clause | error_clause )* )
   {
       $command = builder.buildCommand( loc, builder.unquote( $EXECCOMMAND.text ), shipPaths,
           cachePaths, $input_clause.inputHandleSpecs, $output_clause.outputHandleSpecs,
           $error_clause.dir, $error_clause.limit );
   }
;

ship_clause[List<String> paths]
 : ^( SHIP path_list[$paths]? )
;

path_list[List<String> paths]
 : ( QUOTEDSTRING { $paths.add( builder.unquote( $QUOTEDSTRING.text ) ); } )+
;

cache_clause[List<String> paths]
 : ^( CACHE path_list[$paths] )
;

input_clause returns[List<HandleSpec> inputHandleSpecs]
@init {
    $inputHandleSpecs = new ArrayList<HandleSpec>();
}
 : ^( INPUT ( stream_cmd[true] { $inputHandleSpecs.add( $stream_cmd.handleSpec ); } )+ )
;

stream_cmd[boolean in] returns[HandleSpec handleSpec]
@init {
    String handleName = null;
    FuncSpec fs = null;
    String deserializer = PigStreaming.class.getName() + "()";
    byte ft = $in ? FunctionType.PIGTOSTREAMFUNC : FunctionType.STREAMTOPIGFUNC;
}
@after {
    if( fs != null )
        deserializer =  fs.toString();
    $handleSpec = new HandleSpec( handleName, deserializer );
}
 : ^( STDIN { handleName = "stdin"; }
      ( func_clause[ft] { fs = $func_clause.funcSpec; } )? )
 | ^( STDOUT { handleName = "stdout"; }
      ( func_clause[ft] { fs = $func_clause.funcSpec; } )? )
 | ^( QUOTEDSTRING { handleName = builder.unquote( $QUOTEDSTRING.text ); }
      ( func_clause[ft] { fs = $func_clause.funcSpec; } )? )
;

output_clause returns[List<HandleSpec> outputHandleSpecs]
@init {
    $outputHandleSpecs = new ArrayList<HandleSpec>();
}
 : ^( OUTPUT ( stream_cmd[false] { $outputHandleSpecs.add( $stream_cmd.handleSpec ); } )+ )
;

error_clause returns[String dir, Integer limit]
@init {
    $limit = StreamingCommand.MAX_TASKS;
}
 : ^( STDERROR
      ( QUOTEDSTRING
        {
            $dir = builder.unquote( $QUOTEDSTRING.text );
        }
        ( INTEGER
          {
              $limit = Integer.parseInt( $INTEGER.text );
          }
        )?
      )?
    )
;

load_clause returns[String alias]
 : ^( LOAD filename func_clause[FunctionType.LOADFUNC]? as_clause? )
  {
      SourceLocation loc = new SourceLocation( (PigParserNode)$load_clause.start );
      $alias = builder.buildLoadOp( loc, $statement::alias,
          $filename.filename, $func_clause.funcSpec, $as_clause.logicalSchema  );
  }
;

filename returns[String filename]
 : QUOTEDSTRING { $filename = builder.unquote( $QUOTEDSTRING.text ); }
;

as_clause returns[LogicalSchema logicalSchema]
 : ^( AS field_def_list )
   {
        LogicalPlanBuilder.setBytearrayForNULLType($field_def_list.schema);
        $logicalSchema = $field_def_list.schema;
   }
;

field_def[NumValCarrier nvc] returns[LogicalFieldSchema fieldSchema]
@init {
    byte datatype = DataType.NULL;
    if ($nvc==null) {
        $nvc=new NumValCarrier();
    }
}
 : ^( FIELD_DEF IDENTIFIER ( type { datatype = $type.datatype;} )? )
   {
           $fieldSchema = new LogicalFieldSchema( $IDENTIFIER.text, $type.logicalSchema, datatype );
   }
 | ^( FIELD_DEF_WITHOUT_IDENTIFIER ( type { datatype = $type.datatype; } ) )
   {
           $fieldSchema = new LogicalFieldSchema ( $nvc.makeNameFromDataType(datatype) , $type.logicalSchema, datatype );
   }
;

field_def_list returns[LogicalSchema schema]
@init {
    $schema = new LogicalSchema();
    NumValCarrier nvc = new NumValCarrier();
}
 : ( field_def[nvc] { $schema.addField( $field_def.fieldSchema ); } )+
;


type returns[Byte datatype, LogicalSchema logicalSchema]
 : simple_type
   {
        $datatype = $simple_type.datatype;
   }
 | tuple_type
   {
       $datatype = DataType.TUPLE;
       $logicalSchema = $tuple_type.logicalSchema;
   }
 | bag_type
   {
       $datatype = DataType.BAG;
       $logicalSchema = $bag_type.logicalSchema;
   }
 | map_type
   {
       $datatype = DataType.MAP;
       $logicalSchema = $map_type.logicalSchema;
   }
;

simple_type returns[byte datatype]
 : BOOLEAN { $datatype = DataType.BOOLEAN; }
 | INT { $datatype = DataType.INTEGER; }
 | LONG { $datatype = DataType.LONG; }
 | FLOAT { $datatype = DataType.FLOAT; }
 | DOUBLE { $datatype = DataType.DOUBLE; }
 | BIGINTEGER { $datatype = DataType.BIGINTEGER; }
 | BIGDECIMAL { $datatype = DataType.BIGDECIMAL; }
 | DATETIME { $datatype = DataType.DATETIME; }
 | CHARARRAY { $datatype = DataType.CHARARRAY; }
 | BYTEARRAY { $datatype = DataType.BYTEARRAY; }
;

tuple_type returns[LogicalSchema logicalSchema]
 : ^( TUPLE_TYPE
      ( field_def_list
        {
            LogicalPlanBuilder.setBytearrayForNULLType($field_def_list.schema);
            $logicalSchema = $field_def_list.schema;
        }
      )?
    )
;

bag_type returns[LogicalSchema logicalSchema]
 : ^( BAG_TYPE IDENTIFIER? tuple_type? )
   {
       LogicalSchema s = new LogicalSchema();
       s.addField(new LogicalFieldSchema($IDENTIFIER.text, $tuple_type.logicalSchema, DataType.TUPLE));
       $logicalSchema = s;
   }
;

map_type returns[LogicalSchema logicalSchema]
 : ^( MAP_TYPE IDENTIFIER? type? )
   {
       LogicalSchema s = null;
       if( $type.datatype != null ) {
           s = new LogicalSchema();
           s.addField( new LogicalFieldSchema( $IDENTIFIER.text, $type.logicalSchema, $type.datatype ) );
       }
       $logicalSchema = s;
   }
;

func_clause[byte ft] returns[FuncSpec funcSpec]
@init {
    SourceLocation loc = new SourceLocation( (PigParserNode)$func_clause.start );
}
 : ^( FUNC_REF func_name )
   {
       $funcSpec = builder.lookupFunction( $func_name.funcName );
       if( $funcSpec == null )
           $funcSpec = builder.buildFuncSpec( loc, $func_name.funcName, new ArrayList<String>(), $ft );
   }
 | ^( FUNC func_name func_args? )
   {
       $funcSpec = builder.lookupFunction( $func_name.funcName );
       if( $funcSpec == null ) {
           List<String> argList = new ArrayList<String>();
           if( $func_args.args != null )
               argList = $func_args.args;
           $funcSpec = builder.buildFuncSpec( loc, $func_name.funcName, argList, $ft );
       }
   }
;

func_name returns[String funcName]
@init { StringBuilder buf = new StringBuilder(); }
 : p1 = eid { buf.append( $p1.id ); }
      ( ( PERIOD { buf.append( $PERIOD.text ); } | DOLLAR { buf.append( $DOLLAR.text ); } )
      p2 = eid { buf.append( $p2.id ); } )*
   {
       $funcName = buf.toString();
   }
;

func_args returns[List<String> args]
@init { $args = new ArrayList<String>(); }
: ( QUOTEDSTRING { $args.add( builder.unquote( $QUOTEDSTRING.text ) ); }
    | MULTILINE_QUOTEDSTRING { $args.add( builder.unquote( $MULTILINE_QUOTEDSTRING.text ) ); }
  )+
;

// Sets the current operator as CUBE and creates LogicalExpressionPlans based on the user input.
// Ex: x = CUBE inp BY CUBE(a,b), ROLLUP(c,d);
// For the above example this grammar creates LogicalExpressionPlan with ProjectExpression for a,b and c,d dimensions.
// It also outputs the order of operations i.e in this case CUBE operation followed by ROLLUP operation
// These inputs are passed to buildCubeOp methods which then builds the logical plan for CUBE operator.
// If user specifies STAR or RANGE expression for dimensions then it will be expanded inside buildCubeOp.
cube_clause returns[String alias]
scope {
    LOCube cubeOp;
    MultiMap<Integer, LogicalExpressionPlan> cubePlans;
    List<String> operations;
    int inputIndex;
}
scope GScope;
@init {
    $cube_clause::cubeOp = builder.createCubeOp();
    $GScope::currentOp = $cube_clause::cubeOp;
    $cube_clause::cubePlans = new MultiMap<Integer, LogicalExpressionPlan>();
    $cube_clause::operations = new ArrayList<String>();
}
 : ^( CUBE cube_item )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$cube_clause.start );
       $alias = builder.buildCubeOp( loc, $cube_clause::cubeOp, $statement::alias,
       $statement::inputAlias, $cube_clause::operations, $cube_clause::cubePlans );
   }
;

cube_item
 : rel ( cube_by_clause
   {
       $cube_clause::cubePlans = $cube_by_clause.plans;
       $cube_clause::operations = $cube_by_clause.operations;
   } )
;

cube_by_clause returns[List<String> operations, MultiMap<Integer, LogicalExpressionPlan> plans]
@init {
    $operations = new ArrayList<String>();
    $plans = new MultiMap<Integer, LogicalExpressionPlan>();
}
 : ^( BY cube_or_rollup { $operations = $cube_or_rollup.operations; $plans = $cube_or_rollup.plans; })
;

cube_or_rollup returns[List<String> operations, MultiMap<Integer, LogicalExpressionPlan> plans]
@init {
    $operations = new ArrayList<String>();
    $plans = new MultiMap<Integer, LogicalExpressionPlan>();
}
 : ( cube_rollup_list
   {
       $operations.add($cube_rollup_list.operation);
       $plans.put( $cube_clause::inputIndex, $cube_rollup_list.plans);
       $cube_clause::inputIndex++;
   } )+
;

cube_rollup_list returns[String operation, List<LogicalExpressionPlan> plans]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
}
 : ^( ( CUBE { $operation = "CUBE"; } | ROLLUP { $operation = "ROLLUP"; } ) cube_by_expr_list { $plans = $cube_by_expr_list.plans; } )
;

cube_by_expr_list returns[List<LogicalExpressionPlan> plans]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
}
 : ( cube_by_expr { $plans.add( $cube_by_expr.plan ); } )+
;

cube_by_expr returns[LogicalExpressionPlan plan]
@init {
    $plan = new LogicalExpressionPlan();
}
 : col_range[$plan]
 | expr[$plan]
 | STAR
   {
       builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), $plan, $GScope::currentOp, 0, null, -1 );
   }
;

group_clause returns[String alias]
scope {
    MultiMap<Integer, LogicalExpressionPlan> groupPlans;
    int inputIndex;
    List<String> inputAliases;
    List<Boolean> innerFlags;
}
scope GScope;
@init {
    $GScope::currentOp = builder.createGroupOp();
    $group_clause::groupPlans = new MultiMap<Integer, LogicalExpressionPlan>();
    $group_clause::inputAliases = new ArrayList<String>();
    $group_clause::innerFlags = new ArrayList<Boolean>();
    GROUPTYPE groupType = GROUPTYPE.REGULAR;
    SourceLocation loc = new SourceLocation( (PigParserNode)$group_clause.start );
    int oldStatementIndex = $statement::inputIndex;
}
@after { $statement::inputIndex = oldStatementIndex; }
 : ^( GROUP group_item+ ( group_type { groupType = $group_type.type; ((LOCogroup)$GScope::currentOp).pinOption(LOCogroup.OPTION_GROUPTYPE); } )? partition_clause? )
   {
       $alias = builder.buildGroupOp( loc, (LOCogroup)$GScope::currentOp, $statement::alias,
           $group_clause::inputAliases, $group_clause::groupPlans, groupType, $group_clause::innerFlags,
           $partition_clause.partitioner );
   }
 | ^( COGROUP group_item+ ( group_type { groupType = $group_type.type;((LOCogroup)$GScope::currentOp).pinOption(LOCogroup.OPTION_GROUPTYPE); } )? partition_clause? )
   {
       $alias = builder.buildGroupOp( loc, (LOCogroup)$GScope::currentOp, $statement::alias,
           $group_clause::inputAliases, $group_clause::groupPlans, groupType, $group_clause::innerFlags,
           $partition_clause.partitioner );
   }
;

group_type returns[GROUPTYPE type]
 : QUOTEDSTRING
   {
       $type =builder.parseGroupType( $QUOTEDSTRING.text, new SourceLocation( (PigParserNode)$QUOTEDSTRING ) );
   }
;

group_item
@init { boolean inner = false; }
 : rel ( join_group_by_clause
         {
             $group_clause::groupPlans.put( $group_clause::inputIndex, $join_group_by_clause.plans );
         }
         | ALL
         {
             LogicalExpressionPlan plan = new LogicalExpressionPlan();
             ConstantExpression ce = new ConstantExpression( plan, "all");
             ce.setLocation( new SourceLocation( (PigParserNode)$ALL ) );
             List<LogicalExpressionPlan> plans = new ArrayList<LogicalExpressionPlan>( 1 );
             plans.add( plan );
             $group_clause::groupPlans.put( $group_clause::inputIndex, plans );
         }
         | ANY
         {
             LogicalExpressionPlan plan = new LogicalExpressionPlan();
             UserFuncExpression udf = new UserFuncExpression( plan, new FuncSpec( GFAny.class.getName() ) );
             udf.setLocation( new SourceLocation( (PigParserNode)$ANY ) );
             List<LogicalExpressionPlan> plans = new ArrayList<LogicalExpressionPlan>( 1 );
             plans.add( plan );
             $group_clause::groupPlans.put( $group_clause::inputIndex, plans );
         }
        ) ( INNER { inner =  true; } | OUTER )?
   {
       $group_clause::inputAliases.add( $statement::inputAlias );
       $group_clause::innerFlags.add( inner );
       $group_clause::inputIndex++;
       $statement::inputIndex++;
   }
;

rel
 : alias
   {
       $statement::inputAlias = $alias.name;
   }
 | previous_rel
   {
       $statement::inputAlias = $previous_rel.name;
   }
 | inline_op
;

previous_rel returns[String name] : ARROBA { $name = builder.getLastRel(new SourceLocation((PigParserNode)$ARROBA)); }
;

inline_op
@init {
    String al = $statement::alias;
    $statement::alias = null;
}
@after {
    $statement::alias = al;
}
 : op_clause parallel_clause?
   {
       Operator op = builder.lookupOperator( $op_clause.alias );
       builder.setParallel( (LogicalRelationalOperator)op, $statement::parallel );
       $statement::inputAlias = $op_clause.alias;
   }
;

flatten_generated_item returns[LogicalExpressionPlan plan, boolean flattenFlag, LogicalSchema schema]
@init {
    $plan = new LogicalExpressionPlan();
}
 : ( flatten_clause[$plan] { $flattenFlag = true; }
   | col_range[$plan]
   | expr[$plan]
   | STAR
     {
         builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), $plan, $GScope::currentOp,
             $statement::inputIndex, null, -1 );
     }
   )
   ( field_def_list { $schema = $field_def_list.schema; } )?
;

flatten_clause[LogicalExpressionPlan plan]
 : ^( FLATTEN expr[$plan] )
;

store_clause returns[String alias]
 : ^( STORE rel filename func_clause[FunctionType.STOREFUNC]? )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$STORE );
       $alias= builder.buildStoreOp( loc, $statement::alias,
          $statement::inputAlias, $filename.filename, $func_clause.funcSpec );
   }
;

assert_clause returns[String alias]
scope GScope;
@init {
    $GScope::currentOp = builder.createFilterOp();
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
}
 : ^( ASSERT rel cond[exprPlan] comment? )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$ASSERT );
       $alias= builder.buildAssertOp(loc, (LOFilter)$GScope::currentOp, $statement::alias,
          $statement::inputAlias, $cond.expr, $comment.comment, exprPlan);
   }
;

comment returns[String comment]
 : QUOTEDSTRING { $comment = builder.unquote( $QUOTEDSTRING.text ); }
;

filter_clause returns[String alias]
scope GScope;
@init {
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
    $GScope::currentOp = builder.createFilterOp();
}
 : ^( FILTER rel cond[exprPlan] )
   {
       $alias = builder.buildFilterOp( new SourceLocation( (PigParserNode)$FILTER ),
           (LOFilter)$GScope::currentOp, $statement::alias,
           $statement::inputAlias, exprPlan );
   }
;

cond[LogicalExpressionPlan exprPlan] returns[LogicalExpression expr]
 : ^( OR left = cond[exprPlan] right = cond[exprPlan] )
   {
       $expr = new OrExpression( $exprPlan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$OR ) );
   }
 | ^( AND left = cond[exprPlan] right = cond[exprPlan] )
   {
       $expr = new AndExpression( $exprPlan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$AND ) );
   }
 | ^( NOT c = cond[exprPlan] )
   {
       $expr = new NotExpression( $exprPlan, $c.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$NOT ) );
   }
 | ^( NULL expr[$exprPlan] NOT? )
   {
       $expr = new IsNullExpression( $exprPlan, $expr.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$NULL ) );
       if( $NOT != null ) {
           $expr = new NotExpression( $exprPlan, $expr );
           $expr.setLocation( new SourceLocation( (PigParserNode)$NOT ) );
       }
   }
 | ^( rel_op_eq e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new EqualExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_eq.start ) );
   }
 | ^( rel_op_ne e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new NotEqualExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_ne.start ) );
   }
 | ^( rel_op_lt e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new LessThanExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_lt.start ) );
   }
 | ^( rel_op_lte e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new LessThanEqualExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_lte.start ) );
   }
 | ^( rel_op_gt e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new GreaterThanExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_gt.start ) );
   }
 | ^( rel_op_gte e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new GreaterThanEqualExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$rel_op_gte.start ) );
   }
 | ^( STR_OP_MATCHES e1 = expr[$exprPlan] e2 = expr[$exprPlan] )
   {
       $expr = new RegexExpression( $exprPlan, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$STR_OP_MATCHES ) );
   }
 | in_eval[$exprPlan]
   {
       $expr = $in_eval.expr;
   }
 | func_eval[$exprPlan]
   {
       $expr = $func_eval.expr;
   }
 | ^( BOOL_COND e1 = expr[$exprPlan] )
   {
       $expr = $e1.expr;
       $expr.setLocation( new SourceLocation( (PigParserNode)$BOOL_COND ) );
   }
;

in_eval[LogicalExpressionPlan plan] returns[LogicalExpression expr]
@init {
    List<LogicalExpression> lhsExprs = new ArrayList<LogicalExpression>();
    List<LogicalExpression> rhsExprs = new ArrayList<LogicalExpression>();
}
 : ^( IN ( ^( IN_LHS lhs = expr[$plan] ) { lhsExprs.add($lhs.expr); }
           ^( IN_RHS rhs = expr[$plan] ) { rhsExprs.add($rhs.expr); } )+ )
    {
        // Convert IN tree to nested or expressions. Please also see
        // QueryParser.g for how IN tree is constructed from IN expression.
        EqualExpression firstBoolExpr = new EqualExpression(plan, lhsExprs.get(0), rhsExprs.get(0));
        if (lhsExprs.size() == 1) {
            $expr = firstBoolExpr;
        } else {
            OrExpression currOrExpr = null;
            OrExpression prevOrExpr = null;
            for (int i = 1; i < lhsExprs.size(); i++) {
                EqualExpression boolExpr = new EqualExpression(plan, lhsExprs.get(i), rhsExprs.get(i));
                currOrExpr = new OrExpression( $plan, prevOrExpr == null ? firstBoolExpr : prevOrExpr, boolExpr );
                prevOrExpr = currOrExpr;
            }
            $expr = currOrExpr;
        }
        $expr.setLocation( new SourceLocation( (PigParserNode)$in_eval.start ) );
    }
;

func_eval[LogicalExpressionPlan plan] returns[LogicalExpression expr]
@init {
    List<LogicalExpression> args = new ArrayList<LogicalExpression>();
}
 : ^( FUNC_EVAL func_name ( real_arg[$plan] { args.add( $real_arg.expr ); } )* )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$func_name.start );
       $expr = builder.buildUDF( loc, $plan, $func_name.funcName, args );
   }
 | ^( INVOKER_FUNC_EVAL package_name=IDENTIFIER function_name=IDENTIFIER is_static=IDENTIFIER ( real_arg[$plan] { args.add( $real_arg.expr ); } )* )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$function_name );
       $expr = builder.buildInvokerUDF( loc, $plan, $package_name.text, $function_name.text, Boolean.parseBoolean($is_static.text), args );
   }
;

real_arg [LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : e = expr[$plan] { $expr = $e.expr; }
 | STAR
   {
       $expr = builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), $plan, $GScope::currentOp,
           $statement::inputIndex, null, -1 );
   }
 | cr = col_range[$plan] { $expr = $cr.expr;}
;

expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : ^( PLUS left = expr[$plan] right = expr[$plan] )
   {
       $expr = new AddExpression( $plan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$PLUS ) );
   }
 | ^( MINUS left = expr[$plan] right = expr[$plan] )
   {
       $expr = new SubtractExpression( $plan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$MINUS ) );
   }
 | ^( STAR left = expr[$plan] right = expr[$plan] )
   {
       $expr = new MultiplyExpression( $plan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$STAR ) );
   }
 | ^( DIV left = expr[$plan] right = expr[$plan] )
   {
       $expr = new DivideExpression( $plan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$DIV ) );
   }
 | ^( PERCENT left = expr[$plan] right = expr[$plan] )
   {
       $expr = new ModExpression( $plan, $left.expr, $right.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$PERCENT ) );
   }
 | const_expr[$plan]
   {
       $expr = $const_expr.expr;
   }
 | var_expr[$plan]
   {
       $expr = $var_expr.expr;
   }
 | ^( NEG e = expr[$plan] )
   {
       $expr = new NegativeExpression( $plan, $e.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$e.start ) );
   }
 | ^( CAST_EXPR type_cast e = expr[$plan] ) // cast expr
   {
       $expr = new CastExpression( $plan, $e.expr, $type_cast.fieldSchema );
       $expr.setLocation( new SourceLocation( (PigParserNode)$type_cast.start ) );
   }
 | ^( EXPR_IN_PAREN e = expr[$plan] ) // unary expr
   {
       $expr = $e.expr;
   }
;

type_cast returns[LogicalFieldSchema fieldSchema]
 : simple_type
   {
        $fieldSchema = new LogicalFieldSchema( null, null, $simple_type.datatype );
   }
 | map_type
   {
       $fieldSchema = new LogicalFieldSchema( null, $map_type.logicalSchema, DataType.MAP );
   }
 | tuple_type_cast
   {
       $fieldSchema = new LogicalFieldSchema( null, $tuple_type_cast.logicalSchema, DataType.TUPLE );
   }
 | bag_type_cast
   {
       $fieldSchema = new LogicalFieldSchema( null, $bag_type_cast.logicalSchema, DataType.BAG );
   }
;

tuple_type_cast returns[LogicalSchema logicalSchema]
@init {
    $logicalSchema = new LogicalSchema();
}
 : ^( TUPLE_TYPE_CAST ( type_cast { $logicalSchema.addField( $type_cast.fieldSchema ); } )* )
;

bag_type_cast returns[LogicalSchema logicalSchema]
@init {
    $logicalSchema = new LogicalSchema();
}
 : ^( BAG_TYPE_CAST tuple_type_cast? )
   {
       $logicalSchema.addField( new LogicalFieldSchema( null, $tuple_type_cast.logicalSchema, DataType.TUPLE ) );
   }
;

var_expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
@init {
    List<Object> columns = null;
    SourceLocation loc = new SourceLocation( (PigParserNode)$var_expr.start );
}
 : projectable_expr[$plan] { $expr = $projectable_expr.expr; }
   ( dot_proj
     {
         columns = $dot_proj.cols;
         boolean processScalar = false;
         if( $expr instanceof ScalarExpression ) {
             List<Operator> succs = plan.getSuccessors( $expr );
             if( succs == null || succs.size() == 0 ) {
                 // We haven't process this scalar projection yet. Set the flag so as to process it next.
                 // This will handle a projection such as A.u.x, where we need to build ScalarExpression
                 // for A.u, while for x, we need to treat it as a normal dereference (on the output of
                 // the ScalarExpression.
                 processScalar = true;
             }
         }

         if( processScalar ) {
             // This is a scalar projection.
             ScalarExpression scalarExpr = (ScalarExpression)$expr;

             if( $dot_proj.cols.size() > 1 ) {
                 throw new InvalidScalarProjectionException( input, loc, scalarExpr );
             }

             Object val = $dot_proj.cols.get( 0 );
             int pos = -1;
             LogicalRelationalOperator relOp = (LogicalRelationalOperator)scalarExpr.getImplicitReferencedOperator();
             LogicalSchema schema = null;
             try {
                 schema = relOp.getSchema();
             } catch(FrontendException e) {
                 throw new PlanGenerationFailureException( input, loc, e );
             }
             if( val instanceof Integer ) {
                 pos = (Integer)val;
                 if( schema != null && pos >= schema.size() ) {
                     throw new InvalidScalarProjectionException( input, loc, scalarExpr );
                 }
             } else {
                 String colAlias = (String)val;
                 pos = schema.getFieldPosition( colAlias );
                 if( schema == null || pos == -1 ) {
                     throw new InvalidScalarProjectionException( input, loc, scalarExpr );
                 }
             }

             ConstantExpression constExpr = new ConstantExpression( $plan, pos);
             plan.connect( $expr, constExpr );
             constExpr = new ConstantExpression( $plan, "filename"); // place holder for file name.
             plan.connect( $expr, constExpr );
         } else {
             DereferenceExpression e = new DereferenceExpression( $plan );
             e.setRawColumns( $dot_proj.cols );
             e.setLocation( new SourceLocation( (PigParserNode)$dot_proj.start ) );
             $plan.connect( e, $expr );
             $expr = e;
         }
     }
   | pound_proj
     {
         MapLookupExpression e = new MapLookupExpression( $plan, $pound_proj.key );
         e.setLocation( new SourceLocation( (PigParserNode)$pound_proj.start ) );
         $plan.connect( e, $expr );
         $expr = e;
     }
  )*
  {
      if( ( $expr instanceof ScalarExpression ) && columns == null ) {
          throw new InvalidScalarProjectionException( input, loc, (ScalarExpression)$expr, " : A column needs to be projected from a relation for it to be used as a scalar" );
      }
  }
;

projectable_expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : func_eval[$plan]
   {
       $expr = $func_eval.expr;
   }
 | col_ref[$plan]
   {
       $expr = $col_ref.expr;
   }
 | bin_expr[$plan]
   {
       $expr = $bin_expr.expr;
   }
 | case_expr[$plan]
   {
       $expr = $case_expr.expr;
   }
 | case_cond[$plan]
   {
       $expr = $case_cond.expr;
   }
;

dot_proj returns[List<Object> cols]
@init {
    $cols = new ArrayList<Object>();
}
 : ^( PERIOD ( col_alias_or_index { $cols.add( $col_alias_or_index.col ); } )+ )
;

col_alias_or_index returns[Object col]
 : col_alias { $col = $col_alias.col; } | col_index { $col = $col_index.col; }
;

col_alias returns[Object col]
 : GROUP { $col = $GROUP.text; }
 | CUBE { $col = $CUBE.text; }
 | IDENTIFIER { $col = $IDENTIFIER.text; }
;

col_index returns[Integer col]
 : DOLLARVAR { $col = builder.undollar( $DOLLARVAR.text ); }
;


col_range[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 :  ^(COL_RANGE (startExpr = col_ref[$plan])? DOUBLE_PERIOD (endExpr = col_ref[$plan])? )
    {
        SourceLocation loc = new SourceLocation( (PigParserNode)$col_range.start );
        $expr = builder.buildRangeProjectExpr(
                    loc, plan, $GScope::currentOp,
                    $statement::inputIndex,
                    $startExpr.expr,
                    $endExpr.expr
                );
    }
;

pound_proj returns[String key]
 : ^( POUND ( QUOTEDSTRING { $key = builder.unquote( $QUOTEDSTRING.text ); } | NULL ) )
;

bin_expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : ^( BIN_EXPR cond[$plan] e1 = expr[$plan] e2 = expr[$plan] )
   {
       $expr = new BinCondExpression( $plan, $cond.expr, $e1.expr, $e2.expr );
       $expr.setLocation( new SourceLocation( (PigParserNode)$bin_expr.start ) );
   }
;

case_expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
@init {
    List<LogicalExpression> lhsExprs = new ArrayList<LogicalExpression>();
    List<LogicalExpression> rhsExprs = new ArrayList<LogicalExpression>();
}
 : ^( CASE_EXPR ( ( ^( CASE_EXPR_LHS lhs = expr[$plan] { lhsExprs.add($lhs.expr); } ) )
                  ( ^( CASE_EXPR_RHS rhs = expr[$plan] { rhsExprs.add($rhs.expr); } ) )+ )+ )
    {
        // Convert CASE tree to nested bincond expressions. Please also see
        // QueryParser.g for how CASE tree is constructed from case statement.
        boolean hasElse = rhsExprs.size() \% 2 == 1;
        LogicalExpression elseExpr = hasElse ? rhsExprs.get(rhsExprs.size()-1)
                                             : new ConstantExpression($plan, null);

        int numWhenBranches = rhsExprs.size() / 2;
        BinCondExpression prevBinCondExpr = null;
        BinCondExpression currBinCondExpr = null;
        for (int i = 0; i < numWhenBranches; i++) {
            currBinCondExpr = new BinCondExpression( $plan,
                new EqualExpression( $plan, lhsExprs.get(i), rhsExprs.get(2*i) ), rhsExprs.get(2*i+1),
                prevBinCondExpr == null ? elseExpr : prevBinCondExpr);
            prevBinCondExpr = currBinCondExpr;
        }
        $expr = currBinCondExpr;
        $expr.setLocation( new SourceLocation( (PigParserNode)$case_expr.start ) );
    }
;

case_cond[LogicalExpressionPlan plan] returns[LogicalExpression expr]
@init {
    List<LogicalExpression> conds = new ArrayList<LogicalExpression>();
    List<LogicalExpression> exprs = new ArrayList<LogicalExpression>();
}
 : ^( CASE_COND ^( WHEN ( cond[$plan] { conds.add($cond.expr); } )+ )
                ^( THEN ( expr[$plan] { exprs.add($expr.expr); } )+ ) )
    {
        // Convert CASE tree to nested bincond expressions. Please also see
        // QueryParser.g for how CASE tree is constructed from case statement.
        boolean hasElse = exprs.size() != conds.size();
        LogicalExpression elseExpr = hasElse ? exprs.remove(exprs.size()-1)
                                             : new ConstantExpression($plan, null);
        Collections.reverse(exprs);
        Collections.reverse(conds);
        int numWhenBranches = conds.size();
        BinCondExpression prevBinCondExpr = null;
        BinCondExpression currBinCondExpr = null;
        for (int i = 0; i < numWhenBranches; i++) {
            currBinCondExpr = new BinCondExpression( $plan,
                conds.get(i), exprs.get(i),
                prevBinCondExpr == null ? elseExpr : prevBinCondExpr);
            prevBinCondExpr = currBinCondExpr;
        }
        $expr = currBinCondExpr;
        $expr.setLocation( new SourceLocation( (PigParserNode)$case_cond.start ) );
    }
;

limit_clause returns[String alias]
scope GScope;
@init {
    $GScope::currentOp = builder.createLimitOp();
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
}
 :  ^( LIMIT rel ( INTEGER
   {
       $alias = builder.buildLimitOp( new SourceLocation( (PigParserNode)$LIMIT ),
         $statement::alias, $statement::inputAlias, Long.valueOf( $INTEGER.text ) );
   }
 | LONGINTEGER
   {
       $alias = builder.buildLimitOp( new SourceLocation( (PigParserNode)$LIMIT ),
         $statement::alias, $statement::inputAlias, builder.parseLong( $LONGINTEGER.text ) );
   }
 | expr[exprPlan]
   {
       $alias = builder.buildLimitOp( new SourceLocation( (PigParserNode)$LIMIT ),
           (LOLimit)$GScope::currentOp, $statement::alias, $statement::inputAlias, exprPlan);
   }
 ) )
;

sample_clause returns[String alias]
scope GScope;
@init {
    $GScope::currentOp = builder.createSampleOp();
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
}
 : ^( SAMPLE rel ( DOUBLENUMBER
   {
       $alias = builder.buildSampleOp( new SourceLocation( (PigParserNode)$SAMPLE ), $statement::alias,
           $statement::inputAlias, Double.valueOf( $DOUBLENUMBER.text ),
           new SourceLocation( (PigParserNode)$DOUBLENUMBER ) );
   }
 | expr[exprPlan]
   {
       $alias = builder.buildSampleOp( new SourceLocation( (PigParserNode)$SAMPLE ),
           (LOFilter)$GScope::currentOp, $statement::alias, $statement::inputAlias, exprPlan, $expr.expr);
   }
  ) )
;

rank_clause returns[String alias]
scope {
	LORank rankOp;
}
scope GScope;
@init {
	$GScope::currentOp = builder.createRankOp();
}
@after {
}
 : ^( RANK rel rank_by_statement? )
 {
	SourceLocation loc = new SourceLocation( (PigParserNode) $rank_clause.start );

	List<LogicalExpressionPlan> tempPlans = $rank_by_statement.plans;
	List<Boolean> tempAscFlags = $rank_by_statement.ascFlags;

	if(tempPlans == null && tempAscFlags == null) {
		tempPlans = new ArrayList<LogicalExpressionPlan>();
		tempAscFlags = new ArrayList<Boolean>();

		((LORank)$GScope::currentOp).setIsRowNumber( true );
	}

	((LORank)$GScope::currentOp).setIsDenseRank( $rank_by_statement.isDenseRank != null?$rank_by_statement.isDenseRank:false );

	$alias = builder.buildRankOp( loc, (LORank)$GScope::currentOp, $statement::alias, $statement::inputAlias, tempPlans, tempAscFlags );
 }
;

rank_by_statement returns[List<LogicalExpressionPlan> plans, List<Boolean> ascFlags, Boolean isDenseRank]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
    $ascFlags = new ArrayList<Boolean>();
    $isDenseRank = false;
}
 : ^( BY rank_by_clause ( DENSE { $isDenseRank =  true; }  )? )
 {
	$plans = $rank_by_clause.plans;
	$ascFlags = $rank_by_clause.ascFlags;
 }
;

rank_by_clause returns[List<LogicalExpressionPlan> plans, List<Boolean> ascFlags]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
    $ascFlags = new ArrayList<Boolean>();
}
 : STAR {
		LogicalExpressionPlan plan = new LogicalExpressionPlan();
		builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), plan, $GScope::currentOp, $statement::inputIndex, null, -1 );
		$plans.add( plan );
   }
   ( ASC { $ascFlags.add( true ); } | DESC { $ascFlags.add( false ); } )?
 | ( rank_col
   {
       $plans.add( $rank_col.plan );
       $ascFlags.add( $rank_col.ascFlag );
   } )+
;

rank_col returns[LogicalExpressionPlan plan, Boolean ascFlag]
@init {
    $plan = new LogicalExpressionPlan();
    $ascFlag = true;
}
 : col_range[$plan] (ASC | DESC { $ascFlag = false; } )?
 | col_ref[$plan] ( ASC | DESC { $ascFlag = false; } )?
;

order_clause returns[String alias]
scope GScope;
@init {
    $GScope::currentOp = builder.createSortOp();
}
 : ^( ORDER rel order_by_clause func_clause[FunctionType.COMPARISONFUNC]? )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$ORDER );
       $alias = builder.buildSortOp( loc, (LOSort)$GScope::currentOp, $statement::alias,
           $statement::inputAlias, $order_by_clause.plans,
           $order_by_clause.ascFlags, $func_clause.funcSpec );
   }
;

order_by_clause returns[List<LogicalExpressionPlan> plans, List<Boolean> ascFlags]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
    $ascFlags = new ArrayList<Boolean>();
}
 : STAR {
       LogicalExpressionPlan plan = new LogicalExpressionPlan();
       builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), plan, $GScope::currentOp,
           $statement::inputIndex, null, -1 );
       $plans.add( plan );
   }
   ( ASC { $ascFlags.add( true ); } | DESC { $ascFlags.add( false ); } )?
 | ( order_col
   {
       $plans.add( $order_col.plan );
       $ascFlags.add( $order_col.ascFlag );
   } )+
;

order_col returns[LogicalExpressionPlan plan, Boolean ascFlag]
@init {
    $plan = new LogicalExpressionPlan();
    $ascFlag = true;
}
 : col_range[$plan] (ASC | DESC { $ascFlag = false; } )?
 | col_ref[$plan] ( ASC | DESC { $ascFlag = false; } )?
;

distinct_clause returns[String alias]
 : ^( DISTINCT rel partition_clause? )
   {
       $alias = builder.buildDistinctOp( new SourceLocation( (PigParserNode)$DISTINCT ), $statement::alias,
          $statement::inputAlias, $partition_clause.partitioner );
   }
;

partition_clause returns[String partitioner]
 : ^( PARTITION func_name )
   {
       $partitioner = $func_name.funcName;
   }
;

cross_clause returns[String alias]
 : ^( CROSS rel_list partition_clause? )
   {
       $alias = builder.buildCrossOp( new SourceLocation( (PigParserNode)$CROSS ), $statement::alias,
          $rel_list.aliasList, $partition_clause.partitioner );
   }
;

rel_list returns[List<String> aliasList]
@init { $aliasList = new ArrayList<String>(); }
 : ( rel { $aliasList.add( $statement::inputAlias ); } )+
;

join_clause returns[String alias]
scope {
    MultiMap<Integer, LogicalExpressionPlan> joinPlans;
    int inputIndex;
    List<String> inputAliases;
    List<Boolean> innerFlags;
}
scope GScope;
@init {
    $GScope::currentOp = builder.createJoinOp();
    $join_clause::joinPlans = new MultiMap<Integer, LogicalExpressionPlan>();
    $join_clause::inputAliases = new ArrayList<String>();
    $join_clause::innerFlags = new ArrayList<Boolean>();
    int oldStatementIndex = $statement::inputIndex;
}
@after {
   $statement::inputIndex=oldStatementIndex;
}
 : ^( JOIN join_sub_clause join_type? partition_clause? )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$JOIN );
       $alias = builder.buildJoinOp( loc, (LOJoin)$GScope::currentOp, $statement::alias,
          $join_clause::inputAliases, $join_clause::joinPlans,
          $join_type.type, $join_clause::innerFlags, $partition_clause.partitioner );
   }
;

join_type returns[JOINTYPE type]
 : QUOTEDSTRING
   {
       $type = builder.parseJoinType( $QUOTEDSTRING.text, new SourceLocation( (PigParserNode)$QUOTEDSTRING ) );
   }
;

join_sub_clause
 : join_item ( LEFT { $join_clause::innerFlags.add( true );
                      $join_clause::innerFlags.add( false ); }
             | RIGHT { $join_clause::innerFlags.add( false );
                       $join_clause::innerFlags.add( true ); }
             | FULL { $join_clause::innerFlags.add( false );
                      $join_clause::innerFlags.add( false ); } ) OUTER? join_item
   {
   }
 | join_item+
;

join_item
 : ^( JOIN_ITEM rel join_group_by_clause )
   {
       $join_clause::inputAliases.add( $statement::inputAlias );
       $join_clause::joinPlans.put( $join_clause::inputIndex, $join_group_by_clause.plans );
       $join_clause::inputIndex++;
       $statement::inputIndex++;
   }
;

join_group_by_clause returns[List<LogicalExpressionPlan> plans]
@init {
    $plans = new ArrayList<LogicalExpressionPlan>();
}
 : ^( BY ( join_group_by_expr { $plans.add( $join_group_by_expr.plan ); } )+ )
;

join_group_by_expr returns[LogicalExpressionPlan plan]
@init {
    $plan = new LogicalExpressionPlan();
}
 : col_range[$plan]
 | expr[$plan]
 | STAR
   {
       builder.buildProjectExpr( new SourceLocation( (PigParserNode)$STAR ), $plan, $GScope::currentOp,
           $statement::inputIndex, null, -1 );
   }
;

union_clause returns[String alias]
@init {
    boolean onSchema = false;
}
 : ^( UNION ( ONSCHEMA { onSchema = true; } )? rel_list )
   {
      $alias = builder.buildUnionOp( new SourceLocation( (PigParserNode)$UNION ), $statement::alias,
          $rel_list.aliasList, onSchema );
   }
;

foreach_clause returns[String alias]
scope {
    LOForEach foreachOp;
}
scope GScope;
@init {
     $foreach_clause::foreachOp = builder.createForeachOp();
     $GScope::currentOp = $foreach_clause::foreachOp;
}
 : ^( FOREACH rel foreach_plan )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$FOREACH );
       $alias = builder.buildForeachOp( loc, $foreach_clause::foreachOp, $statement::alias,
          $statement::inputAlias, $foreach_plan.plan );
   }
;

foreach_plan returns[LogicalPlan plan]
scope {
    LogicalPlan innerPlan;
    Map<String, LogicalExpressionPlan> exprPlans;
    Map<String, Operator> operators;
}
@init {
    inForeachPlan = true;
    $foreach_plan::innerPlan = new LogicalPlan();
    $foreach_plan::exprPlans = new HashMap<String, LogicalExpressionPlan>();
    $foreach_plan::operators = new HashMap<String, Operator>();
}
@after {
    $plan = $foreach_plan::innerPlan;
    inForeachPlan = false;
}
 : ^( FOREACH_PLAN_SIMPLE generate_clause )
 | ^( FOREACH_PLAN_COMPLEX nested_blk )
;

nested_blk : nested_command* generate_clause
;

nested_command
@init {
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
    inNestedCommand = true;
}
@after {
	inNestedCommand = false;
}
 : ^( NESTED_CMD IDENTIFIER nested_op[$IDENTIFIER.text] )
   {
       $foreach_plan::operators.put( $IDENTIFIER.text, $nested_op.op );
       $foreach_plan::exprPlans.remove( $IDENTIFIER.text );
   }
 |
   ^( NESTED_CMD_ASSI IDENTIFIER expr[exprPlan] )
   {
        $foreach_plan::exprPlans.put( $IDENTIFIER.text, exprPlan );
   }
;

nested_op[String alias] returns[Operator op]
 : nested_proj[$alias] { $op = $nested_proj.op; }
 | nested_filter[$alias] { $op = $nested_filter.op; }
 | nested_sort [$alias] { $op = $nested_sort.op; }
 | nested_distinct[$alias] { $op = $nested_distinct.op; }
 | nested_limit[$alias] { $op = $nested_limit.op; }
 | nested_cross[$alias] { $op = $nested_cross.op; }
 | nested_foreach[$alias] { $op = $nested_foreach.op; }
;

nested_proj[String alias] returns[Operator op]
@init {
    LogicalExpressionPlan plan = new LogicalExpressionPlan();
    List<LogicalExpressionPlan> plans = new ArrayList<LogicalExpressionPlan>();
}
 : ^( NESTED_PROJ
      cr0 = col_ref[plan]
      ( cr = col_ref[new LogicalExpressionPlan()]
        {
            plans.add( (LogicalExpressionPlan)( $cr.expr.getPlan() ) );
        }
      )+ )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$cr0.start );
       $op = builder.buildNestedProjectOp( loc, $foreach_plan::innerPlan, $foreach_clause::foreachOp,
           $foreach_plan::operators, $alias, (ProjectExpression)$cr0.expr, plans );
   }
;

nested_filter[String alias] returns[Operator op]
scope GScope;
@init {
    LogicalExpressionPlan plan = new LogicalExpressionPlan();
    Operator inputOp = null;
    $GScope::currentOp = builder.createNestedFilterOp( $foreach_plan::innerPlan );
}
 : ^( FILTER nested_op_input cond[plan] )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$FILTER );
       $op = builder.buildNestedFilterOp( loc, (LOFilter)$GScope::currentOp, $foreach_plan::innerPlan, $alias,
           $nested_op_input.op, plan );
   }
;

nested_sort[String alias] returns[Operator op]
scope GScope;
@init {
    Operator inputOp = null;
    $GScope::currentOp = builder.createNestedSortOp( $foreach_plan::innerPlan );
}
 : ^( ORDER nested_op_input order_by_clause func_clause[FunctionType.COMPARISONFUNC]? )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$ORDER );
       $op = builder.buildNestedSortOp( loc, (LOSort)$GScope::currentOp, $foreach_plan::innerPlan, $alias,
           $nested_op_input.op,
           $order_by_clause.plans, $order_by_clause.ascFlags, $func_clause.funcSpec );
   }
;

nested_distinct[String alias] returns[Operator op]
@init {
    Operator inputOp = null;
}
 : ^( DISTINCT nested_op_input )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$DISTINCT );
       $op = builder.buildNestedDistinctOp( loc, $foreach_plan::innerPlan, $alias, $nested_op_input.op );
   }
;

nested_limit[String alias] returns[Operator op]
scope GScope;
@init {
    Operator inputOp = null;
    LogicalExpressionPlan exprPlan = new LogicalExpressionPlan();
    $GScope::currentOp = builder.createNestedLimitOp( $foreach_plan::innerPlan );
}
 : ^( LIMIT nested_op_input ( INTEGER
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$LIMIT );
       $op = builder.buildNestedLimitOp( loc, $foreach_plan::innerPlan, $alias, $nested_op_input.op,
           Integer.valueOf( $INTEGER.text ) );
   }
 | expr[exprPlan]
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$LIMIT );
       $op = builder.buildNestedLimitOp( loc, (LOLimit)$GScope::currentOp, $foreach_plan::innerPlan, $alias,
           $nested_op_input.op, exprPlan);
   }
  ) )
;

nested_cross[String alias] returns[Operator op]
@init {
    Operator inputOp = null;
}
 : ^( CROSS nested_op_input_list )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$CROSS );
       $op = builder.buildNestedCrossOp( loc, $foreach_plan::innerPlan, $alias, $nested_op_input_list.opList );
   }
;

nested_foreach[String alias] returns[Operator op]
scope {
    LogicalPlan innerPlan;
    LOForEach foreachOp;
}
@init {
	Operator inputOp = null;
	$nested_foreach::innerPlan = new LogicalPlan();
	$nested_foreach::foreachOp = builder.createNestedForeachOp( $foreach_plan::innerPlan );
}
 : ^( FOREACH nested_op_input generate_clause )
   {
   		SourceLocation loc = new SourceLocation( (PigParserNode)$FOREACH );
   		$op = builder.buildNestedForeachOp( loc, (LOForEach)$nested_foreach::foreachOp, $foreach_plan::innerPlan,
   							$alias, $nested_op_input.op, $nested_foreach::innerPlan);
   }
;

generate_clause
scope GScope;
@init {
	$GScope::currentOp = builder.createGenerateOp(inNestedCommand ? $nested_foreach::innerPlan : $foreach_plan::innerPlan );
    List<LogicalExpressionPlan> plans = new ArrayList<LogicalExpressionPlan>();
    List<Boolean> flattenFlags = new ArrayList<Boolean>();
    List<LogicalSchema> schemas = new ArrayList<LogicalSchema>();
}
 : ^( GENERATE ( flatten_generated_item
                 {
                     plans.add( $flatten_generated_item.plan );
                     flattenFlags.add( $flatten_generated_item.flattenFlag );
                     schemas.add( $flatten_generated_item.schema );
                 }
               )+
    )
   {
       builder.buildGenerateOp( new SourceLocation( (PigParserNode)$GENERATE ),
       	   inNestedCommand ? $nested_foreach::foreachOp : $foreach_clause::foreachOp,
           (LOGenerate)$GScope::currentOp, plans, flattenFlags, schemas );
   }
;

nested_op_input returns[Operator op]
@init {
    LogicalExpressionPlan plan = new LogicalExpressionPlan();
}
 : col_ref[plan]
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$col_ref.start );
       $op = builder.buildNestedOperatorInput( loc, $foreach_plan::innerPlan,
           $foreach_clause::foreachOp, $foreach_plan::operators, $col_ref.expr );
   }
 | nested_proj[null]
   {
       $op = $nested_proj.op;
   }
;

nested_op_input_list returns[List<Operator> opList]
@init { $opList = new ArrayList<Operator>(); }
 : ( nested_op_input { $opList.add( $nested_op_input.op ); } )+
;

stream_clause returns[String alias]
@init {
    StreamingCommand cmd = null;
    SourceLocation loc = new SourceLocation( (PigParserNode)$stream_clause.start );
}
 : ^( STREAM rel ( EXECCOMMAND { cmd = builder.buildCommand( loc, builder.unquote( $EXECCOMMAND.text ) ); }
                 | IDENTIFIER
                   {
                       cmd = builder.lookupCommand( $IDENTIFIER.text );
                       if( cmd == null ) {
                           String msg = "Undefined command-alias [" + $IDENTIFIER.text + "]";
                           throw new ParserValidationException( input,
                               new SourceLocation( (PigParserNode)$IDENTIFIER ), msg );
                       }
                   }
                 ) as_clause? )
   {
       $alias = builder.buildStreamOp( loc, $statement::alias,
          $statement::inputAlias, cmd, $as_clause.logicalSchema, input );
   }
;

mr_clause returns[String alias]
@init {
    List<String> paths = new ArrayList<String>();
    String alias = $statement::alias;
    SourceLocation loc = new SourceLocation( (PigParserNode)$mr_clause.start );
}
 : ^( MAPREDUCE QUOTEDSTRING path_list[paths]?
     { $statement::alias = null; } store_clause
     { $statement::alias = alias; } load_clause
     EXECCOMMAND? )
   {
       $alias = builder.buildNativeOp( loc,
           builder.unquote( $QUOTEDSTRING.text ), builder.unquote( $EXECCOMMAND.text ),
           paths, $store_clause.alias, $load_clause.alias, input );
   }
;

split_clause
 : ^( SPLIT
      rel
      {
          SourceLocation loc = new SourceLocation( (PigParserNode)$SPLIT );
          $statement::inputAlias = builder.buildSplitOp( loc, $statement::inputAlias );
      }
      split_branch+ split_otherwise?
    )
;

split_branch
scope GScope;
@init {
    LogicalExpressionPlan splitPlan = new LogicalExpressionPlan();
    $GScope::currentOp = builder.createSplitOutputOp();
}
 : ^( SPLIT_BRANCH alias cond[splitPlan] )
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$alias.start );
       builder.buildSplitOutputOp( loc, (LOSplitOutput)$GScope::currentOp, $alias.name,
           $statement::inputAlias, splitPlan );
   }
;

split_otherwise throws PlanGenerationFailureException
scope GScope;
@init {
	boolean allowNulls = false;
    $GScope::currentOp = builder.createSplitOutputOp();
}
 : ^( OTHERWISE alias ( ALL { allowNulls = true; } )? )
  {
       SourceLocation loc = new SourceLocation( (PigParserNode)$alias.start );
       builder.buildSplitOtherwiseOp( loc, (LOSplitOutput)$GScope::currentOp, $alias.name,
           $statement::inputAlias, allowNulls);
  }
;

col_ref[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : alias_col_ref[$plan] { $expr = $alias_col_ref.expr; }
 | dollar_col_ref[$plan] { $expr = $dollar_col_ref.expr; }
;

alias_col_ref[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : GROUP
   {
       $expr = builder.buildProjectExpr( new SourceLocation( (PigParserNode)$GROUP ), $plan, $GScope::currentOp,
           $statement::inputIndex, $GROUP.text, 0 );
   }
 | CUBE
   {
       $expr = builder.buildProjectExpr( new SourceLocation( (PigParserNode)$CUBE ), $plan, $GScope::currentOp,
           $statement::inputIndex, $CUBE.text, 0 );
   }
 | IDENTIFIER
   {
       SourceLocation loc = new SourceLocation( (PigParserNode)$IDENTIFIER );
       String alias = $IDENTIFIER.text;
       Operator inOp = builder.lookupOperator( $statement::inputAlias );
       if(null == inOp)
       {
           throw new UndefinedAliasException (input,loc,$statement::inputAlias);
       }
       LogicalSchema schema;
       try {
           schema = ((LogicalRelationalOperator)inOp).getSchema();
       } catch (FrontendException e) {
           throw new PlanGenerationFailureException( input, loc, e );
       }

       // PIG-3581
       // check within foreach scope before looking at outer scope for scalar
       if( inForeachPlan && ($foreach_plan::operators).containsKey(alias)) {
           $expr = builder.buildProjectExpr( loc, $plan, $GScope::currentOp,
               $foreach_plan::operators, $foreach_plan::exprPlans, alias, 0 );
       } else {
           Operator op = builder.lookupOperator( alias );
           if( op != null && ( schema == null || schema.getFieldPosition( alias ) == -1 ) ) {
               $expr = new ScalarExpression( plan, op,
                   inForeachPlan ? $foreach_clause::foreachOp : $GScope::currentOp );
               $expr.setLocation( loc );
           } else if( inForeachPlan ) {
               $expr = builder.buildProjectExpr( loc, $plan, $GScope::currentOp,
                   $foreach_plan::operators, $foreach_plan::exprPlans, alias, 0 );
           } else {
               $expr = builder.buildProjectExpr( loc, $plan, $GScope::currentOp,
                   $statement::inputIndex, alias, 0 );
           }
       }
   }
;

dollar_col_ref[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : DOLLARVAR
   {
       int col = builder.undollar( $DOLLARVAR.text );
       $expr = builder.buildProjectExpr( new SourceLocation( (PigParserNode)$DOLLARVAR ), $plan, $GScope::currentOp,
           $statement::inputIndex, null, col );
   }
;

const_expr[LogicalExpressionPlan plan] returns[LogicalExpression expr]
 : literal
   {
       $expr = new ConstantExpression( $plan, $literal.value);
       $expr.setLocation( new SourceLocation( (PigParserNode)$const_expr.start ) );
   }
;

literal returns[Object value, byte type]
 : scalar
   {
       $value = $scalar.value;
       $type = $scalar.type;
   }
 | map
   {
       $value = $map.value;
       $type = DataType.MAP;
   }
 | bag
   {
       $value = $bag.value;
       $type = DataType.BAG;
   }
 | tuple
   {
       $value = $tuple.value;
       $type = DataType.TUPLE;
   }
;

scalar returns[Object value, byte type]
 : num_scalar
   {
       $type = $num_scalar.type;
       $value = $num_scalar.value;
   }
 | QUOTEDSTRING
   {
       $type = DataType.CHARARRAY;
       $value = builder.unquote( $QUOTEDSTRING.text );
   }
 | NULL
   {
       $type = DataType.NULL;
   }
 | TRUE
   {
       $type = DataType.BOOLEAN;
       $value = Boolean.TRUE;
   }
 | FALSE
   {
       $type = DataType.BOOLEAN;
       $value = Boolean.FALSE;
   }
;

num_scalar returns[Object value, byte type]
@init {
    int sign = 1;
}
 : ( MINUS { sign = -1; } ) ?
   ( INTEGER
     {
         $type = DataType.INTEGER;
         $value = sign * Integer.valueOf( $INTEGER.text );
     }
   | LONGINTEGER
     {
         $type = DataType.LONG;
         $value = sign * builder.parseLong( $LONGINTEGER.text );
     }
   | FLOATNUMBER
     {
         $type = DataType.FLOAT;
         $value = sign * Float.valueOf( $FLOATNUMBER.text );
     }
   | DOUBLENUMBER
     {
         $type = DataType.DOUBLE;
         $value = sign * Double.valueOf( $DOUBLENUMBER.text );
     }
   | BIGINTEGERNUMBER
     {
         $type = DataType.BIGINTEGER;
         $value = builder.parseBigInteger( $BIGINTEGERNUMBER.text );
         if ( sign == -1 ) {
             $value = ((BigInteger)$value).negate();
         }
     }
   | BIGDECIMALNUMBER
     {
         $type = DataType.BIGDECIMAL;
         $value = builder.parseBigDecimal( $BIGDECIMALNUMBER.text );
         if ( sign == -1 ) {
             $value = ((BigDecimal)$value).negate();
         }
     }
   )
;

map returns[Object value]
@init { Map<String, Object> kvs = new HashMap<String, Object>(); }
 : ^( MAP_VAL ( keyvalue { kvs.put( $keyvalue.key, $keyvalue.value ); } )* )
   {
       $value = kvs;
   }
;

keyvalue returns[String key, Object value]
 : ^( KEY_VAL_PAIR map_key literal )
   {
       $key = $map_key.value;
       $value = $literal.value;
   }
;

map_key returns[String value]
 : QUOTEDSTRING { $value = builder.unquote( $QUOTEDSTRING.text ); }
;

bag returns[Object value]
@init { DataBag dataBag = builder.createDataBag(); }
 : ^( BAG_VAL ( tuple { dataBag.add( $tuple.value ); } )* )
   {
       $value = dataBag;
   }
;

tuple returns[Tuple value]
@init { List<Object> objList = new ArrayList<Object>(); }
 : ^( TUPLE_VAL ( literal { objList.add( $literal.value ); } )* )
   {
       $value = builder.buildTuple( objList );
   }
;

// extended identifier, handling the keyword and identifier conflicts. Ugly but there is no other choice.
eid returns[String id] : rel_str_op { $id = $rel_str_op.id; }
    | IMPORT { $id = $IMPORT.text; }
    | RETURNS { $id = $RETURNS.text; }
    | DEFINE { $id = $DEFINE.text; }
    | LOAD { $id = $LOAD.text; }
    | FILTER { $id = $FILTER.text; }
    | FOREACH { $id = $FOREACH.text; }
    | MATCHES { $id = $MATCHES.text; }
    | ORDER { $id = $ORDER.text; }
    | DISTINCT { $id = $DISTINCT.text; }
    | COGROUP { $id = $COGROUP.text; }
    | CUBE { $id = $CUBE.text; }
    | ROLLUP { $id = $ROLLUP.text; }
    | JOIN { $id = $JOIN.text; }
    | CROSS { $id = $CROSS.text; }
    | UNION { $id = $UNION.text; }
    | SPLIT { $id = $SPLIT.text; }
    | INTO { $id = $INTO.text; }
    | IF { $id = $IF.text; }
    | ALL { $id = $ALL.text; }
    | AS { $id = $AS.text; }
    | BY { $id = $BY.text; }
    | USING { $id = $USING.text; }
    | INNER { $id = $INNER.text; }
    | OUTER { $id = $OUTER.text; }
    | PARALLEL { $id = $PARALLEL.text; }
    | PARTITION { $id = $PARTITION.text; }
    | GROUP { $id = $GROUP.text; }
    | AND { $id = $AND.text; }
    | OR { $id = $OR.text; }
    | NOT { $id = $NOT.text; }
    | GENERATE { $id = $GENERATE.text; }
    | FLATTEN { $id = $FLATTEN.text; }
    | EVAL { $id = $EVAL.text; }
    | ASC { $id = $ASC.text; }
    | DESC { $id = $DESC.text; }
    | BOOLEAN { $id = $BOOLEAN.text; }
    | INT { $id = $INT.text; }
    | LONG { $id = $LONG.text; }
    | FLOAT { $id = $FLOAT.text; }
    | DOUBLE { $id = $DOUBLE.text; }
    | BIGINTEGER { $id = $BIGINTEGER.text; }
    | BIGDECIMAL { $id = $BIGDECIMAL.text; }
    | DATETIME { $id = $DATETIME.text; }
    | CHARARRAY { $id = $CHARARRAY.text; }
    | BYTEARRAY { $id = $BYTEARRAY.text; }
    | BAG { $id = $BAG.text; }
    | TUPLE { $id = $TUPLE.text; }
    | MAP { $id = $MAP.text; }
    | IS { $id = $IS.text; }
    | NULL { $id = $NULL.text; }
    | TRUE { $id = $TRUE.text; }
    | FALSE { $id = $FALSE.text; }
    | STREAM { $id = $STREAM.text; }
    | THROUGH { $id = $THROUGH.text; }
    | STORE { $id = $STORE.text; }
    | MAPREDUCE { $id = $MAPREDUCE.text; }
    | SHIP { $id = $SHIP.text; }
    | CACHE { $id = $CACHE.text; }
    | INPUT { $id = $INPUT.text; }
    | OUTPUT { $id = $OUTPUT.text; }
    | STDERROR { $id = $STDERROR.text; }
    | STDIN { $id = $STDIN.text; }
    | STDOUT { $id = $STDOUT.text; }
    | LIMIT { $id = $LIMIT.text; }
    | SAMPLE { $id = $SAMPLE.text; }
    | LEFT { $id = $LEFT.text; }
    | RIGHT { $id = $RIGHT.text; }
    | FULL { $id = $FULL.text; }
    | IDENTIFIER { $id = $IDENTIFIER.text; }
    | TOBAG { $id = "TOBAG"; }
    | TOMAP { $id = "TOMAP"; }
    | TOTUPLE { $id = "TOTUPLE"; }
    | ASSERT { $id = "ASSERT"; } 
;

// relational operator
rel_op : rel_op_eq
       | rel_op_ne
       | rel_op_gt
       | rel_op_gte
       | rel_op_lt
       | rel_op_lte
       | STR_OP_MATCHES
;

rel_op_eq : STR_OP_EQ | NUM_OP_EQ
;

rel_op_ne : STR_OP_NE | NUM_OP_NE
;

rel_op_gt : STR_OP_GT | NUM_OP_GT
;

rel_op_gte : STR_OP_GTE | NUM_OP_GTE
;

rel_op_lt : STR_OP_LT | NUM_OP_LT
;

rel_op_lte : STR_OP_LTE | NUM_OP_LTE
;

rel_str_op returns[String id]
 : STR_OP_EQ { $id = $STR_OP_EQ.text; }
 | STR_OP_NE { $id = $STR_OP_NE.text; }
 | STR_OP_GT { $id = $STR_OP_GT.text; }
 | STR_OP_LT { $id = $STR_OP_LT.text; }
 | STR_OP_GTE { $id = $STR_OP_GTE.text; }
 | STR_OP_LTE { $id = $STR_OP_LTE.text; }
 | STR_OP_MATCHES { $id = $STR_OP_MATCHES.text; }
;
