diff options
Diffstat (limited to 'source4/lib/appweb/ejs-2.0/ejs/ejsVar.c')
-rw-r--r-- | source4/lib/appweb/ejs-2.0/ejs/ejsVar.c | 4033 |
1 files changed, 4033 insertions, 0 deletions
diff --git a/source4/lib/appweb/ejs-2.0/ejs/ejsVar.c b/source4/lib/appweb/ejs-2.0/ejs/ejsVar.c new file mode 100644 index 0000000000..1f8e9266a3 --- /dev/null +++ b/source4/lib/appweb/ejs-2.0/ejs/ejsVar.c @@ -0,0 +1,4033 @@ +/** + * @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, NULL) < 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, NULL) < 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 = ¤tProp->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 + */ |