diff --git a/TinyJS.cpp b/TinyJS.cpp index e2e4c77..038d1d4 100755 --- a/TinyJS.cpp +++ b/TinyJS.cpp @@ -56,12 +56,16 @@ Changed Callbacks to include user data pointer Added some support for objects Added more Java-esque builtin functions + Version 0.17 : Now we don't deepCopy the parent object of the class + Added JSON.stringify and eval() + Nicer JSON indenting + Fixed function output in JSON + Added evaluateComplex + Fixed some reentrancy issues with evaluate/execute NOTE: This doesn't support constructors for objects Recursive loops of data such as a.foo = a; fail to be garbage collected - - */ #include "TinyJS.h" @@ -252,7 +256,7 @@ void CScriptLex::match(int expected_tk) { if (tk!=expected_tk) { ostringstream errorString; errorString << "Got " << getTokenStr(tk) << " expected " << getTokenStr(expected_tk) - << " at " << getPosition(tokenStart) << "in '" << data << "'"; + << " at " << getPosition(tokenStart) << " in '" << data << "'"; throw new CScriptException(errorString.str()); } getNextToken(); @@ -531,6 +535,18 @@ CScriptVarLink::~CScriptVarLink() { var->unref(); } +CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) { + // Copy constructor +#if DEBUG_MEMORY + mark_allocated(this); +#endif + this->name = link.name; + this->nextSibling = 0; + this->prevSibling = 0; + this->var = link.var->ref(); + this->owned = false; +} + void CScriptVarLink::replaceWith(CScriptVar *newVar) { CScriptVar *oldVar = var; var = newVar->ref(); @@ -593,6 +609,11 @@ CScriptVar *CScriptVar::getReturnVar() { return getParameter(TINYJS_RETURN_VAR); } +void CScriptVar::setReturnVar(CScriptVar *var) { + findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var); +} + + CScriptVar *CScriptVar::getParameter(const std::string &name) { return findChildOrCreate(name)->var; } @@ -855,8 +876,16 @@ void CScriptVar::copyValue(CScriptVar *val) { // copy children of 'val' CScriptVarLink *child = val->firstChild; while (child) { - addChild(child->name, child->var->deepCopy()); - child = child->nextSibling; + CScriptVar *copied; + // don't copy the 'parent' object... + if (child->name != TINYJS_PARENT_CLASS) + copied = child->var->deepCopy(); + else + copied = child->var; + + addChild(child->name, copied); + + child = child->nextSibling; } } else { setUndefined(); @@ -868,7 +897,14 @@ CScriptVar *CScriptVar::deepCopy() { // copy children CScriptVarLink *child = firstChild; while (child) { - newVar->addChild(child->name, child->var->deepCopy()); + CScriptVar *copied; + // don't copy the 'parent' object... + if (child->name != TINYJS_PARENT_CLASS) + copied = child->var->deepCopy(); + else + copied = child->var; + + newVar->addChild(child->name, copied); child = child->nextSibling; } return newVar; @@ -908,14 +944,13 @@ string CScriptVar::getParsableString() { funcStr << "function ("; // get list of parameters CScriptVarLink *link = firstChild; - bool first = true; while (link) { - if (!first) { funcStr << ","; first = false; } funcStr << link->name; + if (link->nextSibling) funcStr << ","; link = link->nextSibling; } // add function body - funcStr << ")" << getString(); + funcStr << ") " << getString(); return funcStr.str(); } // if it is a string then we quote it @@ -926,28 +961,29 @@ string CScriptVar::getParsableString() { return "undefined"; } -void CScriptVar::getJSON(ostringstream &destination) { - if (firstChild) { +void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) { + if (!firstChild || isFunction()) { + // no children or a function... just write value directly + destination << getParsableString(); + } else { + string indentedLinePrefix = linePrefix+" "; // children - handle with bracketed list - destination << " { \n"; + destination << "{ \n"; CScriptVarLink *link = firstChild; while (link) { + destination << indentedLinePrefix; if (isAlphaNum(link->name)) destination << link->name; else destination << getJSString(link->name); destination << " : "; - link->var->getJSON(destination); - link = link->nextSibling; - if (link) - destination << ",\n"; - else - destination << "\n"; + link->var->getJSON(destination, indentedLinePrefix); + link = link->nextSibling; + if (link) { + destination << ",\n"; + } } - destination << " }\n"; - } else { - // no children, just write value directly - destination << getParsableString(); + destination << "\n" << linePrefix << "}"; } } @@ -1004,6 +1040,7 @@ void CTinyJS::trace() { void CTinyJS::execute(const string &code) { CScriptLex *oldLex = l; + vector oldScopes = scopes; l = new CScriptLex(code); scopes.clear(); scopes.push_back(root); @@ -1019,10 +1056,13 @@ void CTinyJS::execute(const string &code) { } delete l; l = oldLex; + scopes = oldScopes; } -string CTinyJS::evaluate(const string &code) { +CScriptVarLink CTinyJS::evaluateComplex(const string &code) { CScriptLex *oldLex = l; + vector oldScopes = scopes; + l = new CScriptLex(code); scopes.clear(); scopes.push_back(root); @@ -1039,11 +1079,15 @@ string CTinyJS::evaluate(const string &code) { } delete l; l = oldLex; + scopes = oldScopes; - string result = v ? v->var->getString() : ""; - CLEAN(v); + if (v) return *v; + // return undefined... + return CScriptVarLink(new CScriptVar()); +} - return result; +string CTinyJS::evaluate(const string &code) { + return evaluateComplex(code).var->getString(); } void CTinyJS::parseFunctionArguments(CScriptVar *funcVar) { diff --git a/TinyJS.h b/TinyJS.h index c80e0af..c430da6 100755 --- a/TinyJS.h +++ b/TinyJS.h @@ -154,6 +154,7 @@ public: bool owned; CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); + CScriptVarLink(const CScriptVarLink &link); ///< Copy constructor ~CScriptVarLink(); void replaceWith(CScriptVar *newVar); ///< Replace the Variable pointed to void replaceWith(CScriptVarLink *newVar); ///< Replace the Variable pointed to (just dereferences) @@ -169,6 +170,7 @@ public: ~CScriptVar(void); CScriptVar *getReturnVar(); ///< If this is a function, get the result value (for use by native functions) + void setReturnVar(CScriptVar *var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() CScriptVar *getParameter(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) CScriptVarLink *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 @@ -210,7 +212,7 @@ public: void trace(std::string indentStr = "", const std::string &name = ""); ///< Dump out the contents of this using trace std::string getFlagsAsString(); - void getJSON(std::ostringstream &destination); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) + void getJSON(std::ostringstream &destination, const std::string linePrefix=""); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) void setCallback(JSCallback callback, void *userdata); CScriptVarLink *firstChild; @@ -239,6 +241,14 @@ public: ~CTinyJS(); void execute(const std::string &code); + /** Evaluate the given code and return a link to a javascript object, + * useful for (dangerous) JSON parsing. If nothing to return, will return + * 'undefined' variable type. CScriptVarLink is returned as this will + * automatically unref the result as it goes out of scope. If you want to + * keep it, you must use ref() and unref() */ + CScriptVarLink evaluateComplex(const std::string &code); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ std::string evaluate(const std::string &code); /// add a native function to be called from TinyJS diff --git a/TinyJS_Functions.cpp b/TinyJS_Functions.cpp index 1c49f0b..2a22c27 100755 --- a/TinyJS_Functions.cpp +++ b/TinyJS_Functions.cpp @@ -26,6 +26,7 @@ #include "TinyJS_Functions.h" #include #include +#include using namespace std; // ----------------------------------------------- Actual Functions @@ -114,8 +115,21 @@ void scIntegerValueOf(CScriptVar *c, void *) { c->getReturnVar()->setInt(val); } +void scJSONStringify(CScriptVar *c, void *) { + std::ostringstream result; + c->getParameter("obj")->getJSON(result); + c->getReturnVar()->setString(result.str()); +} + +void scEval(CScriptVar *c, void *data) { + CTinyJS *tinyJS = (CTinyJS *)data; + std::string str = c->getParameter("jsCode")->getString(); + c->setReturnVar(tinyJS->evaluateComplex(str).var); +} + // ----------------------------------------------- Register Functions void registerFunctions(CTinyJS *tinyJS) { + tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); // execute the given string and return the result tinyJS->addNative("function trace()", scTrace, tinyJS); tinyJS->addNative("function Object.dump()", scObjectDump, 0); tinyJS->addNative("function Object.clone()", scObjectClone, 0); @@ -128,4 +142,7 @@ void registerFunctions(CTinyJS *tinyJS) { tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character + tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); // convert to JSON. replacer is ignored at the moment + // JSON.parse is left out as you can (unsafely!) use eval instead } + diff --git a/tests/test021.js b/tests/test021.js new file mode 100644 index 0000000..d94e231 --- /dev/null +++ b/tests/test021.js @@ -0,0 +1,5 @@ +/* Javascript eval */ + +myfoo = eval("{ foo: 42 }"); + +result = eval("4*10+2")==42 && myfoo.foo==42; diff --git a/tests/test022.js b/tests/test022.js new file mode 100644 index 0000000..fc3f427 --- /dev/null +++ b/tests/test022.js @@ -0,0 +1,9 @@ +/* Javascript eval */ + +mystructure = { a:39, b:3, addStuff : function(c,d) { return c+d; } }; + +mystring = JSON.stringify(mystructure, undefined); + +mynewstructure = eval(mystring); + +result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b);