/* * @file ejs.c * @brief Embedded JavaScript (EJS) * @overview Main module interface logic. */ /********************************* Copyright **********************************/ /* * @copy default.g * * Copyright (c) Mbedthis Software LLC, 2003-2005. All Rights Reserved. * Portions Copyright (c) GoAhead Software, 1995-2000. 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 */ /********************************** Includes **********************************/ #include "ejsInternal.h" #if BLD_FEATURE_EJS /********************************** Local Data ********************************/ /* * These fields must be locked before any access when multithreaded */ static MprVar master; /* Master object */ static MprArray *ejsList; /* List of ej handles */ #if BLD_FEATURE_MULTITHREAD static EjsLock lock; static EjsUnlock unlock; static void *lockData; #define ejsLock() if (lock) { (lock)(lockData); } else #define ejsUnlock() if (unlock) { (unlock)(lockData); } else #else #define ejsLock() #define ejsUnlock() #endif /* save/restore global ejs state - used to cope with simultaneous ejs requests this is a workaround for the use of global variables in ejs */ struct ejs_state_ctx { MprVar master; MprArray *ejsList; }; void *ejs_save_state(void) { struct ejs_state_ctx *ctx = talloc(talloc_autofree_context(), struct ejs_state_ctx); ctx->master = master; ctx->ejsList = ejsList; return ctx; } void ejs_restore_state(void *ptr) { struct ejs_state_ctx *ctx = talloc_get_type(ptr, struct ejs_state_ctx); master = ctx->master; ejsList = ctx->ejsList; talloc_free(ctx); } /****************************** Forward Declarations **************************/ static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen); /************************************* Code ***********************************/ /* * Initialize the EJ subsystem */ int ejsOpen(EjsLock lockFn, EjsUnlock unlockFn, void *data) { MprVar *np; #if BLD_FEATURE_MULTITHREAD if (lockFn) { lock = lockFn; unlock = unlockFn; lockData = data; } #endif ejsLock(); /* * Master is the top level object (above global). It is used to clone its * contents into the global scope for each. This is never visible to the * user, so don't use ejsCreateObj(). */ master = mprCreateObjVar("master", EJS_SMALL_OBJ_HASH_SIZE); if (master.type == MPR_TYPE_UNDEFINED) { ejsUnlock(); return MPR_ERR_CANT_ALLOCATE; } ejsList = mprCreateArray(); ejsDefineStandardProperties(&master); /* * Make these objects immutable */ np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA); while (np) { mprSetVarReadonly(np, 1); np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA); } ejsUnlock(); return 0; } /******************************************************************************/ void ejsClose() { ejsLock(); mprDestroyArray(ejsList); mprDestroyVar(&master); ejsUnlock(); } /******************************************************************************/ /* * Create and initialize an EJS engine */ EjsId ejsOpenEngine(EjsHandle primaryHandle, EjsHandle altHandle) { MprVar *np; Ejs *ep; ep = mprMalloc(sizeof(Ejs)); if (ep == 0) { return (EjsId) -1; } memset(ep, 0, sizeof(Ejs)); ejsLock(); ep->eid = (EjsId) mprAddToArray(ejsList, ep); ejsUnlock(); /* * Create array of local variable frames */ ep->frames = mprCreateArray(); if (ep->frames == 0) { ejsCloseEngine(ep->eid); return (EjsId) -1; } ep->primaryHandle = primaryHandle; ep->altHandle = altHandle; /* * Create first frame: global variables */ ep->global = (MprVar*) mprMalloc(sizeof(MprVar)); *ep->global = ejsCreateObj("global", EJS_OBJ_HASH_SIZE); if (ep->global->type == MPR_TYPE_UNDEFINED) { ejsCloseEngine(ep->eid); return (EjsId) -1; } mprAddToArray(ep->frames, ep->global); /* * Create first local variable frame */ ep->local = (MprVar*) mprMalloc(sizeof(MprVar)); *ep->local = ejsCreateObj("local", EJS_OBJ_HASH_SIZE); if (ep->local->type == MPR_TYPE_UNDEFINED) { ejsCloseEngine(ep->eid); return (EjsId) -1; } mprAddToArray(ep->frames, ep->local); /* * Clone all master variables into the global frame. This does a * reference copy. * * ejsDefineStandardProperties(ep->global); */ np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA); while (np) { mprCreateProperty(ep->global, np->name, np); np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA); } mprCreateProperty(ep->global, "global", ep->global); mprCreateProperty(ep->global, "this", ep->global); mprCreateProperty(ep->local, "local", ep->local); return ep->eid; } /******************************************************************************/ /* * Close an EJS instance */ void ejsCloseEngine(EjsId eid) { Ejs *ep; MprVar *vp; void **handles; int i; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return; } mprFree(ep->error); mprDestroyVar(&ep->result); mprDestroyVar(&ep->tokenNumber); if (ep->local) { mprDeleteProperty(ep->local, "local"); } mprDeleteProperty(ep->global, "this"); mprDeleteProperty(ep->global, "global"); handles = ep->frames->handles; for (i = 0; i < ep->frames->max; i++) { vp = handles[i]; if (vp) { #if BLD_DEBUG if (vp->type == MPR_TYPE_OBJECT && vp->properties->refCount > 1) { mprLog(7, "ejsCloseEngine: %s has ref count %d\n", vp->name, vp->properties->refCount); } #endif mprDestroyVar(vp); mprFree(vp); mprRemoveFromArray(ep->frames, i); } } mprDestroyArray(ep->frames); ejsLock(); mprRemoveFromArray(ejsList, (int) ep->eid); ejsUnlock(); mprFree(ep); } /******************************************************************************/ /* * Evaluate an EJS script file */ int ejsEvalFile(EjsId eid, char *path, MprVar *result, char **emsg) { struct stat sbuf; Ejs *ep; char *script; int rc, fd; mprAssert(path && *path); if (emsg) { *emsg = NULL; } if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); goto error; } if ((fd = open(path, O_RDONLY | O_BINARY, 0666)) < 0) { ejsError(ep, "Can't open %s\n", path); goto error; } if (stat(path, &sbuf) < 0) { close(fd); ejsError(ep, "Cant stat %s", path); goto error; } if ((script = (char*) mprMalloc(sbuf.st_size + 1)) == NULL) { close(fd); ejsError(ep, "Cant malloc %d", (int) sbuf.st_size); goto error; } if (read(fd, script, sbuf.st_size) != (int) sbuf.st_size) { close(fd); mprFree(script); ejsError(ep, "Error reading %s", path); goto error; } script[sbuf.st_size] = '\0'; close(fd); rc = ejsEvalBlock(eid, script, result, emsg); mprFree(script); return rc; /* * Error return */ error: if(emsg) *emsg = mprStrdup(ep->error); return -1; } /******************************************************************************/ /* * Create a new variable scope block. This pushes the old local frame down * the stack and creates a new local variables frame. */ int ejsOpenBlock(EjsId eid) { Ejs *ep; if((ep = ejsPtr(eid)) == NULL) { return -1; } ep->local = (MprVar*) mprMalloc(sizeof(MprVar)); *ep->local = ejsCreateObj("localBlock", EJS_OBJ_HASH_SIZE); mprCreateProperty(ep->local, "local", ep->local); return mprAddToArray(ep->frames, ep->local); } /******************************************************************************/ /* * Close a variable scope block opened via ejsOpenBlock. Pop back the old * local variables frame. */ int ejsCloseBlock(EjsId eid, int fid) { Ejs *ep; if((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } /* * Must remove self-references before destroying "local" */ mprDeleteProperty(ep->local, "local"); mprDestroyVar(ep->local); mprFree(ep->local); mprRemoveFromArray(ep->frames, fid); ep->local = (MprVar*) ep->frames->handles[ep->frames->used - 1]; return 0; } /******************************************************************************/ /* * Create a new variable scope block and evaluate a script. All frames * created during this context will be automatically deleted when complete. * vp and emsg are optional. i.e. created local variables will be discarded * when this routine returns. */ int ejsEvalBlock(EjsId eid, char *script, MprVar *vp, char **emsg) { int rc, fid; mprAssert(script); fid = ejsOpenBlock(eid); rc = ejsEvalScript(eid, script, vp, emsg); ejsCloseBlock(eid, fid); return rc; } /******************************************************************************/ /* * Parse and evaluate a EJS. Return the result in *vp. The result is "owned" * by EJ and the caller must not free it. Returns -1 on errors and zero * for success. On errors, emsg will be set to the reason. The caller must * free emsg. */ int ejsEvalScript(EjsId eid, char *script, MprVar *vp, char **emsg) { Ejs *ep; int state; void *endlessLoopTest; int loopCounter; if (emsg) { *emsg = NULL; } if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } mprDestroyVar(&ep->result); if (script == 0) { return 0; } /* * Allocate a new evaluation block, and save the old one */ ejsLexOpenScript(ep, script); /* * Do the actual parsing and evaluation */ loopCounter = 0; endlessLoopTest = NULL; ep->exitStatus = 0; do { state = ejsParse(ep, EJS_STATE_BEGIN, EJS_FLAGS_EXE); if (state == EJS_STATE_RET) { state = EJS_STATE_EOF; } /* * Stuck parser and endless recursion protection. */ if (endlessLoopTest == ep->input->scriptServp) { if (loopCounter++ > 10) { state = EJS_STATE_ERR; ejsError(ep, "Syntax error"); } } else { endlessLoopTest = ep->input->scriptServp; loopCounter = 0; } } while (state != EJS_STATE_EOF && state != EJS_STATE_ERR); ejsLexCloseScript(ep); /* * Return any error string to the user */ if (state == EJS_STATE_ERR && emsg) { *emsg = mprStrdup(ep->error); } if (state == EJS_STATE_ERR) { return -1; } if (vp) { *vp = ep->result; } return ep->exitStatus; } /******************************************************************************/ /* * Core error handling */ static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args) PRINTF_ATTRIBUTE(2, 0); static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args) { EjsInput *ip; char *errbuf, *msgbuf; int frame = 0; mprAssert(ep); msgbuf = NULL; mprAllocVsprintf(&msgbuf, MPR_MAX_STRING, fmt, args); ip = ep->input; mprAllocSprintf(&errbuf, MPR_MAX_STRING, "%s\nBacktrace:\n", msgbuf); /* form a backtrace */ while (ip) { char *msg2, *ebuf2; mprAllocSprintf(&msg2, MPR_MAX_STRING, "\t[%2d] %20s:%-4d -> %s\n", frame++, ip->procName?ip->procName:"", ip->lineNumber, ip->line); ebuf2 = mprRealloc(errbuf, strlen(errbuf) + strlen(msg2) + 1); if (ebuf2 == NULL) break; errbuf = ebuf2; memcpy(errbuf+strlen(errbuf), msg2, strlen(msg2)+1); mprFree(msg2); ip = ip->next; } mprFree(ep->error); ep->error = errbuf; mprFree(msgbuf); } /******************************************************************************/ /* * Internal use function to set the error message */ void ejsError(Ejs* ep, const char* fmt, ...) { va_list args; va_start(args, fmt); ejsErrorCore(ep, fmt, args); va_end(args); } /******************************************************************************/ /* * Public routine to set the error message */ void ejsSetErrorMsg(EjsId eid, const char* fmt, ...) { va_list args; Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return; } va_start(args, fmt); ejsErrorCore(ep, fmt, args); va_end(args); } /******************************************************************************/ /* * Get the current line number */ int ejsGetLineNumber(EjsId eid) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } return ep->input->lineNumber; } /******************************************************************************/ /* * Return the local object */ MprVar *ejsGetLocalObject(EjsId eid) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return 0; } return ep->local; } /******************************************************************************/ /* * Return the global object */ MprVar *ejsGetGlobalObject(EjsId eid) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return 0; } return ep->global; } /******************************************************************************/ /* * Copy the value of an object property. Return value is in "value". * If deepCopy is true, copy all object/strings. Otherwise, object reference * counts are incremented. Callers must always call mprDestroyVar on the * return value to prevent leaks. * * Returns: -1 on errors or if the variable is not found. */ int ejsCopyVar(EjsId eid, const char *var, MprVar *value, bool deepCopy) { Ejs *ep; MprVar *vp; mprAssert(var && *var); mprAssert(value); if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) { return -1; } return mprCopyProperty(value, vp, deepCopy); } /******************************************************************************/ /* * Return the value of an object property. Return value is in "value". * Objects and strings are not copied and reference counts are not modified. * Callers should NOT call mprDestroyVar. Returns: -1 on errors or if the * variable is not found. */ int ejsReadVar(EjsId eid, const char *var, MprVar *value) { Ejs *ep; MprVar *vp; mprAssert(var && *var); mprAssert(value); if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) { return -1; } return mprReadProperty(vp, value); } /******************************************************************************/ /* * Set a variable that may be an arbitrarily complex object or array reference. * Will always define in the top most variable frame. */ int ejsWriteVar(EjsId eid, const char *var, MprVar *value) { Ejs *ep; MprVar *vp; mprAssert(var && *var); if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } if (ejsGetVarCore(ep, var, 0, &vp, EJS_FLAGS_CREATE) < 0) { return -1; } mprAssert(vp); /* * Only copy the value. Don't overwrite the object's name */ mprWriteProperty(vp, value); return 0; } /******************************************************************************/ /* * Set a variable that may be an arbitrarily complex object or array reference. * Will always define in the top most variable frame. */ int ejsWriteVarValue(EjsId eid, const char *var, MprVar value) { return ejsWriteVar(eid, var, &value); } /******************************************************************************/ /* * Delete a variable */ int ejsDeleteVar(EjsId eid, const char *var) { Ejs *ep; MprVar *vp; MprVar *obj; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return -1; } if (ejsGetVarCore(ep, var, &obj, &vp, 0) < 0) { return -1; } mprDeleteProperty(obj, vp->name); return 0; } /******************************************************************************/ /* * Set the expression return value */ void ejsSetReturnValue(EjsId eid, MprVar value) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return; } mprCopyVar(&ep->result, &value, MPR_SHALLOW_COPY); } /******************************************************************************/ /* * Set the expression return value to a string value */ void ejsSetReturnString(EjsId eid, const char *str) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return; } mprCopyVarValue(&ep->result, mprCreateStringVar(str, 0), MPR_SHALLOW_COPY); } /******************************************************************************/ /* * Get the expression return value */ MprVar *ejsGetReturnValue(EjsId eid) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return 0; } return &ep->result; } /******************************************************************************/ /* * Define a C function. If eid < 0, then update the master object with this * function. NOTE: in this case, functionName must be simple without any "." or * "[]" elements. If eid >= 0, add to the specified script engine. In this * case, functionName can be an arbitrary object reference and can contain "." * or "[]". */ void ejsDefineCFunction(EjsId eid, const char *functionName, MprCFunction fn, void *thisPtr, int flags) { if (eid < 0) { ejsLock(); mprCreatePropertyValue(&master, functionName, mprCreateCFunctionVar(fn, thisPtr, flags)); ejsUnlock(); } else { ejsWriteVarValue(eid, functionName, mprCreateCFunctionVar(fn, thisPtr, flags)); } } /******************************************************************************/ /* * Define a C function with String arguments */ void ejsDefineStringCFunction(EjsId eid, const char *functionName, MprStringCFunction fn, void *thisPtr, int flags) { if (eid < 0) { ejsLock(); mprCreatePropertyValue(&master, functionName, mprCreateStringCFunctionVar(fn, thisPtr, flags)); ejsUnlock(); } else { ejsWriteVarValue(eid, functionName, mprCreateStringCFunctionVar(fn, thisPtr, flags)); } } /******************************************************************************/ /* * Define a JavaScript function. Args should be comma separated. * Body should not contain braces. */ void ejsDefineFunction(EjsId eid, const char *functionName, char *args, char *body) { MprVar v; v = mprCreateFunctionVar(args, body, 0); if (eid < 0) { ejsLock(); mprCreateProperty(&master, functionName, &v); ejsUnlock(); } else { ejsWriteVar(eid, functionName, &v); } mprDestroyVar(&v); } /******************************************************************************/ void *ejsGetThisPtr(EjsId eid) { Ejs *ep; if ((ep = ejsPtr(eid)) == NULL) { mprAssert(ep); return 0; } return ep->thisPtr; } /******************************************************************************/ /* * Find a variable given a variable name and return the parent object and * the variable itself, the variable . This routine supports variable names * that may be objects or arrays but may NOT have expressions in the array * indicies. Returns -1 on errors or if the variable is not found. */ int ejsGetVarCore(Ejs *ep, const char *vname, MprVar **obj, MprVar **varValue, int flags) { MprVar *currentObj; MprVar *currentVar; char tokBuf[EJS_MAX_ID]; char *propertyName, *token, *next, *cp, *varName; if (obj) { *obj = 0; } if (varValue) { *varValue = 0; } currentObj = ejsFindObj(ep, 0, vname, flags); currentVar = 0; propertyName = 0; next = varName = mprStrdup(vname); token = getNextVarToken(&next, tokBuf, sizeof(tokBuf)); while (currentObj != 0 && token != 0 && *token) { if (*token == '[') { token = getNextVarToken(&next, tokBuf, sizeof(tokBuf)); propertyName = token; if (*propertyName == '\"') { propertyName++; if ((cp = strchr(propertyName, '\"')) != 0) { *cp = '\0'; } } else if (*propertyName == '\'') { propertyName++; if ((cp = strchr(propertyName, '\'')) != 0) { *cp = '\0'; } } currentObj = currentVar; currentVar = ejsFindProperty(ep, 0, currentObj, propertyName, 0); token = getNextVarToken(&next, tokBuf, sizeof(tokBuf)); if (*token != ']') { mprFree(varName); return -1; } } else if (*token == '.') { token = getNextVarToken(&next, tokBuf, sizeof(tokBuf)); if (!isalpha((int) token[0]) && token[0] != '_' && token[0] != '$') { mprFree(varName); return -1; } propertyName = token; currentObj = currentVar; currentVar = ejsFindProperty(ep, 0, currentObj, token, 0); } else { currentVar = ejsFindProperty(ep, 0, currentObj, token, 0); } token = getNextVarToken(&next, tokBuf, sizeof(tokBuf)); } mprFree(varName); if (currentVar == 0 && currentObj >= 0 && flags & EJS_FLAGS_CREATE) { currentVar = mprCreatePropertyValue(currentObj, propertyName, mprCreateUndefinedVar()); } if (obj) { *obj = currentObj; } /* * Don't use mprCopyVar as it will copy the data */ if (varValue) { *varValue = currentVar; } return currentVar ? 0 : -1; } /******************************************************************************/ /* * 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; } /******************************************************************************/ /* * Get the EJS structure pointer */ Ejs *ejsPtr(EjsId eid) { Ejs *handle; int intId; intId = (int) eid; ejsLock(); mprAssert(0 <= intId && intId < ejsList->max); if (intId < 0 || intId >= ejsList->max || ejsList->handles[intId] == NULL) { mprAssert(0); ejsUnlock(); return NULL; } handle = ejsList->handles[intId]; ejsUnlock(); return handle; } /******************************************************************************/ /* * Utility routine to crack JavaScript arguments. Return the number of args * seen. This routine only supports %s and %d type args. * * Typical usage: * * if (ejsParseArgs(argc, argv, "%s %d", &name, &age) < 2) { * mprError("Insufficient args\n"); * return -1; * } */ int ejsParseArgs(int argc, char **argv, char *fmt, ...) { va_list vargs; bool *bp; char *cp, **sp, *s; int *ip, argn; va_start(vargs, fmt); if (argv == 0) { return 0; } for (argn = 0, cp = fmt; cp && *cp && argn < argc && argv[argn]; ) { if (*cp++ != '%') { continue; } s = argv[argn]; switch (*cp) { case 'b': bp = va_arg(vargs, bool*); if (bp) { if (strcmp(s, "true") == 0 || s[0] == '1') { *bp = 1; } else { *bp = 0; } } else { *bp = 0; } break; case 'd': ip = va_arg(vargs, int*); *ip = atoi(s); break; case 's': sp = va_arg(vargs, char**); *sp = s; break; default: mprAssert(0); } argn++; } va_end(vargs); return argn; } /******************************************************************************/ #else void ejsDummy() {} /******************************************************************************/ #endif /* BLD_FEATURE_EJS */ /******************************************************************************/ /* * 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 */