/**
 *	@file 	ejsVar.c
 *	@brief 	Mbedthis Portable Runtime Universal Variable Type
 */

/*
 *	@copy	default
 *	
 *	Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved.
 *	Copyright (c) Michael O'Brien, 1994-1995. All Rights Reserved.
 *	
 *	This software is distributed under commercial and open source licenses.
 *	You may use the GPL open source license described below or you may acquire 
 *	a commercial license from Mbedthis Software. You agree to be fully bound 
 *	by the terms of either license. Consult the LICENSE.TXT distributed with 
 *	this software for full details.
 *	
 *	This software is open source; you can redistribute it and/or modify it 
 *	under the terms of the GNU General Public License as published by the 
 *	Free Software Foundation; either version 2 of the License, or (at your 
 *	option) any later version. See the GNU General Public License for more 
 *	details at: http://www.mbedthis.com/downloads/gplLicense.html
 *	
 *	This program is distributed WITHOUT ANY WARRANTY; without even the 
 *	implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *	
 *	This GPL license does NOT permit incorporating this software into 
 *	proprietary programs. If you are unable to comply with the GPL, you must
 *	acquire a commercial license to use this software. Commercial licenses 
 *	for this software and support services are available from Mbedthis 
 *	Software at http://www.mbedthis.com 
 *	
 *	@end
 */

/******************************* Documentation ********************************/

/*
 *	This module is NOT multithreaded. 
 *
 *	Properties are variables that are stored in an object type variable.
 *	Properties can be primitive data types, other objects or methods.
 *	Properties are indexed by a character name.
 */

/********************************** Includes **********************************/

#include	"ejs.h"

/***************************** Forward Declarations ***************************/

static EjsProperty	*allocProperty(Ejs *ep, EjsVar *op, const char *property, 
						int propertyIndex, EjsProperty *last);
static EjsVar		*copyVar(EJS_LOC_DEC(ep, loc), EjsVar *dest, 
						const EjsVar *src, EjsCopyDepth copyDepth);
static EjsObj 		*createObj(EJS_LOC_DEC(ep, loc));
static char 		*getNextVarToken(char **next, char *tokBuf, int tokBufLen);
static int			hash(const char *property);
static void 		unlinkProperty(EjsObj *obj, EjsPropLink *propLink);
static void 		linkPropertyBefore(EjsObj *obj, EjsPropLink *at, 
						EjsPropLink *propLink);
static int 			sortAllProperties(Ejs *ep, EjsProperty *p1, 
						EjsProperty *p2, const char *propertyName, int order);
static int 			sortByProperty(Ejs *ep, EjsProperty *p1, EjsProperty *p2,
						const char *propertyName, int order);
static int 			dupString(MPR_LOC_DEC(ctx, loc), uchar **dest, 
						const void *src, int nbytes);
#if UNUSED && KEEP
static void 		linkPropertyAfter(EjsObj *obj, EjsPropLink *at, 
						EjsPropLink *propLink);
#endif

static EjsProperty 	*hashLookup(EjsObj *obj, const char *property, 
						int *propertyIndex, EjsProperty **hashTail);

/******************************************************************************/
/********************************** Var Routines ******************************/
/******************************************************************************/

EjsType ejsGetVarType(EjsVar *vp)
{
	mprAssert(vp);

	return vp->type;
}

/******************************************************************************/

void ejsFreeVar(Ejs *ep, EjsVar *vp)
{
	if (vp) {
		ejsClearVar(ep, vp);
		ejsFree(ep, vp, EJS_SLAB_VAR);
	}
}

/******************************************************************************/
#if UNUSED
/*
 *	Clear the value by freeing any allocated data. This will release objects
 *	so that later garbage collection can reclaim storage if there are no other
 *	object references.
 */

void ejsZeroVar(Ejs *ep, EjsVar *vp)
{
	vp->type = EJS_TYPE_UNDEFINED;
	vp->objectState = 0;
	vp->method.body = 0;
	vp->method.args = 0;
	vp->callsSuper = 0;
	vp->ptr.destructor = 0;
	vp->allocatedData = 0;
}

#endif
/******************************************************************************/
/*
 *	Clear the value by freeing any allocated data. This will release objects
 *	so that later garbage collection can reclaim storage if there are no other
 *	object references.
 */

void ejsClearVar(Ejs *ep, EjsVar *vp)
{
	MprArray	*argList;
	int			i;

	mprAssert(vp);
	mprAssert(ep);

	if (! vp->allocatedData) {
		vp->type = EJS_TYPE_UNDEFINED;
		return;
	}
	if (vp->type == EJS_TYPE_UNDEFINED) {
		return;
	}

	switch (vp->type) {
	default:
		break;

	case EJS_TYPE_STRING:
		mprFree(vp->string);
		vp->string = 0;
		break;

	case EJS_TYPE_OBJECT:
		/* 
 		 *	Set the "alive" bit so that the GC will cleanup if no 
		 *	other references.
		 */
		if (vp->objectState) {
			vp->objectState->alive = 1;
		}
		vp->objectState = 0;
		break;

	case EJS_TYPE_METHOD:
		argList = vp->method.args;
		/* 
		 *	MOB OPT -- should be able to do just one mprFree(vp->method.args)
		 */
		mprFree(vp->method.body);
		if (argList) {
			for (i = 0; i < argList->length; i++) {
				mprFree(argList->items[i]);
			}
			mprFree(vp->method.args);
		}
		vp->method.args = 0;
		vp->method.body = 0;
		vp->callsSuper = 0;
		break;

	case EJS_TYPE_PTR:
		if (vp->ptr.destructor) {
			(vp->ptr.destructor)(ep, vp);
		}
		break;
	}

	vp->type = EJS_TYPE_UNDEFINED;
	vp->allocatedData = 0;
}

/******************************************************************************/
/*
 *	Initialize an undefined value.
 */

EjsVar *ejsCreateUndefinedVar(Ejs *ep)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_UNDEFINED;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize an null value.
 */

EjsVar *ejsCreateNullVar(Ejs *ep)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_NULL;
	}
	return vp;
}

/******************************************************************************/

EjsVar *ejsCreateBoolVar(Ejs *ep, int value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_BOOL;
		vp->boolean = value;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize a C method.
 */

EjsVar *ejsCreateCMethodVar(Ejs *ep, EjsCMethod fn, void *userData, int flags)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_CMETHOD;
		vp->cMethod.fn = fn;
		vp->cMethod.userData = userData;
		vp->flags = flags;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize a C method.
 */

EjsVar *ejsCreateStringCMethodVar(Ejs *ep, EjsStringCMethod fn, 
	void *userData, int flags)
{
	EjsVar	*vp;

	mprAssert(ep);
	mprAssert(fn);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_STRING_CMETHOD;
		vp->cMethodWithStrings.fn = fn;
		vp->cMethodWithStrings.userData = userData;
		vp->flags = flags;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize an opaque pointer. 
 */

EjsVar *ejsCreatePtrVar(Ejs *ep, void *ptr, EjsDestructor destructor)
{
	EjsVar	*vp;

	mprAssert(ep);
	mprAssert(ptr);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_PTR;
		vp->ptr.userPtr = ptr;
		vp->ptr.destructor = destructor;
		vp->allocatedData = 1;
	}
	return vp;
}

/******************************************************************************/
#if BLD_FEATURE_FLOATING_POINT
/*
 *	Initialize a floating value.
 */

EjsVar *ejsCreateFloatVar(Ejs *ep, double value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_FLOAT;
		vp->floating = value;
	}
	return vp;
}

#endif
/******************************************************************************/
/*
 *	Initialize an integer value.
 */

EjsVar *ejsCreateIntegerVar(Ejs *ep, int value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_INT;
		vp->integer = value;
	}
	return vp;
}

/******************************************************************************/
#if BLD_FEATURE_INT64
/*
 *	Initialize a 64-bit integer value.
 */

EjsVar *ejsCreateInteger64Var(Ejs *ep, int64 value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_INT64;
		vp->integer64 = value;
	}
	return vp;
}

#endif /* BLD_FEATURE_INT64 */
/******************************************************************************/
/*
 *	Initialize an number variable. Type is defined by configure.
 */

EjsVar *ejsCreateNumberVar(Ejs *ep, EjsNum value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	mprAssert(vp);

	if (vp) {
		vp->type = BLD_FEATURE_NUM_TYPE_ID;
#if   BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_INT64
		vp->integer64 = value;
#elif BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_FLOAT
		vp->float = value;
#else
		vp->integer = value;
#endif
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize a (bare) JavaScript method. args and body can be null.
 */

EjsVar *ejsCreateMethodVar(Ejs *ep, const char *body, MprArray *args, int flags)
{
	EjsVar	*vp;
	int		i;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	mprAssert(vp);

	if (vp == 0) {
		return 0;
	}

	vp->type = EJS_TYPE_METHOD;

	vp->allocatedData = 1;

	vp->method.args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS);
	if (vp->method.args == 0) {
		mprAssert(vp->method.args);
		ejsFreeVar(ep, vp);
		return 0;
	}

	if (args) {
		for (i = 0; i < args->length; i++) {
			mprAddItem(vp->method.args, 
				mprStrdup(vp->method.args, mprGetItem(args, i)));
		}
	}
	vp->method.body = mprStrdup(vp->method.args, body);

	if (vp->method.body == 0) {
		ejsFreeVar(ep, vp);
		return 0;
	}
	vp->flags = flags;

	return vp;
}

/******************************************************************************/
/*
 *	Initialize an object variable. 
 */

EjsVar *ejsCreateObjVarInternal(EJS_LOC_DEC(ep, loc))
{
	EjsVar		*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_PASS(ep, loc));
	mprAssert(vp);

	if (vp) {
		vp->type = EJS_TYPE_OBJECT;
		vp->objectState = createObj(EJS_LOC_PASS(ep, loc));
		if (vp->objectState == 0) {
			ejsFreeVar(ep, vp);
			return 0;
		}
		vp->allocatedData = 1;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize a string value.
 */

EjsVar *ejsCreateStringVarInternal(EJS_LOC_DEC(ep, loc), const char *value)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_PASS(ep, loc));
	mprAssert(vp);

	if (vp) {
		vp->type = EJS_TYPE_STRING;
		vp->string = mprStrdupInternal(EJS_LOC_PASS(ep, loc), value);
		if (vp->string == 0) {
			ejsFreeVar(ep, vp);
			return 0;
		}
		vp->length = strlen(vp->string);
		vp->allocatedData = 1;
	}
	return vp;
}

/******************************************************************************/
/*
 *	Initialize a binary string value.
 */

EjsVar *ejsCreateBinaryStringVar(Ejs *ep, const uchar *value, int len)
{
	EjsVar	*vp;

	mprAssert(ep);

	vp = ejsAllocVar(EJS_LOC_ARGS(ep));
	if (vp) {
		vp->type = EJS_TYPE_STRING;
		vp->length = dupString(MPR_LOC_ARGS(ep), &vp->ustring, value, len);
		if (vp->length < 0) {
			ejsFreeVar(ep, vp);
			return 0;
		}
		vp->allocatedData = 1;
	}
	return vp;
}

/******************************************************************************/

void ejsSetClassName(Ejs *ep, EjsVar *vp, const char *name)
{
	EjsObj	*obj;

	if (vp == 0 || !ejsVarIsObject(vp) || vp->objectState == 0) {
		mprAssert(0);
		return;
	}
	obj = vp->objectState;

	if (obj->className) {
		mprFree(obj->className);
	}
	obj->className = mprStrdup(ep, name);
}

/******************************************************************************/

EjsVar *ejsDupVarInternal(EJS_LOC_DEC(ep, loc), EjsVar *src, 
	EjsCopyDepth copyDepth)
{
	EjsVar	*vp;

	vp = ejsAllocVar(EJS_LOC_PASS(ep, loc));
	if (vp == 0) {
		return 0;
	}

	vp->type = EJS_TYPE_UNDEFINED;

	return copyVar(EJS_LOC_PASS(ep, loc), vp, src, copyDepth);
}

/******************************************************************************/
/*
 *	Set a var to a new value
 */

EjsVar *ejsWriteVarInternal(EJS_LOC_DEC(ep, loc), EjsVar *dest, 
	const EjsVar *src, EjsCopyDepth copyDepth)
{
	mprAssert(dest);
	mprAssert(src);

	return copyVar(EJS_LOC_PASS(ep, loc), dest, src, copyDepth);
}

/******************************************************************************/
/*
 *	Set a var using a new bool value
 */

EjsVar *ejsWriteVarAsBoolean(Ejs *ep, EjsVar *dest, int value)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_BOOL;
	dest->boolean = value;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var using a new C Method
 */

EjsVar *ejsWriteVarAsCMethod(Ejs *ep, EjsVar *dest, EjsCMethod fn, 
	void *userData, int flags)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_CMETHOD;
	dest->cMethod.fn = fn;
	dest->cMethod.userData = userData;
	dest->flags = flags;
	dest->allocatedData = 0;

	return dest;
}

/******************************************************************************/
#if BLD_FEATURE_FLOATING_POINT
/*
 *	Set a var using a new float value
 */

EjsVar *ejsWriteVarAsFloat(Ejs *ep, EjsVar *dest, double value)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_FLOAT;
	dest->floating = value;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

#endif
/******************************************************************************/
/*
 *	Set a var using a new integer value
 */

EjsVar *ejsWriteVarAsInteger(Ejs *ep, EjsVar *dest, int value)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_INT;
	dest->integer = value;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
#if BLD_FEATURE_INT64
/*
 *	Set a var using a new integer value
 */

EjsVar *ejsWriteVarAsInteger64(Ejs *ep, EjsVar *dest, int64 value)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_INT64;
	dest->integer64 = value;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

#endif
/******************************************************************************/
/*
 *	Set a var using a new Method
 */

EjsVar *ejsWriteVarAsMethod(Ejs *ep, EjsVar *dest, const char *body,
	MprArray *args)
{
	EjsVar		**srcArgs, *arg;
	int			i;

	mprAssert(ep);
	mprAssert(dest);
	mprAssert(body);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->method.args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS);
	if (dest->method.args == 0) {
		return 0;
	}

	dest->type = EJS_TYPE_METHOD;

	if (args) {
		srcArgs = (EjsVar**) args->items;
		for (i = 0; i < args->length; i++) {
			arg = ejsDupVar(ep, srcArgs[i], EJS_SHALLOW_COPY);
			if (arg == 0) {
				return 0;
			}
			if (mprAddItem(dest->method.args, arg) < 0) {
				return 0;
			}
		}
	}

	dest->method.body = mprStrdup(dest->method.args, body);
	if (dest->method.body == 0) {
		return 0;
	}

	dest->allocatedData = 1;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var to null
 */

EjsVar *ejsWriteVarAsNull(Ejs *ep, EjsVar *dest)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_NULL;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var using a new number value
 */

EjsVar *ejsWriteVarAsNumber(Ejs *ep, EjsVar *dest, EjsNum value)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_NUM_VAR;
	dest->ejsNumber = value;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var using a new C Method
 */

EjsVar *ejsWriteVarAsStringCMethod(Ejs *ep, EjsVar *dest, EjsStringCMethod fn, 
	void *userData, int flags)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_CMETHOD;
	dest->cMethodWithStrings.fn = fn;
	dest->cMethodWithStrings.userData = userData;
	dest->flags = flags;
	dest->allocatedData = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var using a new string value
 */

EjsVar *ejsWriteVarAsStringInternal(EJS_LOC_DEC(ep, loc), EjsVar *dest, 
	const char *value)
{
	mprAssert(dest);
	mprAssert(value);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->string = mprStrdupInternal(EJS_LOC_PASS(ep, loc), value);
	if (dest->string == 0) {
		return 0;
	}

	dest->length = strlen(dest->string);

	dest->type = EJS_TYPE_STRING;
	dest->allocatedData = 1;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var using a new string value
 */

EjsVar *ejsWriteVarAsBinaryString(Ejs *ep, EjsVar *dest, const uchar *value,
	int len)
{
	mprAssert(dest);
	mprAssert(value);

	ejsClearVar(ep, dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->length = dupString(MPR_LOC_ARGS(ep), &dest->ustring, value, len);
	if (dest->length < 0) {
		return 0;
	}

	dest->type = EJS_TYPE_STRING;
	dest->allocatedData = 1;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Set a var to undefined
 */

EjsVar *ejsWriteVarAsUndefined(Ejs *ep, EjsVar *dest)
{
	mprAssert(dest);

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->type = EJS_TYPE_UNDEFINED;
	dest->allocatedData = 0;
	dest->flags = 0;

	return dest;
}

/******************************************************************************/
/*
 *	Convert a value to a text based representation of its value
 *	If you provide a format, you MUST ensure you know the type.
 *	Caller must free the result.
 */

char *ejsFormatVar(Ejs *ep, const char *fmt, EjsVar *vp)
{
	char	*buf, *src, *value, *allocValue;
	uchar	*ubuf;
	int		len;

	buf = 0;
	allocValue = 0;
	value = 0;

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
		value = "undefined";
		break;

	case EJS_TYPE_NULL:
		value = "null";
		break;

	case EJS_TYPE_PTR:
		if (fmt == NULL || *fmt == '\0') {
			len = mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, 
				"[Opaque Pointer %p]", vp->ptr.userPtr);
		} else {
			len = mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, vp->ptr);
		}
		goto done;

	case EJS_TYPE_BOOL:
		value = (vp->boolean) ? "true" : "false";
		break;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		if (fmt == NULL || *fmt == '\0') {
			fmt = "%f";
		}
		len = mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, vp->floating);
		goto done;
#endif

	case EJS_TYPE_INT:
		if (fmt == NULL || *fmt == '\0') {
			fmt = "%d";
		}
		mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, vp->integer);
		goto done;

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		if (fmt == NULL || *fmt == '\0') {
			fmt = "%Ld";
		}
		mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, vp->integer64);
		goto done;
#endif

	case EJS_TYPE_CMETHOD:
		value = "[C Method]";
		break;

	case EJS_TYPE_STRING_CMETHOD:
		value = "[C StringMethod]";
		break;

	case EJS_TYPE_METHOD:
		value = ejsVarToString(ep, vp);
		break;

	case EJS_TYPE_OBJECT:
		value = ejsVarToString(ep, vp);
		break;

	case EJS_TYPE_STRING:
		src = vp->string;
		mprAssert(src);

		if (fmt && *fmt && src) {
			mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, src);

		} else if (src == NULL) {
			buf = mprStrdup(ep, "null");

		} else {
			ubuf = (uchar*) buf;
			if (dupString(MPR_LOC_ARGS(ep), &ubuf, src, vp->length) < 0) {
				return mprStrdup(ep, "");
			}
			buf = (char*) ubuf;
		}
		break;

	default:
		mprAssert(0);
	}

	if (fmt == NULL || *fmt == '\0') {
		len = mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, "%s", value);
	} else {
		len = mprAllocSprintf(MPR_LOC_ARGS(ep), &buf, 0, fmt, value);
	}

done:
	if (allocValue) {
		mprFree(allocValue);
	}
	return buf;
}

/******************************************************************************/
/*
 *	Convert the variable to a boolean. Only for primitive types.
 */

int ejsVarToBoolean(EjsVar *vp)
{
	mprAssert(vp);

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
	case EJS_TYPE_STRING_CMETHOD:
	case EJS_TYPE_CMETHOD:
	case EJS_TYPE_METHOD:
		return 0;

	case EJS_TYPE_OBJECT:
		return (vp->objectState != NULL);

	case EJS_TYPE_PTR:
		return (vp->ptr.userPtr != NULL);

	case EJS_TYPE_BOOL:
		return vp->boolean;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		return (vp->floating != 0 && !ejsIsNan(vp->floating));
#endif

	case EJS_TYPE_INT:
		return (vp->integer != 0);

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		return (vp->integer64 != 0);
#endif

	case EJS_TYPE_STRING:
		return (vp->length > 0);
#if UNUSED
		if (strcmp(vp->string, "true") == 0 || 
				strcmp(vp->string, "TRUE") == 0) {
			return 1;

		} else if (strcmp(vp->string, "false") == 0 || 
				strcmp(vp->string, "FALSE") == 0) {
			return 0;

		} else {
			return atoi(vp->string);
		}
#endif
	}

	/* Not reached */
	return 0;
}

/******************************************************************************/
#if BLD_FEATURE_FLOATING_POINT
/*
 *	Convert the variable to a floating point number. Only for primitive types.
 */

double ejsVarToFloat(EjsVar *vp)
{
	mprAssert(vp);

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
	case EJS_TYPE_STRING_CMETHOD:
	case EJS_TYPE_CMETHOD:
	case EJS_TYPE_METHOD:
	case EJS_TYPE_OBJECT:
	case EJS_TYPE_PTR:
		return 0;

	case EJS_TYPE_BOOL:
		return (vp->boolean) ? 1.0 : 0.0;

	case EJS_TYPE_FLOAT:
		return vp->floating;

	case EJS_TYPE_INT:
		return (double) vp->integer;

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		return (double) vp->integer64;
#endif

	case EJS_TYPE_STRING:
		if (vp->length == 0) {
			return 0.0;
		} else {
			return atof(vp->string);
		}
	}

	/* Not reached */
	return 0;
}

#endif
/******************************************************************************/
/*
 *	Convert the variable to an Integer type. Only works for primitive types.
 */

int ejsVarToInteger(EjsVar *vp)
{
	mprAssert(vp);

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
	case EJS_TYPE_STRING_CMETHOD:
	case EJS_TYPE_CMETHOD:
	case EJS_TYPE_METHOD:
	case EJS_TYPE_OBJECT:
		return 0;

	case EJS_TYPE_BOOL:
		return (vp->boolean) ? 1 : 0;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		if (ejsIsNan(vp->floating)) {
			return 0;
		}
		return (int) vp->floating;
#endif

	case EJS_TYPE_INT:
		return vp->integer;

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		return (int) vp->integer64;
#endif

	case EJS_TYPE_STRING:
		if (vp->length == 0) {
			return 0;
		} else {
			return ejsParseInteger(vp->string);
		}
	}

	/* Not reached */
	return 0;
}

/******************************************************************************/
#if BLD_FEATURE_INT64
/*
 *	Convert the variable to an Integer64 type. Only works for primitive types.
 */

int64 ejsVarToInteger64(EjsVar *vp)
{
	mprAssert(vp);

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
	case EJS_TYPE_STRING_CMETHOD:
	case EJS_TYPE_CMETHOD:
	case EJS_TYPE_METHOD:
	case EJS_TYPE_OBJECT:
	case EJS_TYPE_PTR:
		return 0;

	case EJS_TYPE_BOOL:
		return (vp->boolean) ? 1 : 0;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		if (ejsIsNan(vp->floating)) {
			return 0;
		}
		return (int64) vp->floating;
#endif

	case EJS_TYPE_INT:
		return vp->integer;

	case EJS_TYPE_INT64:
		return vp->integer64;

	case EJS_TYPE_STRING:
		if (vp->length == 0) {
			return 0;
		} else {
			return ejsParseInteger64(vp->string);
		}
	}

	/* Not reached */
	return 0;
}

#endif /* BLD_FEATURE_INT64 */
/******************************************************************************/
/*
 *	Convert the variable to a number type. Only works for primitive types.
 */

EjsNum ejsVarToNumber(EjsVar *vp)
{
#if BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_INT64
	return ejsVarToInteger64(vp);
#elif BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_FLOAT
	return ejsVarToFloat(vp);
#else 
	return ejsVarToInteger(vp);
#endif
}

/******************************************************************************/
/*
 *	Convert a var to a string. Store the result in ep->castTemp. If allocated
 *	set ep->castAlloc to TRUE. Caller must NOT free the result.
 */

char *ejsVarToString(Ejs *ep, EjsVar *vp)
{
	MprBuf	*bp;
	char	numBuf[16];
	int		len, i;

	if (ep->castAlloc) {
		mprFree(ep->castTemp);
	}
	ep->castTemp = 0;
	ep->castAlloc = 0;

	switch (vp->type) {
	case EJS_TYPE_UNDEFINED:
		ep->castTemp = "undefined";
		break;

	case EJS_TYPE_NULL:
		ep->castTemp = "null";
		break;

	case EJS_TYPE_PTR:
		len = mprAllocSprintf(MPR_LOC_ARGS(ep), &ep->castTemp, 0, 
			"[Opaque Pointer %p]", vp->ptr.userPtr);
		ep->castAlloc = 1;
		break;

	case EJS_TYPE_BOOL:
		if (vp->boolean) {
			ep->castTemp = "true";
		} else {
			ep->castTemp = "false";
		}
		break;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		len = mprAllocSprintf(MPR_LOC_ARGS(ep), &ep->castTemp, 0, 
			"%f", vp->floating);
		ep->castAlloc = 1;
		break;
#endif

	case EJS_TYPE_INT:
		mprItoa(numBuf, sizeof(numBuf), vp->integer);
		ep->castTemp = mprStrdup(ep, numBuf);
		ep->castAlloc = 1;
		break;

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		mprAllocSprintf(MPR_LOC_ARGS(ep), &ep->castTemp, 0, 
			"%Ld", vp->integer64);
		ep->castAlloc = 1;
		break;
#endif

	case EJS_TYPE_CMETHOD:
		ep->castTemp = "[C Method]";
		break;

	case EJS_TYPE_STRING_CMETHOD:
		ep->castTemp = "[C StringMethod]";
		break;

	case EJS_TYPE_METHOD:
		bp = mprCreateBuf(ep, 0, 0);
		mprPutStringToBuf(bp, "function (");
		for (i = 0; i < vp->method.args->length; i++) {
			mprPutStringToBuf(bp, vp->method.args->items[i]);
			if ((i + 1) < vp->method.args->length) {
				mprPutStringToBuf(bp, ", ");
			}
		}
		mprPutStringToBuf(bp, ") {");
		mprPutStringToBuf(bp, vp->method.body);
		mprPutStringToBuf(bp, "}");
		mprAddNullToBuf(bp);
		ep->castTemp = mprStealBuf(ep, bp);
		ep->castAlloc = 1;
		mprFree(bp);
		break;

	case EJS_TYPE_OBJECT:
		if (ejsRunMethod(ep, vp, "toString", 0) < 0) {
			return mprStrdup(ep, "[object Object]");
		}
		ep->castTemp = mprStrdup(ep, ep->result->string);
		ep->castAlloc = 1;
		break;

	case EJS_TYPE_STRING:
		if (vp->string == 0) {
			ep->castTemp = "null";
		} else {
			ep->castTemp = vp->string;
		}
		break;

	default:
		mprAssert(0);
	}

	mprAssert(ep->castTemp);
	return ep->castTemp;
}

/******************************************************************************/

char *ejsVarToStringEx(Ejs *ep, EjsVar *vp, bool *alloc)
{
	char	*str;

	mprAssert(alloc);

	str = ejsVarToString(ep, vp);
	*alloc = ep->castAlloc;
	ep->castAlloc = 0;
	ep->castTemp = 0;
	return str;
}

/******************************************************************************/
/*
 *	Parse a string based on formatting instructions and intelligently 
 *	create a variable.
 *
 *	Float format: [+|-]DIGITS][DIGITS][(e|E)[+|-]DIGITS]
 */

EjsVar *ejsParseVar(Ejs *ep, const char *buf, EjsType preferredType)
{
	EjsType			type;
	const char		*cp;
	int				isHex;

	mprAssert(buf);

	type = preferredType;

	if (preferredType == EJS_TYPE_UNDEFINED) {
		isHex = 0;
		if (*buf == '-' || *buf == '+') {
			type = EJS_NUM_VAR;

		} else if (!isdigit((int) *buf)) {
			if (strcmp(buf, "true") == 0 || strcmp(buf, "false") == 0) {
				type = EJS_TYPE_BOOL;
			} else {
				type = EJS_TYPE_STRING;
			}

		} else if (isdigit((int) *buf)) {
			type = EJS_NUM_VAR;
			cp = buf;
			if (*cp && tolower(cp[1]) == 'x') {
				cp = &cp[2];
				isHex = 1;
				for (cp = buf; *cp; cp++) {
					if (! isxdigit((int) *cp)) {
						break;
					}
				}
			} else {
#if BLD_FEATURE_FLOATING_POINT
				/* Could be integer or float */
				for (cp = buf; *cp; cp++) {
					if (! isdigit((int) *cp)) {
						int c = tolower(*cp);
						if (c == '.' || c == 'e' || c == 'f') {
							type = EJS_TYPE_FLOAT;
							break;
						}
					}
				}
#endif
			}
		}
	}

	switch (type) {
	case EJS_TYPE_OBJECT:
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
	case EJS_TYPE_PTR:
	default:
		break;

	case EJS_TYPE_BOOL:
		return ejsCreateBoolVar(ep, ejsParseBoolean(buf));

	case EJS_TYPE_INT:
		return ejsCreateIntegerVar(ep, ejsParseInteger(buf));

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		return ejsCreateInteger64Var(ep, ejsParseInteger64(buf));
#endif

	case EJS_TYPE_STRING:
		if (strcmp(buf, "null") == 0) {
			return ejsCreateNullVar(ep);

		} else if (strcmp(buf, "undefined") == 0) {
			return ejsCreateUndefinedVar(ep);
		} 
			
		return ejsCreateStringVar(ep, buf);

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		return ejsCreateFloatVar(ep, atof(buf));
#endif

	}
	return ejsCreateUndefinedVar(ep);
}

/******************************************************************************/
/*
 *	Convert the variable to a number type. Only works for primitive types.
 */

bool ejsParseBoolean(const char *s)
{
	if (s == 0 || *s == '\0') {
		return 0;
	}
	if (strcmp(s, "false") == 0 || strcmp(s, "FALSE") == 0) {
		return 0;
	}
	return 1;
}

/******************************************************************************/
/*
 *	Convert the variable to a number type. Only works for primitive types.
 */

EjsNum ejsParseNumber(const char *s)
{
#if BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_INT64
	return ejsParseInteger64(s);
#elif BLD_FEATURE_NUM_TYPE_ID == EJS_TYPE_FLOAT
	return ejsParseFloat(s);
#else 
	return ejsParseInteger(s);
#endif
}

/******************************************************************************/
#if BLD_FEATURE_INT64
/*
 *	Convert the string buffer to an Integer64.
 */

int64 ejsParseInteger64(const char *str)
{
	const char	*cp;
	int64		num64;
	int			radix, c, negative;

	mprAssert(str);

	cp = str;
	num64 = 0;
	negative = 0;

	if (*cp == '-') {
		cp++;
		negative = 1;
	} else if (*cp == '+') {
		cp++;
	}

	/*
	 *	Parse a number. Observe hex and octal prefixes (0x, 0)
	 */
	if (*cp != '0') {
		/* 
		 *	Normal numbers (Radix 10)
		 */
		while (isdigit((int) *cp)) {
			num64 = (*cp - '0') + (num64 * 10);
			cp++;
		}
	} else {
		cp++;
		if (tolower(*cp) == 'x') {
			cp++;
			radix = 16;
			while (*cp) {
				c = tolower(*cp);
				if (isdigit(c)) {
					num64 = (c - '0') + (num64 * radix);
				} else if (c >= 'a' && c <= 'f') {
					num64 = (c - 'a' + 10) + (num64 * radix);
				} else {
					break;
				}
				cp++;
			}

		} else{
			radix = 8;
			while (*cp) {
				c = tolower(*cp);
				if (isdigit(c) && c < '8') {
					num64 = (c - '0') + (num64 * radix);
				} else {
					break;
				}
				cp++;
			}
		}
	}

	if (negative) {
		return 0 - num64;
	}
	return num64;
}

#endif /* BLD_FEATURE_INT64 */
/******************************************************************************/
/*
 *	Convert the string buffer to an Integer.
 */

int ejsParseInteger(const char *str)
{
	const char	*cp;
	int			num;
	int			radix, c, negative;

	mprAssert(str);

	cp = str;
	num = 0;
	negative = 0;

	if (*cp == '-') {
		cp++;
		negative = 1;
	} else if (*cp == '+') {
		cp++;
	}

	/*
	 *	Parse a number. Observe hex and octal prefixes (0x, 0)
	 */
	if (*cp != '0') {
		/* 
		 *	Normal numbers (Radix 10)
		 */
		while (isdigit((int) *cp)) {
			num = (*cp - '0') + (num * 10);
			cp++;
		}
	} else {
		cp++;
		if (tolower(*cp) == 'x') {
			cp++;
			radix = 16;
			while (*cp) {
				c = tolower(*cp);
				if (isdigit(c)) {
					num = (c - '0') + (num * radix);
				} else if (c >= 'a' && c <= 'f') {
					num = (c - 'a' + 10) + (num * radix);
				} else {
					break;
				}
				cp++;
			}

		} else{
			radix = 8;
			while (*cp) {
				c = tolower(*cp);
				if (isdigit(c) && c < '8') {
					num = (c - '0') + (num * radix);
				} else {
					break;
				}
				cp++;
			}
		}
	}

	if (negative) {
		return 0 - num;
	}
	return num;
}

/******************************************************************************/
#if BLD_FEATURE_FLOATING_POINT
/*
 *	Convert the string buffer to an Floating.
 */

double ejsParseFloat(const char *str)
{
	return atof(str);
}

/******************************************************************************/

int ejsIsNan(double f)
{
#if WIN
	return _isnan(f);
#elif VXWORKS
	/* FUTURE */
	return (0);
#else
	return (f == FP_NAN);
#endif
}
/******************************************************************************/

int ejsIsInfinite(double f)
{
#if WIN
	return !_finite(f);
#elif VXWORKS
	/* FUTURE */
	return (0);
#else
	return (f == FP_INFINITE);
#endif
}

#endif /* BLD_FEATURE_FLOATING_POINT */

/******************************************************************************/
/*
 *	Single point of control for all assignment to properties.
 * 
 *	Copy an objects core value (only). This preserves the destination object's 
 *	name. This implements copy by reference for objects and copy by value for 
 *	strings and other types. Caller must free dest prior to calling.
 */

static EjsVar *copyVar(EJS_LOC_DEC(ep, loc), EjsVar *dest, const EjsVar *src, 
	EjsCopyDepth copyDepth)
{
	Ejs				*ejsContext;
	EjsObj			*srcObj;
	EjsProperty		*destp;
	const char		**srcArgs;
	char			*str;
	int				i;

	mprAssert(dest);
	mprAssert(src);

	if (dest == src) {
		return dest;
	}

	if (dest->type != EJS_TYPE_UNDEFINED) {
		ejsClearVar(ep, dest);
	}

	dest->allocatedData = 0;

	switch (src->type) {
	default:
	case EJS_TYPE_UNDEFINED:
	case EJS_TYPE_NULL:
		break;

	case EJS_TYPE_BOOL:
		dest->boolean = src->boolean;
		break;

	case EJS_TYPE_PTR:
		dest->ptr = src->ptr;
		if (dest->ptr.destructor) {
			dest->allocatedData = 1;
		}
		break;

	case EJS_TYPE_STRING_CMETHOD:
		dest->cMethodWithStrings = src->cMethodWithStrings;
		break;

	case EJS_TYPE_CMETHOD:
		dest->cMethod = src->cMethod;
		break;

#if BLD_FEATURE_FLOATING_POINT
	case EJS_TYPE_FLOAT:
		dest->floating = src->floating;
		break;
#endif

	case EJS_TYPE_INT:
		dest->integer = src->integer;
		break;

#if BLD_FEATURE_INT64
	case EJS_TYPE_INT64:
		dest->integer64 = src->integer64;
		break;
#endif

	case EJS_TYPE_OBJECT:
		if (copyDepth == EJS_SHALLOW_COPY) {

			/*
			 *	If doing a shallow copy and the src object is from the same
			 *	interpreter, or we are copying from the master interpreter, or
			 *	we are using a shared slab, then we can do a shallow copy.
			 *	Otherwise, we must do a deep copy.
			 */
			srcObj = src->objectState;
			if (srcObj->ejs == ep || srcObj->ejs == ep->service->master ||
					(ep->flags & EJS_FLAGS_SHARED_SLAB)) {
				dest->objectState = src->objectState;
				dest->allocatedData = 1;
				break;
			}
		}

		/*
		 *	Doing a deep or recursive deep. Can get here if doing a shallow
		 *	copy and the object is from another non-master interpeter and not
		 *	using a shared slab.
		 *
		 *	We must make sure the data is allocated using the right memory
		 *	context.  It must be the same as the destination parent object.
		 *	Otherwise, when we free the property memory, the parent may
		 *	have a dangling pointer.
		 */
		if (dest->isProperty) {
			destp = ejsGetPropertyPtr(dest);
			if (destp->parentObj == 0) {
				ejsContext = ep;

			} else {
				mprAssert(destp->parentObj);
				ejsContext = destp->parentObj->ejs;
				mprAssert(ejsContext);
			}

		} else {
			ejsContext = ep;
		}

		dest->objectState = createObj(EJS_LOC_PASS(ejsContext, loc));
		if (dest->objectState == 0) {
			/* Memory Error */
			return 0;
		}

		dest->objectState->baseClass = src->objectState->baseClass;
		dest->objectState->methods = src->objectState->methods;
		dest->objectState->noConstructor = src->objectState->noConstructor;
		dest->objectState->objName = 
			mprStrdup(ejsContext, src->objectState->objName);

		if (dest->objectState->objName == 0) {
			return 0;
		}

		if (ejsCopyProperties(ep, dest, src, copyDepth) == 0) {
			return 0;
		}
		dest->allocatedData = 1;
		break;

	case EJS_TYPE_METHOD:
		dest->method.args = mprCreateItemArray(ep, EJS_INC_ARGS, 
			EJS_MAX_ARGS);
		if (dest->method.args == 0) {
			return 0;
		}
		dest->allocatedData = 1;
		if (src->method.args) {
			srcArgs = (const char**) src->method.args->items;
			for (i = 0; i < src->method.args->length; i++) {
				str = mprStrdupInternal(EJS_LOC_PASS(dest->method.args, 
					loc), srcArgs[i]);
				if (str == 0) {
					mprFree(dest->method.args);
					dest->method.args = 0;
					return 0;
				}
				if (mprAddItem(dest->method.args, str) < 0) {
					mprFree(dest->method.args);
					dest->method.args = 0;
					return 0;
				}
			}
		}
		dest->method.body = mprStrdup(dest->method.args, src->method.body);
		if (dest->method.body == 0) {
			mprFree(dest->method.args);
			dest->method.args = 0;
			return 0;
		}
		dest->callsSuper = src->callsSuper;
		break;

	case EJS_TYPE_STRING:
		dest->length = src->length;
		if (src->string) {
			/* Shallow, deep or recursive deep */
			dest->length = dupString(MPR_LOC_PASS(ep, loc), &dest->ustring, 
				src->ustring, src->length);
			if (dest->length < 0) {
				return 0;
			}
			dest->allocatedData = 1;

		} else {
			dest->string = src->string;
			dest->allocatedData = 0;
		}
		break;
	}

	dest->type = src->type;
	dest->flags = src->flags;
	dest->isArray = src->isArray;

	return dest;
}

/******************************************************************************/
/*
 *	Copy all properies in an object. Must preserve property order
 */

EjsVar *ejsCopyProperties(Ejs *ep, EjsVar *dest, const EjsVar *src, 
	EjsCopyDepth copyDepth)
{
	EjsProperty	*srcProp, *destProp, *last, *next;
	int			propertyIndex;
	
	srcProp = ejsGetFirstProperty(src, EJS_ENUM_ALL);
	while (srcProp) {
		next = ejsGetNextProperty(srcProp, EJS_ENUM_ALL);
		if (srcProp->visited) {
			srcProp = next;
			continue;
		}

		/*
		 *	This finds the last variable in the hash chain
		 *	FUTURE OPT. This is slow. If used double link, we could locate the
		 *	tail more easily.
		 */
		destProp = hashLookup(dest->objectState, srcProp->name,  
			&propertyIndex, &last);
		mprAssert(destProp == 0);

		destProp = allocProperty(ep, dest, srcProp->name, propertyIndex, last);
		if (destProp == 0) {
			mprAssert(destProp);
			return 0;
		}

		/*
		 *	Recursively copy the object. If DEEP_COPY, then we
		 *	will do a shallow copy of the object contents. If
		 *	RECURSIVE_DEEP, then we do a deep copy at all levels.
		 */
		srcProp->visited = 1;

		if (copyVar(EJS_LOC_ARGS(ep), ejsGetVarPtr(destProp), 
				ejsGetVarPtr(srcProp), 
				(copyDepth == EJS_DEEP_COPY) ? EJS_SHALLOW_COPY : copyDepth) 
				== 0) {
			return 0;
		}
		srcProp->visited = 0;

		srcProp = next;
	}
	return dest;
}

/******************************************************************************/
/********************************** Properties ********************************/
/******************************************************************************/
/*
 *	Create a property in an object and return a pointer to it. If the property
 *	already exists then just return a pointer to it (no error).
 *	To test for existance of a property, use GetProperty
 */

static EjsProperty *hashLookup(EjsObj *obj, const char *property, 
	int *propertyIndex, EjsProperty **hashTail)
{
	EjsProperty	*prop, *last;
	int			index;

	mprAssert(obj);
	mprAssert(property);

	if (obj == 0 || property == 0 || *property == '\0') {
		mprAssert(0);
		return 0;
	}

	/*
	 *	Find the property in the hash chain if it exists
 	 */
	index = hash(property);
	prop = obj->propertyHash[index];
	for (last = 0; prop != 0; last = prop, prop = prop->hashNext) {
		if (prop->name[0] == property[0] && 
				strcmp(prop->name, property) == 0) {
			break;
		}
	}
	if (propertyIndex) {
		*propertyIndex = index;
	}
	if (hashTail) {
		*hashTail = last;
	}

	return prop;
}

/******************************************************************************/
/*
 *	Create a property in an object and return a pointer to it. If the property
 *	already exists then just return a pointer to it (no error). If the property
 *	does not exist, create an undefined variable. To test for existance of a 
 *	property, use GetProperty.
 */

EjsProperty *ejsCreateSimpleProperty(Ejs *ep, EjsVar *op, const char *property)
{
	EjsProperty	*prop, *last;
	int			propertyIndex;

	if (op == 0 || op->type != EJS_TYPE_OBJECT || property == 0 || 
			*property == '\0') {
		mprAssert(0);
		return 0;
	}

	/*
	 *	Find the property in the hash chain if it exists
 	 */
	prop = hashLookup(op->objectState, property,  &propertyIndex, &last);

	if (prop == 0) {
		/*
		 *	Create a new property
		 */
		prop = allocProperty(ep, op, property, propertyIndex, last);
		if (prop == 0) {
			mprAssert(prop == 0);
			return 0;
		}
	}
	return prop;
}

/******************************************************************************/
/*
 *	Create a property in an object and return a pointer to it. If the property
 *	already exists then just return a pointer to it (no error).
 *	To test for existance of a property, use GetProperty
 */

EjsProperty *ejsCreateSimpleNonUniqueProperty(Ejs *ep, EjsVar *op, 
	const char *property)
{
	EjsProperty	*prop, *last;
	int			propertyIndex;

	if (op == 0 || op->type != EJS_TYPE_OBJECT || property == 0 || 
			*property == '\0') {
		mprAssert(0);
		return 0;
	}

	/*
	 *	Find end of chain
	 */
	propertyIndex = hash(property);
	prop = op->objectState->propertyHash[propertyIndex];
	for (last = 0; prop != 0; last = prop, prop = prop->hashNext) {
		;
	}

	return allocProperty(ep, op, property, propertyIndex, last);
}

/******************************************************************************/
/*
 *	Find a property in an object and return a pointer to it.
 *	This does NOT traverse base classes.
 */

EjsProperty *ejsGetSimpleProperty(Ejs *ep, EjsVar *op, const char *property)
{
	mprAssert(op);
	mprAssert(op->type == EJS_TYPE_OBJECT);
	mprAssert(property && *property);

	/* 
	 *	This is an internal API. It has very little checking.
	 */
	return hashLookup(op->objectState, property,  0, 0);
}

/******************************************************************************/

/*
 *	NOTE: There is no ejsSetSimpleProperty as all the ejsSetProperty routines
 *	operate only on the instance and don't follow base classes. ie. there is
 *	no simple version required. However, there is a ejsSetBaseProperty routine
 *	that will follow base classes and is used to set static properties in base
 *	classes
 */

/******************************************************************************/
/******************************* Property Access ******************************/
/******************************************************************************/
/*
 *	The property get routines follow base classes and utilize the propery 
 *	method access routines. The property set routines do not follow base
 *	classes. The property ejsSetBase... routines do follow base classes.
 */

/*
 *	Find a property in an object and return a pointer to it.
 *	This follows base classes.
 */

EjsProperty *ejsGetProperty(Ejs *ep, EjsVar *op, const char *property)
{
	EjsVar		*vp, *newOp;
	int			maxBaseClasses = 50;

	do {
		if (op->type != EJS_TYPE_OBJECT) {
			mprAssert(op->type == EJS_TYPE_OBJECT);
			return 0;
		}
		mprAssert(op->objectState);

		vp = ejsGetPropertyMethod(ep, op, property);
		if (vp != 0) {
			/*
			 *	Found
			 */
			break;
		}

		newOp = op->objectState->baseClass;
		if (newOp == 0) {
			if (op->objectState != ep->objectClass->objectState) {
				newOp = ep->objectClass;
			}
		}
		op = newOp;

		/*
		 *	A little bit of sanity checking
		 */
		if (--maxBaseClasses <= 0) {
			mprAssert(maxBaseClasses > 0);
			break;
		}

	} while (op);

	return ejsGetPropertyPtr(vp);
}

/******************************************************************************/
/*
 *	Get the property's variable. Optionally create if it does not exist.
 */

EjsVar *ejsGetPropertyAsVar(Ejs *ep, EjsVar *vp, const char *property)
{
	return ejsGetVarPtr(ejsGetProperty(ep, vp, property));
}

/******************************************************************************/
/*
 *	Get the property's value as a binary string. 
 */

const uchar *ejsGetPropertyAsBinaryString(Ejs *ep, EjsVar *obj, 
	const char *property, int *length)
{
	EjsVar			*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}

	if (vp->type == EJS_TYPE_STRING) {
		if (length) {
			*length = vp->length;
		}
		return vp->ustring;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Get the property's value as a string.
 */

const char *ejsGetPropertyAsString(Ejs *ep, EjsVar *obj, const char *property)
{
	EjsVar			*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}

	if (vp->type == EJS_TYPE_STRING) {
		return vp->string;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Get the property's value as a number.
 */

BLD_FEATURE_NUM_TYPE ejsGetPropertyAsNumber(Ejs *ep, EjsVar *obj, 
	const char *property)
{
	EjsVar		*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}

	return ejsVarToNumber(vp);
}

/******************************************************************************/
/*
 *	Get the property's value as a integer.
 */

int ejsGetPropertyAsInteger(Ejs *ep, EjsVar *obj, const char *property)
{
	EjsVar		*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}

	return ejsVarToInteger(vp);
}

/******************************************************************************/
/*
 *	Get the property's value as a boolean.
 */

bool ejsGetPropertyAsBoolean(Ejs *ep, EjsVar *obj, const char *property)
{
	EjsVar		*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}

	return ejsVarToBoolean(vp);
}

/******************************************************************************/
/*
 *	Get the property's value as a pointer.
 */

void *ejsGetPropertyAsPtr(Ejs *ep, EjsVar *obj, const char *property)
{
	EjsVar		*vp;

	vp = ejsGetVarPtr(ejsGetProperty(ep, obj, property));
	if (vp == 0 || ejsVarIsUndefined(vp)) {
		return 0;
	}
	if (vp->type == EJS_TYPE_PTR) {
		return vp->ptr.userPtr;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Create a property in the object. This will override any base class
 *	properties.
 *
 *	MOB -- need to spell out the difference between ejsSetProperty and
 *	ejsCreateProperty.
 */

EjsProperty *ejsCreateProperty(Ejs *ep, EjsVar *obj, const char *property)
{
	EjsVar	*vp;

	vp = ejsCreatePropertyMethod(ep, obj, property);
	return ejsGetPropertyPtr(vp);
}

/******************************************************************************/
/*
 *	Set a property's variable value. Create the property if it does not exist.
 *	This routine DOES follow base classes.
 */

EjsProperty *ejsSetBaseProperty(Ejs *ep, EjsVar *op, const char *property, 
	const EjsVar *value)
{
	EjsVar		*vp, *newOp;
	int			maxBaseClasses = 50;

	do {
		if (op->type != EJS_TYPE_OBJECT) {
			mprAssert(op->type == EJS_TYPE_OBJECT);
			return 0;
		}
		mprAssert(op->objectState);

		vp = ejsGetPropertyMethod(ep, op, property);
		if (vp != 0) {
			/*
			 *	Found
			 */
			vp = ejsSetPropertyMethod(ep, op, property, value);
			break;
		}

		newOp = op->objectState->baseClass;
		if (newOp == 0) {
			if (op->objectState != ep->objectClass->objectState) {
				newOp = ep->objectClass;
			}
		}
		op = newOp;

		/*
		 *	A little bit of sanity checking
		 */
		if (--maxBaseClasses <= 0) {
			mprAssert(maxBaseClasses > 0);
			break;
		}

	} while (op);

	return ejsGetPropertyPtr(vp);
}

/******************************************************************************/
/*
 *	Set a property's variable value. Create the property if it does not exist.
 *	This does NOT follow base classes. Okay when updating instance properties,
 *	but not for class (static) properties. This does a shallow copy which 
 *	will copy references.
 */

EjsProperty *ejsSetProperty(Ejs *ep, EjsVar *obj, const char *property, 
	const EjsVar *value)
{
	EjsVar		*vp;

	vp = ejsSetPropertyMethod(ep, obj, property, value);

	return ejsGetPropertyPtr(vp);
}

/******************************************************************************/
/*
 *	Set a property's variable value by assigning the given value. The caller
 *	must NOT free value as it is assigned directly into the property's value.
 */

EjsProperty *ejsSetPropertyAndFree(Ejs *ep, EjsVar *obj, 
	const char *property, EjsVar *value)
{
	EjsVar		*vp;

	vp = ejsSetPropertyMethod(ep, obj, property, value);

	ejsFree(ep, value, EJS_SLAB_VAR);
	
	return ejsGetPropertyPtr(vp);
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToCMethod(Ejs *ep, EjsVar *vp, const char *prop, 
	EjsCMethod fn, void *userData, int flags)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_CMETHOD);
	v.cMethod.fn = fn;
	v.cMethod.userData = userData;
	v.flags = flags;

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToBoolean(Ejs *ep, EjsVar *vp, const char *prop, 
	int value)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_BOOL);
	v.boolean = value;

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/
#if BLD_FEATURE_FLOATING_POINT

EjsProperty *ejsSetPropertyToFloat(Ejs *ep, EjsVar *vp, const char *prop, 
	double value)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_FLOAT);
	v.floating = value;

	return ejsSetProperty(ep, vp, prop, &v);
}

#endif
/******************************************************************************/

EjsProperty *ejsSetPropertyToInteger(Ejs *ep, EjsVar *vp, const char *prop, 
	int value)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_INT);
	v.integer = value;

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/
#if BLD_FEATURE_INT64

EjsProperty *ejsSetPropertyToInteger64(Ejs *ep, EjsVar *vp, const char *prop, 
	int64 value)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_INT64);
	v.integer64 = value;

	return ejsSetProperty(ep, vp, prop, &v);
}

#endif
/******************************************************************************/

EjsProperty *ejsSetPropertyToNull(Ejs *ep, EjsVar *vp, const char *prop)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_NULL);

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToMethod(Ejs *ep, EjsVar *vp, const char *prop, 
	const char *body, MprArray *args, int flags)
{
	return ejsSetPropertyAndFree(ep, vp, prop, 
		ejsCreateMethodVar(ep, body, args, flags));
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToNumber(Ejs *ep, EjsVar *vp, const char *prop, 
	EjsNum value)
{
	return ejsSetPropertyAndFree(ep, vp, prop, ejsCreateNumberVar(ep, value));
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToStringCMethod(Ejs *ep, EjsVar *vp, 
	const char *prop, EjsStringCMethod fn, void *userData, int flags)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_STRING_CMETHOD);
	v.cMethodWithStrings.fn = fn;
	v.cMethodWithStrings.userData = userData;
	v.flags = flags;

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToString(Ejs *ep, EjsVar *vp, const char *prop, 
	const char *value)
{
	EjsProperty		*pp;
	EjsVar			v;

	ejsInitVar(&v, EJS_TYPE_STRING);

	/* FUTURE OPT */
	v.string = mprStrdupInternal(EJS_LOC_ARGS(ep), value);
	if (v.string == 0) {
		return 0;
	}
	v.length = strlen(v.string);
	v.allocatedData = 1;

	pp = ejsSetProperty(ep, vp, prop, &v);

	mprFree(v.string);

	return pp;
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToBinaryString(Ejs *ep, EjsVar *vp, 
	const char *prop, const uchar *value, int len)
{
	EjsProperty		*pp;
	EjsVar			v;

	ejsInitVar(&v, EJS_TYPE_STRING);

	/* FUTURE OPT */
	v.length = dupString(MPR_LOC_ARGS(ep), &v.ustring, value, len);
	if (v.length < 0) {
		return 0;
	}
	v.allocatedData = 1;

	pp = ejsSetProperty(ep, vp, prop, &v);

	mprFree(v.ustring);

	return pp;
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToUndefined(Ejs *ep, EjsVar *vp, const char *prop)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_UNDEFINED);

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/

EjsProperty	*ejsSetPropertyToPtr(Ejs *ep, EjsVar *vp, const char *prop, 
	void *ptr, EjsDestructor destructor)
{
	EjsVar		v;

	ejsInitVar(&v, EJS_TYPE_PTR);
	v.ptr.userPtr = ptr;
	v.ptr.destructor = destructor;
	v.allocatedData = 1;

	return ejsSetProperty(ep, vp, prop, &v);
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToNewObj(Ejs *ep, EjsVar *vp, const char *prop,
	const char *className, MprArray *args)
{
	return ejsSetPropertyAndFree(ep, vp, prop, 
		ejsCreateObjUsingArgv(ep, 0, className, args));
}

/******************************************************************************/

EjsProperty *ejsSetPropertyToObj(Ejs *ep, EjsVar *op, const char *prop)
{
	return ejsSetPropertyAndFree(ep, op, prop, ejsCreateObjVar(ep));
}

/******************************************************************************/
/*
 *	Convenience routines
 */

EjsVar *ejsSetPropertyToObjAsVar(Ejs *ep, EjsVar *op, const char *prop)
{
	return ejsGetVarPtr(ejsSetPropertyToObj(ep, op, prop));
}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/*
 *	Create a script method
 */

EjsProperty *ejsDefineMethod(Ejs *ep, EjsVar *vp, const char *prop, 
	const char *body, MprArray *args)
{
	if (vp == 0) {
		vp = ejsGetGlobalObj(ep);
	}
	return ejsSetPropertyToMethod(ep, vp, prop, body, args, 0);
}

/******************************************************************************/
/*
 *	Create a C language method
 */

EjsProperty *ejsDefineCMethod(Ejs *ep, EjsVar *vp, const char *prop, 
	EjsCMethod fn, int flags)
{
	if (vp == 0) {
		vp = ejsGetGlobalObj(ep);
	}
	return ejsSetPropertyToCMethod(ep, vp, prop, fn, 0, flags);
}

/******************************************************************************/
/*
 *	Define accessors
 */

EjsProperty *ejsDefineAccessors(Ejs *ep, EjsVar *vp, const char *prop, 
	const char *getBody, const char *setBody)
{
	EjsProperty	*pp;
	MprArray	*args;
	char		*propName;

	if (vp == 0) {
		vp = ejsGetGlobalObj(ep);
	}

	if (ejsSetPropertyToMethod(ep, vp, prop, getBody, 0, EJS_GET_ACCESSOR) < 0){
		ejsMemoryError(ep);
		return 0;
	}

	/* MOB -- OPT to use SLAB */
	/* MOB -- need to encapsulate this logic */

	if (mprAllocStrcat(MPR_LOC_ARGS(ep), &propName, EJS_MAX_ID+5, 0, 
			"-set-", prop, 0) < 0) {
		ejsMemoryError(ep);
		return 0;
	}

	args = mprCreateItemArray(ep, EJS_INC_ARGS, EJS_MAX_ARGS);
	mprAddItem(args, mprStrdup(args, "value"));

	pp = ejsSetPropertyToMethod(ep, vp, propName, setBody, args, 
		EJS_SET_ACCESSOR);
	mprFree(propName);

	if (pp == 0) {
		ejsMemoryError(ep);
		return 0;
	}

	return pp;
}

/******************************************************************************/
/*
 *	Define C accessors
 */

EjsProperty *ejsDefineCAccessors(Ejs *ep, EjsVar *vp, const char *prop, 
	EjsCMethod getFn, EjsCMethod setFn, int flags)
{
	EjsProperty	*pp;
	char		*propName;

	if (vp == 0) {
		vp = ejsGetGlobalObj(ep);
	}
	pp = ejsSetPropertyToCMethod(ep, vp, prop, getFn, 0, 
			flags | EJS_GET_ACCESSOR);
	if (pp == 0) {
		ejsMemoryError(ep);
		return 0;
	}

	/* MOB -- OPT to use SLAB */
	if (mprAllocStrcat(MPR_LOC_ARGS(ep), &propName, EJS_MAX_ID + 5, 0, 
			"-set-", prop, 0) < 0) {
		ejsMemoryError(ep);
		return 0;
	}
	pp = ejsSetPropertyToCMethod(ep, vp, propName, setFn, 0, 
		flags | EJS_SET_ACCESSOR);
	mprFree(propName);

	if (pp == 0) {
		ejsMemoryError(ep);
		return 0;
	}
	return pp;
}

/******************************************************************************/
/*
 *	Create a C language method with string arguments
 */

EjsProperty *ejsDefineStringCMethod(Ejs *ep, EjsVar *vp, const char *prop, 
	EjsStringCMethod fn, int flags)
{
	if (vp == 0) {
		vp = ejsGetGlobalObj(ep);
	}
	return ejsSetPropertyToStringCMethod(ep, vp, prop, fn, 0, flags);
}

/******************************************************************************/

void ejsSetCMethodUserData(EjsVar *obj, void *userData)
{
	/*
	 *	This is a little dirty. We rely on the userData being in the same
	 *	place in the var structure.
	 */
	obj->cMethod.userData = userData;
}

/******************************************************************************/

void ejsSetVarFlags(EjsVar *obj, int flags)
{
	obj->flags = flags;
}

/******************************************************************************/

void *ejsGetCMethodUserData(EjsVar *obj)
{
	return obj->cMethod.userData;
}

/******************************************************************************/

int ejsGetVarFlags(EjsVar *obj)
{
	return obj->flags;
}

/******************************************************************************/

void ejsSetObjDestructor(Ejs *ep, EjsVar *obj, EjsDestructor destructor)
{
	obj->objectState->destructor = destructor;
}

/******************************************************************************/

void ejsClearObjDestructor(Ejs *ep, EjsVar *obj)
{
	obj->objectState->destructor = 0;
}

/******************************************************************************/
/*
 *	Create a new property
 */

static EjsProperty *allocProperty(Ejs *ep, EjsVar *op, const char *property, 
	int propertyIndex, EjsProperty *last)
{
	EjsProperty		*prop;
	EjsObj			*obj;

	obj = op->objectState;

	/*
	 *	Allocate the property using the memory context of the owning object
	 */
	prop = ejsAllocProperty(EJS_LOC_ARGS(obj->ejs));
	if (prop == 0) {
		return 0;
	}
	if (mprStrcpy(prop->name, sizeof(prop->name), property) < 0) {
		ejsError(ep, EJS_REFERENCE_ERROR, 
			"Property name %s is too long. Max is %d letters.", 
			prop->name, EJS_MAX_ID);
		return 0;
	}

	ejsSetVarName(ep, ejsGetVarPtr(prop), &prop->name[0]);

	/*
	 *	Do hash linkage
	 */
	if (last) {
		last->hashNext = prop;
	} else {
		obj->propertyHash[propertyIndex] = prop;
	}

#if BLD_DEBUG
	prop->link.propertyName = prop->name;
	prop->link.property = prop;
	prop->link.head = &obj->link;
#endif

	/*
	 *	Inserting before the dummy head will append to the end
	 */
	linkPropertyBefore(obj, &obj->link, &prop->link);

	obj->numProperties++;
	prop->parentObj = obj;
	mprAssert(obj->ejs);

	return prop;
}

/******************************************************************************/
/*
 *	Delete a property from this object
 */

int ejsDeleteProperty(Ejs *ep, EjsVar *vp, const char *property)
{
	EjsProperty		*prop, *last;
	EjsObj			*obj;
	int				propertyIndex;

	mprAssert(vp);
	mprAssert(property && *property);
	mprAssert(vp->type == EJS_TYPE_OBJECT);

	if (vp->type != EJS_TYPE_OBJECT) {
		mprAssert(vp->type == EJS_TYPE_OBJECT);
		return MPR_ERR_BAD_ARGS;
	}

	prop = hashLookup(vp->objectState, property,  &propertyIndex, &last);
	if (prop == (EjsProperty*) 0) {
		return MPR_ERR_NOT_FOUND;
	}
	obj = vp->objectState;

#if FUTURE
 	if (prop->readonly) {
		mprAssert(! prop->readonly);
		return MPR_ERR_READ_ONLY;
	}
#endif

	/*
     *	If doing enumerations, then the object will mark preventDelete to
	 *	prevent any properties being deleted and thus disturbing the
	 *	traversal.
	 */
	if (obj->preventDeleteProp) {
		obj->delayedDeleteProp = 1;
		prop->delayedDelete = 1;
		return 0;
	}

	/*
	 *	Remove from hash 
	 */
	if (last) {
		last->hashNext = prop->hashNext;
	} else {
		obj->propertyHash[propertyIndex] = prop->hashNext;
	}

	unlinkProperty(obj, &prop->link);
	obj->numProperties--;
	
	/*
	 *	Free any property data and return to the slab
	 */
	if (prop->var.type != EJS_TYPE_OBJECT) {
		ejsClearVar(ep, ejsGetVarPtr(prop));
	}
	ejsFree(ep, prop, EJS_SLAB_PROPERTY);

	return 0;
}

/******************************************************************************/
/*
 *	Remove a property's value from this object. The property is set to 
 *	undefined.
 */

EjsVar *ejsClearProperty(Ejs *ep, EjsVar *vp, const char *property)
{
	EjsProperty		*prop;

	mprAssert(vp);
	mprAssert(property && *property);
	mprAssert(vp->type == EJS_TYPE_OBJECT);

	if (vp->type != EJS_TYPE_OBJECT) {
		mprAssert(vp->type == EJS_TYPE_OBJECT);
		return 0;
	}

	prop = hashLookup(vp->objectState, property, 0, 0);
	if (prop == (EjsProperty*) 0) {
		return 0;
	}
#if FUTURE
 	if (prop->readonly) {
		mprAssert(! prop->readonly);
		return 0;
	}
#endif

	ejsClearVar(ep, &prop->var);
	return &prop->var;
}

/******************************************************************************/
/*
 *	Unlink a property from the ordered list of properties
 */

static void unlinkProperty(EjsObj *obj, EjsPropLink *propLink)
{
	propLink->prev->next = propLink->next;
	propLink->next->prev = propLink->prev;
}

/******************************************************************************/
#if UNUSED && KEEP
/*
 *	Insert a link after a specified link. 
 */

static void linkPropertyAfter(EjsObj *obj, EjsPropLink *at, 
	EjsPropLink *propLink)
{
	propLink->next = at->next;
	propLink->prev = at;

	at->next->prev = propLink;
	at->next = propLink;
}

#endif
/******************************************************************************/
/*
 *	Insert a link before a specified link. 
 */

static void linkPropertyBefore(EjsObj *obj, EjsPropLink *at, 
	EjsPropLink *propLink)
{
	propLink->prev = at->prev;
	propLink->next = at;

	at->prev->next = propLink;
	at->prev = propLink;
}

/******************************************************************************/
/*
 *	This routine will sort properties in an object. If propertyName is not
 *	null, then the properties in op must be objects with a property of the
 *	name propertyName. If propertyName is null, then the properties of op
 *	are directly sorted. If order is 1, they are sorted in ascending order.
 *	If -1, they are sorted in descending order.
 *
 *	NOTE: arrays keep their original index values.
 */
	
void ejsSortProperties(Ejs *ep, EjsVar *op, EjsSortFn fn, 
	const char *propertyName, int order)
{
	EjsProperty		*p1, *p2, *tmp;
	EjsPropLink			*l1, *l2, *oldL1Spot;
	EjsObj			*obj;

	obj = op->objectState;

	p1 = ejsGetFirstProperty(op, 0);
	while (p1) {
		if (p1->dontEnumerate) {
			p1 = ejsGetNextProperty(p1, 0);
			continue;
		}

		p2 = ejsGetFirstProperty(op, 0);
		while (p2 && p2 != p1) {

			if (p2->dontEnumerate) {
				p2 = ejsGetNextProperty(p2, 0);
				continue;
			}
			
			if (fn == 0) {
				if (propertyName) {
					fn = sortByProperty;
				} else {
					fn = sortAllProperties;
				}
			}

			if (fn(ep, p1, p2, propertyName, order) < 0) {

				l1 = &p1->link;
				l2 = &p2->link;

				/*
				 *	Swap the properties without disturbing the hash chains.
				 * 	l1 is always after l2 in the list. Unlink l1 and remember 
				 *	the one after l1.
				 */
				oldL1Spot = l1->next;
				unlinkProperty(obj, l1);

				/*
				 *	Manually reinsert l1 by replacing l2 with l1. l2 is out of
				 *	the chain.
			 	 */
				l2->prev->next = l1;
				l2->next->prev = l1;
				l1->prev = l2->prev;
				l1->next = l2->next;

				/*
				 *	Reinsert l2 before the spot where l1 was.
				 */
				linkPropertyBefore(obj, oldL1Spot, l2);

				/*
 				 *	Swap the pointers so we continue to traverse correctly
				 */
				tmp = p1;
				p1 = p2;
				p2 = tmp;
			}
			p2 = ejsGetNextProperty(p2, 0);
		}
		p1 = ejsGetNextProperty(p1, 0);
	}
}

/******************************************************************************/
/*
 *	Sort properties. Strings are sorted in ascending ASCII collating sequence
 *	Numbers are sorted in increasing numerical order.
 */
static int sortAllProperties(Ejs *ep, EjsProperty *p1, EjsProperty *p2,
	const char *propertyName, int order)
{
	EjsVar	*v1, *v2;
	char	*buf1, *buf2;
	int		rc, buf1Alloc;

	v1 = ejsGetVarPtr(p1);
	v2 = ejsGetVarPtr(p2);

	if (v1->type == v2->type) {
		/* MOB -- should support Numbers */
		if (v1->type == EJS_TYPE_INT) {
			if (v1->integer < v2->integer) {
				return - order;

			} else if (v1->integer == v2->integer) {
				return 0;
			}
			return order;

#if BLD_FEATURE_FLOATING_POINT
		} else if (v1->type == EJS_TYPE_FLOAT) {
			if (v1->floating < v2->floating) {
				return - order;

			} else if (v1->floating == v2->floating) {
				return 0;
			}
			return order;

#endif
		} else if (v1->type == EJS_TYPE_STRING) {
			/* MOB -- need binary support ? */
			return strcmp(v1->string, v2->string) * order;

		} else {

			buf1 = ejsVarToStringEx(ep, v1, &buf1Alloc);
			buf2 = ejsVarToString(ep, v2);

			rc = strcmp(buf1, buf2);

			if (buf1Alloc) {
				mprFree(buf1);
			}

			return rc * order;
		}

	} else {
		/* Type mismatch in array */
		return 0;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Sort an object by a given property. 
 */
static int sortByProperty(Ejs *ep, EjsProperty *p1, EjsProperty *p2,
	const char *propertyName, int order)
{
	EjsVar	*o1, *o2, *v1, *v2;
	char	*buf1, *buf2;
	int		rc, buf1Alloc;

	o1 = ejsGetVarPtr(p1);
	o2 = ejsGetVarPtr(p2);

	if (!ejsVarIsObject(o1) || !ejsVarIsObject(o2)) {
		mprAssert(ejsVarIsObject(o1));
		mprAssert(ejsVarIsObject(o2));
		return 0;
	}

	v1 = ejsGetPropertyAsVar(ep, o1, propertyName);
	v2 = ejsGetPropertyAsVar(ep, o2, propertyName);

	if (v1 == 0 || v2 == 0) {
		/* Property name not found */
		return 0;
	}

	if (v1->type != v2->type) {
		mprAssert(v1->type == v2->type);
		return 0;
	}

	if (v1->type == v2->type) {
		/* MOB -- should support Numbers */
		if (v1->type == EJS_TYPE_INT) {
			if (v1->integer < v2->integer) {
				return -order;

			} else if (v1->integer == v2->integer) {
				return 0;
			}
			return order;

#if BLD_FEATURE_FLOATING_POINT
		} else if (v1->type == EJS_TYPE_FLOAT) {
			if (v1->floating < v2->floating) {
				return -order;

			} else if (v1->floating == v2->floating) {
				return 0;
			}
			return order;

#endif
		} else if (v1->type == EJS_TYPE_STRING) {
			/* MOB -- need binary support ? */
			return strcmp(v1->string, v2->string) * order;

		} else {
			buf1 = ejsVarToStringEx(ep, v1, &buf1Alloc);

			buf2 = ejsVarToString(ep, v2);

			rc = strcmp(buf1, buf2);

			if (buf1Alloc) {
				mprFree(buf1);
			}

			return rc * order;
		}

	} else {
		/* Type mismatch in array */
		return 0;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Set a property's name
 */

void ejsSetPropertyName(EjsProperty *pp, const char *property)
{
	mprStrcpy(pp->name, sizeof(pp->name), property);
}

/******************************************************************************/

int ejsMakePropertyEnumerable(EjsProperty *prop, int enumerate)
{
	int		oldValue;

	oldValue = prop->dontEnumerate;
	prop->dontEnumerate = !enumerate;
	return oldValue;
}

/******************************************************************************/

void ejsMakePropertyPrivate(EjsProperty *prop, int isPrivate)
{
	prop->isPrivate = isPrivate;
}

/******************************************************************************/
/*
 *	Make a variable read only. Can still be deleted.
 */

void ejsMakePropertyReadOnly(EjsProperty *prop, int readonly)
{
	prop->readonly = readonly;
}

/******************************************************************************/

int ejsMakeObjPermanent(EjsVar *vp, int permanent)
{
	int		oldValue;

	if (vp && vp->type == EJS_TYPE_OBJECT) {
		oldValue = vp->objectState->permanent;
		vp->objectState->permanent = permanent;
	} else {
		oldValue = 0;
	}
	return oldValue;
}

/******************************************************************************/

int ejsMakeObjLive(EjsVar *vp, bool alive)
{
	int		oldValue;

	oldValue = 0;
	if (vp && vp->type == EJS_TYPE_OBJECT) {
		oldValue = vp->objectState->alive;
		vp->objectState->alive = alive;
	} else {
		oldValue = 0;
	}
	return oldValue;
}

/******************************************************************************/

void ejsMakeClassNoConstructor(EjsVar *vp)
{
	mprAssert(vp->type == EJS_TYPE_OBJECT);

	if (vp->type == EJS_TYPE_OBJECT) {
		vp->objectState->noConstructor = 1;
	}
}

/******************************************************************************/
/*
 *	Get the count of properties.
 */

int ejsGetPropertyCount(EjsVar *vp)
{
	EjsProperty		*pp;
	EjsPropLink		*lp, *head;
	int				count;

	mprAssert(vp);

	if (vp->type != EJS_TYPE_OBJECT) {
		return 0;
	}

	count = 0;

	head = &vp->objectState->link;
	for (lp = head->next; lp != head; lp = lp->next) {
		pp = ejsGetPropertyFromLink(lp);
		if (! pp->dontEnumerate) {
			count++;
		}
	}
	return count;
}

/******************************************************************************/
/*
 *	Get the first property in an object. Used for walking all properties in an
 *	object. This will only enumerate properties in this class and not in base
 *	classes.
 */

EjsProperty *ejsGetFirstProperty(const EjsVar *op, int flags)
{
	EjsProperty		*pp;
	EjsObj			*obj;
	EjsPropLink		*head, *lp;

	mprAssert(op);
	mprAssert(op->type == EJS_TYPE_OBJECT);

	if (op->type != EJS_TYPE_OBJECT) {
		mprAssert(op->type == EJS_TYPE_OBJECT);
		return 0;
	}
	pp = 0;

	do {
		obj = op->objectState;

		head = &obj->link;
		lp = head->next;

		while (lp != head) {
			pp = ejsGetPropertyFromLink(lp);
			if (! pp->dontEnumerate || (flags & EJS_ENUM_HIDDEN)) {
				break;
			}
			lp = lp->next;
		}
		if (lp != head || op->type != EJS_TYPE_OBJECT || 
				!(flags & EJS_ENUM_CLASSES)) {
			break;
		}

		op = obj->baseClass;

	} while (lp == 0 && op);

	return pp;
}

/******************************************************************************/
/*
 *	Get the next property in sequence. This will only enumerate properties in 
 *	this class and not in base classes.
 */

EjsProperty *ejsGetNextProperty(EjsProperty *last, int flags)
{
	EjsProperty		*pp;
	EjsObj			*obj;
	EjsPropLink		*lp, *head;

	obj = last->parentObj;

	lp = last->link.next;
	head = &obj->link;
	pp = 0;

	while (obj) {
		while (lp != head) {
			pp = ejsGetPropertyFromLink(lp);
			if (! pp->dontEnumerate || (flags & EJS_ENUM_HIDDEN)) {
				break;
			}
			lp = lp->next;
		}
		if (lp != head || !(flags & EJS_ENUM_CLASSES)) {
			break;
		}

		/*
		 *	Now iterate over properties in base classes (down the chain)
		 */
		if (obj->baseClass == 0) {
			break;
		}

		obj = obj->baseClass->objectState;
		if (obj == 0) {
			break;
		}
	}
	return pp;
}

/******************************************************************************/
/*
 *	Find a variable given a variable name and return the parent object and 
 *	the variable itself. This routine supports literal variable and property 
 *	names that may be objects or arrays but may NOT have expressions. 
 *	Returns -1 on errors or if the variable is not found.
 *	FUTURE -- Needs OPT
 */

EjsVar *ejsFindProperty(Ejs *ep, EjsVar **obj, char **property, EjsVar *global, 
	EjsVar *local, const char *fullName, int create)
{
	EjsProperty	*currentProp;
	EjsVar		*currentObj;
	/* MOB -- WARNING BIG */
	char		tokBuf[EJS_MAX_ID], propertyName[EJS_MAX_ID];
	char		*token, *next, *cp, *endp;

	mprAssert(fullName && *fullName);

	currentProp = 0;
	currentObj = 0;

	if (global == 0) {
		global = ep->global;
	}

	if (obj) {
		*obj = 0;
	}
	if (property) {
		*property = 0;
	}

	if (fullName == 0) {
		return 0;
	}

	next = (char*) fullName;
	token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
	mprStrcpy(propertyName, sizeof(propertyName), token);

	if (local) {
		currentProp = ejsGetProperty(ep, local, token);
		currentObj = local;
	}
	if (currentProp == 0) {
		currentProp = ejsGetProperty(ep, global, token);
		currentObj = global;
	}

	token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));

	while (currentObj != 0 && token != 0 && *token) {

		if (currentProp == 0) {
			return 0;
		}
		currentObj = &currentProp->var;
		currentProp = 0;

		if (*token == '[') {
			token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));

			mprStrcpy(propertyName, sizeof(propertyName), token);
			cp = propertyName;
			if (*cp == '\"') {
				cp++;
				if ((endp = strchr(cp, '\"')) != 0) {
					*endp = '\0';
				}
			} else if (*cp == '\'') {
				cp++;
				if ((endp = strchr(cp, '\'')) != 0) {
					*endp = '\0';
				}
			}

			currentProp = ejsGetProperty(ep, currentObj, propertyName);

			token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
			if (*token != ']') {
				return 0;
			}

		} else if (*token == '.') {
			token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
			if (!isalpha((int) token[0]) && 
					token[0] != '_' && token[0] != '$') {
				return 0;
			}

			mprStrcpy(propertyName, sizeof(propertyName), token);
			currentProp = ejsGetProperty(ep, currentObj, token);

		} else {
			currentProp = ejsGetProperty(ep, currentObj, token);
		}

		if (next == 0 || *next == '\0') {
			break;
		}
		token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
	}

	if (obj) {
		*obj = currentObj;
	}


	if (currentProp == 0 && currentObj >= 0 && create) {
		currentProp = ejsCreateSimpleProperty(ep, currentObj, propertyName);
	}

	if (property) {
		*property = currentProp->name;
	}
	return ejsGetVarPtr(currentProp);
}

/******************************************************************************/
/*
 *	Get the next token as part of a variable specification. This will return
 *	a pointer to the next token and will return a pointer to the next token 
 *	(after this one) in "next". The tokBuf holds the parsed token.
 */

static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen)
{
	char	*start, *cp;
	int		len;

	start = *next;
	while (isspace((int) *start) || *start == '\n' || *start == '\r') {
		start++;
	}
	cp = start;

	if (*cp == '.' || *cp == '[' || *cp == ']') {
		cp++;
	} else {
		while (*cp && *cp != '.' && *cp != '[' && *cp != ']' && 
				!isspace((int) *cp) && *cp != '\n' && *cp != '\r') {
			cp++;
		}
	}
	len = mprMemcpy(tokBuf, tokBufLen - 1, start, cp - start);
	tokBuf[len] = '\0';
	
	*next = cp;
	return tokBuf;
}

/******************************************************************************/

EjsVar *ejsGetGlobalClass(Ejs *ep)
{
	return ep->global;
}

/******************************************************************************/
/*************************** Property Access Methods **************************/
/******************************************************************************/
/*
 *	Create an undefined property. This routine calls the object method hooks.
 */

/* MOB -- better suffix than "Method" */
EjsVar *ejsCreatePropertyMethod(Ejs *ep, EjsVar *op, const char *property)
{
	EjsVar		*vp;

	mprAssert(ep);
	mprAssert(op);
	mprAssert(property && *property);

	if (op == 0) {
		return 0;
	}

	mprAssert(op->type == EJS_TYPE_OBJECT);
	mprAssert(op->objectState);

	if (op->objectState == 0) {
		return 0;
	}

	if (op->objectState->methods == 0) {
		vp = ejsGetVarPtr(ejsCreateSimpleProperty(ep, op, property));
	} else {
		vp = (op->objectState->methods->createProperty)(ep, op, property);
	}

	if (vp == 0) {
		mprAssert(vp);
		op->objectState->hasErrors = 1;
		return 0;
	}

	/*
	 * 	FUTURE - find a better way.
	 */
	if (op->isArray) {
		ejsSetArrayLength(ep, op, property, 0, 0);
	}
	return vp;
}

/******************************************************************************/

int ejsDeletePropertyMethod(Ejs *ep, EjsVar *op, const char *property)
{
	int		rc;

	mprAssert(ep);
	mprAssert(op);
	mprAssert(property && *property);

	if (op == 0) {
		return -1;
	}

	mprAssert(op->type == EJS_TYPE_OBJECT);
	mprAssert(op->objectState);

	if (op->objectState == 0) {
		return -1;
	}

	if (op->objectState->methods == 0) {
		rc = ejsDeleteProperty(ep, op, property);
	} else {
		rc = (op->objectState->methods->deleteProperty)(ep, op, property);
	}

	if (rc < 0) {
		op->objectState->hasErrors = 1;
	}

	op->objectState->dirty = 1;

	return rc;
}

/******************************************************************************/
/*
 *	Set the value of a property. Create if it does not exist
 *	If the object has property accessor methods defined, use those.
 */

EjsVar *ejsSetPropertyMethod(Ejs *ep, EjsVar *op, const char *property, 
	const EjsVar *value)
{
	EjsVar			*vp;

	mprAssert(ep);
	mprAssert(op);
	mprAssert(property && *property);
	mprAssert(value);

	if (op == 0) {
		return 0;
	}

	mprAssert(op->type == EJS_TYPE_OBJECT);
	mprAssert(op->objectState);

	if (op->objectState == 0) {
		return 0;
	}

	if (op->objectState->methods == 0) {
		vp = ejsGetVarPtr(ejsCreateSimpleProperty(ep, op, property));
		if (vp && ejsWriteVar(ep, vp, (EjsVar*) value, EJS_SHALLOW_COPY) < 0) {
			mprAssert(0);
			op->objectState->hasErrors = 1;
			return 0;
		}

	} else {
		vp = (op->objectState->methods->setProperty)(ep, op, property, value);
	}

	if (vp == 0) {
		mprAssert(vp);
		op->objectState->hasErrors = 1;
		return 0;
	}
	
	if (vp->type == EJS_TYPE_OBJECT) {
		/*
		 *	We make an object alive (and subject to garbage collection) when
		 *	it is referenced in some other object. If this is undesirable, the
		 *	caller should make the object permanent while calling this routine
		 *	and then afterward clear the alive bit by calling ejsMakeObjLive().
		 */
		if (op->objectState != vp->objectState) {
			vp->objectState->alive = 1;
		}
#if BLD_DEBUG
		{
			EjsProperty	*pp = ejsGetPropertyPtr(vp);
			ejsSetVarName(ep, vp, &pp->name[0]);
			if (value->propertyName == 0) {
				ejsSetVarName(ep, (EjsVar*) value, &pp->name[0]);
			}
		}
#endif
	}

	/*
	 *	Trap assignments to array.length. MOB - find a better way.
	 */
	if (vp->isArrayLength) {
		ejsSetArrayLength(ep, op, 0, 0, value);
	}

	op->objectState->dirty = 1;

	return vp;
}

/******************************************************************************/

EjsVar *ejsGetPropertyMethod(Ejs *ep, EjsVar *op, const char *property)
{
	mprAssert(ep);
	mprAssert(op);
	mprAssert(property && *property);

	if (op == 0) {
		return 0;
	}

	mprAssert(op->type == EJS_TYPE_OBJECT);
	mprAssert(op->objectState);

	if (op->objectState == 0) {
		return 0;
	}

	if (op->objectState->methods == 0) {
		return ejsGetVarPtr(ejsGetSimpleProperty(ep, op, property));
	} else {
		return (op->objectState->methods->getProperty)(ep, op, property);
	}
}

/******************************************************************************/
/*************************** Advisory Locking Support *************************/
/******************************************************************************/
#if BLD_FEATURE_MULTITHREAD

void ejsLockObj(EjsVar *vp)
{
	mprAssert(vp);
	mprAssert(vp->type == EJS_TYPE_OBJECT);
	mprAssert(vp->objectState);

	if (vp->objectState->mutex == 0) {
		vp->objectState->mutex = mprCreateLock(vp->objectState->ejs);
	}
	mprLock(vp->objectState->mutex);
}

/******************************************************************************/

void ejsUnlockObj(EjsVar *vp)
{
	mprAssert(vp);
	mprAssert(vp->type == EJS_TYPE_OBJECT);
	mprAssert(vp->objectState);

	if (vp->objectState->mutex) {
		mprUnlock(vp->objectState->mutex);
	}
}

#endif
/******************************************************************************/
/************************** Internal Support Routines *************************/
/******************************************************************************/
/*
 *	Create an object.
 */

static EjsObj *createObj(EJS_LOC_DEC(ep, loc))
{
	EjsObj			*op;
	EjsPropLink		*lp;

	op = (EjsObj*) ejsAllocObj(EJS_LOC_PASS(ep, loc));
	if (op == NULL) {
		return 0;
	}

	/*
	 *	The objectState holds the dummy head for the ordered list of properties
	 */
	lp = &op->link;
	lp->next = lp->prev = lp;

#if BLD_DEBUG
	/*
	 *	This makes it much easier to debug the list
	 */
	lp->head = lp;
	lp->propertyName = "dummyHead";
#endif

	return op;
}

/******************************************************************************/
/*
 *	Destroy an object. Called by the garbage collector if there are no more 
 *	references to an object.
 */

int ejsDestroyObj(Ejs *ep, EjsObj *obj)
{
	EjsProperty		*pp;
	EjsPropLink		*lp, *head, *nextLink;

	mprAssert(obj);

	if (obj->destructor) {
		EjsVar	v;
		memset(&v, 0, sizeof(v));
		v.type = EJS_TYPE_OBJECT;
		v.objectState = obj;
		ejsSetVarName(ep, &v, "destructor");

#if BLD_FEATURE_ALLOC_LEAK_TRACK
		v.gc.allocatedBy = "static";
#endif

		if ((obj->destructor)(ep, &v) < 0) {
			return -1;
		}
	}
	mprFree(obj->objName);
	obj->objName = 0;

	/*
	 *	Just for safety. An object may be marked by a GC on the default 
 	 *	interpreter. After destroying, it won't be on the free list and so
	 *	won't be reset.
	 */
	obj->gcMarked = 0;
	obj->visited = 0;

	head = &obj->link;
	for (lp = head->next; lp != head; lp = nextLink) {

		pp = ejsGetPropertyFromLink(lp);
		nextLink = lp->next;

		/*
		 *	We don't unlink as we are destroying all properties.
 		 *	If an object, we don't need to clear either.
		 */
		if (pp->var.type != EJS_TYPE_OBJECT) {
			ejsClearVar(ep, ejsGetVarPtr(pp));
		}
		ejsFree(ep, pp, EJS_SLAB_PROPERTY);
	}

#if BLD_FEATURE_MULTITHREAD
	if (obj->mutex) {
		mprDestroyLock(obj->mutex);
	}
#endif

	ejsFree(ep, obj, EJS_SLAB_OBJ);
	return 0;
}

/******************************************************************************/
/*
 *	Fast hash. The history of this algorithm is part of lost computer science 
 *	folk lore.
 */

static int hash(const char *property)
{
	uint	sum;

	mprAssert(property);

	sum = 0;
	while (*property) {
		sum += (sum * 33) + *property++;
	}

	return sum % EJS_OBJ_HASH_SIZE;
}

/******************************************************************************/
/*
 *	Set a new length for an array. If create is non-null, then it is the name
 *	of a new array index. If delete is set, it is the name of an index being
 *	deleted. If setLength is set to a variable, it counts the new length for the
 *	array. Note that create and delete are ignored if they are non-integer 
 *	array indexes (eg. normal properties).
 */

void ejsSetArrayLength(Ejs *ep, EjsVar *obj, const char *create, 
	const char *delete, const EjsVar *setLength)
{
	EjsVar			*vp;
	char			idx[16];
	int				oldSize, newSize, i;

	vp = ejsGetPropertyAsVar(ep, obj, "length");
 	oldSize = vp->integer;
	newSize = oldSize;

	if (create) {
		if (isdigit(*create)) {
			i = atoi(create);
			newSize = max(i + 1, oldSize);
		}
	} else if (delete) {
		if (isdigit(*delete)) {
			i = atoi(delete);
			newSize = (i == (oldSize - 1) ? oldSize - 1 : oldSize);
		}
	} else {
		newSize = setLength->integer;
	}

	for (i = newSize; i < oldSize; i++) {
		mprItoa(idx, sizeof(idx), i);
		ejsDeleteProperty(ep, obj, idx);
	}
	
	if (ejsWriteVarAsInteger(ep, vp, newSize) == 0) {
		mprAssert(0);
	}
}

/******************************************************************************/

void ejsClearObjErrors(EjsVar *vp)
{
	if (vp == 0 || vp->type != EJS_TYPE_OBJECT || vp->objectState == 0) {
		mprAssert(0);
		return;
	}
	vp->objectState->hasErrors = 0;
}

/******************************************************************************/

int ejsObjHasErrors(EjsVar *vp)
{
	if (vp == 0 || vp->type != EJS_TYPE_OBJECT || vp->objectState == 0) {
		mprAssert(0);
		return -1;
	}
	return vp->objectState->hasErrors;
}

/******************************************************************************/

bool ejsIsObjDirty(EjsVar *vp)
{
	mprAssert(vp->type == EJS_TYPE_OBJECT && vp->objectState);

	if (vp->type == EJS_TYPE_OBJECT && vp->objectState) {
		return vp->objectState->dirty;
	}
	return 0;
}

/******************************************************************************/

void ejsResetObjDirtyBit(EjsVar *vp)
{
	mprAssert(vp->type == EJS_TYPE_OBJECT && vp->objectState);

	if (vp->type == EJS_TYPE_OBJECT && vp->objectState) {
		vp->objectState->dirty = 0;
	}
}

/******************************************************************************/
/*
 *	Copy a string. Always null terminate.
 */

static int dupString(MPR_LOC_DEC(ctx, loc), uchar **dest, const void *src, 
	int nbytes)
{
	mprAssert(dest);
	mprAssert(src);

	if (nbytes > 0) {
		*dest = mprMemdupInternal(MPR_LOC_PASS(ctx, loc), src, nbytes + 1);
		if (*dest == 0) {
			return MPR_ERR_MEMORY;
		}

	} else {
		*dest = (uchar*) mprAlloc(ctx, 1);
		nbytes = 0;
	}

	(*dest)[nbytes] = '\0';

	return nbytes;
}

/******************************************************************************/

const char *ejsGetVarTypeAsString(EjsVar *vp)
{
	switch (vp->type) {
	default:
	case EJS_TYPE_UNDEFINED:
		return "undefined";
	case EJS_TYPE_NULL:
		return "null";
	case EJS_TYPE_BOOL:
		return "bool";
	case EJS_TYPE_CMETHOD:
		return "cmethod";
	case EJS_TYPE_FLOAT:
		return "float";
	case EJS_TYPE_INT:
		return "int";
	case EJS_TYPE_INT64:
		return "int64";
	case EJS_TYPE_OBJECT:
		return "object";
	case EJS_TYPE_METHOD:
		return "method";
	case EJS_TYPE_STRING:
		return "string";
	case EJS_TYPE_STRING_CMETHOD:
		return "string method";
	case EJS_TYPE_PTR:
		return "ptr";
	}
}

/******************************************************************************/

void *ejsGetVarUserPtr(EjsVar *vp)
{
	mprAssert(vp);
	mprAssert(vp->type == EJS_TYPE_PTR);

	if (!ejsVarIsPtr(vp)) {
		return 0;
	}
	return vp->ptr.userPtr;
}

/******************************************************************************/

void ejsSetVarUserPtr(EjsVar *vp, void *data)
{
	mprAssert(vp);
	mprAssert(vp->type == EJS_TYPE_PTR);

	vp->ptr.userPtr = data;
}

/******************************************************************************/
/*
 *	Return TRUE if target is a subclass (or the same class) as baseClass.
 */

bool ejsIsSubClass(EjsVar *target, EjsVar *baseClass)
{
	do {
		if (target->objectState == baseClass->objectState) {
			return 1;
		}
		target = target->objectState->baseClass;
	} while (target);

	return 0;
}

/******************************************************************************/
/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim:tw=78
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */
