/****************************************************************************
** $Id: qsclass.cpp  beta1   edited Dec 13 18:26 $
**
** 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 "qsclass.h"
#include "qsenv.h"
#include "qsoperations.h"
#include "qstypes.h"
#include "qsnodes.h"
#include "qsfuncref.h"

using namespace QS;

QSClass::QSClass( QSEnv *e, int a )
    : en( e ),
      bclass( 0 ),
      encClass( 0 ),
      attrs( a )
{
    Q_ASSERT( e );
    init();
}

QSClass::QSClass( QSClass *b, int a )
    : bclass( b ),
      encClass( 0 ),
      attrs( a )
{
    Q_ASSERT( b && b->env() );
    en = b->env();
    init();
}

QSClass::~QSClass()
{
}

void QSClass::clear()
{
    delete mmap;
    mmap = 0;
    staticMembers.clear();
}


void QSClass::init()
{
    mmap = new QSMemberMap();
    numVariables = base() ? base()->numVariables : 0 ;
    numStaticVars = 0;
    en->registerClass( this );
}

/*!
  Checks if the two objects are equal. Returns positive if equal, 0 if
  not equal and negative if the class is unable to determine.
*/
int QSClass::isEqual( const QSObject &a, const QSObject &/*b*/ ) const
{
    Q_ASSERT( a.isA( this ) );
//     qDebug( "QSClass::isEqual( %s, %s )",
// 	    a.typeName().ascii(), b.typeName().ascii() );
    return -1;
}

QSCompareResult QSClass::compare( const QSObject &a, const QSObject &b ) const
{
//     qDebug( "\nQSClass::compare" );
//     qDebug( "a: %s\nb: %s", a.toString().latin1(), b.toString().latin1() );

    QSObject primA = a.toPrimitive(); // Hint numberClass();
    QSObject primB = b.toPrimitive(); // Hint numberClass();
    if( primA.isString() && primB.isString() ) {
	int ret = QString::compare( primA.toString(), primB.toString() );
	if( ret==0 )
	    return CompareEqual;
	else if( ret<0 )
	    return CompareLess;
	else
	    return CompareGreater;
    }
    double doubA = primA.toNumber();
    double doubB = primB.toNumber();

//     qDebug( "a=%f,   b=%f", doubA, doubB );

    if( isNaN( doubA ) || isNaN( doubB ) ) {
	return CompareUndefined;
    }

    // ### +0 vs -0 cases...

    if( doubA==doubB ) {
	return CompareEqual;
    } else if( doubA < doubB ) {
	return CompareLess;
    } else {
	return CompareGreater;
    }

    return CompareUndefined;
}


QSClassClass* QSClass::asClass() const
{
    return name() == "Class" ? (QSClassClass*)this : 0;
}

QSObject QSClass::execute( const QSObject *, QSObject *, const QSList & ) const
{
    throwError( TypeError, QString("Cannot invoke objects of type %1 as function").arg(name()) );
    return QSUndefined();
}


/*!
  Returns TRUE if this class is a subclass of \c; FALSE otherwise.
  A class is defined to be a subclass of itself.
 */
bool QSClass::inherits( const QSClass *c ) const
{
    const QSClass *b = this;
    while ( b && b != c )
	b = b->base();

    return b == c;
}

/*!
 * Mark \a o and its sub-properties as referenced. This is used
 * for the mark & sweep garbage collection.
 *
 * The default implementation does nothing but you should reimplement
 * this function if objects of this class contain other objects
 * or references to them.
 */

void QSClass::mark( QSObject * /*o*/ ) const
{
    // do nothing
}

void QSClass::ref( QSObject * /*o*/ ) const
{
}

void QSClass::deref( QSObject * /*o*/ ) const
{
}

/*!
 * Returns \a obj converted to a boolean value.
 *
 * The default implementation returns TRUE.
 */

bool QSClass::toBoolean( const QSObject * /*obj*/ ) const
{
    return TRUE;
}

/*!
 * Return \a obj converted to a floating point number; NaN if
 * the conversion failed.
 *
 * The default implementation returns NaN.
 */

double QSClass::toNumber( const QSObject * ) const
{
    return NaN;
}

/*!
 * Return \a obj converted to a string.
 *
 * The default implementation returns "[object N]" where N is
 * the name of this class as retrieved by name().
 */

QString QSClass::toString( const QSObject * ) const
{
    return "[object " + name() + "]";
}

QSObject QSClass::toPrimitive( const QSObject *obj, Type preferred ) const
{
    if( preferred != NumberType )
	return QSString( toString( obj ) );
    else
	return QSNumber( toNumber( obj ) );
}


QString QSClass::debugString( const QSObject *obj ) const
{
    if ( obj->isPrimitive() )
	return toString( obj ) + ":" + name();
    QSMemberMap m = members( obj );
    if (  m.isEmpty() )
	return toString( obj ) + ":" + name();

    QSMemberMap::ConstIterator it = m.begin();
    QString result = "{";
    for ( ;; ) {
	QSObject p = env()->resolveValue( it.key() );
	if ( !p.isValid() ) {
	    // ### should never happen (but sometimes does)
	    ++it;
	    if ( it == m.end() )
		break;
	    else
		continue;
	}
	result += it.key() + "=" + p.debugString();
	++it;
	if ( it == m.end() )
	    break;
	else
	    result += ",";
    }
    result += "}:" + identifier();
    return result;
}


bool QSClass::deleteProperty( QSObject *, const QSMember & ) const
{
    return FALSE;
}

/*!
  Retrieves a pointer to the class member \a n; 0 if no such member exists.
*/

bool QSClass::member( const QSObject *, const QString &n, QSMember *m ) const
{
//     qDebug( "QSClass::member() class = %s, name = %s", name().latin1(), n.latin1() );
    Q_ASSERT( !n.isEmpty() && m && mmap );
    QSMemberMap::Iterator it = mmap->find( n );
    if( it == mmap->end() ) {
	return FALSE;
    } else {
	*m = it.data();
	return TRUE;
    }
}


QSObject QSClass::fetchValue( const QSObject *objPtr,
			      const QSMember &mem ) const
{
//     qDebug( "fetching from: "  + identifier() + ", " + mem );
    if( !mem.isReadable() ) {
	qDebug( "QSClass:fetchValue() - not readable: " + mem );
	return QSUndefined();
    }

    if( mem.type()==QSMember::Variable ) {
	if ( !mem.isStatic() ) {
	    QSInstanceData *data = (QSInstanceData*)objPtr->shVal();
	    if ( mem.idx >= data->size() ) {
		// ### could throw error in member()
// 		qWarning( "QSClass::fetchValue: non-resized array access" );
		return QSUndefined();
	    }
	    QSObject * ptr = data->value( mem.idx );

	    if ( !ptr->isValid() ) {
//  		qWarning( "QSMember::fetch: Accessed uninitialized variable" );
		return QSUndefined();
	    }
	    return *ptr;
	} else {
	    return staticMember( mem.idx );
	}
    } else if ( mem.isExecutable() ) {
	return env()->funcRefClass()->createReference( *objPtr, mem );
    }

    return QSUndefined();
}

void QSClass::write( QSObject *objPtr, const QSMember &mem,
		     const QSObject &val ) const
{
    Q_ASSERT( mem.isWritable() );
    Q_ASSERT( mem.type()==QSMember::Variable );
    if ( mem.isWritable() && mem.type()==QSMember::Variable ) {
	if ( !mem.isStatic() ) {
	    QSInstanceData *data = (QSInstanceData*)objPtr->shVal();
	    if ( mem.idx >= data->size() ) {
		qWarning( "QSClass::write(), index=%d greater than array size=%d",
			  mem.idx, data->size() );
		// ### could throw error in member()
		return;
	    }
	    data->setValue( mem.idx, val );
	} else {
	    QSClass * cl = (QSClass*) this;
	    cl->setStaticMember( mem.idx, val );
	}
    }
}

/*!
  Only use for objPtr's that absolutly have QSInstanceData
*/
void QSClass::write( QSObject * objPtr, int index, const QSObject &val ) const
{
    QSInstanceData *idata = (QSInstanceData*)objPtr->shVal();
    idata->ensureSize( index+1 );
    idata->setValue( index, val );
}

void QSClass::setStaticMember( int idx, const QSObject &val )
{
    Q_ASSERT( idx>=0 && idx<numStaticVars );
    staticMembers[idx] = val;
}

QSObject QSClass::staticMember( int idx ) const
{
    Q_ASSERT( idx>=0 && idx<numStaticVars );
    return staticMembers[idx];
}

static bool compareScopes( const QSObject &a, const QSObject &b )
{
    return a.objectType()==b.objectType() && a.shVal()==b.shVal();
}

QSObject QSClass::invoke( QSObject * objPtr, const QSMember &mem,
			  const QSList &args ) const
{
    Q_ASSERT( mem.isExecutable() );
    Q_ASSERT( objPtr->objectType() == this );
    switch( mem.type() ) {
    case QSMember::NativeFunction:
	return (*mem.nativeFunction)( args );
    case QSMember::NativeMemberFunction:
	Q_ASSERT( !mem.isStatic() );
	return (*mem.nativeMemberFunction)( objPtr, args );
    case QSMember::ScriptFunction:
	{
	    Q_ASSERT( mem.scriptFunction );


#ifdef QSDEBUGGER
	    Debugger *dbg = QSEngineImp::current()->debugger();
	    // arguments as string for call stack info
	    QString argStr = "";
	    for ( int j = 0; j < args.size(); j++ ) {
		if ( j > 0 )
		    argStr += ", ";
		QSObject a = args[j];
		argStr += a.toString() + " : " + a.typeName();
	    }
	    QString n = mem.scriptFunction->scopeDefinition()->identifier();
	    if( dbg )
		dbg->callEvent( n, argStr );
	    // debugger has to be told that we are potentially
	    // jumping to script code in a different source unit
	    int oldSourceId = -1;
	    if ( dbg ) {
		oldSourceId = dbg->sourceId();
		dbg->setSourceId( mem.scriptFunction->sourceId() );
	    }
#endif
	    QSFunctionScopeClass *scopeDef = mem.scriptFunction->scopeDefinition();

// 	    qDebug( "Calling function:  " + scopeDef->identifier() );
// 	    qDebug( "objPtr is:         " + objPtr->objectType()->identifier() );
// 	    qDebug( "currentScope is:   " + env()->currentScope().objectType()->identifier() );
// 	    env()->printScopeChain();

	    // Use invalid object for scopes that don't have variables..
	    QSObject scope = scopeDef->construct( args );

	    QSObject returnValue;

	    if ( compareScopes( *objPtr, env()->currentScope() ) ) {
//  		qDebug( "Push scope type 1" );
		env()->pushScope( scope );
		returnValue = mem.scriptFunction->execute( env() );
		env()->popScope();
	    } else if( objPtr->objectType()->enclosingClass()==env()->currentScope().objectType() ) {
//  		qDebug( "Push scope type 1b" );
		env()->pushScope( *objPtr );
		env()->pushScope( scope );
		returnValue = mem.scriptFunction->execute( env() );
		env()->popScope();
		env()->popScope();
	    } else if ( objPtr->objectType()->enclosingClass() == 0 ) {
// 		qDebug( "Push scope type 1c" );
		env()->pushScopeBlock();
		env()->pushScope( env()->globalObject() );
		env()->pushScope( *objPtr );
		env()->pushScope( scope );
//   		env()->printScopeChain();
		returnValue = mem.scriptFunction->execute( env() );
		env()->popScopeBlock();
	    } else {
//  		qDebug( "Push scope type 2" );
		// ### This loop is a duplicate of the one already done in resolvenode
		ScopeChain chain = env()->scope();
		ScopeChain::Iterator it = chain.begin();

		bool pushObj = FALSE;

		while( it!=chain.end() ) {
		    if( compareScopes( *it, *objPtr ) ) {
			break;
		    } else if ( (*it).objectType() ==
				objPtr->objectType()->enclosingClass() ) {
			pushObj = TRUE;
			break;
		    }
		    else {
			it = chain.remove( it );
		    }
		}

		env()->pushScopeBlock();
		while( chain.size()>0 ) {
		    env()->pushScope( chain.back() );
		    chain.pop_back();
		}
		if( pushObj ) env()->pushScope( *objPtr );
		env()->pushScope( scope );
//   		env()->printScopeChain();
		returnValue = mem.scriptFunction->execute( env() );
		env()->popScopeBlock();
	    }
#ifdef QSDEBUGGER
	    if ( dbg )
		dbg->returnEvent();
	    // restore previous source id
	    if (dbg)
		dbg->setSourceId(oldSourceId);
#endif
	    return env()->isReturnValueMode() ?  returnValue : QSUndefined();
	}
    case QSMember::Variable: {
	QSObject o = fetchValue( objPtr, mem );
	if ( o.objectType() == env()->typeClass() )
	    return o.classValue()->cast( args );
	qFatal( "QSClass::invoke: Unhandled variable type" );
	break;
    }
    default:
	qFatal( "QSClass::invoke: Unhandled switch case %d", mem.type() );
    }
    return QSUndefined();
}

void QSClass::addFunctionMember( const QString &n, QSFunctionBodyNode * f, int attributes )
{
    addMember( n, QSMember( f, attributes ) );
}

int QSClass::addVariableMember( const QString &n, int attributes )
{
    addMember( n, QSMember( QSMember::Variable, attributes ) );
    return attributes & AttributeStatic ? numStaticVars : numVariables-1;
}

void QSClass::addStaticVariableMember( const QString &name, const QSObject &value, int attr )
{
    addMember( name, QSMember( QSMember::Variable, attr | AttributeStatic ), value );
}

/*!
  Add member \a member with name \a n to this class. \stVal contains the value
  if the member is a static variable.
 */
void QSClass::addMember( const QString &n, const QSMember &member, const QSObject &stVal )
{
    Q_ASSERT( !mmap->contains( n ) );

    QSMember m = member;
    m.setName( n );
    m.setOwner( this );

    if( m.type()==QSMember::Variable ) {
	if( m.isStatic() ) {
	    m.setIndex( numStaticVars++ );
	    staticMembers.append( stVal );
	} else {
	    m.setIndex( numVariables++ );
	}
    }
    mmap->insert( n, m );
}

/* Factored out from replace member */
void QSClass::removeStaticVar( const QSMember &old )
{
    staticMembers.remove( staticMembers.at(old.idx) );
    QSMemberMap::iterator it = mmap->begin();
    while( it!=mmap->end() ) {
	QSMember &cur = *it;
	if( cur.type()==QSMember::Variable && cur.isStatic() && cur.idx>old.idx )
	    cur.idx--;
	it++;
    }
    numStaticVars--;
}


/* Factored out from replaceMember */
void QSClass::fillMemberVarIndex( QSMember *member )
{
    if( !replacedVars.isEmpty() ) { // Reuse old varspace if possible.
	member->idx = replacedVars[0];
	replacedVars.pop_front();
    } else {
	member->idx = numVariables++;
    }
}


/*!
  Replaces the member \name with \member. \stVal can contain the value if the
  member is a static variable.
*/
void QSClass::replaceMember( const QString &name, QSMember *member,
			     const QSObject &stVal )
{
//     qDebug( "QSClass::replaceMember(%s)", name.latin1() );
    Q_ASSERT( mmap->contains( name ) );
    QSMember old = *(mmap->find( name ));
    QSMember &m = *member;
    m.setName( name );
    m.setOwner( this );

    if( old.type()==QSMember::Variable && m.type()==QSMember::Variable ) {
	if( old.isStatic() == m.isStatic() ) { // both static or both nonstatic
	    m.idx = old.idx;
	    if( old.isStatic() )  // replace value if static
		staticMembers[m.idx] = stVal;
	} else if( old.isStatic() ) {
	    removeStaticVar( old );
	    fillMemberVarIndex( &m );
	} else if( m.isStatic() ) {
	    m.idx = numStaticVars++;
	    staticMembers.append( stVal );
	    replacedVars.append( old.idx );
	}

    } else if( (old.type()==QSMember::ScriptFunction ||
		old.type()==QSMember::NativeFunction ||
		old.type()==QSMember::NativeMemberFunction) &&
	       (m.type()==QSMember::ScriptFunction ||
		m.type()==QSMember::NativeFunction ||
		m.type()==QSMember::NativeMemberFunction) ) {
	// Replace only...

    } else if ( old.type()==QSMember::Variable ) { // Variable -> function
	if( old.isStatic() ) { // Delete and update member indexes
	    removeStaticVar( old );
	} else { // Store index for reuse later.
	    replacedVars.append( old.idx );
	}

    } else if ( m.type()==QSMember::Variable ) {
	if( m.isStatic() ) {
	    m.idx = numStaticVars++;
	    staticMembers.append( stVal );
	} else {
	    fillMemberVarIndex( &m );
	}
    } else {
	qFatal( "QSClass::replaceMember() -- Unhandled case" );
    }

    mmap->replace( name, m );
}

/*!
  Deletes the member under name \name. If deletion was not possible, FALSE
  is returned
*/
bool QSClass::deleteMember( const QString &name ) {
    qDebug( "QSClass::deleteMember( %s )", name.latin1() );
    if( !mmap->contains( name ) ) {
	return FALSE;
    }
    // ### What do we do about variable indexes??
    mmap->remove( name );
    return TRUE;
}

bool QSClass::hasProperty( const QSObject *obj,
			   const QString &p ) const
{
    // standard class property ?
    QSMember m;
    if ( member( obj, p, &m ) && m.type() != QSMember::Identifier )
	return TRUE;
//     // dynamic property
//     return data( obj )->hasProperty( p );
    return FALSE;
}

QSObject QSClass::get( const QSObject *objPtr, const QString &p ) const
{
    QSMember mem;
    if ( !member( objPtr, p, &mem ) || mem.type() == QSMember::Identifier )
	return QSUndefined();
    return fetchValue( objPtr, mem );
}

void QSClass::put( QSObject *objPtr, const QString &p,
		   const QSObject &v, int /*attrs*/ ) const
{
    QSMember mem;
    if ( !member( objPtr, p, &mem  ) && mem.type() != QSMember::Identifier ) {
	qWarning( "QSClass::put: refused write of %s", p.ascii() );
	return;
    }
    mem.setName( p );
    write( objPtr, mem, v );
}

QSObject QSClass::construct( const QSList & /* args */ ) const
{
    return QSUndefined();
}

QSObject QSClass::cast( const QSList &args ) const
{
    qDebug( "QSClass::cast: trying to cast to QSClass: %s",
	    args[0].typeName().latin1() );
    return args[0];
}

/*!  Returns the map of members of \a obj. The default implementation
  will list all members pre-defined via the addMember() function or
  one of its specializations. Class inherting from QSClass can
  reimplement this function to also give information about custom
  properties.
 */

QSMemberMap QSClass::members( const QSObject * /*obj*/ ) const
{
    Q_ASSERT( mmap );
    return *mmap;
}

/*!
  Convenience function to throw an error of type \a a with the user
  visible message \a msg.
*/

void QSClass::throwError( ErrorType e, const QString &msg ) const
{
    (void)env()->throwError( e, msg, -1 );
}

bool QSUndefinedClass::toBoolean( const QSObject * ) const
{
    return FALSE;
}

double QSUndefinedClass::toNumber( const QSObject * ) const
{
    return NaN;
}

QString QSUndefinedClass::toString( const QSObject * ) const
{
    return "undefined";
}

QSObject QSUndefinedClass::toPrimitive( const QSObject *obj, Type ) const
{
    return *obj;
}

int QSUndefinedClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    Q_ASSERT( a.isA( this ) );
    if ( b.isUndefined() || b.isNull() )
	return TRUE;
    else
	return -1;
}

bool QSNullClass::toBoolean( const QSObject * ) const
{
    return FALSE;
}

double QSNullClass::toNumber( const QSObject * ) const
{
    return 0.0;
}

QString QSNullClass::toString( const QSObject * ) const
{
    return "null";
}

QSObject QSNullClass::toPrimitive( const QSObject *obj, Type ) const
{
    return *obj;
}

int QSNullClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    Q_ASSERT( a.isA( this ) );
    if( b.isNull() || b.isUndefined() )
	return TRUE;
    return -1;
}


bool QSCharacterClass::toBoolean( const QSObject *obj ) const
{
    return !obj->sVal()[0].isNull();
}

double QSCharacterClass::toNumber( const QSObject *obj ) const
{
    return QSString::toDouble( obj->sVal() );
}

QString QSCharacterClass::toString( const QSObject *obj ) const
{
    return obj->sVal();
}

void QSSharedClass::ref( QSObject * o ) const
{
#ifndef QS_LEAK
    o->shVal()->ref();
#endif
}

void QSSharedClass::deref( QSObject * o ) const
{
#ifndef QS_LEAK
    o->shVal()->deref();
    if( o->shVal()->count==0 ) {
	delete o->shVal();
	o->setVal( (QSShared*)0 );
    }
#endif
}

QSClassClass::QSClassClass( QSClass *b, int a, const QString &n )
    : QSSharedClass( b, a ), cname( n ), defaultCtor( FALSE ), bodyNode( 0 )
{
    memberInit = new QSNodeList();
    staticInit = new QSNodeList();
}

QSClassClass::~QSClassClass()
{
}

bool QSClassClass::toBoolean( const QSObject * ) const
{
    return TRUE;
}

double QSClassClass::toNumber( const QSObject * ) const
{
    return NaN;
}

QString QSClassClass::toString( const QSObject * ) const
{
    return "[class " + cname + "]";
}

QSInstanceData* QSClassClass::data( QSObject *obj )
{
    return (QSInstanceData*)obj->shVal();
}

const QSInstanceData* QSClassClass::data( const QSObject *obj )
{
    return (const QSInstanceData*)obj->shVal();
}

/*!
  \reimp
  Construct an instance of the class described by this object.
 */

QSObject QSClassClass::construct( const QSList &args ) const
{
    // Initialize all entries to undefined
    QSInstanceData *data = new QSInstanceData( numberOfVariables() );
    for( int i=0; i<numberOfVariables(); i++ )
	data->setValue( i, QSUndefined() );
    QSObject inst( this, data );

    // Set up scope
    ScopeChain chain = env()->scope();
    ScopeChain::Iterator sit = chain.begin();
    while( sit!=chain.end() ) {
	if( (*sit).objectType()==enclosingClass() ) {
	    break;
	}
	sit = chain.remove( sit );
    }
    // Fill up scope chain.
    env()->pushScopeBlock();
    while( chain.size()>0 ) {
	env()->pushScope( chain.back() );
	chain.pop_back();
    }
    env()->pushScope( inst );

    initVariables( data );

    // Clean up scope
    env()->popScopeBlock();

    if ( hasDefaultConstructor() && !env()->isExceptionMode() ) {
	QSObject ctor = get( &inst, cname );
	Q_ASSERT( ctor.isExecutable() );
	ctor.execute( args );
	// ### do something with the return value/type ?
    }

    return inst;
}


int QSClassClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    if( b.objectType() == this ) {
	return b.shVal()==a.shVal();
    }
    return false;
}

void QSClassClass::addMemberInitializer( QSNode * node )
{
    memberInit->append( node );
}

void QSClassClass::addStaticInitializer( QSNode * node )
{
    staticInit->append( node );
}

void QSClassClass::setClassBodyNode( QSFunctionBodyNode * node )
{
    bodyNode = node;
}

/*!
  Execute statements contained in this class' block.
*/

void QSClassClass::executeBlock( QSEnv *env )
{
    // Set up scope
    ScopeChain chain = env->scope();
    ScopeChain::Iterator sit = chain.begin();
    while( sit!=chain.end() ) {
	if( (*sit).objectType()==enclosingClass() ) {
	    break;
	}
	sit = chain.remove( sit );
    }
    // Fill up scope chain.
    env->pushScopeBlock();
    while( chain.size()>0 ) {
	env->pushScope( chain.back() );
	chain.pop_back();
    }
    // Push the type object...
    QSObject type( env->typeClass() );
    type.setValue( (QSClass*) this );
    env->pushScope( type );

   // Call initializers
    QPtrListIterator<QSNode> it( *staticInit );
    for ( uint j = 0; j<staticInit->count(); j++ ) {
	QSNode *init = it();
	if ( init ) {
	    setStaticMember( j, init->rhs( env ) );
	    // abort if init code caused an error
	    if ( env->isExceptionMode() )
		break;
	}
    }

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

    // Clean up scope
    env->popScopeBlock();
}

/*!
  \internal
  Runs the initializers on the variables declared in this class. This function
  assumes that scopes are set up correctly and can only be called from
  within the QSClassClass::construct function.
*/
int QSClassClass::initVariables( QSInstanceData * data ) const
{
    int offset = 0;
    QSClassClass *cl = base() ? base()->asClass() : 0;
    if( cl )
	offset = cl->initVariables( data );

    // Call initializers
    QPtrListIterator<QSNode> it( *memberInit );
    for ( uint j = 0; j<memberInit->count(); j++ ) {
	QSNode *init = it();
	if ( init ) {
	    data->setValue( offset + j, init->rhs( env() ) );
	    // abort if init code caused an error
	    if ( env()->isExceptionMode() )
		break;
	}
    }
    return offset + memberInit->count();
}

/*!
  \reimp
*/

void QSWritableClass::mark( QSObject * /*o*/ ) const
{
}

bool QSWritableClass::member( const QSObject *o, const QString &n,
			      QSMember *m ) const
{
    //    qDebug( "QSWritableClass::member() class = %s, name = %s", name().latin1(), n.latin1() );
    Q_ASSERT( /* o &&*/ !n.isEmpty() && m );

    if( !o || !o->isDefined() ) {
	return QSClass::member( o, n, m );
    }

    if( !o->shVal() ) {
	return QSClass::member( 0, n, m );
    }


    // The error here is that the object is a dummy!!!
    const QSWritable *w = (QSWritable*) o->shVal(); //data( o );

    if ( !w->hasProperty( n ) ) {
	if ( QSClass::member( o, n, m ) )
	    return TRUE;
	// property doesn't exit, yet. We'll offer to create a new one
	m->setType( QSMember::Identifier );
	m->setName( n );
	m->setOwner( this );
	return FALSE;
    }

    m->setType( QSMember::Object );
    m->obj = &w->reference( n )->object;
    m->setName( n );
    m->setOwner( this );
    return TRUE;
}

QSObject QSWritableClass::fetchValue( const QSObject *objPtr,
				      const QSMember &mem ) const
{
//     qDebug( "QSWritableClass::fetchValue() -> mem.type = %s", mem.typeName().latin1() );
    if( mem.type()==QSMember::Object ) {
	return *mem.obj;
    }
    return QSClass::fetchValue( objPtr, mem );
}

void QSWritableClass::write( QSObject *objPtr, const QSMember &mem,
			     const QSObject &val ) const
{
//     qDebug( "QSWritableClass::write() -> mem.type = %s", mem.typeName().latin1() );
    if( mem.type()==QSMember::Object ) {
	*mem.obj = val;
    } else if( mem.type()==QSMember::Identifier ) {
// 	qDebug( "Writing to QSMember::Identifier: name = %s", mem.str.latin1() );
	data( objPtr )->setProperty( mem.name(), QSProperty( val, None ) );
    } else {
	QSClass::write( objPtr, mem, val );
    }
}

bool QSWritableClass::deleteProperty( QSObject *obj, const QSMember &mem ) const
{
    if ( mem.type()==QSMember::Object ) {
	properties( obj )->remove( mem.name() );
	return TRUE;
    }
    return FALSE;
}

QSObject QSWritableClass::invoke( QSObject * objPtr, const QSMember &mem,
				  const QSList &args ) const
{
    if( mem.type()==QSMember::Object ) {
	Q_ASSERT( mem.obj->isValid() );
	return objPtr->invoke( mem, args );
    }
    return QSClass::invoke( objPtr, mem, args );
}

QSWritable *QSWritableClass::data( QSObject *obj )
{
    return (QSWritable*)obj->shVal();
}

const QSWritable *QSWritableClass::data( const QSObject *obj )
{
    return (const QSWritable*)obj->shVal();
}

/*!
  Create an empty, writable object of this class.
*/

QSObject QSWritableClass::createWritable() const
{
    return QSObject( this, new QSWritable() );
}

QSPropertyMap *QSWritableClass::properties( const QSObject *obj ) const
{
    return data( obj )->properties();
}

/*!
  \reimp
  Returns the pre-defined members plus dynamic properties of \a obj.
*/

QSMemberMap QSWritableClass::members( const QSObject *obj ) const
{
    QSMemberMap map = QSClass::members( obj );
    if ( obj ) {
	QSPropertyMap *pmap = properties( obj );
	if ( pmap ) {
	    QSPropertyMap::ConstIterator it = pmap->begin();
	    while ( it != pmap->end() ) {
		QSMember mem( QSMember::Object, AttributeEnumerable );
		mem.setName( it.key() );
		mem.setExecutable( it.data().object.isExecutable() );
		map.insert( it.key(), mem );
		++it;
	    }
	}
    }
    return map;
}

int QSWritableClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    if( b.objectType() == this ) {
	return b.shVal()==a.shVal();
    }
    return false;
}

QSFunctionScopeClass::QSFunctionScopeClass( QSClass *b, QSFuncDeclNode * func )
    : QSWritableClass( b ), ident(func->identifier()), numArgs( 0 )
{
}

QString QSFunctionScopeClass::identifier() const
{
    return ident.isNull() ? QString("[anonymous]") : ident;
}

QSObject QSFunctionScopeClass::construct( const QSList &args ) const
{
//   qDebug( "Creating functions scope for: " + identifier() + ", %d", numberOfVariables() );
    QSInstanceData *dat = new QSInstanceData( numberOfVariables() );
    QSObject scope( this, dat );

    // fill slots for passed arguments
    QSListIterator it = args.begin();
    int i = 0;
    while ( it != args.end() && i < numberOfArguments() ) {
	dat->setValue( i, *it );
	it++;
	i++;
    }
    // intialize remaining ones with "undefined"
    while ( i < numberOfArguments() )
	dat->setValue( i++, QSUndefined() );

    QSArray argObj;
    it = args.begin();
    for ( i = 0; it != args.end(); ++i, ++it )
	argObj.put( QString::number( i ), *it );
    scope.put( "arguments", argObj );

    return scope;
}

QSObject QSEvalScopeClass::construct( const QSList & ) const
{
    return QSObject( this, new QSInstanceData( numberOfVariables() ) );
}

void QSBlockScopeClass::activateScope() const
{
    QSObject scope( this, env()->currentScope().shVal() );
    ref( &scope );
    env()->pushScope( scope );
}

void QSBlockScopeClass::deactivateScope() const
{
    env()->popScope();
}

QSMemberMap QSBlockScopeClass::members( const QSObject *obj ) const
{
    QSMemberMap newMap( *definedMembers() );
    QSMemberMap encMap = enclosingClass()->members( obj );
    QSMemberMap::ConstIterator it = encMap.begin();
    while( it!=encMap.end() ) {
	newMap[ it.key() ] = it.data();
	it++;
    }
    return newMap;
}


bool QSTypeClass::member( const QSObject *o, const QString &n,
			  QSMember *m ) const
{
    if( !o )
	return FALSE;
    Q_ASSERT( o->isA( this ) );
    QSClass *tcl = o->classValue();
    return tcl->member( o, n, m );
}

QSObject QSTypeClass::fetchValue( const QSObject *o,
				  const QSMember &mem ) const
{
    Q_ASSERT( o->isA( this ) );
    if ( !mem.hasAttribute( AttributeStatic ) ) {
	throwError( ReferenceError, "Cannot access a non-static member "
		    "without an object reference" );
	return QSUndefined();
    }
    QSClass *tcl = o->classValue();
    return tcl->fetchValue( o, mem );
}

QSObject QSTypeClass::invoke( QSObject *o, const QSMember &mem,
			      const QSList &args ) const
{
    Q_ASSERT( o->objectType()==this );
    // we are not interested in static functions
    if ( mem.isStatic() )
	 return QSClass::invoke( o, mem, args );
    else if( mem.type()==QSMember::Variable ) // Indirect casting.
	return o->classValue()->cast( args );

    throwError( ReferenceError, "Cannot invoke a non-static function "
	       "without an object reference" );
    return QSUndefined();
}

void QSTypeClass::write( QSObject *objPtr, const QSMember &mem,
			 const QSObject &val ) const
{
    Q_ASSERT( mem.isWritable() );
    // Q_ASSERT( mem.type()==QSMember::Variable );

    if ( !mem.hasAttribute( AttributeStatic ) ) {
	throwError( ReferenceError, "Cannot access a non-static member "
		    "without an object reference" );
	return;
    }

    QSClass * cl = objPtr->classValue();

    if( mem.type()==QSMember::Variable ) {
	cl->setStaticMember( mem.idx, val );
    }
    else {
	throwError( ReferenceError, "Trying to write to a nonvariable" );
	return;
    }
}

int QSTypeClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    if( b.objectType()==this ) {
	return a.classValue()==b.classValue();
    }
    return -1;
}


QSInstanceData::QSInstanceData( int count )
{
    vals = new QSObject[count];
    sz = count;
    for( int i=0; i<count; i++ )
	vals[i] = QSUndefined();
}



void QSInstanceData::resize( int count )
{
    QSObject *tmp = vals;
    vals = new QSObject[count];
    for( int i=0; i<sz; i++ ) {
	vals[i] = tmp[i];
    }
    for( int j=sz; j<count; j++ )
	vals[j] = QSUndefined();
    delete [] tmp;
    sz = count;
}

/*!
  Insure that this object has enough space for \a count objects.
  If that is already the case the array won't be resized.
 */
void QSInstanceData::ensureSize( int count )
{
    if ( count > sz )
	resize( count );
}

QString operator+( const QString &a, const QSMember &b )
{
    QString s;
    s.sprintf( "QSMember(%s.%s, %s, %x)",
	       b.owner() ? b.owner()->identifier().latin1() : "(no owner)",
	       b.name().latin1(),
	       b.typeName().latin1(),
	       b.attributes() );
    return a + s;
}

bool operator==( const QSMember &a, const QSMember &b )
{
    return a.type() == b.type() && a.owner() == b.owner() &&
		     !a.name().isEmpty() && a.name() == b.name();
}

