parser grammar ExprParser;

options{
  language=Java;
  tokenVocab=ExprLexer;
}

@header {
/*
 * 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.
 */
  
//Explicit import...
import java.util.*;
import org.apache.drill.common.expression.*;
import org.apache.drill.common.expression.PathSegment.NameSegment;
import org.apache.drill.common.expression.PathSegment.ArraySegment;
import org.apache.drill.common.types.*;
import org.apache.drill.common.types.TypeProtos.*;
import org.apache.drill.common.types.TypeProtos.DataMode;
import org.apache.drill.common.types.TypeProtos.MajorType;
import org.apache.drill.common.exceptions.ExpressionParsingException;
}

@members{
  private String fullExpression;
  private int tokenPos;

  public static void p(String s){
    System.out.println(s);
  }
  
  public ExpressionPosition pos(Token token){
    return new ExpressionPosition(fullExpression, token.getTokenIndex());
  }
}

parse returns [LogicalExpression e]
  :  expression EOF {
    $e = $expression.e;
    if (fullExpression == null) fullExpression = $expression.text;
    tokenPos = $expression.start.getTokenIndex();
  }
  ;
 
functionCall returns [LogicalExpression e]
  :  Identifier OParen exprList? CParen {$e =
      FunctionCallFactory.createExpression($Identifier.text, pos($Identifier),
        ($exprList.ctx == null ? new ArrayList<>() : $exprList.listE)); }
  ;

convertCall returns [LogicalExpression e]
  :  Convert OParen expression Comma String CParen
      { $e = FunctionCallFactory.createConvert($Convert.text, $String.text, $expression.e, pos($Convert));}
  ;

anyValueCall returns [LogicalExpression e]
  :  AnyValue OParen exprList? CParen {$e =
      FunctionCallFactory.createExpression($AnyValue.text, pos($AnyValue),
       ($exprList.ctx == null ? new ArrayList<>() : $exprList.listE)); }
  ;

castCall returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  ExpressionPosition p = null;
	}  
  :  Cast OParen expression As dataType repeat? CParen 
      {  if ($repeat.ctx != null && $repeat.isRep.compareTo(Boolean.TRUE)==0)
           $e = FunctionCallFactory.createCast(TypeProtos.MajorType.newBuilder().mergeFrom($dataType.type).setMode(DataMode.REPEATED).build(), pos($Cast), $expression.e);
         else
           $e = FunctionCallFactory.createCast($dataType.type, pos($Cast), $expression.e);}
  ;

repeat returns [Boolean isRep]
  : Repeat { $isRep = Boolean.TRUE;}
  ;

dataType returns [MajorType type]
	: numType  {$type =$numType.type;}
	| charType {$type =$charType.type;}
	| dateType {$type =$dateType.type;}
	| booleanType {$type =$booleanType.type;}
	;

booleanType returns [MajorType type]
	: BIT { $type = Types.required(TypeProtos.MinorType.BIT); }
	;

numType returns [MajorType type]
	: INT    { $type = Types.required(TypeProtos.MinorType.INT); }
	| BIGINT { $type = Types.required(TypeProtos.MinorType.BIGINT); }
	| FLOAT4 { $type = Types.required(TypeProtos.MinorType.FLOAT4); }
	| FLOAT8 { $type = Types.required(TypeProtos.MinorType.FLOAT8); }
	| DECIMAL9 OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL9).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| DECIMAL18 OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL18).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| DECIMAL28DENSE OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL28DENSE).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| DECIMAL28SPARSE OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL28SPARSE).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| DECIMAL38DENSE OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL38DENSE).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| DECIMAL38SPARSE OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.DECIMAL38SPARSE).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	| VARDECIMAL OParen precision Comma scale CParen { $type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.VARDECIMAL).setMode(DataMode.REQUIRED).setPrecision($precision.value.intValue()).setScale($scale.value.intValue()).build(); }
	;

charType returns [MajorType type]
	:  VARCHAR typeLen {$type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.VARCHAR).setMode(DataMode.REQUIRED).setPrecision($typeLen.length.intValue()).build(); }
	|  VARBINARY typeLen {$type = TypeProtos.MajorType.newBuilder().setMinorType(TypeProtos.MinorType.VARBINARY).setMode(DataMode.REQUIRED).setPrecision($typeLen.length.intValue()).build();}
	;

precision returns [Integer value]
    : Number {$value = Integer.parseInt($Number.text); }
    ;

scale returns [Integer value]
    : Number {$value = Integer.parseInt($Number.text); }
    ;

dateType returns [MajorType type]
    : DATE { $type = Types.required(TypeProtos.MinorType.DATE); }
    | TIMESTAMP   { $type = Types.required(TypeProtos.MinorType.TIMESTAMP); }
    | TIME   { $type = Types.required(TypeProtos.MinorType.TIME); }
    | TIMESTAMPTZ   { $type = Types.required(TypeProtos.MinorType.TIMESTAMPTZ); }
    | INTERVAL { $type = Types.required(TypeProtos.MinorType.INTERVAL); }
    | INTERVALYEAR { $type = Types.required(TypeProtos.MinorType.INTERVALYEAR); }
    | INTERVALDAY { $type = Types.required(TypeProtos.MinorType.INTERVALDAY); }
    ;

typeLen returns [Integer length]
    : OParen Number CParen {$length = Integer.parseInt($Number.text);}
    ;

ifStatement returns [LogicalExpression e]
	@init {
	  IfExpression.Builder s = IfExpression.newBuilder();
	}
	@after {
	  $e = s.build();
	}  
  :  i1=ifStat {s.setIfCondition($i1.i); s.setPosition(pos($i1.start)); } (elseIfStat { s.setIfCondition($elseIfStat.i); } )* Else expression { s.setElse($expression.e); }End
  ;

ifStat returns [IfExpression.IfCondition i]
  : If e1=expression Then e2=expression { $i = new IfExpression.IfCondition($e1.e, $e2.e); }
  ;
elseIfStat returns [IfExpression.IfCondition i]
  : Else If e1=expression Then e2=expression { $i = new IfExpression.IfCondition($e1.e, $e2.e); }
  ;

caseStatement returns [LogicalExpression e]
	@init {
	  IfExpression.Builder s = IfExpression.newBuilder();
	}
	@after {
	  $e = s.build();
	}  
  : Case (caseWhenStat {s.setIfCondition($caseWhenStat.e); }) + caseElseStat { s.setElse($caseElseStat.e); } End
  ;
  
caseWhenStat returns [IfExpression.IfCondition e]
  : When e1=expression Then e2=expression {$e = new IfExpression.IfCondition($e1.e, $e2.e); }
  ;
  
caseElseStat returns [LogicalExpression e]
  : Else expression {$e = $expression.e; }
  ;
  
exprList returns [List<LogicalExpression> listE]
	@init{
	  $listE = new ArrayList<>();
	}
  :  e1=expression {$listE.add($e1.e); } (Comma e2=expression {$listE.add($e2.e); } )*
  ;

expression returns [LogicalExpression e]  
  :  ifStatement {$e = $ifStatement.e; }
  |  caseStatement {$e = $caseStatement.e; }
  |  condExpr {$e = $condExpr.e; }
  ;

condExpr returns [LogicalExpression e]
  :  orExpr {$e = $orExpr.e; }
  ;

orExpr returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  if(exprs.size() == 1){
	    $e = exprs.get(0);
	  }else{
	    $e = FunctionCallFactory.createBooleanOperator("or", p, exprs);
	  }
	}
  :  a1=andExpr { exprs.add($a1.e); p = pos( $a1.start );} (Or a2=andExpr { exprs.add($a2.e); })*
  ;

andExpr returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  if(exprs.size() == 1){
	    $e = exprs.get(0);
	  }else{
	    $e = FunctionCallFactory.createBooleanOperator("and", p, exprs);
	  }
	}
  :  e1=equExpr { exprs.add($e1.e); p = pos( $e1.start );  } ( And e2=equExpr { exprs.add($e2.e);  })*
  ;

equExpr returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  List<String> cmps = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  $e = FunctionCallFactory.createByOp(exprs, p, cmps);
	}
  :  r1=relExpr { exprs.add($r1.e); p = pos( $r1.start );
    } ( cmpr= ( Equals | NEquals ) r2=relExpr {exprs.add($r2.e); cmps.add($cmpr.text); })*
  ;

relExpr returns [LogicalExpression e]
  :  left=addExpr {$e = $left.e; } (cmpr = (GTEquals | LTEquals | GT | LT) right=addExpr {$e = FunctionCallFactory.createExpression($cmpr.text, pos($left.start), $left.e, $right.e); } )?
  ;

addExpr returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  List<String> ops = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  $e = FunctionCallFactory.createByOp(exprs, p, ops);
	}
  :  m1=mulExpr  {exprs.add($m1.e); p = pos($m1.start); } ( op=(Plus|Minus) m2=mulExpr {exprs.add($m2.e); ops.add($op.text); })* 
  ;

mulExpr returns [LogicalExpression e]
	@init{
	  List<LogicalExpression> exprs = new ArrayList<>();
	  List<String> ops = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  $e = FunctionCallFactory.createByOp(exprs, p, ops);
	}
  :  p1=xorExpr  {exprs.add($p1.e); p = pos($p1.start);} (op=(Asterisk|ForwardSlash|Percent) p2=xorExpr {exprs.add($p2.e); ops.add($op.text); } )*
  ;

xorExpr returns [LogicalExpression e]
	@init{
    List<LogicalExpression> exprs = new ArrayList<>();
    List<String> ops = new ArrayList<>();
	  ExpressionPosition p = null;
	}
	@after{
	  $e = FunctionCallFactory.createByOp(exprs, p, ops);
	}
  :  u1=unaryExpr {exprs.add($u1.e); p = pos($u1.start);} (Caret u2=unaryExpr {exprs.add($u2.e); ops.add($Caret.text);} )*
  ;
  
unaryExpr returns [LogicalExpression e]
  :  sign=(Plus|Minus)? Number {$e = ValueExpressions.getNumericExpression($sign.text, $Number.text, pos(($sign != null) ? $sign : $Number)); }
  |  Minus atom {$e = FunctionCallFactory.createExpression("u-", pos($Minus), $atom.e); }
  |  Excl atom {$e= FunctionCallFactory.createExpression("!", pos($Excl), $atom.e); }
  |  atom {$e = $atom.e; }
  ;

atom returns [LogicalExpression e]
  :  Bool {$e = new ValueExpressions.BooleanExpression($Bool.text, pos($Bool)); }
  |  lookup {$e = $lookup.e; }
  ;

pathSegment returns [NameSegment seg]
  : s1=nameSegment {$seg = $s1.seg;}
  ;

nameSegment returns [NameSegment seg]
  : QuotedIdentifier ( (Period s1=pathSegment) | s2=arraySegment)?
  {
    if ($s1.ctx == null && $s2.ctx == null) {
      $seg = new NameSegment($QuotedIdentifier.text);
    } else {
      $seg = new NameSegment($QuotedIdentifier.text, ($s1.ctx == null ? $s2.seg : $s1.seg));
    }
  }
  | Identifier ( (Period s1=pathSegment) | s2=arraySegment)?
  {
    if ($s1.ctx == null && $s2.ctx == null) {
      $seg = new NameSegment($Identifier.text);
    } else {
      $seg = new NameSegment($Identifier.text, ($s1.ctx == null ? $s2.seg : $s1.seg));
    }
   }
  ;
  
arraySegment returns [PathSegment seg]
  :  OBracket Number CBracket ( (Period s1=pathSegment) | s2=arraySegment)?
  {
    if ($s1.ctx == null && $s2.ctx == null) {
      $seg = new ArraySegment($Number.text);
    } else {
      $seg = new ArraySegment($Number.text, ($s1.ctx == null ? $s2.seg : $s1.seg));
    }
  }
  ;

lookup returns [LogicalExpression e]
  :  functionCall {$e = $functionCall.e ;}
  | convertCall {$e = $convertCall.e; }
  | anyValueCall {$e = $anyValueCall.e; }
  | castCall {$e = $castCall.e; }
  | pathSegment {$e = new SchemaPath($pathSegment.seg, pos($pathSegment.start) ); }
  | String {$e = new ValueExpressions.QuotedString($String.text, $String.text.length(), pos($String) ); }
  | OParen expression CParen  {$e = $expression.e; }
  | SingleQuote Identifier SingleQuote {$e = new SchemaPath($Identifier.text, pos($Identifier) ); }
  ;
  
  
  
