/* lexical grammar */
%lex
%x REGEXP
%x COMMENT
%s OTHER_COMMENT

RegularExpressionNonTerminator [^\n\r]
RegularExpressionBackslashSequence \\{RegularExpressionNonTerminator}
RegularExpressionClassChar [^\n\r\]\\]|{RegularExpressionBackslashSequence}
RegularExpressionClass \[{RegularExpressionClassChar}*\]
RegularExpressionFirstChar ([^\n\r\*\\\/\[])|{RegularExpressionBackslashSequence}|{RegularExpressionClass}
RegularExpressionChar ([^\n\r\\\/\[])|{RegularExpressionBackslashSequence}|{RegularExpressionClass}
RegularExpressionBody {RegularExpressionFirstChar}{RegularExpressionChar}*
RegularExpressionLiteral {RegularExpressionBody}\/[igm]*



HexDigit [0-9a-fA-F]
UnicodeEscapeSequence [u]{HexDigit}{4}
HexEscapeSequence [x]{HexDigit}{2}
OctalEscapeSequence (?:[1-7][0-7]{0,2}|[0-7]{2,3})
SingleEscapeCharacter [\'\"\\bfnrtv0xu]
NonEscapeCharacter [^\'\"\\bfnrtv0-9xu]
CharacterEscapeSequence {SingleEscapeCharacter}|{NonEscapeCharacter}
EscapeSequence {CharacterEscapeSequence}|{OctalEscapeSequence}|{HexEscapeSequence}|{UnicodeEscapeSequence}
LineContinuation \\(\r\n|\r|\n)
DoubleStringCharacter ([^\"\\\n\r]+)|(\\{EscapeSequence})|{LineContinuation}
SingleStringCharacter ([^\'\\\n\r]+)|(\\{EscapeSequence})|{LineContinuation}
StringLiteral (\"{DoubleStringCharacter}*\")|(\'{SingleStringCharacter}*\')

%%
<REGEXP>{RegularExpressionLiteral} %{
                                        this.begin("INITIAL");
                                        return "REGEXP_LITERAL";
                                   %}

"'''"                { this.begin('COMMENT'); }
<COMMENT>"'''"       { this.begin('INITIAL'); }
<COMMENT>\n       { ; }
<COMMENT>.          { ; }

"#"                		  	{ this.begin('OTHER_COMMENT'); }
<OTHER_COMMENT>\n       	{ this.begin('INITIAL'); }
<OTHER_COMMENT>.          	{ ; }

"||"                    return '||';
"&&"                    return '&&';
"**"                   	return '**';
"/"                   	return '/';
"-"                   	return '-';
"+"                   	return '+';
"%"                   	return '%';
"("                   	return '(';
")"						return ')';
"PI"					return 'PI';
"E"						return 'E';
"*"						return '*';
"!="					return "!=";
"!"                     return '!';
"<="					return '<=';
">="					return '>=';
"=="					return '==';
"="						return '=';
":"						return ':';
","						return ',';
"<"						return '<';
">"						return '>';
"if"					return 'if';
"else"					return 'else';
"def"					return 'def';
"pass"					return 'pass';
"print"					return 'print';
"end"					return 'end';
"return"				return 'return';
"in"                    return 'in';
"foreach"               return 'foreach';
"["						return '[';
"]"						return ']';
"{"                     return '{';
"}"                     return '}';
"."						return '.';
"len"					return 'len';
"while"					return 'while';
"break"                 return 'break';
"continue"              return 'continue';

[0-9]+("."[0-9]+)?\b  	{return 'NUMBER';}
[a-zA-Z_]([a-zA-Z_]|[0-9])* { return 'IDENT';}
{StringLiteral}			{ return 'STRING';}
\n						{ return 'CR';}
.                     	{ ; }

/lex

/* operator associations and precedence */

%left  '||' '&&'
%left  '+' '-'
%left  '*' '/' '%'
%left  '**'
%left  '<' '<=' '>' '>=' '==' '!='
%left  '!'
%left UMINUS

%nonassoc IF_WITHOUT_ELSE
%nonassoc ELSE

%start startproduction

%% /* language grammar */

startproduction   
	: stmt { 
		finalprogram = $1;
		// Maybe remove all function branches?, then execute the one statement
	}
;

parm_list 
	: id 	{	
		$$ = [];
		$$.push({name : $1, value : null})
	}
	| id ',' parm_list {
		$3.push({name : $1, value : null})
		$$ = $3;
	}
	| num {
		$$ = [];
		$$.push({name : null, value : $1})
	}
	| num ',' parm_list {
		$3.push({name : null, value : $1})
		$$ = $3;
	}
	| string {
		$$ = [];
		$$.push({name:null, value: $1})
	}
	| string ',' parm_list {
		$3.push({name:null, value:$1});
		$$ = $3;
	}
    | RegularExpressionLiteral ',' parm_list {
        $3.push({name: null, value:$1.value});
        $$ = $3;
    }
    | RegularExpressionLiteral {
        $$ = [];
        $$.push({name: null, value: $1.value})
    } 
    | '[' parm_list ']' ',' parm_list {
        $5.push({array:$2});
        $$ = $5;
    }
    | '[' parm_list ']' {
        $$ = [];
        $$.push({name: null, value: $2})
    }
	| { $$ = []; }

;

stmt    
	: stmt line 'CR'	{
		$$ = new AstNode('Statement', {left : $1, right :$2});
	}
	| stmt selection 'CR'	{
		$$ = new AstNode('Statement', {left : $1, right :$2});
	}
	| stmt funcdef 'CR'	{
		$$ = new AstNode('Statement', {left : $1, right : new AstNode('no-op') });
	}
	| stmt 'break' 'CR' {
		$$ = new AstNode('Statement', {left : $1, right : new AstNode('break')});
	}
	| stmt 'continue' 'CR' {
		$$ = new AstNode('Statement', {left : $1, right : new AstNode('continue')});
	}
	| { $$ = new AstNode('no-op'); } // No-op
;

funcdef 
	: 'def' id '(' parm_list ')' ':' stmt 'end' { 
		// AST for function, add to function table
		var mainFunc = new AstNode('function', {left : $7, name : $2, parameters : $4.reverse()});
		functions[$2] = mainFunc; 
	}
;
ifelse 
    : 'if' expr ':' stmt 'end' {
        $$ = new AstNode('if', {left : $2, right : $4});
    }
    | 'if' expr ':' stmt 'else' ':' stmt 'end'  {
        $$ = new AstNode('ifelse', {left : $2, middle: $4,right: $7});      
    }
    | 'if' expr ':' stmt 'else' ifelse {
        $$ = new AstNode('elseif', {left : $2, middle: $4, right: $6});      
    }
    ;
    
selection 
	: ifelse
	| 'while' expr ':' stmt 'end' {
		$$ = new AstNode('while', {left : $2, right:$4});
	}

    | 'foreach' id 'in' id ':' stmt 'end' {
        $$ = new AstNode('foreach', {
            iterator : $2,
            vector : $4,
            statement : $6
        })
    }
	; 

line  
	: id '=' expr	{
		// Identifier assigment	
		var lf= new AstNode('IDENT', {name : $1});	
		$$ = new AstNode('=', {left : lf, right : $3});
	}
	
	| id '[' expr ']' '=' expr { 
		// Assignment of an array index
		var lf  = new AstNode('arrayindex', {name : $1, index : $3}); 
		$$ = new AstNode('=', {left : lf, right : $6});
	}

    | id '[' expr ']' '[' expr ']' '=' expr {
        var lf  = new AstNode('arrayindex_double', {name : $1, first_index : $3, second_index: $6});
        $$ = new AstNode('=', {left : lf, right : $9});
    }

	| id '=' '[' parm_list ']' {
		// Array creation and assignment
		var lf= new AstNode('IDENT', {name : $1});
		var arr = new AstNode('array', {value : $4.reverse()});
		$$ = new AstNode('=', {left :lf, right : arr});
	}
	| id '=' '{' '}' {
        var lf= new AstNode('IDENT', {name : $1});
        var arr = new AstNode('object');
        $$ = new AstNode('=', {left :lf, right : arr});
    }
	| 'print' expr {
		// Print statement
		$$ = new AstNode('print', {left : $2});			
	}

	| 'return' expr{
		// Return statement
		$$ = new AstNode('return', {left: $2});
	}
	| expr
	| { $$ = new AstNode('no-op'); } // No-op
;

expr    
	: expr '+' expr 	{ $$ = new AstNode('+', {left : $1, right : $3}); }
    | expr '||' expr    { $$ = new AstNode('||', {left: $1, right: $3});}
    | expr '&&' expr    { $$ = new AstNode('&&', {left: $1, right: $3});}
	| expr '-' expr 	{ $$ = new AstNode('-', {left : $1, right : $3});}
	| expr '*' expr 	{ $$ = new AstNode('*', {left : $1, right : $3});}
	| expr '**' expr		{ $$ = new AstNode('**', {left : $1, right : $3});}
	| expr '/' expr		{ $$ = new AstNode('/', {left : $1, right : $3});}
	| expr '%' expr		{ $$ = new AstNode('%', {left : $1, right : $3});}
	| expr '<' expr	{ $$ = new AstNode('<', {left : $1, right : $3});}
	| expr '<=' expr	{ $$ = new AstNode('<=', {left : $1, right : $3});}
	| expr '>' expr	{ $$ = new AstNode('>', {left : $1, right : $3});}
	| expr '>=' expr	{ $$ = new AstNode('>=', {left : $1, right : $3});}
	| expr '!=' expr	{ $$ = new AstNode('!=', {left : $1, right : $3});}
	| expr '==' expr		{ $$ = new AstNode('==', {left : $1, right : $3});}
    | '(' expr ')'  { $$ = new AstNode('()', {value : $2});}
	| '-' expr %prec UMINUS	{ $$ = new AstNode('UMINUS', {left : $2}); }
	| num	{ $$ = new AstNode('NUMBER', {value : $1}); }
    | '!' expr {$$ = new AstNode('!', {left: $2});}
	| id		{ $$ = new AstNode('IDENT', {name : $1});	}
	| id '[' expr ']' { $$ = new AstNode('arrayindex', {name : $1, index : $3}); }
    | id '[' expr ']' '[' expr ']' { $$ = new AstNode('arrayindex_double', {name : $1, first_index : $3, second_index: $6}); }
	| 'len' '(' id ')' {$$ = new AstNode('len', {name : $3});}
	| 'STRING' {$$ = new AstNode('STRING', {value: debackslashify(yytext.substring(1, yytext.length - 1))}); }
	| RegularExpressionLiteral
    | id '(' parm_list ')' {
        // Function call
        $$ = new AstNode('FunctionCall', {name : $1, parameters : $3.reverse()});
    }
    | id '.' id '(' parm_list ')' { 
        // No argument method dispatch
        $$ = new AstNode('method', { name : $1, method : $3, argument: $5.reverse()});
    }
    | id '.' id {
        $$ = new AstNode('member', { name : $1, member : $3});
    }
	;
method : 

    ;

id 
	: 'IDENT' {$$ = yytext;}
	;
num 
	: 'NUMBER' {$$ = Number(yytext);}
	;

string
	: 'STRING' {$$ = yytext.substring(1, yytext.length - 1); $$ = debackslashify($$);}
	;

RegularExpressionLiteral
    : RegularExpressionLiteralBegin "REGEXP_LITERAL"
        {
            $$ = new AstNode('regexp', {value : parseRegularExpressionLiteral($1 + $2)});
        }
    ;

RegularExpressionLiteralBegin
    : "/"
        {
            yy.lexer.begin("REGEXP");
        }
    | "/="
        {
            yy.lexer.begin("REGEXP");
        }
    ;

%%
// https://raw.github.com/douglascrockford/JSON-js/master/json_parse_state.js  
var escapes = { // Escapement translation table  
    '\\' : '\\',  
    '"' : '"',  
    '/' : '/',  
    't' : '\t',  
    'n' : '\n',  
    'r' : '\r',  
    'f' : '\f',  
    'b' : '\b',
    '0' : '\0'
};  

function debackslashify(text) {  
    // Remove and replace any backslash escapement.  
    return text.replace(/\\(?:u(.{4})|([^u]))/g, function(a, b, c) {  
                return b ? String.fromCharCode(parseInt(b, 16)) : escapes[c];  
            });  
} 


function AstNode(type, params) {
    this.type = type;
    for(var key in params){ this[key] = params[key];}
    return this;
}

function parseRegularExpressionLiteral(literal) {
    var last = literal.lastIndexOf("/");
    var body = literal.substring(1, last);
    var flags = literal.substring(last + 1);

    return new RegExp(body, flags);
}

// The whole program tree
var finalprogram;
// Function map
var functions = {
    // Pre-create the main function
    '#main#' : new AstNode('function', {name : '#main#'})
};


function resetForRun() {
    functions = {
        // Pre-create the main function
        '#main#' : new AstNode('function', {name : '#main#'})
    };
}

function getFinalProgram() {
    return finalprogram;
}

// exports.finalprogram = finalprogram;
exports.functions = functions;
exports.resetForRun = resetForRun;
exports.getFinalProgram = getFinalProgram;
