3 * @brief Embedded JavaScript (EJS)
4 * @overview Main module interface logic.
6 /********************************* Copyright **********************************/
10 * Copyright (c) Mbedthis Software LLC, 2003-2005. All Rights Reserved.
11 * Portions Copyright (c) GoAhead Software, 1995-2000. All Rights Reserved.
13 * This software is distributed under commercial and open source licenses.
14 * You may use the GPL open source license described below or you may acquire
15 * a commercial license from Mbedthis Software. You agree to be fully bound
16 * by the terms of either license. Consult the LICENSE.TXT distributed with
17 * this software for full details.
19 * This software is open source; you can redistribute it and/or modify it
20 * under the terms of the GNU General Public License as published by the
21 * Free Software Foundation; either version 2 of the License, or (at your
22 * option) any later version. See the GNU General Public License for more
23 * details at: http://www.mbedthis.com/downloads/gplLicense.html
25 * This program is distributed WITHOUT ANY WARRANTY; without even the
26 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
28 * This GPL license does NOT permit incorporating this software into
29 * proprietary programs. If you are unable to comply with the GPL, you must
30 * acquire a commercial license to use this software. Commercial licenses
31 * for this software and support services are available from Mbedthis
32 * Software at http://www.mbedthis.com
36 /********************************** Includes **********************************/
38 #include "ejsInternal.h"
42 /********************************** Local Data ********************************/
45 * These fields must be locked before any access when multithreaded
47 static MprVar master; /* Master object */
48 static MprArray *ejsList; /* List of ej handles */
50 #if BLD_FEATURE_MULTITHREAD
52 static EjsUnlock unlock;
53 static void *lockData;
54 #define ejsLock() if (lock) { (lock)(lockData); } else
55 #define ejsUnlock() if (unlock) { (unlock)(lockData); } else
63 save/restore global ejs state - used to cope with simultaneous ejs requests
64 this is a workaround for the use of global variables in ejs
66 struct ejs_state_ctx {
71 void *ejs_save_state(void)
73 struct ejs_state_ctx *ctx = talloc(talloc_autofree_context(), struct ejs_state_ctx);
75 ctx->ejsList = ejsList;
79 void ejs_restore_state(void *ptr)
81 struct ejs_state_ctx *ctx = talloc_get_type(ptr, struct ejs_state_ctx);
83 ejsList = ctx->ejsList;
88 /****************************** Forward Declarations **************************/
90 static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen);
92 /************************************* Code ***********************************/
94 * Initialize the EJ subsystem
97 int ejsOpen(EjsLock lockFn, EjsUnlock unlockFn, void *data)
101 #if BLD_FEATURE_MULTITHREAD
111 * Master is the top level object (above global). It is used to clone its
112 * contents into the global scope for each. This is never visible to the
113 * user, so don't use ejsCreateObj().
115 master = mprCreateObjVar("master", EJS_SMALL_OBJ_HASH_SIZE);
116 if (master.type == MPR_TYPE_UNDEFINED) {
118 return MPR_ERR_CANT_ALLOCATE;
121 ejsList = mprCreateArray();
122 ejsDefineStandardProperties(&master);
125 * Make these objects immutable
127 np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA);
129 mprSetVarReadonly(np, 1);
130 np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS |
137 /******************************************************************************/
142 mprDestroyArray(ejsList);
143 mprDestroyVar(&master);
147 /******************************************************************************/
149 * Create and initialize an EJS engine
152 EjsId ejsOpenEngine(EjsHandle primaryHandle, EjsHandle altHandle)
157 ep = (Ejs *)mprMalloc(sizeof(Ejs));
161 memset(ep, 0, sizeof(Ejs));
164 ep->eid = (EjsId) mprAddToArray(ejsList, ep);
168 * Create array of local variable frames
170 ep->frames = mprCreateArray();
171 if (ep->frames == 0) {
172 ejsCloseEngine(ep->eid);
175 ep->primaryHandle = primaryHandle;
176 ep->altHandle = altHandle;
179 * Create first frame: global variables
181 ep->global = (MprVar*) mprMalloc(sizeof(MprVar));
182 *ep->global = ejsCreateObj("global", EJS_OBJ_HASH_SIZE);
183 if (ep->global->type == MPR_TYPE_UNDEFINED) {
184 ejsCloseEngine(ep->eid);
187 mprAddToArray(ep->frames, ep->global);
190 * Create first local variable frame
192 ep->local = (MprVar*) mprMalloc(sizeof(MprVar));
193 *ep->local = ejsCreateObj("local", EJS_OBJ_HASH_SIZE);
194 if (ep->local->type == MPR_TYPE_UNDEFINED) {
195 ejsCloseEngine(ep->eid);
198 mprAddToArray(ep->frames, ep->local);
201 * Clone all master variables into the global frame. This does a
204 * ejsDefineStandardProperties(ep->global);
206 np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA);
208 mprCreateProperty(ep->global, np->name, np);
209 np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS |
213 mprCreateProperty(ep->global, "global", ep->global);
214 mprCreateProperty(ep->global, "this", ep->global);
215 mprCreateProperty(ep->local, "local", ep->local);
220 /******************************************************************************/
222 * Close an EJS instance
225 void ejsCloseEngine(EjsId eid)
232 if ((ep = ejsPtr(eid)) == NULL) {
238 mprDestroyVar(&ep->result);
239 mprDestroyVar(&ep->tokenNumber);
242 mprDeleteProperty(ep->local, "local");
244 mprDeleteProperty(ep->global, "this");
245 mprDeleteProperty(ep->global, "global");
247 handles = ep->frames->handles;
248 for (i = 0; i < ep->frames->max; i++) {
252 if (vp->type == MPR_TYPE_OBJECT && vp->properties->refCount > 1) {
253 mprLog(7, "ejsCloseEngine: %s has ref count %d\n",
254 vp->name, vp->properties->refCount);
259 mprRemoveFromArray(ep->frames, i);
262 mprDestroyArray(ep->frames);
265 mprRemoveFromArray(ejsList, (int) ep->eid);
271 /******************************************************************************/
273 * Evaluate an EJS script file
276 int ejsEvalFile(EjsId eid, char *path, MprVar *result, char **emsg)
283 mprAssert(path && *path);
289 if ((ep = ejsPtr(eid)) == NULL) {
294 if ((fd = open(path, O_RDONLY | O_BINARY, 0666)) < 0) {
295 ejsError(ep, "Can't open %s\n", path);
299 if (stat(path, &sbuf) < 0) {
301 ejsError(ep, "Cant stat %s", path);
305 if ((script = (char*) mprMalloc(sbuf.st_size + 1)) == NULL) {
307 ejsError(ep, "Cant malloc %d", (int) sbuf.st_size);
311 if (read(fd, script, sbuf.st_size) != (int) sbuf.st_size) {
314 ejsError(ep, "Error reading %s", path);
318 script[sbuf.st_size] = '\0';
321 rc = ejsEvalBlock(eid, script, result, emsg);
331 *emsg = mprStrdup(ep->error);
335 /******************************************************************************/
337 * Create a new variable scope block. This pushes the old local frame down
338 * the stack and creates a new local variables frame.
341 int ejsOpenBlock(EjsId eid)
345 if((ep = ejsPtr(eid)) == NULL) {
349 ep->local = (MprVar*) mprMalloc(sizeof(MprVar));
350 *ep->local = ejsCreateObj("localBlock", EJS_OBJ_HASH_SIZE);
352 mprCreateProperty(ep->local, "local", ep->local);
354 return mprAddToArray(ep->frames, ep->local);
357 /******************************************************************************/
359 * Close a variable scope block opened via ejsOpenBlock. Pop back the old
360 * local variables frame.
363 int ejsCloseBlock(EjsId eid, int fid)
367 if((ep = ejsPtr(eid)) == NULL) {
373 * Must remove self-references before destroying "local"
375 mprDeleteProperty(ep->local, "local");
377 mprDestroyVar(ep->local);
380 mprRemoveFromArray(ep->frames, fid);
381 ep->local = (MprVar*) ep->frames->handles[ep->frames->used - 1];
386 /******************************************************************************/
388 * Create a new variable scope block and evaluate a script. All frames
389 * created during this context will be automatically deleted when complete.
390 * vp and emsg are optional. i.e. created local variables will be discarded
391 * when this routine returns.
394 int ejsEvalBlock(EjsId eid, char *script, MprVar *vp, char **emsg)
400 fid = ejsOpenBlock(eid);
401 rc = ejsEvalScript(eid, script, vp, emsg);
402 ejsCloseBlock(eid, fid);
407 /******************************************************************************/
409 * Parse and evaluate a EJS. Return the result in *vp. The result is "owned"
410 * by EJ and the caller must not free it. Returns -1 on errors and zero
411 * for success. On errors, emsg will be set to the reason. The caller must
415 int ejsEvalScript(EjsId eid, char *script, MprVar *vp, char **emsg)
419 void *endlessLoopTest;
426 if ((ep = ejsPtr(eid)) == NULL) {
431 mprDestroyVar(&ep->result);
438 * Allocate a new evaluation block, and save the old one
440 ejsLexOpenScript(ep, script);
443 * Do the actual parsing and evaluation
446 endlessLoopTest = NULL;
450 state = ejsParse(ep, EJS_STATE_BEGIN, EJS_FLAGS_EXE);
452 if (state == EJS_STATE_RET) {
453 state = EJS_STATE_EOF;
456 * Stuck parser and endless recursion protection.
458 if (endlessLoopTest == ep->input->scriptServp) {
459 if (loopCounter++ > 10) {
460 state = EJS_STATE_ERR;
461 ejsError(ep, "Syntax error");
464 endlessLoopTest = ep->input->scriptServp;
467 } while (state != EJS_STATE_EOF && state != EJS_STATE_ERR);
469 ejsLexCloseScript(ep);
472 * Return any error string to the user
474 if (state == EJS_STATE_ERR && emsg) {
475 *emsg = mprStrdup(ep->error);
478 if (state == EJS_STATE_ERR) {
486 return ep->exitStatus;
489 /******************************************************************************/
491 * Core error handling
494 static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args)
495 PRINTF_ATTRIBUTE(2, 0);
497 static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args)
500 char *errbuf, *msgbuf;
506 mprAllocVsprintf(&msgbuf, MPR_MAX_STRING, fmt, args);
509 mprAllocSprintf(&errbuf, MPR_MAX_STRING, "%s\nBacktrace:\n", msgbuf);
511 /* form a backtrace */
514 mprAllocSprintf(&msg2, MPR_MAX_STRING,
515 "\t[%2d] %20s:%-4d -> %s\n",
516 frame++, ip->procName?ip->procName:"", ip->lineNumber, ip->line);
517 ebuf2 = mprRealloc(errbuf, strlen(errbuf) + strlen(msg2) + 1);
518 if (ebuf2 == NULL) break;
520 memcpy(errbuf+strlen(errbuf), msg2, strlen(msg2)+1);
529 /******************************************************************************/
531 * Internal use function to set the error message
534 void ejsError(Ejs* ep, const char* fmt, ...)
539 ejsErrorCore(ep, fmt, args);
543 /******************************************************************************/
545 * Public routine to set the error message
548 void ejsSetErrorMsg(EjsId eid, const char* fmt, ...)
553 if ((ep = ejsPtr(eid)) == NULL) {
558 ejsErrorCore(ep, fmt, args);
562 /******************************************************************************/
564 * Get the current line number
567 int ejsGetLineNumber(EjsId eid)
571 if ((ep = ejsPtr(eid)) == NULL) {
575 return ep->input->lineNumber;
578 /******************************************************************************/
580 * Return the local object
583 MprVar *ejsGetLocalObject(EjsId eid)
587 if ((ep = ejsPtr(eid)) == NULL) {
594 /******************************************************************************/
596 * Return the global object
599 MprVar *ejsGetGlobalObject(EjsId eid)
603 if ((ep = ejsPtr(eid)) == NULL) {
610 /******************************************************************************/
612 * Copy the value of an object property. Return value is in "value".
613 * If deepCopy is true, copy all object/strings. Otherwise, object reference
614 * counts are incremented. Callers must always call mprDestroyVar on the
615 * return value to prevent leaks.
617 * Returns: -1 on errors or if the variable is not found.
620 int ejsCopyVar(EjsId eid, const char *var, MprVar *value, bool deepCopy)
625 mprAssert(var && *var);
628 if ((ep = ejsPtr(eid)) == NULL) {
633 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
637 return mprCopyProperty(value, vp, deepCopy);
640 /******************************************************************************/
642 * Return the value of an object property. Return value is in "value".
643 * Objects and strings are not copied and reference counts are not modified.
644 * Callers should NOT call mprDestroyVar. Returns: -1 on errors or if the
645 * variable is not found.
648 int ejsReadVar(EjsId eid, const char *var, MprVar *value)
653 mprAssert(var && *var);
656 if ((ep = ejsPtr(eid)) == NULL) {
661 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
665 return mprReadProperty(vp, value);
668 /******************************************************************************/
670 * Set a variable that may be an arbitrarily complex object or array reference.
671 * Will always define in the top most variable frame.
674 int ejsWriteVar(EjsId eid, const char *var, MprVar *value)
679 mprAssert(var && *var);
681 if ((ep = ejsPtr(eid)) == NULL) {
686 if (ejsGetVarCore(ep, var, 0, &vp, EJS_FLAGS_CREATE) < 0) {
692 * Only copy the value. Don't overwrite the object's name
694 mprWriteProperty(vp, value);
699 /******************************************************************************/
701 * Set a variable that may be an arbitrarily complex object or array reference.
702 * Will always define in the top most variable frame.
705 int ejsWriteVarValue(EjsId eid, const char *var, MprVar value)
707 return ejsWriteVar(eid, var, &value);
710 /******************************************************************************/
715 int ejsDeleteVar(EjsId eid, const char *var)
721 if ((ep = ejsPtr(eid)) == NULL) {
725 if (ejsGetVarCore(ep, var, &obj, &vp, 0) < 0) {
728 mprDeleteProperty(obj, vp->name);
732 /******************************************************************************/
734 * Set the expression return value
737 void ejsSetReturnValue(EjsId eid, MprVar value)
741 if ((ep = ejsPtr(eid)) == NULL) {
745 mprCopyVar(&ep->result, &value, MPR_SHALLOW_COPY);
748 /******************************************************************************/
750 * Set the expression return value to a string value
753 void ejsSetReturnString(EjsId eid, const char *str)
757 if ((ep = ejsPtr(eid)) == NULL) {
761 mprCopyVarValue(&ep->result, mprCreateStringVar(str, 0), MPR_SHALLOW_COPY);
764 /******************************************************************************/
766 * Get the expression return value
769 MprVar *ejsGetReturnValue(EjsId eid)
773 if ((ep = ejsPtr(eid)) == NULL) {
780 /******************************************************************************/
782 * Define a C function. If eid < 0, then update the master object with this
783 * function. NOTE: in this case, functionName must be simple without any "." or
784 * "[]" elements. If eid >= 0, add to the specified script engine. In this
785 * case, functionName can be an arbitrary object reference and can contain "."
789 void ejsDefineCFunction(EjsId eid, const char *functionName, MprCFunction fn,
790 void *thisPtr, int flags)
794 mprCreatePropertyValue(&master, functionName,
795 mprCreateCFunctionVar(fn, thisPtr, flags));
798 ejsWriteVarValue(eid, functionName,
799 mprCreateCFunctionVar(fn, thisPtr, flags));
803 /******************************************************************************/
805 * Define a C function with String arguments
808 void ejsDefineStringCFunction(EjsId eid, const char *functionName,
809 MprStringCFunction fn, void *thisPtr, int flags)
813 mprCreatePropertyValue(&master, functionName,
814 mprCreateStringCFunctionVar(fn, thisPtr, flags));
817 ejsWriteVarValue(eid, functionName,
818 mprCreateStringCFunctionVar(fn, thisPtr, flags));
822 /******************************************************************************/
824 * Define a JavaScript function. Args should be comma separated.
825 * Body should not contain braces.
828 void ejsDefineFunction(EjsId eid, const char *functionName, char *args,
833 v = mprCreateFunctionVar(args, body, 0);
836 mprCreateProperty(&master, functionName, &v);
839 ejsWriteVar(eid, functionName, &v);
844 /******************************************************************************/
846 void *ejsGetThisPtr(EjsId eid)
850 if ((ep = ejsPtr(eid)) == NULL) {
857 /******************************************************************************/
859 * Find a variable given a variable name and return the parent object and
860 * the variable itself, the variable . This routine supports variable names
861 * that may be objects or arrays but may NOT have expressions in the array
862 * indicies. Returns -1 on errors or if the variable is not found.
865 int ejsGetVarCore(Ejs *ep, const char *vname, MprVar **obj,
866 MprVar **varValue, int flags)
870 char tokBuf[EJS_MAX_ID];
871 char *propertyName, *token, *next, *cp, *varName;
879 currentObj = ejsFindObj(ep, 0, vname, flags);
883 next = varName = mprStrdup(vname);
885 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
887 while (currentObj != 0 && token != 0 && *token) {
890 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
892 propertyName = token;
893 if (*propertyName == '\"') {
895 if ((cp = strchr(propertyName, '\"')) != 0) {
898 } else if (*propertyName == '\'') {
900 if ((cp = strchr(propertyName, '\'')) != 0) {
905 currentObj = currentVar;
906 currentVar = ejsFindProperty(ep, 0, currentObj, propertyName, 0);
908 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
914 } else if (*token == '.') {
915 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
916 if (!isalpha((int) token[0]) &&
917 token[0] != '_' && token[0] != '$') {
922 propertyName = token;
923 currentObj = currentVar;
924 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
927 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
929 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
933 if (currentVar == 0 && currentObj >= 0 && flags & EJS_FLAGS_CREATE) {
934 currentVar = mprCreatePropertyValue(currentObj, propertyName,
935 mprCreateUndefinedVar());
942 * Don't use mprCopyVar as it will copy the data
945 *varValue = currentVar;
947 return currentVar ? 0 : -1;
950 /******************************************************************************/
952 * Get the next token as part of a variable specification. This will return
953 * a pointer to the next token and will return a pointer to the next token
954 * (after this one) in "next". The tokBuf holds the parsed token.
956 static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen)
962 while (isspace((int) *start) || *start == '\n' || *start == '\r') {
967 if (*cp == '.' || *cp == '[' || *cp == ']') {
970 while (*cp && *cp != '.' && *cp != '[' && *cp != ']' &&
971 !isspace((int) *cp) && *cp != '\n' && *cp != '\r') {
975 len = mprMemcpy(tokBuf, tokBufLen - 1, start, cp - start);
982 /******************************************************************************/
984 * Get the EJS structure pointer
987 Ejs *ejsPtr(EjsId eid)
995 mprAssert(0 <= intId && intId < ejsList->max);
997 if (intId < 0 || intId >= ejsList->max || ejsList->handles[intId] == NULL) {
1002 handle = ejsList->handles[intId];
1007 /******************************************************************************/
1009 * Utility routine to crack JavaScript arguments. Return the number of args
1010 * seen. This routine only supports %s and %d type args.
1014 * if (ejsParseArgs(argc, argv, "%s %d", &name, &age) < 2) {
1015 * mprError("Insufficient args\n");
1020 int ejsParseArgs(int argc, char **argv, char *fmt, ...)
1027 va_start(vargs, fmt);
1033 for (argn = 0, cp = fmt; cp && *cp && argn < argc && argv[argn]; ) {
1041 bp = va_arg(vargs, bool*);
1043 if (strcmp(s, "true") == 0 || s[0] == '1') {
1054 ip = va_arg(vargs, int*);
1059 sp = va_arg(vargs, char**);
1073 /******************************************************************************/
1078 /******************************************************************************/
1079 #endif /* BLD_FEATURE_EJS */
1081 /******************************************************************************/
1088 * vim600: sw=4 ts=4 fdm=marker
1089 * vim<600: sw=4 ts=4