From ecc4cc8fbd1b6ad7586d56d4c9b029c08adae0a1 Mon Sep 17 00:00:00 2001 From: pur3mail Date: Tue, 9 Aug 2011 09:20:12 +0000 Subject: [PATCH] Version 0.29 : Added new object via functions Fixed getString() for double on some platforms --- TinyJS.cpp | 214 +++++++++++++++++++++++++---------------------- TinyJS.h | 1 + tests/test035.js | 10 +++ tests/test036.js | 9 ++ 4 files changed, 135 insertions(+), 99 deletions(-) create mode 100644 tests/test035.js create mode 100644 tests/test036.js diff --git a/TinyJS.cpp b/TinyJS.cpp index 42dfb05..17cf4e2 100755 --- a/TinyJS.cpp +++ b/TinyJS.cpp @@ -92,9 +92,10 @@ Rudimentary call stack on error Added String Character functions Added shift operators + Version 0.29 : Added new object via functions + Fixed getString() for double on some platforms - - NOTE: This doesn't support constructors for objects + NOTE: Constructing an array with an initial length 'Array(5)' doesn't work Recursive loops of data such as a.foo = a; fail to be garbage collected length variable cannot be set @@ -951,7 +952,7 @@ const string &CScriptVar::getString() { } if (isDouble()) { char buffer[32]; - sprintf_s(buffer, sizeof(buffer), "%lf", doubleData); + sprintf_s(buffer, sizeof(buffer), "%f", doubleData); data = buffer; return data; } @@ -1429,6 +1430,104 @@ CScriptVarLink *CTinyJS::parseFunctionDefinition() { return funcVar; } +/** Handle a function call (assumes we've parsed the function name and we're + * on the start bracket). 'parent' is the object that contains this method, + * if there was one (otherwise it's just a normnal function). + */ +CScriptVarLink *CTinyJS::functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent) { + if (execute) { + if (!function->var->isFunction()) { + string errorMsg = "Expecting '"; + errorMsg = errorMsg + function->name + "' to be a function"; + throw new CScriptException(errorMsg.c_str()); + } + l->match('('); + // create a new symbol table entry for execution of this function + CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); + if (parent) + functionRoot->addChildNoDup("this", parent); + // grab in all parameters + CScriptVarLink *v = function->var->firstChild; + while (v) { + CScriptVarLink *value = base(execute); + if (execute) { + if (value->var->isBasic()) { + // pass by value + functionRoot->addChild(v->name, value->var->deepCopy()); + } else { + // pass by reference + functionRoot->addChild(v->name, value->var); + } + } + CLEAN(value); + if (l->tk!=')') l->match(','); + v = v->nextSibling; + } + l->match(')'); + // setup a return variable + CScriptVarLink *returnVar = NULL; + // execute function! + // add the function's execute space to the symbol table so we can recurse + CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); + scopes.push_back(functionRoot); +#ifdef TINYJS_CALL_STACK + call_stack.push_back(function->name + " from " + l->getPosition()); +#endif + + if (function->var->isNative()) { + ASSERT(function->var->jsCallback); + function->var->jsCallback(functionRoot, function->var->jsCallbackUserData); + } else { + /* we just want to execute the block, but something could + * have messed up and left us with the wrong ScriptLex, so + * we want to be careful here... */ + CScriptException *exception = 0; + CScriptLex *oldLex = l; + CScriptLex *newLex = new CScriptLex(function->var->getString()); + l = newLex; + try { + block(execute); + // because return will probably have called this, and set execute to false + execute = true; + } catch (CScriptException *e) { + exception = e; + } + delete newLex; + l = oldLex; + + if (exception) + throw exception; + } +#ifdef TINYJS_CALL_STACK + if (!call_stack.empty()) call_stack.pop_back(); +#endif + scopes.pop_back(); + /* get the real return var before we remove it from our function */ + returnVar = new CScriptVarLink(returnVarLink->var); + functionRoot->removeLink(returnVarLink); + delete functionRoot; + if (returnVar) + return returnVar; + else + return new CScriptVarLink(new CScriptVar()); + } else { + // function, but not executing - just parse args and be done + l->match('('); + while (l->tk != ')') { + CScriptVarLink *value = base(execute); + CLEAN(value); + if (l->tk!=')') l->match(','); + } + l->match(')'); + if (l->tk == '{') { + block(execute); + } + /* function will be a blank scriptvarlink if we're not executing, + * so just return it rather than an alloc/free */ + return function; + } +} + CScriptVarLink *CTinyJS::factor(bool &execute) { if (l->tk=='(') { l->match('('); @@ -1466,94 +1565,7 @@ CScriptVarLink *CTinyJS::factor(bool &execute) { l->match(LEX_ID); while (l->tk=='(' || l->tk=='.' || l->tk=='[') { if (l->tk=='(') { // ------------------------------------- Function Call - if (execute) { - if (!a->var->isFunction()) { - string errorMsg = "Expecting '"; - errorMsg = errorMsg + a->name + "' to be a function"; - throw new CScriptException(errorMsg.c_str()); - } - l->match('('); - // create a new symbol table entry for execution of this function - CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); - if (parent) - functionRoot->addChildNoDup("this", parent); - // grab in all parameters - CScriptVarLink *v = a->var->firstChild; - while (v) { - CScriptVarLink *value = base(execute); - if (execute) { - if (value->var->isBasic()) { - // pass by value - functionRoot->addChild(v->name, value->var->deepCopy()); - } else { - // pass by reference - functionRoot->addChild(v->name, value->var); - } - } - CLEAN(value); - if (l->tk!=')') l->match(','); - v = v->nextSibling; - } - l->match(')'); - // setup a return variable - CScriptVarLink *returnVar = NULL; - // execute function! - // add the function's execute space to the symbol table so we can recurse - CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); - scopes.push_back(functionRoot); -#ifdef TINYJS_CALL_STACK - call_stack.push_back(a->name + " from " + l->getPosition()); -#endif - - if (a->var->isNative()) { - ASSERT(a->var->jsCallback); - a->var->jsCallback(functionRoot, a->var->jsCallbackUserData); - } else { - /* we just want to execute the block, but something could - * have messed up and left us with the wrong ScriptLex, so - * we want to be careful here... */ - CScriptException *exception = 0; - CScriptLex *oldLex = l; - CScriptLex *newLex = new CScriptLex(a->var->getString()); - l = newLex; - try { - block(execute); - // because return will probably have called this, and set execute to false - execute = true; - } catch (CScriptException *e) { - exception = e; - } - delete newLex; - l = oldLex; - - if (exception) - throw exception; - } -#ifdef TINYJS_CALL_STACK - if (!call_stack.empty()) call_stack.pop_back(); -#endif - scopes.pop_back(); - /* get the real return var before we remove it from our function */ - returnVar = new CScriptVarLink(returnVarLink->var); - functionRoot->removeLink(returnVarLink); - delete functionRoot; - if (returnVar) - a = returnVar; - else - a = new CScriptVarLink(new CScriptVar()); - } else { - // function, but not executing - just parse args and be done - l->match('('); - while (l->tk != ')') { - CScriptVarLink *value = base(execute); - CLEAN(value); - if (l->tk!=')') l->match(','); - } - l->match(')'); - if (l->tk == '{') { - block(execute); - } - } + a = functionCall(execute, a, parent); } else if (l->tk == '.') { // ------------------------------------- Record Access l->match('.'); if (execute) { @@ -1656,20 +1668,24 @@ CScriptVarLink *CTinyJS::factor(bool &execute) { l->match(LEX_R_NEW); const string &className = l->tkStr; if (execute) { - CScriptVarLink *objClass = findInScopes(className); - if (!objClass) { + CScriptVarLink *objClassOrFunc = findInScopes(className); + if (!objClassOrFunc) { TRACE("%s is not a valid class name", className.c_str()); return new CScriptVarLink(new CScriptVar()); } l->match(LEX_ID); CScriptVar *obj = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); - obj->addChild(TINYJS_PROTOTYPE_CLASS, objClass->var); - if (l->tk == '(') { - l->match('('); - l->match(')'); + CScriptVarLink *objLink = new CScriptVarLink(obj); + if (objClassOrFunc->var->isFunction()) { + CLEAN(functionCall(execute, objClassOrFunc, obj)); + } else { + obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var); + if (l->tk == '(') { + l->match('('); + l->match(')'); + } } - // TODO: Object constructors - return new CScriptVarLink(obj); + return objLink; } else { l->match(LEX_ID); if (l->tk == '(') { diff --git a/TinyJS.h b/TinyJS.h index a527771..a37cf21 100755 --- a/TinyJS.h +++ b/TinyJS.h @@ -333,6 +333,7 @@ private: CScriptVar *arrayClass; /// Built in array class // parsing - in order of precedence + CScriptVarLink *functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent); CScriptVarLink *factor(bool &execute); CScriptVarLink *unary(bool &execute); CScriptVarLink *term(bool &execute); diff --git a/tests/test035.js b/tests/test035.js new file mode 100644 index 0000000..73a8a7f --- /dev/null +++ b/tests/test035.js @@ -0,0 +1,10 @@ +// the 'lf' in the printf caused issues writing doubles on some compilers +function Person(name) { + this.name = name; + this.kill = function() { this.name += " is dead"; }; +} + +var a = new Person("Kenny"); +a.kill(); +result = a.name == "Kenny is dead"; + diff --git a/tests/test036.js b/tests/test036.js new file mode 100644 index 0000000..efd3418 --- /dev/null +++ b/tests/test036.js @@ -0,0 +1,9 @@ +// the 'lf' in the printf caused issues writing doubles on some compilers +var a=5.0/10.0*100.0; +var b=5.0*110.0; +var c=50.0/10.0; +a.dump(); +b.dump(); +c.dump(); +result = a==50 && b==550 && c==5; +