grammar NCalc;

options
{
	output=AST;
	ASTLabelType=CommonTree;
	language=CSharp;
}

@header {
using System.Text;
using System.Globalization;
using System.Collections.Generic;
using NCalc.Domain;
}

@members {
private const char BS = '\\';
private static NumberFormatInfo numberFormatInfo = new NumberFormatInfo();

private string extractString(string text) {
    
    StringBuilder sb = new StringBuilder(text);
    int startIndex = 1; // Skip initial quote
    int slashIndex = -1;

    while ((slashIndex = sb.ToString().IndexOf(BS, startIndex)) != -1)
    {
        char escapeType = sb[slashIndex + 1];
        switch (escapeType)
        {
            case 'u':
              string hcode = String.Concat(sb[slashIndex+4], sb[slashIndex+5]);
              string lcode = String.Concat(sb[slashIndex+2], sb[slashIndex+3]);
              char unicodeChar = Encoding.Unicode.GetChars(new byte[] { System.Convert.ToByte(hcode, 16), System.Convert.ToByte(lcode, 16)} )[0];
              sb.Remove(slashIndex, 6).Insert(slashIndex, unicodeChar); 
              break;
            case 'n': sb.Remove(slashIndex, 2).Insert(slashIndex, '\n'); break;
            case 'r': sb.Remove(slashIndex, 2).Insert(slashIndex, '\r'); break;
            case 't': sb.Remove(slashIndex, 2).Insert(slashIndex, '\t'); break;
            case '\'': sb.Remove(slashIndex, 2).Insert(slashIndex, '\''); break;
            case '\\': sb.Remove(slashIndex, 2).Insert(slashIndex, '\\'); break;
            default: throw new RecognitionException("Unvalid escape sequence: \\" + escapeType);
        }

        startIndex = slashIndex + 1;

    }

    sb.Remove(0, 1);
    sb.Remove(sb.Length - 1, 1);

    return sb.ToString();
}

public List<string> Errors { get; private set; }

public override void DisplayRecognitionError(String[] tokenNames, RecognitionException e) {
    
    base.DisplayRecognitionError(tokenNames, e);
    
    if(Errors == null)
    {
    	Errors = new List<string>();
    }
    
    String hdr = GetErrorHeader(e);
    String msg = GetErrorMessage(e, tokenNames);
    Errors.Add(msg + " at " + hdr);
}
}

@init {
    numberFormatInfo.NumberDecimalSeparator = ".";
}

ncalcExpression returns [LogicalExpression value]
	: logicalExpression EOF! {$value = $logicalExpression.value; }
	;

logicalExpression returns [LogicalExpression value]
	:	left=conditionalExpression { $value = $left.value; } ( '?' middle=conditionalExpression ':' right=conditionalExpression { $value = new TernaryExpression($left.value, $middle.value, $right.value); })? 
	;

conditionalExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=booleanAndExpression { $value = $left.value; } (
			('||' | 'or') { type = BinaryExpressionType.Or; } 
			right=conditionalExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;
		
booleanAndExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=bitwiseOrExpression { $value = $left.value; } (
			('&&' | 'and') { type = BinaryExpressionType.And; } 
			right=bitwiseOrExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

bitwiseOrExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=bitwiseXOrExpression { $value = $left.value; } (
			'|' { type = BinaryExpressionType.BitwiseOr; } 
			right=bitwiseOrExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;
		
bitwiseXOrExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=bitwiseAndExpression { $value = $left.value; } (
			'^' { type = BinaryExpressionType.BitwiseXOr; } 
			right=bitwiseAndExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

bitwiseAndExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=equalityExpression { $value = $left.value; } (
			'&' { type = BinaryExpressionType.BitwiseAnd; } 
			right=equalityExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;
		
equalityExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=relationalExpression { $value = $left.value; } (
			( ('==' | '=' ) { type = BinaryExpressionType.Equal; } 
			| ('!=' | '<>' ) { type = BinaryExpressionType.NotEqual; } ) 
			right=relationalExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;
	
relationalExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=shiftExpression { $value = $left.value; } (
			( '<' { type = BinaryExpressionType.Lesser; } 
			| '<=' { type = BinaryExpressionType.LesserOrEqual; }  
			| '>' { type = BinaryExpressionType.Greater; } 
			| '>=' { type = BinaryExpressionType.GreaterOrEqual; } ) 
			right=shiftExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

shiftExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	: left=additiveExpression { $value = $left.value; } (
			( '<<' { type = BinaryExpressionType.LeftShift; } 
			| '>>' { type = BinaryExpressionType.RightShift; }  )
			right=additiveExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

additiveExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=multiplicativeExpression { $value = $left.value; } (
			( '+' { type = BinaryExpressionType.Plus; } 
			| '-' { type = BinaryExpressionType.Minus; } ) 
			right=multiplicativeExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

multiplicativeExpression returns [LogicalExpression value]
@init {
BinaryExpressionType type = BinaryExpressionType.Unknown;
}
	:	left=unaryExpression { $value = $left.value); } (
			( '*' { type = BinaryExpressionType.Times; } 
			| '/' { type = BinaryExpressionType.Div; } 
			| '%' { type = BinaryExpressionType.Modulo; } ) 
			right=unaryExpression { $value = new BinaryExpression(type, $value, $right.value); } 
			)* 
	;

	
unaryExpression returns [LogicalExpression value]
	:	primaryExpression { $value = $primaryExpression.value; }
    	|	('!' | 'not') primaryExpression { $value = new UnaryExpression(UnaryExpressionType.Not, $primaryExpression.value); }
    	|	('~') primaryExpression { $value = new UnaryExpression(UnaryExpressionType.BitwiseNot, $primaryExpression.value); }
    	|	'-' primaryExpression { $value = new UnaryExpression(UnaryExpressionType.Negate, $primaryExpression.value); }
   	;
		
primaryExpression returns [LogicalExpression value]
	:	'(' logicalExpression ')' 	{ $value = $logicalExpression.value; }
	|	expr=value		{ $value = $expr.value; }
	|	identifier {$value = (LogicalExpression) $identifier.value; } (arguments {$value = new Function($identifier.value, ($arguments.value).ToArray()); })?
	;

value returns [ValueExpression value]
	: 	INTEGER		{ try { $value = new ValueExpression(int.Parse($INTEGER.text)); } catch(System.OverflowException) { $value = new ValueExpression(long.Parse($INTEGER.text)); } }
	|	FLOAT		{ $value = new ValueExpression(double.Parse($FLOAT.text, NumberStyles.Float, numberFormatInfo)); }
	|	STRING		{ $value = new ValueExpression(extractString($STRING.text)); }
	| 	DATETIME	{ $value = new ValueExpression(DateTime.Parse($DATETIME.text.Substring(1, $DATETIME.text.Length-2))); }
	|	TRUE		{ $value = new ValueExpression(true); }
	|	FALSE		{ $value = new ValueExpression(false); }
	;

identifier returns[Identifier value]
	: 	ID { $value = new Identifier($ID.text); }
	| 	NAME { $value = new Identifier($NAME.text.Substring(1, $NAME.text.Length-2)); }
	;

expressionList returns [List<LogicalExpression> value]
@init {
List<LogicalExpression> expressions = new List<LogicalExpression>();
}
	:	first=logicalExpression {expressions.Add($first.value);}  ( ',' follow=logicalExpression {expressions.Add($follow.value);})* 
	{ $value = expressions; }
	;
	
arguments returns [List<LogicalExpression> value]
@init {
$value = new List<LogicalExpression>();
}
	:	'(' ( expressionList {$value = $expressionList.value;} )? ')' 
	;			

TRUE
	:	'true'
	;

FALSE
	:	'false'
	;
			
ID 
	: 	LETTER (LETTER | DIGIT)*
	;

INTEGER
	:	DIGIT+
	;

FLOAT 
	:	DIGIT* '.' DIGIT+ E?
	|	DIGIT+ E
	;

STRING
    	:  	'\'' ( EscapeSequence | (options {greedy=false;} : ~('\u0000'..'\u001f' | '\\' | '\'' ) ) )* '\''
    	;

DATETIME 
 	:	'#' (options {greedy=false;} : ~('#')*) '#'
        ;

NAME	:	'[' (options {greedy=false;} : ~(']')*) ']'
	;
	
E	:	('E'|'e') ('+'|'-')? DIGIT+ 
	;	
	
fragment LETTER
	:	'a'..'z'
	|	'A'..'Z'
	|	'_'
	;

fragment DIGIT
	:	'0'..'9'
	;
	
fragment EscapeSequence 
	:	'\\'
  	(	
  		'n' 
	|	'r' 
	|	't'
	|	'\'' 
	|	'\\'
	|	UnicodeEscape
	)
  ;

fragment HexDigit 
	: 	('0'..'9'|'a'..'f'|'A'..'F') ;


fragment UnicodeEscape
    	:    	'u' HexDigit HexDigit HexDigit HexDigit 
    	;

/* Ignore white spaces */	
WS	:  (' '|'\r'|'\t'|'\u000C'|'\n') {$channel=HIDDEN;}
	;