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
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)
472 PRINTF_ATTRIBUTE(2, 0);
474 static void ejsErrorCore(Ejs* ep, const char *fmt, va_list args)
477 char *errbuf, *msgbuf;
483 mprAllocVsprintf(&msgbuf, MPR_MAX_STRING, fmt, args);
488 mprAllocSprintf(&errbuf, MPR_MAX_STRING,
489 "%s\nError on line %d. Offending line: %s\n\n",
490 msgbuf, ip->lineNumber, ip->line);
492 mprAllocSprintf(&errbuf, MPR_MAX_STRING, "%s\n", msgbuf);
500 /******************************************************************************/
502 * Internal use function to set the error message
505 void ejsError(Ejs* ep, const char* fmt, ...)
510 ejsErrorCore(ep, fmt, args);
514 /******************************************************************************/
516 * Public routine to set the error message
519 void ejsSetErrorMsg(EjsId eid, const char* fmt, ...)
524 if ((ep = ejsPtr(eid)) == NULL) {
529 ejsErrorCore(ep, fmt, args);
533 /******************************************************************************/
535 * Get the current line number
538 int ejsGetLineNumber(EjsId eid)
542 if ((ep = ejsPtr(eid)) == NULL) {
546 return ep->input->lineNumber;
549 /******************************************************************************/
551 * Return the local object
554 MprVar *ejsGetLocalObject(EjsId eid)
558 if ((ep = ejsPtr(eid)) == NULL) {
565 /******************************************************************************/
567 * Return the global object
570 MprVar *ejsGetGlobalObject(EjsId eid)
574 if ((ep = ejsPtr(eid)) == NULL) {
581 /******************************************************************************/
583 * Copy the value of an object property. Return value is in "value".
584 * If deepCopy is true, copy all object/strings. Otherwise, object reference
585 * counts are incremented. Callers must always call mprDestroyVar on the
586 * return value to prevent leaks.
588 * Returns: -1 on errors or if the variable is not found.
591 int ejsCopyVar(EjsId eid, const char *var, MprVar *value, bool deepCopy)
596 mprAssert(var && *var);
599 if ((ep = ejsPtr(eid)) == NULL) {
604 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
608 return mprCopyProperty(value, vp, deepCopy);
611 /******************************************************************************/
613 * Return the value of an object property. Return value is in "value".
614 * Objects and strings are not copied and reference counts are not modified.
615 * Callers should NOT call mprDestroyVar. Returns: -1 on errors or if the
616 * variable is not found.
619 int ejsReadVar(EjsId eid, const char *var, MprVar *value)
624 mprAssert(var && *var);
627 if ((ep = ejsPtr(eid)) == NULL) {
632 if (ejsGetVarCore(ep, var, 0, &vp, 0) < 0) {
636 return mprReadProperty(vp, value);
639 /******************************************************************************/
641 * Set a variable that may be an arbitrarily complex object or array reference.
642 * Will always define in the top most variable frame.
645 int ejsWriteVar(EjsId eid, const char *var, MprVar *value)
650 mprAssert(var && *var);
652 if ((ep = ejsPtr(eid)) == NULL) {
657 if (ejsGetVarCore(ep, var, 0, &vp, EJS_FLAGS_CREATE) < 0) {
663 * Only copy the value. Don't overwrite the object's name
665 mprWriteProperty(vp, value);
670 /******************************************************************************/
672 * Set a variable that may be an arbitrarily complex object or array reference.
673 * Will always define in the top most variable frame.
676 int ejsWriteVarValue(EjsId eid, const char *var, MprVar value)
678 return ejsWriteVar(eid, var, &value);
681 /******************************************************************************/
686 int ejsDeleteVar(EjsId eid, const char *var)
692 if ((ep = ejsPtr(eid)) == NULL) {
696 if (ejsGetVarCore(ep, var, &obj, &vp, 0) < 0) {
699 mprDeleteProperty(obj, vp->name);
703 /******************************************************************************/
705 * Set the expression return value
708 void ejsSetReturnValue(EjsId eid, MprVar value)
712 if ((ep = ejsPtr(eid)) == NULL) {
716 mprCopyVar(&ep->result, &value, MPR_SHALLOW_COPY);
719 /******************************************************************************/
721 * Set the expression return value to a string value
724 void ejsSetReturnString(EjsId eid, const char *str)
728 if ((ep = ejsPtr(eid)) == NULL) {
732 mprCopyVarValue(&ep->result, mprCreateStringVar(str, 0), MPR_SHALLOW_COPY);
735 /******************************************************************************/
737 * Get the expression return value
740 MprVar *ejsGetReturnValue(EjsId eid)
744 if ((ep = ejsPtr(eid)) == NULL) {
751 /******************************************************************************/
753 * Define a C function. If eid < 0, then update the master object with this
754 * function. NOTE: in this case, functionName must be simple without any "." or
755 * "[]" elements. If eid >= 0, add to the specified script engine. In this
756 * case, functionName can be an arbitrary object reference and can contain "."
760 void ejsDefineCFunction(EjsId eid, const char *functionName, MprCFunction fn,
761 void *thisPtr, int flags)
765 mprCreatePropertyValue(&master, functionName,
766 mprCreateCFunctionVar(fn, thisPtr, flags));
769 ejsWriteVarValue(eid, functionName,
770 mprCreateCFunctionVar(fn, thisPtr, flags));
774 /******************************************************************************/
776 * Define a C function with String arguments
779 void ejsDefineStringCFunction(EjsId eid, const char *functionName,
780 MprStringCFunction fn, void *thisPtr, int flags)
784 mprCreatePropertyValue(&master, functionName,
785 mprCreateStringCFunctionVar(fn, thisPtr, flags));
788 ejsWriteVarValue(eid, functionName,
789 mprCreateStringCFunctionVar(fn, thisPtr, flags));
793 /******************************************************************************/
795 * Define a JavaScript function. Args should be comma separated.
796 * Body should not contain braces.
799 void ejsDefineFunction(EjsId eid, const char *functionName, char *args,
804 v = mprCreateFunctionVar(args, body, 0);
807 mprCreateProperty(&master, functionName, &v);
810 ejsWriteVar(eid, functionName, &v);
815 /******************************************************************************/
817 void *ejsGetThisPtr(EjsId eid)
821 if ((ep = ejsPtr(eid)) == NULL) {
828 /******************************************************************************/
830 * Find a variable given a variable name and return the parent object and
831 * the variable itself, the variable . This routine supports variable names
832 * that may be objects or arrays but may NOT have expressions in the array
833 * indicies. Returns -1 on errors or if the variable is not found.
836 int ejsGetVarCore(Ejs *ep, const char *vname, MprVar **obj,
837 MprVar **varValue, int flags)
841 char tokBuf[EJS_MAX_ID];
842 char *propertyName, *token, *next, *cp, *varName;
850 currentObj = ejsFindObj(ep, 0, vname, flags);
854 next = varName = mprStrdup(vname);
856 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
858 while (currentObj != 0 && token != 0 && *token) {
861 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
863 propertyName = token;
864 if (*propertyName == '\"') {
866 if ((cp = strchr(propertyName, '\"')) != 0) {
869 } else if (*propertyName == '\'') {
871 if ((cp = strchr(propertyName, '\'')) != 0) {
876 currentObj = currentVar;
877 currentVar = ejsFindProperty(ep, 0, currentObj, propertyName, 0);
879 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
885 } else if (*token == '.') {
886 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
887 if (!isalpha((int) token[0]) &&
888 token[0] != '_' && token[0] != '$') {
893 propertyName = token;
894 currentObj = currentVar;
895 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
898 currentVar = ejsFindProperty(ep, 0, currentObj, token, 0);
900 token = getNextVarToken(&next, tokBuf, sizeof(tokBuf));
904 if (currentVar == 0 && currentObj >= 0 && flags & EJS_FLAGS_CREATE) {
905 currentVar = mprCreatePropertyValue(currentObj, propertyName,
906 mprCreateUndefinedVar());
913 * Don't use mprCopyVar as it will copy the data
916 *varValue = currentVar;
918 return currentVar ? 0 : -1;
921 /******************************************************************************/
923 * Get the next token as part of a variable specification. This will return
924 * a pointer to the next token and will return a pointer to the next token
925 * (after this one) in "next". The tokBuf holds the parsed token.
927 static char *getNextVarToken(char **next, char *tokBuf, int tokBufLen)
933 while (isspace((int) *start) || *start == '\n' || *start == '\r') {
938 if (*cp == '.' || *cp == '[' || *cp == ']') {
941 while (*cp && *cp != '.' && *cp != '[' && *cp != ']' &&
942 !isspace((int) *cp) && *cp != '\n' && *cp != '\r') {
946 len = mprMemcpy(tokBuf, tokBufLen - 1, start, cp - start);
953 /******************************************************************************/
955 * Get the EJS structure pointer
958 Ejs *ejsPtr(EjsId eid)
966 mprAssert(0 <= intId && intId < ejsList->max);
968 if (intId < 0 || intId >= ejsList->max || ejsList->handles[intId] == NULL) {
973 handle = ejsList->handles[intId];
978 /******************************************************************************/
980 * Utility routine to crack JavaScript arguments. Return the number of args
981 * seen. This routine only supports %s and %d type args.
985 * if (ejsParseArgs(argc, argv, "%s %d", &name, &age) < 2) {
986 * mprError("Insufficient args\n");
991 int ejsParseArgs(int argc, char **argv, char *fmt, ...)
998 va_start(vargs, fmt);
1004 for (argn = 0, cp = fmt; cp && *cp && argn < argc && argv[argn]; ) {
1012 bp = va_arg(vargs, bool*);
1014 if (strcmp(s, "true") == 0 || s[0] == '1') {
1025 ip = va_arg(vargs, int*);
1030 sp = va_arg(vargs, char**);
1044 /******************************************************************************/
1049 /******************************************************************************/
1050 #endif /* BLD_FEATURE_EJS */
1052 /******************************************************************************/
1059 * vim600: sw=4 ts=4 fdm=marker
1060 * vim<600: sw=4 ts=4