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
);
215 mprDeleteProperty(ep
->local
, "local");
217 mprDeleteProperty(ep
->global
, "this");
218 mprDeleteProperty(ep
->global
, "global");
220 handles
= ep
->frames
->handles
;
221 for (i
= 0; i
< ep
->frames
->max
; i
++) {
225 if (vp
->type
== MPR_TYPE_OBJECT
&& vp
->properties
->refCount
> 1) {
226 mprLog(7, "ejsCloseEngine: %s has ref count %d\n",
227 vp
->name
, vp
->properties
->refCount
);
232 mprRemoveFromArray(ep
->frames
, i
);
235 mprDestroyArray(ep
->frames
);
238 mprRemoveFromArray(ejsList
, (int) ep
->eid
);
244 /******************************************************************************/
246 * Evaluate an EJS script file
249 int ejsEvalFile(EjsId eid
, char *path
, MprVar
*result
, char **emsg
)
256 mprAssert(path
&& *path
);
262 if ((ep
= ejsPtr(eid
)) == NULL
) {
267 if ((fd
= open(path
, O_RDONLY
| O_BINARY
, 0666)) < 0) {
268 ejsError(ep
, "Can't open %s\n", path
);
272 if (stat(path
, &sbuf
) < 0) {
274 ejsError(ep
, "Cant stat %s", path
);
278 if ((script
= (char*) mprMalloc(sbuf
.st_size
+ 1)) == NULL
) {
280 ejsError(ep
, "Cant malloc %d", (int) sbuf
.st_size
);
284 if (read(fd
, script
, sbuf
.st_size
) != (int) sbuf
.st_size
) {
287 ejsError(ep
, "Error reading %s", path
);
291 script
[sbuf
.st_size
] = '\0';
294 rc
= ejsEvalBlock(eid
, script
, result
, emsg
);
303 *emsg
= mprStrdup(ep
->error
);
307 /******************************************************************************/
309 * Create a new variable scope block. This pushes the old local frame down
310 * the stack and creates a new local variables frame.
313 int ejsOpenBlock(EjsId eid
)
317 if((ep
= ejsPtr(eid
)) == NULL
) {
321 ep
->local
= (MprVar
*) mprMalloc(sizeof(MprVar
));
322 *ep
->local
= ejsCreateObj("localBlock", EJS_OBJ_HASH_SIZE
);
324 mprCreateProperty(ep
->local
, "local", ep
->local
);
326 return mprAddToArray(ep
->frames
, ep
->local
);
329 /******************************************************************************/
331 * Close a variable scope block opened via ejsOpenBlock. Pop back the old
332 * local variables frame.
335 int ejsCloseBlock(EjsId eid
, int fid
)
339 if((ep
= ejsPtr(eid
)) == NULL
) {
345 * Must remove self-references before destroying "local"
347 mprDeleteProperty(ep
->local
, "local");
349 mprDestroyVar(ep
->local
);
352 mprRemoveFromArray(ep
->frames
, fid
);
353 ep
->local
= (MprVar
*) ep
->frames
->handles
[ep
->frames
->used
- 1];
358 /******************************************************************************/
360 * Create a new variable scope block and evaluate a script. All frames
361 * created during this context will be automatically deleted when complete.
362 * vp and emsg are optional. i.e. created local variables will be discarded
363 * when this routine returns.
366 int ejsEvalBlock(EjsId eid
, char *script
, MprVar
*vp
, char **emsg
)
372 fid
= ejsOpenBlock(eid
);
373 rc
= ejsEvalScript(eid
, script
, vp
, emsg
);
374 ejsCloseBlock(eid
, fid
);
379 /******************************************************************************/
381 * Parse and evaluate a EJS. Return the result in *vp. The result is "owned"
382 * by EJ and the caller must not free it. Returns -1 on errors and zero
383 * for success. On errors, emsg will be set to the reason. The caller must
387 int ejsEvalScript(EjsId eid
, char *script
, MprVar
*vp
, char **emsg
)
391 void *endlessLoopTest
;
398 if ((ep
= ejsPtr(eid
)) == NULL
) {
403 mprDestroyVar(&ep
->result
);
410 * Allocate a new evaluation block, and save the old one
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
);
450 if (state
== EJS_STATE_ERR
) {
458 return ep
->exitStatus
;
461 /******************************************************************************/
463 * Core error handling
466 static void ejsErrorCore(Ejs
* ep
, const char *fmt
, va_list args
)
467 PRINTF_ATTRIBUTE(2, 0);
469 static void ejsErrorCore(Ejs
* ep
, const char *fmt
, va_list args
)
472 char *errbuf
, *msgbuf
;
478 mprAllocVsprintf(&msgbuf
, MPR_MAX_STRING
, fmt
, args
);
481 mprAllocSprintf(&errbuf
, MPR_MAX_STRING
, "%s\nBacktrace:\n", msgbuf
);
483 /* form a backtrace */
486 mprAllocSprintf(&msg2
, MPR_MAX_STRING
,
487 "\t[%2d] %20s:%-4d -> %s\n",
488 frame
++, ip
->procName
?ip
->procName
:"", ip
->lineNumber
, ip
->line
);
489 ebuf2
= mprRealloc(errbuf
, strlen(errbuf
) + strlen(msg2
) + 1);
490 if (ebuf2
== NULL
) break;
492 memcpy(errbuf
+strlen(errbuf
), msg2
, strlen(msg2
)+1);
501 /******************************************************************************/
503 * Internal use function to set the error message
506 void ejsError(Ejs
* ep
, const char* fmt
, ...)
511 ejsErrorCore(ep
, fmt
, args
);
515 /******************************************************************************/
517 * Public routine to set the error message
520 void ejsSetErrorMsg(EjsId eid
, const char* fmt
, ...)
525 if ((ep
= ejsPtr(eid
)) == NULL
) {
530 ejsErrorCore(ep
, fmt
, args
);
534 /******************************************************************************/
536 * Get the current line number
539 int ejsGetLineNumber(EjsId eid
)
543 if ((ep
= ejsPtr(eid
)) == NULL
) {
547 return ep
->input
->lineNumber
;
550 /******************************************************************************/
552 * Return the local object
555 MprVar
*ejsGetLocalObject(EjsId eid
)
559 if ((ep
= ejsPtr(eid
)) == NULL
) {
566 /******************************************************************************/
568 * Return the global object
571 MprVar
*ejsGetGlobalObject(EjsId eid
)
575 if ((ep
= ejsPtr(eid
)) == NULL
) {
582 /******************************************************************************/
584 * Copy the value of an object property. Return value is in "value".
585 * If deepCopy is true, copy all object/strings. Otherwise, object reference
586 * counts are incremented. Callers must always call mprDestroyVar on the
587 * return value to prevent leaks.
589 * Returns: -1 on errors or if the variable is not found.
592 int ejsCopyVar(EjsId eid
, const char *var
, MprVar
*value
, bool deepCopy
)
597 mprAssert(var
&& *var
);
600 if ((ep
= ejsPtr(eid
)) == NULL
) {
605 if (ejsGetVarCore(ep
, var
, 0, &vp
, 0) < 0) {
609 return mprCopyProperty(value
, vp
, deepCopy
);
612 /******************************************************************************/
614 * Return the value of an object property. Return value is in "value".
615 * Objects and strings are not copied and reference counts are not modified.
616 * Callers should NOT call mprDestroyVar. Returns: -1 on errors or if the
617 * variable is not found.
620 int ejsReadVar(EjsId eid
, const char *var
, MprVar
*value
)
625 mprAssert(var
&& *var
);
628 if ((ep
= ejsPtr(eid
)) == NULL
) {
633 if (ejsGetVarCore(ep
, var
, 0, &vp
, 0) < 0) {
637 return mprReadProperty(vp
, value
);
640 /******************************************************************************/
642 * Set a variable that may be an arbitrarily complex object or array reference.
643 * Will always define in the top most variable frame.
646 int ejsWriteVar(EjsId eid
, const char *var
, MprVar
*value
)
651 mprAssert(var
&& *var
);
653 if ((ep
= ejsPtr(eid
)) == NULL
) {
658 if (ejsGetVarCore(ep
, var
, 0, &vp
, EJS_FLAGS_CREATE
) < 0) {
664 * Only copy the value. Don't overwrite the object's name
666 mprWriteProperty(vp
, value
);
671 /******************************************************************************/
673 * Set a variable that may be an arbitrarily complex object or array reference.
674 * Will always define in the top most variable frame.
677 int ejsWriteVarValue(EjsId eid
, const char *var
, MprVar value
)
679 return ejsWriteVar(eid
, var
, &value
);
682 /******************************************************************************/
687 int ejsDeleteVar(EjsId eid
, const char *var
)
693 if ((ep
= ejsPtr(eid
)) == NULL
) {
697 if (ejsGetVarCore(ep
, var
, &obj
, &vp
, 0) < 0) {
700 mprDeleteProperty(obj
, vp
->name
);
704 /******************************************************************************/
706 * Set the expression return value
709 void ejsSetReturnValue(EjsId eid
, MprVar value
)
713 if ((ep
= ejsPtr(eid
)) == NULL
) {
717 mprCopyVar(&ep
->result
, &value
, MPR_SHALLOW_COPY
);
720 /******************************************************************************/
722 * Set the expression return value to a string value
725 void ejsSetReturnString(EjsId eid
, const char *str
)
729 if ((ep
= ejsPtr(eid
)) == NULL
) {
733 mprCopyVarValue(&ep
->result
, mprCreateStringVar(str
, 0), MPR_SHALLOW_COPY
);
736 /******************************************************************************/
738 * Get the expression return value
741 MprVar
*ejsGetReturnValue(EjsId eid
)
745 if ((ep
= ejsPtr(eid
)) == NULL
) {
752 /******************************************************************************/
754 * Define a C function. If eid < 0, then update the master object with this
755 * function. NOTE: in this case, functionName must be simple without any "." or
756 * "[]" elements. If eid >= 0, add to the specified script engine. In this
757 * case, functionName can be an arbitrary object reference and can contain "."
761 void ejsDefineCFunction(EjsId eid
, const char *functionName
, MprCFunction fn
,
762 void *thisPtr
, int flags
)
766 mprCreatePropertyValue(&master
, functionName
,
767 mprCreateCFunctionVar(fn
, thisPtr
, flags
));
770 ejsWriteVarValue(eid
, functionName
,
771 mprCreateCFunctionVar(fn
, thisPtr
, flags
));
775 /******************************************************************************/
777 * Define a C function with String arguments
780 void ejsDefineStringCFunction(EjsId eid
, const char *functionName
,
781 MprStringCFunction fn
, void *thisPtr
, int flags
)
785 mprCreatePropertyValue(&master
, functionName
,
786 mprCreateStringCFunctionVar(fn
, thisPtr
, flags
));
789 ejsWriteVarValue(eid
, functionName
,
790 mprCreateStringCFunctionVar(fn
, thisPtr
, flags
));
794 /******************************************************************************/
796 * Define a JavaScript function. Args should be comma separated.
797 * Body should not contain braces.
800 void ejsDefineFunction(EjsId eid
, const char *functionName
, char *args
,
805 v
= mprCreateFunctionVar(args
, body
, 0);
808 mprCreateProperty(&master
, functionName
, &v
);
811 ejsWriteVar(eid
, functionName
, &v
);
816 /******************************************************************************/
818 void *ejsGetThisPtr(EjsId eid
)
822 if ((ep
= ejsPtr(eid
)) == NULL
) {
829 /******************************************************************************/
831 * Find a variable given a variable name and return the parent object and
832 * the variable itself, the variable . This routine supports variable names
833 * that may be objects or arrays but may NOT have expressions in the array
834 * indicies. Returns -1 on errors or if the variable is not found.
837 int ejsGetVarCore(Ejs
*ep
, const char *vname
, MprVar
**obj
,
838 MprVar
**varValue
, int flags
)
842 char tokBuf
[EJS_MAX_ID
];
843 char *propertyName
, *token
, *next
, *cp
, *varName
;
851 currentObj
= ejsFindObj(ep
, 0, vname
, flags
);
855 next
= varName
= mprStrdup(vname
);
857 token
= getNextVarToken(&next
, tokBuf
, sizeof(tokBuf
));
859 while (currentObj
!= 0 && token
!= 0 && *token
) {
862 token
= getNextVarToken(&next
, tokBuf
, sizeof(tokBuf
));
864 propertyName
= token
;
865 if (*propertyName
== '\"') {
867 if ((cp
= strchr(propertyName
, '\"')) != 0) {
870 } else if (*propertyName
== '\'') {
872 if ((cp
= strchr(propertyName
, '\'')) != 0) {
877 currentObj
= currentVar
;
878 currentVar
= ejsFindProperty(ep
, 0, currentObj
, propertyName
, 0);
880 token
= getNextVarToken(&next
, tokBuf
, sizeof(tokBuf
));
886 } else if (*token
== '.') {
887 token
= getNextVarToken(&next
, tokBuf
, sizeof(tokBuf
));
888 if (!isalpha((int) token
[0]) &&
889 token
[0] != '_' && token
[0] != '$') {
894 propertyName
= token
;
895 currentObj
= currentVar
;
896 currentVar
= ejsFindProperty(ep
, 0, currentObj
, token
, 0);
899 currentVar
= ejsFindProperty(ep
, 0, currentObj
, token
, 0);
901 token
= getNextVarToken(&next
, tokBuf
, sizeof(tokBuf
));
905 if (currentVar
== 0 && currentObj
>= 0 && flags
& EJS_FLAGS_CREATE
) {
906 currentVar
= mprCreatePropertyValue(currentObj
, propertyName
,
907 mprCreateUndefinedVar());
914 * Don't use mprCopyVar as it will copy the data
917 *varValue
= currentVar
;
919 return currentVar
? 0 : -1;
922 /******************************************************************************/
924 * Get the next token as part of a variable specification. This will return
925 * a pointer to the next token and will return a pointer to the next token
926 * (after this one) in "next". The tokBuf holds the parsed token.
928 static char *getNextVarToken(char **next
, char *tokBuf
, int tokBufLen
)
934 while (isspace((int) *start
) || *start
== '\n' || *start
== '\r') {
939 if (*cp
== '.' || *cp
== '[' || *cp
== ']') {
942 while (*cp
&& *cp
!= '.' && *cp
!= '[' && *cp
!= ']' &&
943 !isspace((int) *cp
) && *cp
!= '\n' && *cp
!= '\r') {
947 len
= mprMemcpy(tokBuf
, tokBufLen
- 1, start
, cp
- start
);
954 /******************************************************************************/
956 * Get the EJS structure pointer
959 Ejs
*ejsPtr(EjsId eid
)
967 mprAssert(0 <= intId
&& intId
< ejsList
->max
);
969 if (intId
< 0 || intId
>= ejsList
->max
|| ejsList
->handles
[intId
] == NULL
) {
974 handle
= ejsList
->handles
[intId
];
979 /******************************************************************************/
981 * Utility routine to crack JavaScript arguments. Return the number of args
982 * seen. This routine only supports %s and %d type args.
986 * if (ejsParseArgs(argc, argv, "%s %d", &name, &age) < 2) {
987 * mprError("Insufficient args\n");
992 int ejsParseArgs(int argc
, char **argv
, char *fmt
, ...)
999 va_start(vargs
, fmt
);
1005 for (argn
= 0, cp
= fmt
; cp
&& *cp
&& argn
< argc
&& argv
[argn
]; ) {
1013 bp
= va_arg(vargs
, bool*);
1015 if (strcmp(s
, "true") == 0 || s
[0] == '1') {
1026 ip
= va_arg(vargs
, int*);
1031 sp
= va_arg(vargs
, char**);
1045 /******************************************************************************/
1050 /******************************************************************************/
1051 #endif /* BLD_FEATURE_EJS */
1053 /******************************************************************************/
1060 * vim600: sw=4 ts=4 fdm=marker
1061 * vim<600: sw=4 ts=4