/****************************************************************************
** $Id: qsnodes.cpp  beta1   edited Dec 13 18:37 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "qsnodes.h"
#include "qsclass.h"
#include "qsenv.h"
#include "qsengine.h"
#include "qslexer.h"
#include "qsinternal.h"
#include "qsoperations.h"
#include "qsobject_object.h"
#include "qsregexp_object.h"
#include "qsdebugger.h"
#include "qsfuncref.h"

#include <math.h>
#include <assert.h>

using namespace QS;

#ifdef QSDEBUGGER
#define QSBREAKPOINT if( !hitStatement() ) { env->setExecutionMode( QSEnv::Normal ); return QSObject(); }
#define QSABORTPOINT if( !abortStatement() ) { env->setExecutionMode( QSEnv::Normal ); return QSObject(); }
#else
#define QSBREAKPOINT
#define QSABORTPOINT
#endif

#if 0
#define QS_NODES_DEBUG( x ) qDebug( QString("qsnodes.cpp:%1 --> ").arg(lineNo()) + x );
#else
#define QS_NODES_DEBUG( x )
#endif

int   QSNode::nodeCount = 0;
QSNode* QSNode::first = 0;

QSNode::QSNode()
{
    assert( QSLexer::curr() );
    line = QSLexer::curr()->lineNo();
    nodeCount++;

    // create a list of allocated objects. Makes
    // deleting (even after a parse error) quite easy
    next = first;
    prev = 0;
    if ( first )
	first->prev = this;
    first = this;
}

QSNode::~QSNode()
{
    if ( next )
	next->prev = prev;
    if ( prev )
	prev->next = next;
    nodeCount--;
}

QSObject QSNode::evaluate( QSEnv *env )
{
    //    qWarning( "QSNode::evaluate()" );
    return rhs( env );
}

QSObject QSNode::rhs( QSEnv *env ) const
{
    return ((QSNode*)this)->lhs( env ).dereference();
}

QSReference QSNode::lhs( QSEnv *env )
{
    // qWarning( "QSNode::lhs( QSEnv * )" );
    return QSReference( rhs( env ) ); // no real reference
}

void QSNode::deleteAllNodes()
{
    QSNode *tmp, *n = first;

    while ( (tmp = n) ) {
	n = n->next;
	delete tmp;
    }
    first = 0L;
}

QSObject QSNode::throwError( QSEnv *env, ErrorType e, const char *msg ) const
{
    return env->throwError( e, msg, lineNo() );
}

#ifdef QSDEBUGGER
void QSStatementNode::setLoc( int line0, int line1 )
{
    l0 = line0;
    l1 = line1;
    sid = QSEngineImp::current()->sourceId();
}

bool QSStatementNode::hitStatement()
{
    if ( QSEngineImp::current()->debugger() ) {
	Debugger *deb = QSEngineImp::current()->debugger();
	return deb->hit( firstLine(), breakPoint ) &&
	    deb->mode() != Debugger::Stop;
    } else {
	return TRUE;
    }
}

// return TRUE if the debugger wants us to stop at this point
bool QSStatementNode::abortStatement()
{
    if ( QSEngineImp::current()->debugger() &&
	 QSEngineImp::current()->debugger()->mode() == Debugger::Stop )
	return TRUE;

    return FALSE;
}

bool QSNode::setBreakpoint( QSNode *firstNode, int id, int line, bool set )
{
    while ( firstNode ) {
	if (firstNode->setBreakpoint( id, line, set ) && line >= 0) // l<0 for all
	    return TRUE;
	firstNode = firstNode->next;
    }
    return FALSE;
}

/**
 * Try to set or delete a breakpoint depending on the value of set.
 * The call will return TRUE if successful, i.e. if line is inside
 * of the statement's range. Additionally, a breakpoint had to
 * be set already if you tried to delete with set=FALSE.
 */
bool QSStatementNode::setBreakpoint( int id, int line, bool set )
{
    // in our source unit and line range ?
    if ( id != sid || (( line < l0 || line > l1 ) && line >= 0) )
	return FALSE;

    if ( !set && !breakPoint )
	return FALSE;

    breakPoint = set;
    return TRUE;
}
#endif

// Completion QSStatementNode::errorCompletion()
// {
//     QSEnv *env = QSEngine::current()->env();
//     QSObject ex = env->exception();
//     assert( ex.isValid() );
//     int l = ex.get( "line" ).toInteger();
//     // no real line number set ? the one of this node is better than none.
//     if ( l == -1 ) {
// 	ex.put( "line", QSNumber( lineNo() ) );
// 	env->setException( ex );
//     }
//     return Completion( Throw, ex );
// }

QSObject QSNullNode::rhs( QSEnv * ) const
{
    return QSNull();
}

QSObject QSBooleanNode::rhs( QSEnv * ) const
{
    return QSBoolean( value );
}

QSObject QSNumberNode::rhs( QSEnv * ) const
{
    return QSNumber( value );
}

QSObject QSStringNode::rhs( QSEnv * ) const
{
    return QSString( value );
}

QSObject QSRegExpNode::rhs( QSEnv *env ) const
{
    QSList list;
    QSString p( pattern );
    QSString f( flags );
    list.append( p );
    list.append( f );

    return env->regexpClass()->construct( list );
}

// ECMA 11.1.1
QSObject QSThisNode::rhs( QSEnv * env ) const
{
    return env->thisValue();
}

QSResolveNode::~QSResolveNode()
{
    delete info;
    info = 0;
}

QSObject QSResolveNode::rhs( QSEnv * env ) const
{
    QS_NODES_DEBUG( "QSResolveNode::rhs(): " + ident );
    if( info ) { // Using direct lookup if possible.
	return env->getValueDirect( info->member.index(), info->level );
    }
    QSObject o = env->resolveValue( ident );
    if ( o.isValid() )
	return o;
    else
	return QSUndefined();
}

QSReference QSResolveNode::lhs( QSEnv * env )
{
    QS_NODES_DEBUG( "QSResolveNode::lhs(): " + ident );
    if( info ) { // Use direct lookup if possible.
	QSObject base = env->scopeObjectAt( info->level );
	QSReference ref( base, info->member, base.objectType() );
	QS_NODES_DEBUG( ( ref.isReference() ? " -> found" : " -> not found" ) );
	return ref;
    }
    const ScopeChain scope = env->scope();
    ScopeChain::const_iterator it = scope.begin();
    int offset;
    QSMember mem;
    while( it!=scope.end() ) {
	offset = 0;
	const QSClass * cl = (*it).resolveMember( ident, &mem, (*it).objectType(), &offset );
	if( cl && mem.type()!=QSMember::Identifier ) {
	    while( offset-- )
		it++;
	    Q_ASSERT( (*it).isValid() );
	    return QSReference( *it, mem, cl );
	}
	it++;
    }

    // identifier not found
    QString msg = QString( "Use of undefined variable %1" ).arg( ident );
    QSEngineImp::current()->interpreter()->warn( msg, lineNo() );
    mem.setType( QSMember::Identifier );
    mem.setName( ident );
//     qDebug( "Unable to find '" + ident + "' in scope" );
    // return an "undefined" reference
    QS_NODES_DEBUG( " -> not found, dynamic Identifier" );

    return QSReference( env->globalObject(), mem, env->globalClass() );
}

// ECMA 11.1.4
QSObject QSArrayNode::rhs( QSEnv *env ) const
{
    QSObject array;
    int length;
    int elisionLen = elision ? elision->rhs( env ).toInt32() : 0;

    if ( element ) {
	array = element->rhs( env );
	length = opt ? array.get( "length" ).toInt32() : 0;
    } else {
	array = QSArray();
	length = 0;
    }

    if ( opt )
	array.put( "length", QSNumber( elisionLen+length ),
		   DontEnum | DontDelete );

    return array;
}

// ECMA 11.1.4
QSObject QSElementNode::rhs( QSEnv *env ) const
{
    QSObject array, val;
    int length = 0;
    int elisionLen = elision ? elision->rhs( env ).toInt32() : 0;

    if ( list ) {
	array = list->rhs( env );
	val = node->rhs( env );
	length = array.get( "length" ).toInt32();
    } else {
	array = QSArray();
	val = node->rhs( env );
    }

    array.put( QSString::from( elisionLen + length ), val );

    return array;
}

// ECMA 11.1.4
QSObject QSElisionNode::rhs( QSEnv *env ) const
{
    if ( elision )
	return QSNumber( elision->rhs( env ).toNumber() + 1.0 );
    else
	return QSNumber( 1 );
}

// ECMA 11.1.5
QSObject QSObjectLiteralNode::rhs( QSEnv *env ) const
{
    if ( list )
	return list->rhs( env );

    return env->objectClass()->construct();
}

// ECMA 11.1.5
QSObject QSPropertyValueNode::rhs( QSEnv *env ) const
{
    QSObject obj;
    if ( list )
	obj = list->rhs( env );
    else
	obj = env->objectClass()->construct();
    QSObject n = name->rhs( env );
    QSObject v = assign->rhs( env );

    obj.put( n.toString(), v );

    return obj;
}

// ECMA 11.1.5
QSObject QSPropertyNode::rhs( QSEnv * ) const
{
    QSObject s;

    if ( str.isNull() ) {
	s = QSString( QSString::from(numeric) );
    } else
	s = QSString( str );

    return s;
}

// ECMA 11.1.6
QSObject QSGroupNode::rhs( QSEnv *env ) const
{
    return group->rhs( env );
}

QSReference QSAccessorNode1::lhs( QSEnv *env )
{
    QSObject v1 = expr1->rhs( env );
    QSObject v2 = expr2->rhs( env );
    QString s = v2.toString();
    QSMember mem;
    int offset = 0;
    const QSClass *cl = v1.resolveMember( s, &mem, v1.objectType(), &offset );
    Q_ASSERT( !offset );
    if ( !mem.isDefined() )
	mem.setWritable( FALSE );
    return QSReference( v1, mem, cl );
}

QSObject QSAccessorNode1::rhs( QSEnv *env ) const
{
    QSObject v1 = expr1->rhs( env );
    QSObject v2 = expr2->rhs( env );

    QString s = v2.toString();
    QSMember mem;
    int offset = 0;
    const QSClass *cl = v1.resolveMember( s, &mem, v1.objectType(), &offset );
    Q_ASSERT( !offset );
    if ( cl && mem.isDefined() )
	return cl->fetchValue( &v1, mem );
    else
	return QSUndefined();
}

QSReference QSAccessorNode2::lhs( QSEnv *env )
{
    QSObject v = expr->rhs( env );
    QS_NODES_DEBUG( "QSAccessorNode2::rhs() " + v.toString() + ", " + ident );
    QSMember mem;
    int offset = 0;
    const QSClass *cl = v.resolveMember( ident, &mem, v.objectType(), &offset );
    Q_ASSERT( !offset );
    if ( !mem.isDefined() )
	mem.setWritable( FALSE );
    return QSReference( v, mem, cl );
}

QSObject QSAccessorNode2::rhs( QSEnv *env ) const
{
    QSObject v = expr->rhs( env );
    QS_NODES_DEBUG( "QSAccessorNode2::rhs() " + v.toString() + ", " + ident );
    QSMember mem;
    int offset = 0;
    const QSClass *cl = v.resolveMember( ident, &mem, v.objectType(), &offset );
    Q_ASSERT( !offset );
    if ( cl && mem.isDefined() )
	return cl->fetchValue( &v, mem );
    else
	return QSUndefined();
}

QSObject QSNewExprNode::rhs( QSEnv *env ) const
{
    // ### ???
    return ((QSNode*)this)->evaluate( env );
}

// ECMA 11.2.2
QSObject QSNewExprNode::evaluate( QSEnv *env )
{
    QSObject v = expr->rhs( env );

    QSList *argList = args ? args->evaluateList( env ) : 0;

    const QSClass *cl = v.objectType();
    if ( cl != env->typeClass() ) {
	delete argList;
	return throwError( env, TypeError, "Expression is no type." );
    }

    if ( !argList )
	argList = new QSList;

    QSObject res = v.classValue()->construct( *argList );

    delete argList;

    return res;
}

// ECMA 11.2.3
QSObject QSFunctionCallNode::rhs( QSEnv *env ) const
{
    QS_NODES_DEBUG( "QSFunctionCallNode::rhs()" );
    QSReference ref = expr->lhs( env );

    QSList *argList = args->evaluateList( env );

    // bail out on error
    if ( env->isExceptionMode() ) {
	delete argList;
	return QSUndefined();
    }

    QSObject base = ref.base();
    const QSClass *cl = base.objectType();
    QSMember mem = ref.member();

    if( cl==env->funcRefClass() ) {
	// ### for the odd construction '( new Function(...))() ' case, a better solution..
    } else if( !mem.isDefined() ) {
	delete argList;
	return env->throwError( TypeError,
				"Trying to invoke an undefined function",
				lineNo() );
    } else if( !mem.isExecutable() ) {
	base = cl->fetchValue( &base, mem );
	cl = base.objectType();
	if( !cl->isExecutable() ) {
	    delete argList;
	    return env->throwError( TypeError,
				    "Trying to invoke an non invokable object",
				    lineNo() );
	}
	QS_NODES_DEBUG( "QSFunctionCallNode::rhs(): Execute through member" );
    }

#if QS_MAX_STACK > 0
  static int depth = 0; // sum of all concurrent interpreters
  if ( ++depth > QS_MAX_STACK ) {
    qDebug( "Exceeded maximum function call depth %d", QS_MAX_STACK );
    delete argList;
    return env->throwError( RangeError, "Exceeded maximum call stack size.", lineNo() );
  }
#endif

#ifdef QSDEBUGGER
    steppingInto( TRUE );
#endif

    QS_NODES_DEBUG( "QSFunctionCallNode::rhs(): " + base.objectType()->identifier() + "." + mem.name() );

    QSObject result = base.invoke( mem, *argList );

#ifdef QSDEBUGGER
    steppingInto( FALSE );
#endif

    --depth;

    delete argList;
    if( env->isReturnValueMode() )
	env->setExecutionMode( QSEnv::Normal );
    return result;
}

#ifdef QSDEBUGGER
void QSFunctionCallNode::steppingInto( bool in ) const
{
    Debugger *dbg = QSEngineImp::current()->debugger();
    if ( !dbg )
	return;
    if (in) {
	// before entering function. Don't step inside if 'Next' is chosen.
	((QSFunctionCallNode*)this)->previousMode = dbg->mode();
	if ( previousMode == Debugger::Next )
	    dbg->setMode( Debugger::Continue );
    } else {
	// restore mode after leaving function
	dbg->setMode( previousMode);
    }
}
#endif

QSObject QSArgumentsNode::rhs( QSEnv * ) const
{
    assert( 0 );
    return QSObject(); // dummy, see evaluateList()
}

// ECMA 11.2.4
QSList* QSArgumentsNode::evaluateList( QSEnv *env )
{
    if ( !list )
	return new QSList();

    return list->evaluateList( env );
}

QSObject QSArgumentListNode::rhs( QSEnv * ) const
{
    assert( 0 );
    return QSObject(); // dummy, see evaluateList()
}

// ECMA 11.2.4
QSList* QSArgumentListNode::evaluateList( QSEnv *env )
{
    QSObject v = expr->rhs( env );

    if ( !list ) {
	QSList *l = new QSList();
	l->append( v );
	return l;
    }

    QSList *l = list->evaluateList( env );
    l->append( v );

    return l;
}

// ECMA 11.8
QSObject QSRelationalNode::rhs( QSEnv *env ) const
{
    QSObject v1 = expr1->rhs( env );
    QSObject v2 = expr2->rhs( env );

    bool b = FALSE;
    if ( oper == OpLess || oper == OpGreaterEq ) {
	QSCompareResult r = v1.compareTo( v2 );
	if( r==CompareUndefined )
	    b = FALSE;
	else
	    b = r==CompareLess ? oper==OpLess : oper==OpGreaterEq ;

    } else if ( oper == OpGreater || oper == OpLessEq ) {
	QSCompareResult r = v1.compareTo( v2 );
	if( r==CompareUndefined )
	    b = FALSE;
	else
	    b = r==CompareGreater ? oper==OpGreater : oper==OpLessEq ;

    } else if ( oper == OpIs || oper == OpInstanceOf ) {
	// ### emit runtime warning about deprecated instanceof operator ?
	// ### without prototype support it doesn't do the right thing anyway
	if( v2.objectType() != env->typeClass() ) {
	    return throwError( env, TypeError,
			       "Right side of operator 'is' is not a type" );
	}
	return QSBoolean( v1.isA( v2.classValue() ) );
    } else if ( oper == OpIn ) {
	/* Is all of this OK for host objects? */
	if ( !v2.isObject() )
	    return throwError( env, TypeError,
			       "Shift expression not an object into IN expression." );
	b = v2.hasProperty( v1.toString() );
    }
    return QSBoolean( b );
}

// ECMA 11.9
QSObject QSEqualNode::rhs( QSEnv *env ) const
{
    QSObject v1 = expr1->rhs( env );
    QSObject v2 = expr2->rhs( env );

    bool result;
    if ( oper == OpEqEq || oper == OpNotEq ) {
	// == and !=
	bool eq = v1.equals( v2 );
	result = oper == OpEqEq ? eq : !eq ;
    } else {
	// === and !==

	if( v1.objectType() == v2.objectType() ) {
	    bool eq = v1.equals( v2 );
	    result = oper == OpStrEq ? eq : !eq ;
	} else {
	    result = false;
	}
    }
    return QSBoolean( result );
}

// ECMA 11.10
QSObject QSBitOperNode::rhs( QSEnv *env ) const
{
    QSObject v1 = expr1->rhs( env );
    QSObject v2 = expr2->rhs( env );
    int i1 = v1.toInt32();
    int i2 = v2.toInt32();
    int result;
    if ( oper == OpBitAnd )
	result = i1 & i2;
    else if ( oper == OpBitXOr )
	result = i1 ^ i2;
    else
	result = i1 | i2;

    return QSNumber( result );
}

// ECMA 11.11
QSObject QSBinaryLogicalNode::rhs( QSEnv *env ) const
{
    QSObject v1 = expr1->rhs( env );

    bool b1 = v1.toBoolean();
    if ( (!b1 && oper == OpAnd) || (b1 && oper == OpOr) )
	return v1;

    QSObject v2 = expr2->rhs( env );

    return v2;
}

// ECMA 11.12
QSObject QSConditionalNode::rhs( QSEnv *env ) const
{
    QSObject v = logical->rhs( env );
    bool b = v.toBoolean();

    return b ? expr1->rhs( env ) : expr2->rhs( env );
}

// ECMA 11.13
QSObject QSAssignNode::rhs( QSEnv *env ) const
{
    QS_NODES_DEBUG( "QSAssignNode::rhs()" );
    QSObject v2 = expr->rhs( env );
    if( env->isExceptionMode() )
	return QSUndefined();
    QSObject v;
    QSReference ref;
    ErrorType err;
    if ( oper == OpEqual ) {
	ref = left->lhs( env );
	if ( !ref.isWritable() )
	    return env->throwError( ReferenceError,
				    "Left hand side value is not writable",
				    lineNo() );
	ref.assign( v2 );
	return v2;
    } else {
	ref = left->lhs( env );
	if ( !ref.isWritable() )
	    return env->throwError( ReferenceError,
				    "Left hand side value is not writable",
				    lineNo() );
	QSObject v1 = ref.dereference();
	int i1 = v1.toInt32();
	int i2 = v2.toInt32();
	uint ui;
	switch ( oper ) {
	case OpMultEq:
	    v = mult( v1, v2, '*' );
	    break;
	case OpDivEq:
	    v = mult( v1, v2, '/' );
	    break;
	case OpPlusEq:
	    v = add( v1, v2, '+' );
	    break;
	case OpMinusEq:
	    v = add( v1, v2, '-' );
	    break;
	case OpLShift:
	    v = QSNumber( i1 <<= i2 );
	    break;
	case OpRShift:
	    v = QSNumber( i1 >>= i2 );
	    break;
	case OpURShift:
	    ui = v1.toUInt32();
	    v = QSNumber( ui >>= i2 );
	    break;
	case OpAndEq:
	    v = QSNumber( i1 &= i2 );
	    break;
	case OpXOrEq:
	    v = QSNumber( i1 ^= i2 );
	    break;
	case OpOrEq:
	    v = QSNumber( i1 |= i2 );
	    break;
	case OpModEq:
	    v = QSNumber( i1 %= i2 );
	    break;
	default:
	    v = QSUndefined();
	}
    };

    err = NoError; ref.assign( v );
    if ( err == NoError )
	return v;
    else
	return throwError( env, err, "Invalid reference." );
}

// ECMA 11.3
QSObject QSPostfixNode::rhs( QSEnv *env ) const
{
    QSReference ref = expr->lhs( env );
    if ( !ref.isWritable() )
	return throwError( env, ReferenceError,
			   "Left hand side value is not writable" );
    QSObject v = ref.dereference();
    double n = v.toNumber();
    double newValue = ( oper == OpPlusPlus ) ? n + 1 : n - 1;
    ref.assign( QSNumber( newValue ) );

    return QSNumber( n );
}

// ECMA 11.4.1
QSObject QSDeleteNode::rhs( QSEnv *env ) const
{
    QSReference ref = expr->lhs( env );
    return QSBoolean( ref.deleteProperty() );
}

// ECMA 11.4.2
QSObject QSVoidNode::rhs( QSEnv *env ) const
{
    (void) expr->rhs( env );

    return QSUndefined();
}

// ECMA 11.4.3
QSObject QSTypeOfNode::rhs( QSEnv *env ) const
{
    QString s;
    QSReference ref = expr->lhs( env );
    QSObject v;
    if ( ref.isReference() ) {
	if ( !ref.isDefined() )
	    return QSString( "undefined" );
	v = ref.dereference();
    } else {
	v = ref.base();
    }

    switch ( v.type() )
	{
	case UndefinedType:
	    s = "undefined";
	    break;
	case NullType:
	    s = "object";
	    break;
	case BooleanType:
	    s = "boolean";
	    break;
	case NumberType:
	    s = "number";
	    break;
	case StringType:
	    s = "string";
	    break;
	default:
	    if ( v.isExecutable() )
		s = "function";
	    else
		s = "object";
	    break;
	}

    return QSString( s );
}

// ECMA 11.4.4 and 11.4.5
QSObject QSPrefixNode::rhs( QSEnv *env ) const
{
    QSReference ref = expr->lhs( env );
    if ( !ref.isWritable() )
	return throwError( env, ReferenceError,
			   "Left hand side value is not writable" );
    QSObject v = ref.dereference();
    double n = v.toNumber();

    double newValue = ( oper == OpPlusPlus ) ? n + 1 : n - 1;
    QSObject n2 = QSNumber( newValue );

    ref.assign( n2 );

    return n2;
}

// ECMA 11.4.6
QSObject QSUnaryPlusNode::rhs( QSEnv *env ) const
{
    QSObject v = expr->rhs( env );

    return QSNumber( v.toNumber() );
}

// ECMA 11.4.7
QSObject QSNegateNode::rhs( QSEnv *env ) const
{
    QSObject v = expr->rhs( env );
    double n = v.toNumber();

    return QSNumber( -n );
}

// ECMA 11.4.8
QSObject QSBitwiseNotNode::rhs( QSEnv *env ) const
{
    QSObject v = expr->rhs( env );
    int i32 = v.toInt32();

    return QSNumber( ~i32 );
}

// ECMA 11.4.9
QSObject QSLogicalNotNode::rhs( QSEnv *env ) const
{
    QSObject v = expr->rhs( env );
    bool b = v.toBoolean();

    return QSBoolean( !b );
}

// ECMA 11.5
QSObject QSMultNode::rhs( QSEnv *env ) const
{
    QSObject v1 = term1->rhs( env );
    QSObject v2 = term2->rhs( env );

    return mult( v1, v2, oper );
}

// ECMA 11.7
QSObject QSShiftNode::rhs( QSEnv *env ) const
{
    QSObject v1 = term1->rhs( env );
    QSObject v2 = term2->rhs( env );
    unsigned int i2 = v2.toUInt32();
    i2 &= 0x1f;

    long result;
    switch ( oper ) {
    case OpLShift:
	result = v1.toInt32() << i2;
	break;
    case OpRShift:
	result = v1.toInt32() >> i2;
	break;
    case OpURShift:
	result = v1.toUInt32() >> i2;
	break;
    default:
	assert( !"ShiftNode: unhandled switch case" );
	result = 0L;
    }

    return QSNumber( double(result) );
}

// ECMA 11.6
QSObject QSAddNode::rhs( QSEnv *env ) const
{
    QSObject v1 = term1->rhs( env );
    QSObject v2 = term2->rhs( env );

    return add( v1, v2, oper );
}

// ECMA 11.14
QSObject QSCommaNode::rhs( QSEnv *env ) const
{
    (void) expr1->rhs( env ); // ignore return value

    return expr2->rhs( env );
}


QSObject QSScopeNode::execute( QSEnv *env )
{
    if( scope ) {
	scope->activateScope();
	QSObject result = executeStatement( env );
	scope->deactivateScope();
	return result;
    }
    return executeStatement( env );
}


// ECMA 12.1
QSObject QSBlockNode::executeStatement( QSEnv *env )
{
    if ( !statlist )
	return QSUndefined();

    return statlist->execute( env );
}

// ECMA 12.1
QSObject QSStatListNode::execute( QSEnv *env )
{
    if( !list ) {
	QSObject r = statement->execute( env );
	return r;
    }

    QSObject l = list->execute( env );
    if( env->isExceptionMode() || !env->isNormalMode() ) {
	return l;
    }


    QSObject s = statement->execute( env );
    if( env->isExceptionMode() || !env->isNormalMode() ) {
	return s;
    }

    return QSObject();
}

// ECMA 12.2
QSObject QSAssignExprNode::rhs( QSEnv *env ) const
{
    return expr->rhs( env );
}

// ECMA 12.3
QSObject QSEmptyStatementNode::execute( QSEnv * )
{
//     return QSObject( Normal );
    return QSObject();
}

// ECMA 12.6.3
QSObject QSForNode::executeStatement( QSEnv *env )
{
    QSBREAKPOINT
	;
    if ( expr1 )
	(void)expr1->rhs( env );
    if( env->isExceptionMode() )
	return QSObject();

    QSObject retVal;

    while ( 1 ) {
	if ( expr2 ) {
	    bool b = expr2->rhs( env ).toBoolean();
	    if ( !b ) {
		break;
	    }
	}
	// bail out on error
	if ( env->isExceptionMode() ) {
	    break;
	}

	QSObject c = stat->execute( env );
	if ( c.isValid() )
	    retVal = c;
	if ( env->isContinueMode() && env->isCurrentLabelValid() ) {
	    env->setExecutionMode( QSEnv::Normal );
	} else if ( env->isBreakMode() && env->isCurrentLabelValid() ) {
	    if( env->currentLabel().isEmpty() )
		env->setExecutionMode( QSEnv::Normal );
	    break;
	}

	if ( expr3 )
	    (void)expr3->rhs( env );
    }
    return retVal;
}

// ECMA 12.6.4
QSObject QSForInNode::executeStatement( QSEnv *env )
{
    QSObject retval = QSUndefined();
    QSObject obj = expr->rhs( env );
    QSMemberMap mmap = obj.objectType()->members( &obj );
    QSMemberMap::ConstIterator it = mmap.begin();
    QSObject base;
    QSMember member;
    if ( lexpr ) {
	QSReference ref = lexpr->lhs( env );
	if ( !ref.isWritable() ) {
	    QSObject e = env->throwError( TypeError, "Non-writable index", lineNo() );
	    return e;
	}
	base = ref.base();
	member = ref.member();
    } else {
	base = env->currentScope();
	member = QSMember( QSMember::Variable, var->index(), AttributeNone );
    }

    if( env->isExceptionMode() ) {
	return QSObject();
    }

    while ( it != mmap.end() ) {
	const QSMember &mem = *it;
	if ( mem.isEnumberable() ) {
	    base.write( member, QSString( mem.name() ) );
	    QSObject c = stat->execute( env );
	    if ( c.isValid() )
		retval = c;

	    if ( env->isContinueMode() && env->isCurrentLabelValid() ) {
		env->setExecutionMode( QSEnv::Normal );
	    } else if ( env->isBreakMode() && env->isCurrentLabelValid() ) {
		if( env->currentLabel().isEmpty() )
		    env->setExecutionMode( QSEnv::Normal );
		break;
	    } else if ( !env->isNormalMode() ) {
		return c;
	    }
	}
	++it;
    }
    return retval;
}

// ECMA 12.4
QSObject QSExprStatementNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    QSObject v = expr->rhs( env );

    // bail out on error
    if ( env->isExceptionMode() ) {
	return QSObject();
    }

    return v;
}

// ECMA 12.5
QSObject QSIfNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    QSObject result;

    QSObject v = expr->rhs( env );
    bool b = v.toBoolean();

    // if ... then
    if ( b )
	result = statement1->execute( env );

    // no else
    else if ( !statement2 )
	env->setExecutionMode( QSEnv::Normal );

    else
    // else
	result = statement2->execute( env );

    return result;
}

// ECMA 12.6.1
QSObject QSDoWhileNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    QSObject bv;
    QSObject c;
    QSObject value;
    QSObject result;

    do {
	// bail out on error
	if ( env->isExceptionMode() ) {
	    result = env->exception();
	    break;
	}

	c = statement->execute( env );
	if ( env->isContinueMode() && env->isCurrentLabelValid() ) {
	    env->setExecutionMode( QSEnv::Normal );
	} else if ( env->isBreakMode() && env->isCurrentLabelValid() ) {
	    if( env->currentLabel().isEmpty() ) // Clean up if no label node is known
		env->setExecutionMode( QSEnv::Normal );
	    result = value;
	    break;
	} else if ( !env->isNormalMode() ) {
	    result = c;
	    break;
	}
	bv = expr->rhs( env );
    } while ( bv.toBoolean() );

    return result;
}

// ECMA 12.6.2
QSObject QSWhileNode::execute( QSEnv *env )
{
    QSBREAKPOINT
    QSObject bv;
    QSObject c, result;
    bool b = FALSE;
    QSObject value;

    while ( 1 ) {
	bv = expr->rhs( env );
	b = bv.toBoolean();

	// bail out on error
	if ( env->isExceptionMode() ) {
	    result = QSObject();
	    break;
	}

	if ( !b )
	    return value;
	c = statement->execute( env );
	if ( c.isValid() )
	    value = c;
	if ( env->isContinueMode() && env->isCurrentLabelValid() ) {
	    env->setExecutionMode( QSEnv::Normal );
	} else if ( env->isBreakMode() && env->isCurrentLabelValid() ) {
	    if( env->currentLabel().isEmpty() ) // Clean up if no label node is known
		env->setExecutionMode( QSEnv::Normal );
	    result = value;
	    break;
	} else if ( !env->isNormalMode() ) {
	    result = c;
	    break;
	}
    }

    return result;
}

// ECMA 12.7
QSObject QSContinueNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    env->setExecutionMode( QSEnv::Continue );
    if ( ident.isEmpty() )
 	return QSObject();
    if( !env->containsLabel( ident ) ) {
	return env->throwError( SyntaxError,
				"Label not found in containing block",
				lineNo() );
    }
    env->setCurrentLabel( ident );
    return QSObject();
}

// ECMA 12.8
QSObject QSBreakNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    env->setExecutionMode( QSEnv::Break );
    if ( ident.isEmpty() )
 	return QSObject();
    if( !env->containsLabel( ident ) ) {
	return env->throwError( SyntaxError,
				"Label not found in containing block",
				lineNo() );
    }
    env->setCurrentLabel( ident );
    return QSObject();
}

// ECMA 12.9
QSObject QSReturnNode::execute( QSEnv *env )
{
    QSBREAKPOINT;
    QSObject ret = value ? value->rhs( env ) : QSUndefined();
    env->setExecutionMode( QSEnv::ReturnValue );
    return ret;
}

// ECMA 12.10
QSObject QSWithNode::executeStatement( QSEnv *env )
{
    QSBREAKPOINT

    QSObject v = expr->rhs( env );
    if( env->isExceptionMode() || v.isUndefined() )
	return QSObject();
    env->pushScope( v );
    QSObject oldThis = env->thisValue();
    env->setThisValue( v );
    QSObject res = stat->execute( env );
    env->setThisValue( oldThis );
    env->popScope();

    return res;
}

// ECMA QS12.11
QSClauseListNode* QSClauseListNode::append( QSCaseClauseNode *c )
{
    QSClauseListNode *l = this;
    while ( l->nx )
	l = l->nx;
    l->nx = new QSClauseListNode( c );

    return this;
}

// ECMA 12.11
QSObject QSSwitchNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    QSObject v = expr->rhs( env );
    QSObject res = block->evalBlock( env, v );

    if ( env->isBreakMode() && ls.contains( env->currentLabel() ) ) {
	env->setExecutionMode( QSEnv::Normal );
	return res;
    }
    else
	return res;
}

// ECMA 12.11
QSObject QSCaseBlockNode::evalBlock( QSEnv *env, const QSObject& input )
{
    QSObject v;
    QSObject res;
    QSClauseListNode *a = list1, *b = list2;
    QSCaseClauseNode *clause;

    if ( a ) {
	while ( a ) {
	    clause = a->clause();
	    a = a->next();
	    v = clause->rhs( env );
	    if ( strictEqual( input, v ) ) {
		res = clause->evalStatements( env );
		if ( !env->isNormalMode() )
		    return res;
		while ( a ) {
		    res = a->clause()->evalStatements( env );
		    if ( !env->isNormalMode() )
			return res;
		    a = a->next();
		}
		break;
	    }
	}
    }

    while ( b ) {
	clause = b->clause();
	b = b->next();
	v = clause->rhs( env );
	if ( strictEqual( input, v ) ) {
	    res = clause->evalStatements( env );
	    if ( !env->isNormalMode() )
		return res;
	    goto step18;
	}
    }

    // default clause
    if ( def ) {
	res = def->evalStatements( env );
	if ( !env->isNormalMode() )
	    return res;
    }
    b = list2;
 step18:
    while ( b ) {
	clause = b->clause();
	res = clause->evalStatements( env );
	if ( !env->isNormalMode() )
	    return res;
	b = b->next();
    }

    return QSObject();
}

// ECMA 12.11
QSObject QSCaseClauseNode::rhs( QSEnv *env ) const
{
    return expr->rhs( env );
}

// ECMA 12.11
QSObject QSCaseClauseNode::evalStatements( QSEnv *env )
{
    if ( list )
	return list->execute( env );
    else {
	env->setExecutionMode( QSEnv::Normal );
	return QSUndefined();
    }
}

// ECMA 12.12
QSObject QSLabelNode::execute( QSEnv *env )
{
    QSObject e;

    if ( env->containsLabel( label ) ) {
	env->throwError( SyntaxError, "Duplicated label found", lineNo() );
	return QSObject(); // Q: What to return here?
    }
    env->pushLabel( label );
    e = stat->execute( env );
    env->popLabel();

    if ( env->currentLabel() == label )
	env->setExecutionMode( QSEnv::Normal );
    return e;
}

// ECMA 12.13
QSObject QSThrowNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    QSObject v = expr->rhs( env );
    env->setException( v );
    return QSObject();
}

// ECMA 12.14
QSObject QSTryNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    if( block )
	block->execute( env );

    if( env->isExceptionMode() && _catch )
	_catch->execute( env );

    if( _final )
	_final->execute( env );

    return QSObject();
}

QSObject QSCatchNode::executeStatement( QSEnv *env )
{
    // Got the
    env->setValueDirect( index, 1, env->exception() );
    env->setExecutionMode( QSEnv::Normal );
    return block->execute( env );
}

// ECMA 12.14
QSObject QSFinallyNode::execute( QSEnv *env )
{
    return block->execute( env );
}

int QSFunctionBodyNode::count = 0;

QSFunctionBodyNode::QSFunctionBodyNode( QSSourceElementsNode *s )
    : source(s), scopeDef( 0 )
{
    idx = ++count;
#ifdef QSDEBUGGER
    setLoc( -1, -1 );
#endif
}

// ECMA 13 + 14 for QSProgramNode
QSObject QSFunctionBodyNode::execute( QSEnv *env )
{
    /* TODO: workaround for empty body which I don't see covered by the spec */
    if ( !source )
	return QSObject();

    return source->execute( env );
}

// ECMA 13
QSObject QSFuncExprNode::rhs( QSEnv *env ) const
{
    QSObject thisValue = env->thisValue();
    if( !thisValue.isDefined() )
	thisValue = env->globalObject();
    QSMember member( body );
    member.setName( body->scopeDefinition()->identifier() );

    return env->funcRefClass()->createReference( thisValue, member );
}

QSParameterNode* QSParameterNode::append( const QString *i, QSTypeNode *t )
{
    QSParameterNode *p = this;
    while ( p->next )
	p = p->next;

    p->next = new QSParameterNode( i, t );

    return this;
}

// ECMA 13
QSObject QSParameterNode::rhs( QSEnv * ) const
{
    return QSUndefined();
}

void QSProgramNode::deleteGlobalStatements()
{
    if ( source )
	source->deleteStatements();
}

// ECMA 14
QSObject QSSourceElementsNode::execute( QSEnv *env )
{
    if ( env->isExceptionMode() )
	return QSObject(); // errorCompletion();

    if ( !elements )
	return element->execute( env );

    QSObject c1 = elements->execute( env );
    if ( env->isExceptionMode() )
	return QSObject();
    if ( !env->isNormalMode() )
	return c1;

    QSObject c2 = element->execute( env );
    if ( env->isExceptionMode() )
	return QSObject();

    return c2;
}

void QSSourceElementsNode::deleteStatements()
{
    element->deleteStatements();

    if ( elements )
	elements->deleteStatements();
}

// ECMA 14
QSObject QSSourceElementNode::execute( QSEnv *env )
{
    return statement->execute( env );
}

void QSSourceElementNode::deleteStatements()
{
    delete statement;
}

QSObject QSClassDefNode::execute( QSEnv *env )
{
    // do nothing if this only a forward declaration
    if ( !body )
	return QSObject();

    Q_ASSERT( cldef );

    // run class initializer code
    cldef->executeBlock( env );
    env->setExecutionMode( QSEnv::Normal );
    return QSObject();
}

QSObject QSTypeNode::rhs( QSEnv *env ) const
{
    return env->resolveValue( ident );
}

QSObject QSTypedVarNode::rhs( QSEnv *env ) const
{
    if ( type )
	return type->rhs( env ).classValue()->construct( QSList() );
    return QSUndefined();  	// unused, therefore 0L is okay
}

void QSVarBindingNode::declare( QSEnv *env ) const
{
    // Member variables (static/nonstatic) should not be initialized
    if ( index() < 0 )
	return;
    QSObject scope = env->currentScope(); // Context::current()->variableObject();
    QSObject val = var->rhs( env );
    if( assign && val.isValid() ) {
	QSObject ass = assign->rhs( env );
	val = ass;
    }

    if( !env->isExceptionMode() )
	scope.objectType()->write( &scope, index(), val );
}


void QSVarBindingListNode::declare( QSEnv *env ) const
{
    if ( list )
	list->declare( env );

    binding->declare( env );
}

QSObject QSVarDefNode::execute( QSEnv *env )
{
    QSBREAKPOINT

    list->declare( env );
    return QSObject();
}

#if 0
void QSPackageNode::processFuncDecl( QSEnv * )
{
//     Context *ctx = Context::current();
//     QSObject oldVar = ctx->variableObject();
//     QSObject var = oldVar;
//     QString p = package;
//     // break up dotted notation like P1.P2.P3
//     while ( !p.isEmpty() ) {
// 	int pos = p.find( '.' );
// 	if ( pos < 0 )
// 	    pos = p.length();
// 	// don't overwrite any existing objects, reuse them
// 	QSObject o = var.get( p.left( pos ) );
// 	if ( o.isDefined() ) {
// 	    var = o;
// 	} else {
// 	    QSObject pkg = env->objectClass()->construct();
// 	    var.put( p.left( pos ), pkg, DontDelete );
// 	    var = pkg;
// 	}
// 	p = p.mid( pos + 1 );
//     }
//     ctx->setVariableObject( var );
//     ctx->pushScope( var );
//     (void)block->execute( env );
//     ctx->popScope();
//     ctx->setVariableObject( oldVar );
}
#endif

QSObject QSImportNode::execute( QSEnv *env )
{
    QString errMsg;
    QSEngineImp::current()->interpreter()->requestPackage( package, errMsg );
    if ( errMsg.isNull() )
	return QSObject();
    else {
	env->setExecutionMode( QSEnv::Throw );
	env->throwError( GeneralError, errMsg, lineNo() );
	return QSObject();
    }
}
