%{
##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id$
##------------------------------------------------------------------------------
use Yasco::Node;
%}

%right			T_ASSIGNMENT '?'
%left			T_LOGIC T_BITWISE T_INSTANCEOF T_IN T_RELATIONAL
%left			T_EQUALITY
%left			T_BITWISE_SHIFT
%left			'+' '-'
%left			'%' '/' '*'
%right			T_UNARY
%nonassoc		T_INCDEC
%right			T_NEW
%left			'.' '[' '('

#	shift/reduce #1:	if .. else .. if
#	shift/reduce #2:	a ? b ? c : d : e
%expect 2


%%
##------------------------------------------------------------------------------
java_script		: statements EOF {
#					use Data::Dumper;print STDERR "\n\n",('*'x80),"\nFINAL BLOCK STACK\n",Dumper(\@Yasco::Node::block_stack),"\n\nSCOPES LEFT:\n",Dumper($_[0]->{OBFUSCATOR}->{_OB_DATA}->{scopes}),('*'x80),"\n\n";
					defined($_[1]) and do {
						my $stck = shift @Yasco::Node::block_stack;
						foreach (qw(FUNCTIONS SYMBOLS)) {
							$_[1]->{$_}= $stck->{$_."_NAMES"};
							delete $_[1]->{$_} unless 0+keys %{$_[1]->{$_}};
						}
					};
					$_[1]
				}
				;

statements		: stmt_list {Yasco::Node->new(qq{STMTS}, $_[1])}
				;

stmt_list		: # empty
				| stmt_list statement_glob { push(@{$_[1]}, $_[2]); $_[1] }
				;

statement_glob	: func_decl
				| statement
				;

statement		: T_IDENTIFIER ':' _statement {Yasco::Node->new(q{LABEL}, $_[1], $_[3])}
				| _statement
				;

_statement		: stmt_line ';'
				| block_stmt
				;

block_stmt		: block
				| if_statement
				| switch_stmt
				| for_loop
				| while_loop
				| with_stmt
				| exception_stmt
				;
##------------------------------------------------------------------------------
stmt_line		: # empty
				| _stmt_line
				;

_stmt_line		: expr
				| vars_decl
				| T_DO block T_WHILE '(' expr ')' {Yasco::Node->new(qq{DO}, $_[2], $_[5])}
				| T_RETURN {Yasco::Node->new(qq{RETURN})}
				| T_RETURN expr {Yasco::Node->new(qq{RETURN}, $_[2])}
				| T_THROW _expr_ {Yasco::Node->new(qq{THROW}, $_[2])}
				| T_BREAK {Yasco::Node->new(qq{BREAK})}
				| T_BREAK T_IDENTIFIER {Yasco::Node->new(qq{BREAK}, $_[2])}
				| T_CONTINUE {Yasco::Node->new(qq{CONTINUE})}
				;
##------------------------------------------------------------------------------
vars_decl		: T_VAR {unshift @{$_[0]->YYData->{LISTS}}, []} var_decl_list {$_[3]}
				;

var_decl_list	: T_IDENTIFIER T_IN _expr_ {shift @{$_[0]->YYData->{LISTS}}; Yasco::Node->new(qq{IN}, Yasco::Node->new(qq{VAR}, $_[1]), $_[3])}
				| _var_decl_list {Yasco::Node->new(qq{VAR}, shift @{$_[0]->YYData->{LISTS}})}
				;

_var_decl_list	: _var_decl_list ',' var_decl
				| var_decl
				;

var_decl		: T_IDENTIFIER {push @{@{$_[0]->YYData->{LISTS}}[0]}, $_[1]}
				| T_IDENTIFIER T_ASSIGNMENT _expr_ {
					$_[0]->YYError("misplaced '%s'", $_[2]) if '=' ne $_[2];
					push @{@{$_[0]->YYData->{LISTS}}[0]}, Yasco::Node->new(qq{ASSIGN}, $_[1], $_[3])
				}
				;
##------------------------------------------------------------------------------
expr			: __expr_ {$_[0]->convert_expr_list($_[1])}
				;

	__expr_		: __expr_ ',' _expr_ {Yasco::Node->new(qq{EXPR_LIST}, $_[1], $_[3])}
				| _expr_
				;

_expr_			: assignment ;

assignment		: inline_cond
				| lvalue T_ASSIGNMENT assignment {$_[0]->validate_assignment($_[2], $_[1], $_[3])}
				;

inline_cond		: _expr_ '?' _expr_ ':' _expr_ {Yasco::Node->new(qq{INLINE}, $_[1], $_[3], $_[5])}
				| arithmetic
				;

arithmetic		: unary
				| arithmetic T_LOGIC arithmetic {Yasco::Node->new($_[2], $_[1], $_[3])}
				| arithmetic T_BITWISE arithmetic {Yasco::Node->new($_[2], $_[1], $_[3])}
				| arithmetic T_EQUALITY arithmetic {Yasco::Node->new($_[2], $_[1], $_[3])}
				| arithmetic T_INSTANCEOF arithmetic {Yasco::Node->new(qq{INSTANCEOF}, $_[1], $_[3])}
				| arithmetic T_IN arithmetic {Yasco::Node->new(qq{IN}, $_[1], $_[3])}
				| arithmetic T_RELATIONAL arithmetic {Yasco::Node->new($_[2], $_[1], $_[3])}
				| arithmetic T_BITWISE_SHIFT arithmetic {Yasco::Node->new($_[2], $_[1], $_[3])}
				| arithmetic '+' arithmetic {Yasco::Node->new('+', $_[1], $_[3])}
				| arithmetic '-' arithmetic {Yasco::Node->new('-', $_[1], $_[3])}
				| arithmetic '*' arithmetic {Yasco::Node->new('*', $_[1], $_[3])}
				| arithmetic '/' arithmetic {Yasco::Node->new('/', $_[1], $_[3])}
				| arithmetic '%' arithmetic {Yasco::Node->new('%', $_[1], $_[3])}
				;

unary			: incdec
				| '-' unary %prec T_UNARY {Yasco::Node->new(qq{U-}, $_[2])}
				| '+' unary %prec T_UNARY {Yasco::Node->new(qq{U+}, $_[2])}
				| T_UNARY unary {Yasco::Node->new($_[1], $_[2])}
				;

incdec			: new_invoke
				| T_INCDEC lvalue {Yasco::Node->new($_[1], $_[2])}
				| lvalue T_INCDEC {Yasco::Node->new($_[2], 0, $_[1])}
				;

new_invoke		: rvalue
				| T_NEW lvalue {Yasco::Node->new(qq{NEW}, $_[2])}
				;

rvalue			: lvalue
				| T_CONSTANT
				;

lvalue			: T_IDENTIFIER
				| T_NUMBER
				| T_STRING
				| T_REGEX
				| T_THIS
				| closure
				| '[' value_list ']' {Yasco::Node->new(qq{ARRAY}, $_[2])}
				| '{' hash_elements '}' {shift @Yasco::Node::block_stack;Yasco::Node->new(qq{HASH}, ref $_[2]?$_[2]:undef)}
				| lvalue '(' value_list ')' {Yasco::Node->new(qq{CALL}, $_[1], $_[3])}
				| lvalue '[' expr ']' {Yasco::Node->new(qq{ARRAY_ACCESS}, $_[1], $_[3])}
				| lvalue '.' lvalue {Yasco::Node->new(qq{DOT}, $_[1], $_[3])}
				| '(' expr ')' {Yasco::Node->new(qq{PAREN}, $_[2])}
				;
##------------------------------------------------------------------------------
id_list			: # empty
				| {unshift @{$_[0]->YYData->{LISTS}}, []} _id_list {
						Yasco::Node->new(q{PARAMETER_LIST}, shift @{$_[0]->YYData->{LISTS}})
				}
				;

_id_list		: _id_list ',' T_IDENTIFIER {push @{@{$_[0]->YYData->{LISTS}}[0]}, $_[3]}
				| T_IDENTIFIER {push @{@{$_[0]->YYData->{LISTS}}[0]}, $_[1]}
				;

value_list		: # empty
				| expr {'ARRAY' ne ref $_[1] ? [$_[1]] : $_[1]}
				;

hash_elements	: # empty
				| {unshift @{$_[0]->YYData->{HASHES}}, []} _hash_elements {shift @{$_[0]->YYData->{HASHES}}}
				;

_hash_elements	: _hash_elements ',' _hash_assign {push @{@{$_[0]->YYData->{HASHES}}[0]}, $_[3]}
				| _hash_assign {push @{@{$_[0]->YYData->{HASHES}}[0]}, $_[1]}
				;

_hash_assign	: T_STRING ':' _expr_ {Yasco::Node->new(qq{KV}, $_[1], $_[3])}
				| T_IDENTIFIER ':' _expr_ {Yasco::Node->new(qq{KV}, $_[1], $_[3])}
				| T_NUMBER ':' _expr_ {Yasco::Node->new(qq{KV}, $_[1], $_[3])}
				| _hash_setget
				;
_hash_setget	: T_GET T_IDENTIFIER '(' ')' block {Yasco::Node->new(qq{GET}, $_[2], undef, $_[5])}
				| T_SET T_IDENTIFIER '(' T_IDENTIFIER ')' block {Yasco::Node->new(qq{GET}, $_[2], $_[4], $_[6])}
				;
##------------------------------------------------------------------------------
block			: '{' statements '}' %prec T_NEW {Yasco::Node->new(qq{BLOCK}, $_[2])}
				;
##------------------------------------------------------------------------------
func_decl		: T_FUNCTION T_IDENTIFIER '(' id_list ')' block {Yasco::Node->new(qq{FUNC_DECL}, $_[2], $_[4], $_[6])}
				;
closure			: T_FUNCTION '(' id_list ')' block {Yasco::Node->new(qq{CLOSURE}, $_[3], $_[5])}
				;
##------------------------------------------------------------------------------
if_statement	: T_IF '(' expr ')' statement if_follows {
						warn sprintf("[WARNING] `if` statement finished at line %d: possible typo? '=' operator found, but comparison intended?", $_[0]->{LINE})
							if '=' eq $_[3]->op;
						Yasco::Node->new(qq{IF}, $_[3], $_[5], $_[6])
					}
				;

if_follows		: # empty
				| T_ELSE statement {$_[2]}
				;
##------------------------------------------------------------------------------
switch_stmt		: T_SWITCH '(' expr ')' '{' switch_cases '}' {shift @Yasco::Node::block_stack;Yasco::Node->new(qq{SWITCH}, $_[3], $_[6])}
				;
switch_cases	: #empty
				| switch_cases switch_case {push @{$_[1]}, $_[2];$_[1]}
				;
switch_case		: T_CASE _expr_ ':' statements {Yasco::Node->new(qq{CASE}, $_[2], $_[4])}
				| T_DEFAULT ':' statements {Yasco::Node->new(qq{DEFAULT}, undef, $_[3])}
				;
##------------------------------------------------------------------------------
for_loop		: T_FOR for_params statement {Yasco::Node->new(qq{FOR}, $_[2], $_[3])}
				;
for_params		: '(' for_left ')' {$_[2]}
				| '(' for_left ';' for_param ';' for_param2 ')' {Yasco::Node->new(qq{FOR_LIST}, $_[2], $_[4], $_[6])}
				;

for_param		: # empty
				| _expr_
				;
for_param2		: # empty
				| expr		# allow for a++,b++ and such
				;
for_left		: # empty
				| vars_decl
				| expr
				;
##------------------------------------------------------------------------------
while_loop		: T_WHILE '(' expr ')' statement {Yasco::Node->new(qq{WHILE}, $_[3], $_[5])}
				;
##------------------------------------------------------------------------------
exception_stmt	: T_TRY block try_catch try_finally {
						$_[0]->YYError('Missing catch or finally after try') unless defined($_[3]) || defined($_[4]);
						Yasco::Node->new(qq{TRY}, $_[2], $_[3], $_[4]);
				}
				;
	try_catch	: # empty
				| T_CATCH '(' expr ')' block {Yasco::Node->new(q{TRY_CATCH}, $_[3], $_[5])}
				;
	try_finally	: # empty
				| T_FINALLY block {Yasco::Node->new(q{TRY_FINALLY}, $_[2])}
				;

with_stmt		: T_WITH '(' expr ')' statement {Yasco::Node->new(qq{WITH}, $_[3], $_[5])}
				;
##------------------------------------------------------------------------------
%%
sub verbose
	{
		my $self = shift;
		$self->{OBFUSCATOR}->verbose(@_);
	}

sub YYError
	{
		my $p = shift;
		$p->{_err_arg} = [@_];
		$p->SUPER::YYError;
	}

sub convert_expr_list
	{
		my $self = shift;
		my $node = shift;
		return $node if 'EXPR_LIST' ne $node->op;

		my @list;
		$self->node_to_list(\@list, $node);
		Yasco::Node->new(qq{LIST}, [@list])
	}

sub node_to_list
	{
		my ($self, $list, $node) = @_;

		push(@$list, $node), return if ('EXPR_LIST' ne $node->op);

		$self->node_to_list($list, $node->left);
		$self->node_to_list($list, $node->right);
	}

sub validate_assignment
	{
		my $self = shift;
		my ($op,$left,$right) = @_;
		my ($temp);

		for($temp = $left; 'DOT' eq $temp->op; $temp = $temp->right){}

		LVALUE_VALID: {
			INVALID_LVALUE: for ($temp->op) {

				'ID'			eq $_ and last LVALUE_VALID;
				'DOT'			eq $_ and last LVALUE_VALID;
				'ARRAY_ACCESS'	eq $_ and last LVALUE_VALID;

			}

			return $self->YYError('invalid lvalue for assignment "%s": %s(%s)', $op, $temp->op, $temp->left)
		}

		Yasco::Node->new($op,$left,$right)
	}
