/** * @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 */