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 "web_server/ejs/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
61 /****************************** Forward Declarations **************************/
63 static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen);
65 /************************************* Code ***********************************/
67 * Initialize the EJ subsystem
70 int ejsOpen(EjsLock lockFn, EjsUnlock unlockFn, void *data)
74 #if BLD_FEATURE_MULTITHREAD
84 * Master is the top level object (above global). It is used to clone its
85 * contents into the global scope for each. This is never visible to the
86 * user, so don't use ejsCreateObj().
88 master = mprCreateObjVar("master", EJS_SMALL_OBJ_HASH_SIZE);
89 if (master.type == MPR_TYPE_UNDEFINED) {
91 return MPR_ERR_CANT_ALLOCATE;
94 ejsList = mprCreateArray();
95 ejsDefineStandardProperties(&master);
98 * Make these objects immutable
100 np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA);
102 mprSetVarReadonly(np, 1);
103 np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS |
110 /******************************************************************************/
115 mprDestroyArray(ejsList);
116 mprDestroyVar(&master);
120 /******************************************************************************/
122 * Create and initialize an EJS engine
125 EjsId ejsOpenEngine(EjsHandle primaryHandle, EjsHandle altHandle)
130 ep = mprMalloc(sizeof(Ejs));
134 memset(ep, 0, sizeof(Ejs));
137 ep->eid = (EjsId) mprAddToArray(ejsList, ep);
141 * Create array of local variable frames
143 ep->frames = mprCreateArray();
144 if (ep->frames == 0) {
145 ejsCloseEngine(ep->eid);
148 ep->primaryHandle = primaryHandle;
149 ep->altHandle = altHandle;
152 * Create first frame: global variables
154 ep->global = (MprVar*) mprMalloc(sizeof(MprVar));
155 *ep->global = ejsCreateObj("global", EJS_OBJ_HASH_SIZE);
156 if (ep->global->type == MPR_TYPE_UNDEFINED) {
157 ejsCloseEngine(ep->eid);
160 mprAddToArray(ep->frames, ep->global);
163 * Create first local variable frame
165 ep->local = (MprVar*) mprMalloc(sizeof(MprVar));
166 *ep->local = ejsCreateObj("local", EJS_OBJ_HASH_SIZE);
167 if (ep->local->type == MPR_TYPE_UNDEFINED) {
168 ejsCloseEngine(ep->eid);
171 mprAddToArray(ep->frames, ep->local);
174 * Clone all master variables into the global frame. This does a
177 * ejsDefineStandardProperties(ep->global);
179 np = mprGetFirstProperty(&master, MPR_ENUM_FUNCTIONS | MPR_ENUM_DATA);
181 mprCreateProperty(ep->global, np->name, np);
182 np = mprGetNextProperty(&master, np, MPR_ENUM_FUNCTIONS |
186 mprCreateProperty(ep->global, "global", ep->global);
187 mprCreateProperty(ep->global, "this", ep->global);
188 mprCreateProperty(ep->local, "local", ep->local);
193 /******************************************************************************/
195 * Close an EJS instance
198 void ejsCloseEngine(EjsId eid)
205 if ((ep = ejsPtr(eid)) == NULL) {
211 mprDestroyVar(&ep->result);
212 mprDestroyVar(&ep->tokenNumber);
214 mprDeleteProperty(ep->local, "local");
215 mprDeleteProperty(ep->global, "this");
216 mprDeleteProperty(ep->global, "global");
218 handles = ep->frames->handles;
219 for (i = 0; i < ep->frames->max; i++) {
223 if (vp->type == MPR_TYPE_OBJECT && vp->properties->refCount > 1) {
224 mprLog(7, "ejsCloseEngine: %s has ref count %d\n",
225 vp->name, vp->properties->refCount);
230 mprRemoveFromArray(ep->frames, i);
233 mprDestroyArray(ep->frames);
236 mprRemoveFromArray(ejsList, (int) ep->eid);
242 /******************************************************************************/
244 * Evaluate an EJS script file
247 int ejsEvalFile(EjsId eid, char *path, MprVar *result, char **emsg)
254 mprAssert(path && *path);
260 if ((ep = ejsPtr(eid)) == NULL) {
265 if ((fd = open(path, O_RDONLY | O_BINARY, 0666)) < 0) {
266 ejsError(ep, "Can't open %s\n", path);
270 if (stat(path, &sbuf) < 0) {
272 ejsError(ep, "Cant stat %s", path);
276 if ((script = (char*) mprMalloc(sbuf.st_size + 1)) == NULL) {
278 ejsError(ep, "Cant malloc %d", (int)sbuf.st_size);
282 if (read(fd, script, sbuf.st_size) != (int)sbuf.st_size) {
285 ejsError(ep, "Error reading %s", path);
289 script[sbuf.st_size] = '\0';
292 rc = ejsEvalBlock(eid, script, result, emsg);
301 *emsg = mprStrdup(ep->error);
305 /******************************************************************************/
307 * Create a new variable scope block. This pushes the old local frame down
308 * the stack and creates a new local variables frame.
311 int ejsOpenBlock(EjsId eid)
315 if((ep = ejsPtr(eid)) == NULL) {
319 ep->local = (MprVar*) mprMalloc(sizeof(MprVar));
320 *ep->local = ejsCreateObj("localBlock", EJS_OBJ_HASH_SIZE);
322 mprCreateProperty(ep->local, "local", ep->local);
324 return mprAddToArray(ep->frames, ep->local);
327 /******************************************************************************/
329 * Close a variable scope block opened via ejsOpenBlock. Pop back the old
330 * local variables frame.
333 int ejsCloseBlock(EjsId eid, int fid)
337 if((ep = ejsPtr(eid)) == NULL) {
343 * Must remove self-references before destroying "local"
345 mprDeleteProperty(ep->local, "local");
347 mprDestroyVar(ep->local);
350 mprRemoveFromArray(ep->frames, fid);
351 ep->local = (MprVar*) ep->frames->handles[ep->frames->used - 1];
356 /******************************************************************************/
358 * Create a new variable scope block and evaluate a script. All frames
359 * created during this context will be automatically deleted when complete.
360 * vp and emsg are optional. i.e. created local variables will be discarded
361 * when this routine returns.
364 int ejsEvalBlock(EjsId eid, char *script, MprVar *vp, char **emsg)
370 fid = ejsOpenBlock(eid);
371 rc = ejsEvalScript(eid, script, vp, emsg);
372 ejsCloseBlock(eid, fid);
377 /******************************************************************************/
379 * Parse and evaluate a EJS. Return the result in *vp. The result is "owned"
380 * by EJ and the caller must not free it. Returns -1 on errors and zero
381 * for success. On errors, emsg will be set to the reason. The caller must
385 int ejsEvalScript(EjsId eid, char *script, MprVar *vp, char **emsg)
390 void *endlessLoopTest;
397 if ((ep = ejsPtr(eid)) == NULL) {
402 mprDestroyVar(&ep->result);
409 * Allocate a new evaluation block, and save the old one
411 oldBlock = ep->input;
412 ejsLexOpenScript(ep, script);
415 * Do the actual parsing and evaluation
418 endlessLoopTest = NULL;
422 state = ejsParse(ep, EJS_STATE_BEGIN, EJS_FLAGS_EXE);
424 if (state == EJS_STATE_RET) {
425 state = EJS_STATE_EOF;
428 * Stuck parser and endless recursion protection.
430 if (endlessLoopTest == ep->input->scriptServp) {
431 if (loopCounter++ > 10) {
432 state = EJS_STATE_ERR;
433 ejsError(ep, "Syntax error");
436 endlessLoopTest = ep->input->scriptServp;
439 } while (state != EJS_STATE_EOF && state != EJS_STATE_ERR);
441 ejsLexCloseScript(ep);
444 * Return any error string to the user
446 if (state == EJS_STATE_ERR && emsg) {
447 *emsg = mprStrdup(ep->error);
451 * Restore the old evaluation block
453 ep->input = oldBlock;
455 if (state == EJS_STATE_ERR) {
463 return ep->exitStatus;
466 /******************************************************************************/
468 * Core error handling
471 static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args) PRINTF_ATTRIBUTE(2, 0);
473 static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args)
476 char *errbuf, *msgbuf;
482 mprAllocVsprintf(&msgbuf, MPR_MAX_STRING, fmt, args);
487 mprAllocSprintf(&errbuf, MPR_MAX_STRING,
488 "%s\nError on line %d. Offending line: %s\n\n",
489 msgbuf, ip->lineNumber, ip->line);
491 mprAllocSprintf(&errbuf, MPR_MAX_STRING, "%s\n", msgbuf);
499 /******************************************************************************/
501 * Internal use function to set the error message
504 void ejsError(Ejs* ep, const char* fmt, ...)
509 ejsErrorCore(ep, fmt, args);
513 /******************************************************************************/
515 * Public routine to set the error message
518 void ejsSetErrorMsg(EjsId eid, const char* fmt, ...)
523 if ((ep = ejsPtr(eid)) == NULL) {
528 ejsErrorCore(ep, fmt, args);
532 /******************************************************************************/
534 * Get the current line number
537 int ejsGetLineNumber(EjsId eid)
541 if ((ep = ejsPtr(eid)) == NULL) {
545 return ep->input->lineNumber;
548 /******************************************************************************/
550 * Return the local object
553 MprVar *ejsGetLocalObject(EjsId eid)
557 if ((ep = ejsPtr(eid)) == NULL) {
564 /******************************************************************************/
566 * Return the global object
569 MprVar *ejsGetGlobalObject(EjsId eid)
573 if ((ep = ejsPtr(eid)) == NULL) {
580 /******************************************************************************/
582 * Copy the value of an object property. Return value is in "value".
583 * If deepCopy is true, copy all object/strings. Otherwise, object reference
584 * counts are incremented. Callers must always call mprDestroyVar on the
585 * return value to prevent leaks.
587 * Returns: -1 on errors or if the variable is not found.
590 int ejsCopyVar(EjsId eid, const char *var, MprVar *value, bool deepCopy)
595 mprAssert(var && *var);
598 if ((ep = ejsPtr(eid)) == NULL) {
603 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
607 return mprCopyProperty(value, vp, deepCopy);
610 /******************************************************************************/
612 * Return the value of an object property. Return value is in "value".
613 * Objects and strings are not copied and reference counts are not modified.
614 * Callers should NOT call mprDestroyVar. Returns: -1 on errors or if the
615 * variable is not found.
618 int ejsReadVar(EjsId eid, const char *var, MprVar *value)
623 mprAssert(var && *var);
626 if ((ep = ejsPtr(eid)) == NULL) {
631 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
635 return mprReadProperty(vp, value);
638 /******************************************************************************/
640 * Set a variable that may be an arbitrarily complex object or array reference.
641 * Will always define in the top most variable frame.
644 int ejsWriteVar(EjsId eid, const char *var, MprVar *value)
649 mprAssert(var && *var);
651 if ((ep = ejsPtr(eid)) == NULL) {
656 if (ejsGetVarCore(ep, var, 0, &vp, EJS_FLAGS_CREATE) < 0) {
662 * Only copy the value. Don't overwrite the object's name
664 mprWriteProperty(vp, value);
669 /******************************************************************************/
671 * Set a variable that may be an arbitrarily complex object or array reference.
672 * Will always define in the top most variable frame.
675 int ejsWriteVarValue(EjsId eid, const char *var, MprVar value)
677 return ejsWriteVar(eid, var, &value);
680 /******************************************************************************/
685 int ejsDeleteVar(EjsId eid, const char *var)
691 if ((ep = ejsPtr(eid)) == NULL) {
695 if (ejsGetVarCore(ep, var, &obj, &vp, 0) < 0) {
698 mprDeleteProperty(obj, vp->name);
702 /******************************************************************************/
704 * Set the expression return value
707 void ejsSetReturnValue(EjsId eid, MprVar value)
711 if ((ep = ejsPtr(eid)) == NULL) {
715 mprCopyVar(&ep->result, &value, MPR_SHALLOW_COPY);
718 /******************************************************************************/
720 * Set the expression return value to a string value
723 void ejsSetReturnString(EjsId eid, const char *str)
727 if ((ep = ejsPtr(eid)) == NULL) {
731 mprCopyVarValue(&ep->result, mprCreateStringVar(str, 0), MPR_SHALLOW_COPY);
734 /******************************************************************************/
736 * Get the expression return value
739 MprVar *ejsGetReturnValue(EjsId eid)
743 if ((ep = ejsPtr(eid)) == NULL) {
750 /******************************************************************************/
752 * Define a C function. If eid < 0, then update the master object with this
753 * function. NOTE: in this case, functionName must be simple without any "." or
754 * "[]" elements. If eid >= 0, add to the specified script engine. In this
755 * case, functionName can be an arbitrary object reference and can contain "."
759 void ejsDefineCFunction(EjsId eid, const char *functionName, MprCFunction fn,
760 void *thisPtr, int flags)
764 mprCreatePropertyValue(&master, functionName,
765 mprCreateCFunctionVar(fn, thisPtr, flags));
768 ejsWriteVarValue(eid, functionName,
769 mprCreateCFunctionVar(fn, thisPtr, flags));
773 /******************************************************************************/
775 * Define a C function with String arguments
778 void ejsDefineStringCFunction(EjsId eid, const char *functionName,
779 MprStringCFunction fn, void *thisPtr, int flags)
783 mprCreatePropertyValue(&master, functionName,
784 mprCreateStringCFunctionVar(fn, thisPtr, flags));
787 ejsWriteVarValue(eid, functionName,
788 mprCreateStringCFunctionVar(fn, thisPtr, flags));
792 /******************************************************************************/
794 * Define a JavaScript function. Args should be comma separated.
795 * Body should not contain braces.
798 void ejsDefineFunction(EjsId eid, const char *functionName, char *args, char *body)
802 v = mprCreateFunctionVar(args, body, 0);
805 mprCreateProperty(&master, functionName, &v);
808 ejsWriteVar(eid, functionName, &v);
813 /******************************************************************************/
815 void *ejsGetThisPtr(EjsId eid)
819 if ((ep = ejsPtr(eid)) == NULL) {
826 /******************************************************************************/
828 * Find a variable given a variable name and return the parent object and
829 * the variable itself, the variable . This routine supports variable names
830 * that may be objects or arrays but may NOT have expressions in the array
831 * indicies. Returns -1 on errors or if the variable is not found.
834 int ejsGetVarCore(Ejs *ep, const char *varName_c, MprVar **obj, MprVar **varValue,
839 char tokBuf[EJS_MAX_ID];
840 char *propertyName, *token, *next, *cp, *varName;
848 currentObj = ejsFindObj(ep, 0, varName_c, flags);
852 varName = mprStrdup(varName_c);
855 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
857 while (currentObj != 0 && token != 0 && *token) {
860 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
862 propertyName = token;
863 if (*propertyName == '\"') {
865 if ((cp = strchr(propertyName, '\"')) != 0) {
868 } else if (*propertyName == '\'') {
870 if ((cp = strchr(propertyName, '\'')) != 0) {
875 currentObj = currentVar;
876 currentVar = ejsFindProperty(ep, 0, currentObj, propertyName, 0);
878 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
884 } else if (*token == '.') {
885 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
886 if (!isalpha((int) token[0]) &&
887 token[0] != '_' && token[0] != '$') {
892 propertyName = token;
893 currentObj = currentVar;
894 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
897 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
899 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
903 if (currentVar == 0 && currentObj >= 0 && flags & EJS_FLAGS_CREATE) {
904 currentVar = mprCreatePropertyValue(currentObj, propertyName,
905 mprCreateUndefinedVar());
912 * Don't use mprCopyVar as it will copy the data
915 *varValue = currentVar;
917 return currentVar ? 0 : -1;
920 /******************************************************************************/
922 * Get the next token as part of a variable specification. This will return
923 * a pointer to the next token and will return a pointer to the next token
924 * (after this one) in "next". The tokBuf holds the parsed token.
926 static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen)
932 while (isspace((int) *start) || *start == '\n' || *start == '\r') {
937 if (*cp == '.' || *cp == '[' || *cp == ']') {
940 while (*cp && *cp != '.' && *cp != '[' && *cp != ']' &&
941 !isspace((int) *cp) && *cp != '\n' && *cp != '\r') {
945 len = mprMemcpy(tokBuf, tokBufLen - 1, start, cp - start);
952 /******************************************************************************/
954 * Get the EJS structure pointer
957 Ejs *ejsPtr(EjsId eid)
965 mprAssert(0 <= intId && intId < ejsList->max);
967 if (intId < 0 || intId >= ejsList->max || ejsList->handles[intId] == NULL) {
972 handle = ejsList->handles[intId];
977 /******************************************************************************/
979 * Utility routine to crack JavaScript arguments. Return the number of args
980 * seen. This routine only supports %s and %d type args.
984 * if (ejsParseArgs(argc, argv, "%s %d", &name, &age) < 2) {
985 * mprError("Insufficient args\n");
990 int ejsParseArgs(int argc, char **argv, char *fmt, ...)
997 va_start(vargs, fmt);
1003 for (argn = 0, cp = fmt; cp && *cp && argn < argc && argv[argn]; ) {
1011 bp = va_arg(vargs, bool*);
1013 if (strcmp(s, "true") == 0 || s[0] == '1') {
1024 ip = va_arg(vargs, int*);
1029 sp = va_arg(vargs, char**);
1043 /******************************************************************************/
1048 /******************************************************************************/
1049 #endif /* BLD_FEATURE_EJS */
1051 /******************************************************************************/
1058 * vim600: sw=4 ts=4 fdm=marker
1059 * vim<600: sw=4 ts=4