package JE::Code;

our $VERSION = '0.056';

use strict;
use warnings; no warnings 'utf8';

#use Data::Dumper;
use Carp 1.01 'shortmess';
use Exporter 5.57 'import';
use Scalar::Util 'tainted';

our @CARP_NOT = 'JE';
our @EXPORT_OK = 'add_line_number';

use constant T => ${AINT}; # perl doesn’t optimise if(${AINT}) away

require JE::Object::Error;
require JE::Object::Error::ReferenceError;
require JE::Object::Error::SyntaxError;
require JE::Object::Error::TypeError;
require JE::Object::Function;
require JE::Object::Array;
require JE::Boolean;
require JE::Object;
require JE::Parser;
require JE::Number;
require JE::LValue;
require JE::String;
require JE::Scope;

sub add_line_number; # so I can call it without parentheses in sub execute


# This is documented in a POD comment at the bottom of the file.
sub parse {
	my($global, $src, $file, $line) = @_;

	($src, my ($tree, $vars)) = JE::Parser::_parse(
		program => $src, $global, $file, $line
	);

	$@ and return;

#print Dumper $tree;

	my $r= bless { global     => $global,
	               ( $JE::Parser::_parser
	                 ? (parser => $JE::Parser::_parser)
	                 : () ),
	               source     => \$src,
	               file       => $file,
	               line       => $line,
	               vars       => $vars,
	               tree       => $tree };
	# $self->{source} is a reference, so that we can share the same
	# source between code objects without the extra memory overhead
	# that copying it  would  have.  (Some  JS  script  files  are
	# rather large.)

	$r->optimise
	 if   $ENV{'YES_I_WANT_JE_TO_OPTIMISE'}
	  and $ENV{'YES_I_WANT_JE_TO_OPTIMISE'} ne 2;

	$r;
}




sub execute_till { # ~~~ Should this be made public?
	(my $code, local our $counting) = (shift,shift);
	local our $ops = 0;
	JE_Code_OP: {
		return $code->execute(@_);
	}
	# If we get here, then we reached the max number of ops.
	$@ = shortmess "max_ops ($counting) exceeded";
	return undef;
}

sub set_global {
	my $code = shift;
	my $old = $code->{global};
	$code->{global} = $_[0];
	{for(@{$code->{cache}||last}) {
		ref eq 'JE::Code' and $_->set_global($_[0]);
	}}
	{for(@{$code->{vars}||last}){
		ref && ref $$_[4] eq 'JE::Code'
		    && $$_[4]->set_global($_[0])
	}}
	defined $old or return;
	my @stack = $code->{tree};
	local *@;
	while(@stack) {
	  for(shift @stack) {
	    for(@$_[1..$#$_]) {
	      my $r = ref || next;
	      $r =~ /^(?:ARRAY\z|JE::Code::)/
	        and push @stack, $_, =~ next;
	      $r eq 'JE::Boolean'
	        and $_ = qw(f t)[$_->value], next;
	      $r eq 'JE::Number'
	        and $_ = $_->value, next;
	      $r eq 'JE::String'
	        and $_ = "s".$_->value16, next;
	      $r eq 'JE::Null' and $_ = 'n', next;
	      $r eq 'JE::Object::RegExp'
	          and $_ = [$_->{source}->value, $$$_{regexp_flags}],
	              next;
	    }
	  }
	}
	return;
}

sub optimise {
	require 'JE/toperl.pl';
	goto &{'optimise'};
}

# Variables pertaining to the current execution context
our $code; # JE::Code object, not source code
our $this;
our $scope;
our $parser;
our $pos; # position within the source code; used to calculate a line no.
our $taint;
our $ops;
our $counting;
our $global;
our $return;
our $cache;

sub execute {
	local $code = shift;
	local $global = $$code{global};

	# We check $ops’ definedness to avoid resetting the op count when
	# called recursively.
	if(!defined our $ops and my $max_ops = $global->max_ops) {
		unshift @_, $code, $max_ops;
		goto &execute_till;
	}

	local $this = defined $_[0] ? $_[0] : $global;
	shift;

	local $scope = shift || bless [$global], 'JE::Scope';

	my $code_type = shift || 0;

	local our $taint = substr(${$$code{source}},0,0) if T;

	my $rv;
	eval {
		# passing these values around is too
		# cumbersome
		local $JE::Code::parser = $code->{parser}; # might be
		local our $pos;                            # undef
		local our $code = $code;
		local $JE::Code::Expression::_eval   = $code_type == 1;

		package JE::Code::Statement;
		local our $_label;
		package JE::Code;
		# This $return variable has two uses. It holds the return
		# value when the JS 'return' statement calls 'last RETURN'.
		# It also is used by statements that return values. It is
		# necessary to use this var, rather than simply returning
		# the value (as in v0.016 and earlier), in order to make
		# 'while(true) { 3; break }'  return  3,  rather  than
		#  undefined.
		local $return;
		local $cache = $$code{cache}||=[];

		RETURN: {
		BREAK: {
		CONT: {
			JE'Code'Statement'_create_vars();
			$$code{sub} ? &{$$code{sub}} :
			$$code{psrc}? (
			# ~~~ temporary hack:
				($$code{psrc}) = $$code{psrc} =~/(.*)/s,
				&{$$code{sub} =
				eval{ eval("sub{$$code{psrc}}")||die }
				|| die "Internal error that should never"
				   . " happen (please report this): $@: "
				   . $$code{psrc}
			}):
			$$code{tree}->eval;
			$code_type == 2 # function
				or defined $return && ($rv = $return);
			goto FINISH;
		} 

		if($JE::Code::Statement::_label) {
			 die new JE::Object::Error::SyntaxError $global,
		  	 add_line_number
			"continue $JE::Code::Statement::_label: label " .
		  	"'$JE::Code::Statement::_label' not found";
		} else { goto FINISH; }

		} # end of BREAK

		if($JE::Code::Statement::_label) {
			 die new JE::Object::Error::SyntaxError $global,
			 add_line_number
		  	"break $JE::Code::Statement::_label: label " .
		  	"'$JE::Code::Statement::_label' not found";
		} else { goto FINISH; }

		} # end of RETURN

		$rv = $return;


		FINISH:  # I have to  put  this  here  inside  the  eval,
		         # because 'eval { goto label }; label:' causes a
		         # a bus error in p5.8.8 if a tie handler  is  in
		         # the call stack (fixed in 5.9.5).
	};

	T and defined $rv and tainted $taint and $rv->can('taint')
		and $rv = taint $rv $taint;

	if(ref $@ eq '' and $@ eq '') {
		!defined $rv and $rv = $scope->undefined;
	}
	else {
		# Catch-all for any errors not dealt with elsewhere
		$@ = _objectify_error($@);
	}

	$rv;
}

sub add_line_number {
	my $msg = shift;
	my $code = @_ ? shift : $code;
	my $pos  = @_ ? shift : $pos ;
	$msg =~  /\n\z/ and return $msg;
	defined(my $file = ($code || return $msg)->{file})
		or defined $pos or return $msg;
	my $first_line = $code->{line};
	defined $first_line or $first_line = 1;
	if(defined $pos) {
	    no warnings 'uninitialized';
	    "$msg at $file" . ', ' x defined($file) . 'line ' .
	        ($first_line + (() = substr(${$code->{source}},0,$pos) =~
	            /\cm\cj?|[\cj\x{2028}\x{2029}]/g))
	        . ".\n";
	} else {
	    "$msg in $file.\n"
	}
}

sub _objectify_error {
	my $msg = shift;

	ref $msg and return $global->upgrade($msg);

	my $class = 'JE::Object::Error';

	if($msg =~ /^Can't\ locate\ object\ method\ 
	    "(?:c(?:all|onstruct)|apply|invoke_with)"/x) {
	     # ~~~ the ‘apply’ in there is legacy and can be removed b4 v1
		$class = 'JE::Object::Error::TypeError';
		$msg = "Argument to new is not a constructor";
	}

	new $class $global, add_line_number $msg;
}

sub DDS_freeze {
	my $self = shift;
	my $copy = bless {%$self}, ref $self;
	delete $copy->{sub};
	$copy;
}



package JE::Code::Statement; # This does not cover expression statements.

our $VERSION = '0.056';

use subs qw'_eval_term';
use List::Util 'first';

our( $_label);

*_eval_term = *JE::Code::Expression::_eval_term;
import JE::Code 'add_line_number';
sub add_line_number;


# Note: each statement object is an array ref. The elems are:
# [0] - an array ref containing
#       [0] - the starting position in the source code and
#       [1] - the ending position
# [1] - the type of statement
# [2..$#] - the various expressions/statements that make up the statement

sub eval {  # evaluate statement
	my $stm = shift;

	my $type = $$stm[1];
	$type eq 'empty' || $type eq 'function' and return;

	my @labels;
	$pos = $$stm[0][0];

	if ($type eq 'labelled') {
		@labels = @$stm[2..$#$stm-1];
		if ($$stm[-1][1] =~ /^(?:do|while|for|switch)\z/) {
			$stm = $$stm[-1];
			$type = $$stm[1];
			no warnings 'deprecated';
			goto LOOPS; # skip unnecessary if statements
		}

		BREAK: {
			my $returned = $$stm[-1]->eval;
			defined $returned and $return = $returned
		}

		# Note that this has 'defined' in it, whereas the similar
		# 'if' statement further down where the loop constructs are
		# doesn't. This is because 'break' without a label sets
		# $_label to '' and exits loops and switches.
		if(! defined $_label || first {$_ eq $_label} @labels) {
			undef $_label;
			return;
		} else {
			no warnings 'exiting';
			last BREAK;
		}
	}

	if ($type eq 'statements') {

		# Execute the statements, one by one, and return the return
		# value of the last statement that actually returned one.
		my $returned;
		for (@$stm[2..$#$stm]) {
			next if $_ eq 'empty';
			defined($returned = $_->eval) and
				$return = $returned,
				ref $return eq 'JE::LValue'
					&& get $return;
		}
		return;
	}
	if ($type eq 'var') {
		for (@$stm[2..$#$stm]) { if (@$_ == 2) {
			my $ret = _eval_term $$_[1];
			ref $ret eq'JE::LValue' and $ret = get $ret;
			$scope->find_var($$_[0])->set($ret);
		}}
		return;
	}
	if ($type eq 'if') {
		#            2       3          4
		# we have:  expr statement statement?
		my $returned;
		if ($$stm[2]->eval->to_boolean->value) {
			$$stm[3] eq 'empty' or $returned = $$stm[3]->eval;
		}
		else {
			exists $$stm[4]
				&& $$stm[4] ne 'empty'
				and $returned = $$stm[4]->eval;
		}
		defined $returned and $return = $returned;
		return
	}
	if ($type =~ /^(?:do|while|for|switch)\z/) {
		# We have one of the following:
		#
		#  1      2          3          4          5
		# 'do'    statement  expression
		# 'while' expression statement
		# 'for'   expression 'in'       expression statement
		# 'for'   var_decl   'in'       expression statement 
		# 'for'   expression expression expression statement
		# 'for'   var_decl   expression expression statement
 		#
		# In those last two cases, expression may be 'empty'.
		# (See further down for 'switch').

		no warnings 'exiting';

		LOOPS:
		my $returned;
		
		BREAK: {
		if ($type eq 'do') {
			do {
			  CONT: {
			    defined ($returned = ref $$stm[2]
			      ? $$stm[2]->eval : undef)
			    and $return = $returned;
			  }

			  if($_label and
			     !first {$_ eq $_label} @labels) {
			       goto NEXT;
			  }
			  undef $_label;
			} while $$stm[3]->eval->to_boolean->value;
		}
		elsif ($type eq 'while') {
			CONT: while ($$stm[2]->eval->to_boolean->value) {
				defined ($returned = ref $$stm[3]
					? $$stm[3]->eval : undef)
				and $return = $returned;
			}
			continue {
				if($_label and
				   !first {$_ eq $_label} @labels) {
					goto NEXT;
				}
			}
			undef $_label;
		}
		elsif ($type eq 'for' and $$stm[3] eq 'in') {
			my $left_side = $$stm[2];
			if ($left_side->[1] eq 'var') {
				$left_side->eval;
				$left_side = $left_side->[2][0];
				# now contains the identifier
			}
			my $obj = $$stm[4]->eval;
			$obj = $obj->get if ref $obj eq 'JE::LValue';
			ref($obj) =~ /^JE::(?:Undefined|Null)\z/
			 # ~~~ Do we need undef $_label here?
			 and undef $_label, return;
			my @keys = $obj->keys;
			CONT: for(@keys) {
				if($_label and
				   !first {$_ eq $_label} @labels) {
					goto NEXT;
				}
				undef $_label;

				next if not defined $obj->prop($_);	
				# in which case it's been deleted
				
				(ref $left_side ? $left_side->eval :
					$scope->find_var($left_side))
				  ->set(_new JE::String $global, $_);

				defined ($returned = ref $$stm[5]
					? $$stm[5]->eval : undef)
				and $return = $returned;
			}

			# In case 'continue LABEL' is called during the
			# last iteration of the loop
			if($_label and
			   !first {$_ eq $_label} @labels) {
				next CONT;
			}
			undef $_label;

		}
		elsif ($type eq 'for') { # for(;;)
			my $tmp;
			CONT: for (
				$tmp = ref $$stm[2] && $$stm[2]->eval,
				ref $tmp eq 'JE::LValue' && get $tmp;

				ref $$stm[3]
					? $$stm[3]->eval->to_boolean->value
					: 1;

				do{if($_label and
				     !first {$_ eq $_label} @labels) {
				       goto NEXT;
				  }
				  undef $_label;
				},
				$tmp = ref $$stm[4] && $$stm[4]->eval,
				ref $tmp eq 'JE::LValue' && get $tmp
			) {
				defined ($returned = ref $$stm[5]
					? $$stm[5]->eval : undef)
				and $return = $returned;
			}			
		}
		else { # switch
			# $stm->[2] is the parenthesized
			# expression.
			# Each pair of elements thereafter
			# represents one case clause, an expr
			# followed by statements, except for
			# the default clause, which has the
			# string 'default' for its first elem

			
			# Evaluate the expression in the header
			my $given = $$stm[2]->eval;
			$given = get $given if ref $given eq 'JE::LValue';
			
			# Look through the case clauses to see
			# which it matches. At the same time,
			# look for the default clause.

			no strict 'refs';

			my($n, $default) = 1;
			while (($n+=2) < @$stm) {
				if($$stm[$n] eq 'default') {
					$default = $n; next;
				}

				# Execute the statements if we have a match
				if("JE::Code::Expression::in==="->(
					$given, $$stm[$n]->eval
				  )) {
					$n++;
					do {
						$$stm[$n]->eval;
					} while ($n+=2) < @$stm;
					undef $default;
					last;
				}
			} ;

			# If we can't find a case that matches, but we
			# did find a default (and $default was not erased
			# when a case matched)
			if(defined $default) {
				$n = $default +1;
				do { $$stm[$n]->eval }
					while ($n+=2) < @$stm;
			}
		} # switch

		} # end of BREAK


		if(!$_label || first {$_ eq $_label} @labels) {
			undef $_label;
			return;
		} else {
			last BREAK;
		}
		
		NEXT: next CONT;
	}
	if ($type eq 'continue') {
		no warnings 'exiting';
		$_label = exists $$stm[2] ? $$stm[2] : '';
		next CONT;
	}
	if ($type eq 'break') {
		no warnings 'exiting';
		$_label = exists $$stm[2] ? $$stm[2] : '';
		last BREAK;
	}
	if ($type eq 'return') {
		no warnings 'exiting';
		if (exists $$stm[2]) {
			ref ($return = $$stm[2]->eval) eq 'JE::LValue'
			and $return = get $return;
		} else { $return = undef }
		last RETURN;
	}
	if ($type eq 'with') {
		local $scope = bless [
			@$scope, $$stm[2]->eval->to_object
		], 'JE::Scope';
		my $returned = $$stm[3]->eval;
		defined $returned and $return = $returned;
		return;
	}
	if ($type eq 'throw') {
		my $excep;
		if (exists $$stm[2]) {
			ref ($excep = $$stm[2]->eval) eq 'JE::LValue'
			and $excep = get $excep;
		}
		die defined $excep? $excep : $global->undefined;
	}
	if ($type eq 'try') {
		# We have one of the following:
		#   1     2     3     4     5
		# 'try' block ident block       (catch)
		# 'try' block block             (finally)
		# 'try' block ident block block (catch & finally)

		my $result;
		my $propagate;

		eval { # try
			local $return;
			no warnings 'exiting';
			RETURN: {
			BREAK: {
			CONT: {
				$result = $$stm[2]->eval;
				goto SAVERESULT;
			} $propagate = sub{ next CONT }; goto SAVERESULT;
			} $propagate = sub{ last BREAK }; goto SAVERESULT;
			} $propagate = sub{ last RETURN }; goto SAVERESULT;

			SAVERESULT:
			defined $result or $result = $return;
			goto FINALLY;
		};
		# check ref first to avoid the overhead of overloading
		if (ref $@ || $@ ne '' and !ref $$stm[3]) { # catch
			undef $result; # prevent { 3; throw ... } from
			                # returning 3

			# Turn miscellaneous errors into Error objects
			$@ = JE'Code'_objectify_error($@);

			(my $new_obj = new JE::Object $global)
			 ->prop({
				name => $$stm[3],
				value => $@,
				dontdel => 1,
			});
			local $scope = bless [
				@$scope, $new_obj
			], 'JE::Scope';
	
			eval { # in case the catch block ends abruptly
			  local $return;
			  no warnings 'exiting';
			  RETURN: {
			  BREAK: {
			  CONT: {
			    $result = $$stm[4]->eval;
			    goto SAVE;
			  } $propagate = sub{ next CONT }; goto SAVE;
			  } $propagate = sub{ last BREAK }; goto SAVE;
			  } $propagate = sub{ last RETURN }; goto SAVE;

			  SAVE:
			  defined $result or $result = $return;
			  $@ = '';
			}
		}
		# In case the 'finally' block resets $@:
		my $exception = $@;
		FINALLY:
		if ($#$stm == 3 or $#$stm == 5) {
			$$stm[-1]->eval;
		}
		defined $exception and ref $exception || $exception ne ''
			and die $exception;
		$return = $result if defined $result;
		$propagate and &$propagate();
	}
}

sub _create_vars {  # Process var and function declarations
	my $vars = $code->{vars};
	for(@$vars) {
		if(ref) { # function
			# format: [[...], function=> 'name',
			#          [ (params) ], $statements_obj, \@vars ]
			# With optimisation on, the $statements_obj will
			# actually be a code object. 
			$scope->[-1]->delete($$_[2], 1);
			my $new_code_obj;
			if(ref $$_[4] eq 'JE::Code') {
				$new_code_obj = $$_[4]
			}
			else {
			  ($new_code_obj = bless {
			    map+($_=>$code->{$_}),
			      qw/global source file line/
			  }, 'JE::Code')
			  ->{tree} = $$_[4];
			  $new_code_obj->{vars} = $$_[5];
			}
			$scope->new_var($$_[2], new JE::Object::Function {
				scope    => $scope,
				name     => $$_[2],
				argnames => $$_[3],
				function => $new_code_obj
			});
		}
		else {
			$scope->new_var($_);
		}
	}
}




package JE::Code::Expression;

our $VERSION = '0.056';

# B::Deparse showed me how to get these values.
use constant nan => sin 9**9**9;
use constant inf => 9**9**9;

use subs qw'_eval_term';
use POSIX 'fmod';
use Scalar::Util 'tainted';

import JE::Code 'add_line_number';
sub add_line_number;

BEGIN{*T = *JE::Code::T;}


#----------for reference------------#
#sub _to_int {
	# call to_number first
	# then...
	# NaN becomes 0
	# 0 and Infinity remain as they are
	# other nums are rounded towards zero ($_ <=> 0) * floor(abs)
#}

# Note that abs in ECMA-262
#sub _to_uint32 {
	# call to_number, then ...

	# return 0 for Nan, -?inf and 0
	# (round toward zero) % 2 ** 32
#}

#sub _to_int32 {
	# calculate _to_uint32 but subtract 2**32 if the result >= 2**31
#}

#sub _to_uint16 { 
	# just like _to_uint32, except that 2**16 is used instead.
#}


#---------------------------------#

{ # JavaScript operators
  # Note: some operators are not dealt with here, but inside
  #       sub eval.
	no strict 'refs';
	*{'predelete'} = sub {
		ref(my $term = shift) eq 'JE::LValue' or return
			new JE::Boolean $global, 1;
		my $base = $term->base;
		new JE::Boolean $global,
			defined $base ? $base->delete($term->property) : 1;
	};
	*{'prevoid'} = sub {
		my $term = shift;
		$term = get $term while ref $term eq 'JE::LValue';
		return $global->undefined;
	};
	*{'pretypeof'} = sub {
		my $term = shift;
		ref  $term eq 'JE::LValue' and
			ref base $term eq '' and
			return _new JE::String $global, 'undefined';
		_new JE::String $global, typeof $term;
	};
	*{'pre++'} = sub {
		# ~~~ These is supposed to use the same rules
		#     as the + infix op for the actual
		#     addition part. Verify that it does this.
		my $term = shift;
		$term->set(new JE::Number $global,
			get $term->to_number + 1);
	};
	*{'pre--'} = sub {
		# ~~~ These is supposed to use the same rules
		#     as the - infix op for the actual
		#     subtraction part. Verify that it does this.
		my $term = shift;
		$term->set(new JE::Number $global,
			get $term->to_number->value - 1);
	};
	*{'pre+'} = sub {
		shift->to_number;
	};
	*{'pre-'} = sub {
		new JE::Number $global, -shift->to_number->value;
	};
	*{'pre~'} = sub {
		my $num = shift->to_number->value;
		$num = 
			$num != $num || abs($num) == inf  # nan/+-inf
			? 0
			: int($num) % 2**32;

		$num -= 2**32 if $num >= 2**31;

		{ use integer; # for signed bitwise negation
		  $num = ~$num; }
		
		new JE::Number $global, $num;	
	};
	*{'pre!'} = sub {
		new JE::Boolean $global, !shift->to_boolean->value
	};
	*{'in*'} = sub {
		new JE::Number $global,
			shift->to_number->value *
			shift->to_number->value;
	};
	*{'in/'} = sub {
		my($num,$denom) = map to_number $_->value, @_[0,1];
		new JE::Number $global,
			$denom ?
				$num/$denom :
			# Divide by zero:
			$num && $num == $num # not zero or nan
				? $num * inf
				: nan;
	};
	*{'in%'} = sub {
		my($num,$denom) = map to_number $_->value,
			@_[0,1];
		new JE::Number $global,
			$num+1 == $num ? nan :
			$num == $num && abs($denom) == inf ?
				$num :
			fmod $num, $denom;
	};
	*{'in+'} = sub {
		my($x, $y) = @_;
		$x = $x->to_primitive;
		$y = $y->to_primitive;
		if($x->typeof eq 'string' or
		   $y->typeof eq 'string') {
			return _new JE::String $global,
				$x->to_string->value16 .
				$y->to_string->value16;
		}
		return new JE::Number $global,
		                      $x->to_number->value +
		                      $y->to_number->value;
	};
	*{'in-'} = sub {
		new JE::Number $global,
			shift->to_number->value -
			shift->to_number->value;
	};
	*{'in<<'} = sub {
		my $num = shift->to_number->value;
		$num = 
			$num != $num || abs($num) == inf  # nan/+-inf
			? $num = 0
			: int($num) % 2**32;
		$num -= 2**32 if $num >= 2**31;

		my $shift_by = shift->to_number->value;
		$shift_by = 
			$shift_by != $shift_by || abs($shift_by) == inf
			? 0
			: int($shift_by) % 32;

		my $ret = ($num << $shift_by) % 2**32;
		$ret -= 2**32 if $ret >= 2**31;

		new JE::Number $global, $ret;

		# Fails on 64-bit:
		#use integer;
		#new JE::Number $global,
		#	$num << $shift_by;
	};
	*{'in>>'} = sub {
		my $num = shift->to_number->value;
		$num = 
			$num != $num || abs($num) == inf  # nan/+-inf
			? $num = 0
			: int($num) % 2**32;
		$num -= 2**32 if $num >= 2**31;

		my $shift_by = shift->to_number->value;
		$shift_by = 
			$shift_by != $shift_by || abs($shift_by) == inf
			? 0
			: int($shift_by) % 32;

		use integer;
		new JE::Number $global,
			$num >> $shift_by;
	};
	*{'in>>>'} = sub {
		my $num = shift->to_number->value;
		$num = 
			$num != $num || abs($num) == inf  # nan/+-inf
			? $num = 0
			: int($num) % 2**32;

		my $shift_by = shift->to_number->value;
		$shift_by = 
			$shift_by != $shift_by || abs($shift_by) == inf
			? 0
			: int($shift_by) % 32;

		new JE::Number $global,
			$num >> $shift_by;
	};
	*{'in<'} = sub {
		my($x,$y) = map to_primitive $_, @_[0,1];
		new JE::Boolean $global,
			$x->typeof eq 'string' &&
			$y->typeof eq 'string'
			? $x->to_string->value16 lt $y->to_string->value16
			: $x->to_number->[0] <  $y->to_number->[0];
	};
	*{'in>'} = sub {
		my($x,$y) = map to_primitive $_, @_[0,1];
		new JE::Boolean $global,
			$x->typeof eq 'string' &&
			$y->typeof eq 'string'
			? $x->to_string->value16 gt $y->to_string->value16
			: $x->to_number->[0] >  $y->to_number->[0];
	};
	*{'in<='} = sub {
		my($x,$y) = map to_primitive $_, @_[0,1];
		new JE::Boolean $global,
			$x->typeof eq 'string' &&
			$y->typeof eq 'string'
			? $x->to_string->value16 le $y->to_string->value16
			: $x->to_number->[0] <= $y->to_number->[0];
	};
	*{'in>='} = sub {
		my($x,$y) = map to_primitive $_, @_[0,1];
		new JE::Boolean $global,
			$x->typeof eq 'string' &&
			$y->typeof eq 'string'
			? $x->to_string->value16 ge $y->to_string->value16
			: $x->to_number->[0] >= $y->to_number->[0];
	};
	*{'ininstanceof'} = sub {
		my($obj,$func) = @_;
		die new JE::Object::Error::TypeError $global,
			add_line_number "$func is not an object"
			if $func->primitive;

		die new JE::Object::Error::TypeError $global,
			add_line_number "$func is not a function"
			if $func->typeof ne 'function';
		
		return new JE::Boolean $global, 0 if $obj->primitive;

		my $proto_id = $func->prop('prototype');
		!defined $proto_id || $proto_id->primitive and die new
		   JE::Object::Error::TypeError $global,
		   add_line_number "Function $$$func{func_name} has no prototype property";
		$proto_id = $proto_id->id;

		0 while (defined($obj = $obj->prototype)
		         or return new JE::Boolean $global, 0),
			$obj->id ne $proto_id;
		
		new JE::Boolean $global, 1;
	};
	*{'inin'} = sub {
		my($prop,$obj) = @_;
		die new JE::Object::Error::TypeError $global,
		    add_line_number "$obj is not an object"
			if $obj->primitive;
		new JE::Boolean $global, defined $obj->prop($prop);
	};
	*{'in=='} = sub {
		my($x,$y) = @_;
		my($xt,$yt) = (typeof $x, typeof $y);
		my($xi,$yi) = (    id $x,     id $y);
		$xt eq $yt and return new JE::Boolean $global,
			$xi eq $yi && $xi ne 'num:nan';

		$xi eq 'null' and
			return new JE::Boolean $global,
				$yi eq 'undef';
		$xi eq 'undef' and
			return new JE::Boolean $global,
				$yi eq 'null';
		$yi eq 'null' and
			return new JE::Boolean $global,
				$xi eq 'undef';
		$yi eq 'undef' and
			return new JE::Boolean $global,
				$xi eq 'null';

		if($xt eq 'boolean') {
			$x = to_number $x;
			$xt = 'number';
		}
		elsif($yt eq 'boolean') {
			$y = to_number $y;
			$yt = 'number';
		}

		if($xt eq 'string' || $xt eq 'number' and !primitive $y)
			{ $y = to_primitive $y; $yt = typeof $y }
		elsif
		  ($yt eq 'string' || $yt eq 'number' and !primitive $x)
			{ $x = to_primitive $x; $xt = typeof $x }

		($xt eq 'number' and $yt eq 'string' || $yt eq 'number')
		  ||
		($yt eq 'number' and $xt eq 'string' || $xt eq 'number')
		  and
			return new JE::Boolean $global,
			to_number $x->[0] == to_number $y->[0];

		$xt eq 'string' && $yt eq 'string' and 
			return new JE::Boolean $global,
			$x->value16 eq $y->value16;
		
		new JE::Boolean $global, 0;
	};
	*{'in!='} = sub {
		new JE::Boolean $global, !&{'in=='}->[0];
	};
	*{'in==='} = sub {
		my($x,$y) = @_;
		my($xi,$yi) = (    id $x,     id $y);
		return new JE::Boolean $global,
			$xi eq $yi && $xi ne 'num:nan';
	};
	*{'in!=='} = sub {
		new JE::Boolean $global, !&{'in==='}->[0];
	};

	# ~~~ These three bitwise operators are slower than molasses. There
	# must be some way to speed them up, but I'm not sure the research
	# is worth it. Does anyone actually use these in JS?
	*{'in&'} = sub {
		my $num = shift->to_number->[0];
		$num = 
			$num != $num || abs($num) == inf
			? 0
			: int($num) % 2**32;
		$num -= 2**32 if $num >= 2**31;

		my $num2 = shift->to_number->[0];
		$num2 = 
			$num2 != $num2 || abs($num2) == inf
			? 0
			: int($num2) % 2**32;
		$num2 -= 2**32 if $num2 >= 2**31;

		use integer;
		new JE::Number $global,
			$num & $num2;
	};
	*{'in^'} = sub {
		my $num = shift->to_number->[0];
		$num = 
			$num != $num || abs($num) == inf
			? 0
			: int($num) % 2**32;
		$num -= 2**32 if $num >= 2**31;

		my $num2 = shift->to_number->[0];
		$num2 = 
			$num2 != $num2 || abs($num2) == inf
			? 0
			: int($num2) % 2**32;
		$num2 -= 2**32 if $num2 >= 2**31;

		use integer;
		new JE::Number $global,
			$num ^ $num2;
	};
	*{'in|'} = sub {
		my $num = shift->to_number->[0];
		$num = 
			$num != $num || abs($num) == inf
			? 0
			: int($num) % 2**32;
		$num -= 2**32 if $num >= 2**31;

		my $num2 = shift->to_number->[0];
		$num2 = 
			$num2 != $num2 || abs($num2) == inf
			? 0
			: int($num2) % 2**32;
		$num2 -= 2**32 if $num2 >= 2**31;

		use integer;
		new JE::Number $global,
			$num | $num2;
	};
}

=begin for-me

Types of expressions:

'new' term args?

'member/call' term ( subscript | args) *  

'postfix' term op

'hash' term*

'array' term? (comma term?)*

'prefix' op+ term

'lassoc' term (op term)*

'assign' term (op term)* (term term)?
	(the last two terms are the 2nd and 3rd terms of ? :

'expr' term*
	(commas are omitted from the array)

'function' ident? params statements

=end for-me

=cut


# Note: each expression object is an array ref. The elems are:
# [0] - an array ref containing
#       [0] - the starting position in the source code and
#       [1] - the ending position
# [1] - the type of expression
# [2..$#] - the various terms/tokens that make up the expr

sub eval {  # evalate (sub)expression
	no warnings 'exiting';
	++ $ops>$counting and last JE_Code_OP  if $counting;
	
	my $expr = shift;

	my $type = $$expr[1];
	my @labels;

	$pos = $$expr[0][0];

	if ($type eq 'expr') {
		my $result;
		if(@$expr == 3) { # no comma
			return _eval_term $$expr[-1];
		}
		else { # comma op
			for (@$expr[2..$#$expr-1]) {
				$result = _eval_term $_ ;
				get $result if ref $result eq 'JE::LValue';
			}
			$result = _eval_term $$expr[-1] ;
			return ref $result eq 'JE::LValue' ? get $result
				: $result;
		}
	}
	if ($type eq 'assign') {
		my @copy = \(@$expr[2..$#$expr]);
		# Evaluation is done left-first in JS, unlike in
		# Perl, so a = b = c is evaluated in this order:
		#  - evaluate a
		#  - evaluate b
		#  - evaluate c
		#  - assign c to b
		#  - assign b to a

		# Check first to see whether we have the terms
		# of a ? : at the end:
		my @qc_terms = @copy >= 3 && (
				ref ${$copy[-2]} # avoid stringification
				|| ${$copy[-2]} =~ /^(?:[tfu]\z|[si0-9])/
		)
			? (pop @copy, pop @copy) : ();
			# @qc_terms is now in reverse order

		# Make a list of operands, evalling each
		my @terms = _eval_term ${shift @copy};
		my @ops;
		while(@copy) {
			push @ops, ${shift @copy};
			push @terms, _eval_term ${shift @copy};
		}

		my $val = pop @terms;		

		# Now apply ? : if it's there
		@qc_terms and $val = _eval_term
			${$qc_terms[$val->to_boolean->[0]]};

		for (reverse @ops) {
			no strict 'refs';
			length > 1 and $val =
				&{'in'.substr $_,0,-1}(
					$terms[-1], $val
				);
			$val = $val->get if ref $val eq 'JE::LValue'; 
			T and tainted $taint and $val->can('taint')
				and $val = taint $val $taint;
			eval { (pop @terms)->set($val) };
			if (my $err = $@) {
				die $err if UNIVERSAL::isa($err, 'JE::Object::Error');
				die new JE::Object::Error::ReferenceError
					$global, add_line_number "Cannot assign to a non-lvalue";
			}
			# ~~~ This needs to check whether it was an error
			#     other than 'Can't locate object method "set"
			#     since store handlers can thrown other errors.
			
		}
		if(!@ops) { # If we only have ? : and no assignment
			$val = $val->get if ref $val eq 'JE::LValue'; 
		}
		return $val;
	}
	if($type eq 'lassoc') { # left-associative
		my @copy = \(@$expr[2..$#$expr]);
		my $result = _eval_term ${shift @copy};
		while(@copy) {
			no strict 'refs';
			# We have to deal with || && here for the sake of
			# short-circuiting
			my $op = ${$copy[0]};
			if ($op eq '&&') {
				$result = _eval_term(${$copy[1]}) if
					$result->to_boolean->[0];
				$result = $result->get
					if ref $result eq 'JE::LValue'; 
			}
			elsif($op eq '||') {
				$result = _eval_term(${$copy[1]}) unless
					$result->to_boolean->[0];
				$result = $result->get
					if ref $result eq 'JE::LValue'; 
			}
			else {
				$result = $result->get
					if ref $result eq 'JE::LValue'; 
				$result = &{"in$op"}(
					$result, _eval_term ${$copy[1]}
				);
			}
			splice @copy, 0, 2; # double shift
		}
		return $result;
	}
	if ($type eq 'prefix') {
		# $$expr[1]     -- 'prefix'
		# @$expr[2..-2] -- prefix ops
		# $$expr[-1]    -- operand
		my $term = _eval_term $$expr[-1];

		no strict 'refs';
		$term = &{"pre$_"}($term) for reverse @$expr[2..@$expr-2];
		return $term;
	}
	if ($type eq 'postfix') {
		# ~~~ These are supposed to use the same rules
		#     as the + and - infix ops for the actual
		#     addition part. Verify that they do this.

		my $ret = (my $term = _eval_term $$expr[2])
			->to_number;
		$term->set(new JE::Number $global,
			$ret->value + (-1,1)[$$expr[3] eq '++']);
		return $ret;
	}
	if ($type eq 'new') {
		return _eval_term($$expr[2])->construct(
			@$expr == 4
			? T && tainted $taint
				? map $_->can('taint') ?taint $_ $taint:$_,
					$$expr[-1]->list
				: $$expr[-1]->list
			: ()
		);
	}
	if($type eq 'member/call') {
		my $obj = _eval_term $$expr[2];
		for (@$expr[3..$#$expr]) {
			if(ref eq 'JE::Code::Subscript') {
				$obj = get $obj
					if ref $obj eq 'JE::LValue';
				$obj = new JE::LValue $obj, $_->str_val;
			}
			else {
				$obj = $obj->call(
					T && tainted $taint
					? map $_->can('taint')
						? taint $_ $taint
						: $_,
					  $_->list
					: $_->list
				);
				# If $obj is an lvalue,
				# JE::LValue::call will make
				# the lvalue's base object the 'this'
				# value. Otherwise,
				# JE::Object::Function::call 
				# will make the
				# global object the 'this' value.
			}
			# ~~~ need some error-checking
		}
		return $obj; # which may be an lvalue
	}
	if($type eq 'array') {
		my @ary;
		for (2..$#$expr) {
			if(ref $$expr[$_] eq 'comma') {
				ref $$expr[$_-1] eq 'comma' || $_ == 2
				and ++$#ary
			}
			else {
				push @ary, _eval_term $$expr[$_];
				$ary[-1] = $ary[-1]->get
				 if ref $ary[-1] eq 'JE::LValue';
			}
		}

		my $ary = new JE::Object::Array $global;
		$$$ary{array} = \@ary; # sticking it in like this
		                       # makes 'undef' elements non-
		                       # existent, rather
		                       # than undefined
		return $ary;
	}
	if($type eq 'hash') {
		my $obj = new JE::Object $global;
		local @_ = \(@$expr[2..$#$expr]);
		my (@keys, $key, $value);
		while(@_) { # I have to loop through them to keep
		            # the order.
			$key = ${+shift};
			$value = _eval_term ${shift;};
			$value = get $value if ref $value eq 'JE::LValue';
			$obj->prop($key, $value);
		}
		return $obj;
	}
	if ($type eq 'func') {
		# format: [[...], function=> 'name',
		#          [ params ], $statements_obj, \@vars] 
		#     or: [[...], function =>
		#          [ params ], $statements_obj, \@vars] 
		my($name,$params,$statements) = ref $$expr[2] ?
			(undef, @$expr[2,3]) : @$expr[2..4];
		my $func_scope = $name
			? bless([@$scope, my $obj=new JE::Object $global], 
				'JE::Scope')
			: $scope;
		(my $new_code_obj = bless {
			map+($_=>$code->{$_}),qw/global source file line/
		}, 'JE::Code')
		 ->{tree} = $statements;
		$new_code_obj->{vars} = $$expr[-1];
		my $f = new JE::Object::Function {
			scope    => $func_scope,
			defined $name ? (name => $name) : (),
			argnames => $params,
			function => $new_code_obj,
		};
		if($name) {
			$obj->prop({
				name    => $name,
				value   => $f,
				readonly => 1,
				dontdel  => 1,
			});
		}
		return $f;
	}
}
sub _eval_term {
	my $term = $_[0];

	return $term->eval if ref $term eq 'JE::Code::Expression';

	ref $term     ? ref $term eq 'ARRAY'
	                ? ( require JE::Object::RegExp,
	                    return JE::Object::RegExp->new(
	                      $global, @$term
	                    ) )
	                : $term :
	$term eq'this'? $this :
	$term =~ /^s/ ? $_[0] = JE::String->_new($global,substr $term,1) :
	$term =~ /^i/ ? $scope->find_var(substr $term,1) :
	$term eq 't'  ? $global->true :
	$term eq 'f'  ? $global->false :
	$term eq 'n'  ? $global->null :
	               ($_[0] = JE::Number->new($global,$term));
}




package JE::Code::Subscript;

our $VERSION = '0.056';

sub str_val {
	my $val = (my $self = shift)->[1];
	ref $val ? ''.$val->eval : $val; 
}




package JE::Code::Arguments;

our $VERSION = '0.056';

sub list {
	my $self = shift;

	#  I can't use map here, because this method is called from within
	#  a foreach loop,  and an exception might be thrown from  within
	# _eval_term, which has strange effects in perl 5.8.x (see perl
	#  bug #24254).

if(1) {
	my @result;
	for(@$self[1..$#$self]) {
		my $val = JE::Code::Expression::_eval_term($_);
		push @result, ref $val eq 'JE::LValue' ? $val->get : $val
	}
	@result;

}else{ # original code
	map { my $val = JE::Code::Expression::_eval_term($_);
	      ref $val eq 'JE::LValue' ? $val->get : $val }
	    @$self[1..$#$self];
}
}




1;
__END__


=head1 NAME

JE::Code - ECMAScript parser and code executor for JE

=head1 SYNOPSIS

  use JE;

  $j = new JE;

  $code = $j->compile('1+1'); # returns a JE::Code object

  $code->execute;

=head1 METHODS

=over 4

=item $code->execute($this, $scope, $code_type);

The C<execute> method of a parse tree executes it. All the arguments are
optional.

The first argument
will be the 'this' value of the execution context. The global object will
be used if it is omitted or undef.

The second argument is the scope chain.
A scope chain containing just the global object will be used if it is
omitted or undef.

The third arg indicates the type of code. B<0> or B<undef> indicates global 
code.
B<1> means eval code (code called by I<JavaScript's> C<eval> function, 
which
has nothing to do with JE's C<eval> method, which runs global code).
Variables created with C<var> and function declarations 
inside
eval code can be deleted, whereas such variables in global or function
code cannot. A value of B<2> means function code, which requires an 
explicit C<return>
statement for a value to be returned.

If an error occurs, C<undef> will be returned and C<$@> will contain the
error message. If no error occurs, C<$@> will be a null string.

=item $code->set_global( $thing )

You can transfer a JE::Code object to another JavaScript environment by
setting the global object this way. You can also set it to C<undef>, if,
for instance, you want to serialise the compiled code without serialising
the entire JS environment. If you do that, you'll need to set the global
object again before you can use the code object.

=back

=head1 FUNCTIONS

=over 4

=item JE::Code::add_line_number($message, $code_object, $position)

B<WARNING:> The parameter list is still subject to change.

This routine append a string such as 'at file, line 76.' to the error 
message passed to it,  
unless it ends with a line break already.

C<$code_object> is a code object as returned by JE's or JE::Parser's
C<parse> method. If it is omitted, the current value of C<$JE::Code::code>
will be used (this is set while JS code is running). If C<$JE::Code::code>
turns out to be undefined, then C<$message> will be returned unchanged
(B<this is subject to change>; later I might make it use Carp to add a Perl 
file and line number).

C<$position> is the position within the source code, which will be used to
determine the line number. If this is omitted, $JE::Code::pos will be used.

=begin private

=item JE::Code::parse($global, $src, $file, $line)

Please don't use this. It is for internal use. It might get renamed,
or change its behaviour without notice (which has happened several times).
Use JE's C<compile> and C<eval> methods instead.

This function returns a JE::Code object.

C<$global> is a global object. C<$src> is the source code. C<$file> is a
filename, or any name you want to give the code. C<$line> is a line number.

=end private

=back

=head1 EXPORTS

C<add_line_number> can optionally be exported.

=head1 SEE ALSO

=over 4

L<JE>

=cut