From 8c790516ea190155bd93c8c3d436a31f9efe26c9 Mon Sep 17 00:00:00 2001 From: pur3mail Date: Fri, 29 Jul 2011 14:33:00 +0000 Subject: [PATCH] Version 0.28 : Ternary operator Rudimentary call stack on error Added String Character functions Added shift operators --- TinyJS.cpp | 156 ++++++++++++++++++++++++++++++++----------- TinyJS.h | 11 +++ TinyJS_Functions.cpp | 18 +++++ tests/test033.js | 5 ++ tests/test034.js | 3 + 5 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 tests/test033.js create mode 100644 tests/test034.js diff --git a/TinyJS.cpp b/TinyJS.cpp index e55fb14..42dfb05 100755 --- a/TinyJS.cpp +++ b/TinyJS.cpp @@ -88,14 +88,18 @@ Add built-in array functions Version 0.27 : Added OZLB's TinyJS.setVariable (with some tweaks) Added OZLB's Maths Functions + Version 0.28 : Ternary operator + Rudimentary call stack on error + Added String Character functions + Added shift operators NOTE: This doesn't support constructors for objects + 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' cannot be set - There is no ternary operator implemented yet - The postfix increment operator returns the current value, not the - previous as it should. + length variable cannot be set + The postfix increment operator returns the current value, not the previous as it should. + There is no prefix increment operator Arrays are implemented as a linked list - hence a lookup time is O(n) TODO: @@ -341,6 +345,7 @@ string CScriptLex::getTokenStr(int token) { case LEX_LSHIFTEQUAL : return "<<="; case LEX_GEQUAL : return ">="; case LEX_RSHIFT : return ">>"; + case LEX_RSHIFTUNSIGNED : return ">>"; case LEX_RSHIFTEQUAL : return ">>="; case LEX_PLUSEQUAL : return "+="; case LEX_MINUSEQUAL : return "-="; @@ -545,9 +550,12 @@ void CScriptLex::getNextToken() { } else if (tk=='>' && currCh=='>') { tk = LEX_RSHIFT; getNextCh(); - if (currCh=='=') { // <<= + if (currCh=='=') { // >>= tk = LEX_RSHIFTEQUAL; getNextCh(); + } else if (currCh=='>') { // >>> + tk = LEX_RSHIFTUNSIGNED; + getNextCh(); } } else if (tk=='+' && currCh=='=') { tk = LEX_PLUSEQUAL; @@ -602,9 +610,9 @@ string CScriptLex::getSubString(int lastPosition) { CScriptLex *CScriptLex::getSubLex(int lastPosition) { int lastCharIdx = tokenLastEnd+1; if (lastCharIdx < dataEnd) - return new CScriptLex( this, lastPosition, lastCharIdx); + return new CScriptLex(this, lastPosition, lastCharIdx); else - return new CScriptLex( this, lastPosition, dataEnd ); + return new CScriptLex(this, lastPosition, dataEnd ); } string CScriptLex::getPosition(int pos) { @@ -1290,6 +1298,9 @@ void CTinyJS::execute(const string &code) { CScriptLex *oldLex = l; vector oldScopes = scopes; l = new CScriptLex(code); +#ifdef TINYJS_CALL_STACK + call_stack.clear(); +#endif scopes.clear(); scopes.push_back(root); try { @@ -1297,9 +1308,15 @@ void CTinyJS::execute(const string &code) { while (l->tk) statement(execute); } catch (CScriptException *e) { ostringstream msg; - msg << "Error " << e->text << " at " << l->getPosition(); + msg << "Error " << e->text; +#ifdef TINYJS_CALL_STACK + for (int i=(int)call_stack.size()-1;i>=0;i--) + msg << "\n" << i << ": " << call_stack.at(i); +#endif + msg << " at " << l->getPosition(); delete l; l = oldLex; + throw new CScriptException(msg.str()); } delete l; @@ -1312,6 +1329,9 @@ CScriptVarLink CTinyJS::evaluateComplex(const string &code) { vector oldScopes = scopes; l = new CScriptLex(code); +#ifdef TINYJS_CALL_STACK + call_stack.clear(); +#endif scopes.clear(); scopes.push_back(root); CScriptVarLink *v = 0; @@ -1323,10 +1343,16 @@ CScriptVarLink CTinyJS::evaluateComplex(const string &code) { if (l->tk!=LEX_EOF) l->match(';'); } while (l->tk!=LEX_EOF); } catch (CScriptException *e) { - ostringstream msg; - msg << "Error " << e->text << " at " << l->getPosition(); - delete l; - l = oldLex; + ostringstream msg; + msg << "Error " << e->text; +#ifdef TINYJS_CALL_STACK + for (int i=(int)call_stack.size()-1;i>=0;i--) + msg << "\n" << i << ": " << call_stack.at(i); +#endif + msg << " at " << l->getPosition(); + delete l; + l = oldLex; + throw new CScriptException(msg.str()); } delete l; @@ -1475,6 +1501,9 @@ CScriptVarLink *CTinyJS::factor(bool &execute) { // 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); @@ -1500,7 +1529,9 @@ CScriptVarLink *CTinyJS::factor(bool &execute) { 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); @@ -1548,7 +1579,7 @@ CScriptVarLink *CTinyJS::factor(bool &execute) { l->match(LEX_ID); } else if (l->tk == '[') { // ------------------------------------- Array Access l->match('['); - CScriptVarLink *index = expression(execute); + CScriptVarLink *index = base(execute); l->match(']'); if (execute) { CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString()); @@ -1719,8 +1750,25 @@ CScriptVarLink *CTinyJS::expression(bool &execute) { return a; } +CScriptVarLink *CTinyJS::shift(bool &execute) { + CScriptVarLink *a = expression(execute); + if (l->tk==LEX_LSHIFT || l->tk==LEX_RSHIFT || l->tk==LEX_RSHIFTUNSIGNED) { + int op = l->tk; + l->match(op); + CScriptVarLink *b = base(execute); + int shift = execute ? b->var->getInt() : 0; + CLEAN(b); + if (execute) { + if (op==LEX_LSHIFT) a->var->setInt(a->var->getInt() << shift); + if (op==LEX_RSHIFT) a->var->setInt(a->var->getInt() >> shift); + if (op==LEX_RSHIFTUNSIGNED) a->var->setInt(((unsigned int)a->var->getInt()) >> shift); + } + } + return a; +} + CScriptVarLink *CTinyJS::condition(bool &execute) { - CScriptVarLink *a = expression(execute); + CScriptVarLink *a = shift(execute); CScriptVarLink *b; while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL || @@ -1728,7 +1776,7 @@ CScriptVarLink *CTinyJS::condition(bool &execute) { l->tk=='<' || l->tk=='>') { int op = l->tk; l->match(l->tk); - b = expression(execute); + b = shift(execute); if (execute) { CScriptVar *res = a->var->mathsOp(b->var, op); CREATE_LINK(a,res); @@ -1775,8 +1823,36 @@ CScriptVarLink *CTinyJS::logic(bool &execute) { return a; } +CScriptVarLink *CTinyJS::ternary(bool &execute) { + CScriptVarLink *lhs = logic(execute); + bool noexec = false; + if (l->tk=='?') { + l->match('?'); + if (!execute) { + CLEAN(lhs); + CLEAN(base(noexec)); + l->match(':'); + CLEAN(base(noexec)); + } else { + bool first = lhs->var->getBool(); + CLEAN(lhs); + if (first) { + lhs = base(execute); + l->match(':'); + CLEAN(base(noexec)); + } else { + CLEAN(base(noexec)); + l->match(':'); + lhs = base(execute); + } + } + } + + return lhs; +} + CScriptVarLink *CTinyJS::base(bool &execute) { - CScriptVarLink *lhs = logic(execute); + CScriptVarLink *lhs = ternary(execute); if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { /* If we're assigning to this and we don't have a parent, * add it to the symbol table root as per JavaScript. */ @@ -1846,27 +1922,31 @@ void CTinyJS::statement(bool &execute) { * hand side. Maybe just have a flag called can_create_var that we * set and then we parse as if we're doing a normal equals.*/ l->match(LEX_R_VAR); - CScriptVarLink *a = 0; - if (execute) - a = scopes.back()->findChildOrCreate(l->tkStr); - l->match(LEX_ID); - // now do stuff defined with dots - while (l->tk == '.') { - l->match('.'); - if (execute) { - CScriptVarLink *lastA = a; - a = lastA->var->findChildOrCreate(l->tkStr); - } - l->match(LEX_ID); - } - // sort out initialiser - if (l->tk == '=') { - l->match('='); - CScriptVarLink *var = base(execute); - if (execute) - a->replaceWith(var); - CLEAN(var); - } + while (l->tk != ';') { + CScriptVarLink *a = 0; + if (execute) + a = scopes.back()->findChildOrCreate(l->tkStr); + l->match(LEX_ID); + // now do stuff defined with dots + while (l->tk == '.') { + l->match('.'); + if (execute) { + CScriptVarLink *lastA = a; + a = lastA->var->findChildOrCreate(l->tkStr); + } + l->match(LEX_ID); + } + // sort out initialiser + if (l->tk == '=') { + l->match('='); + CScriptVarLink *var = base(execute); + if (execute) + a->replaceWith(var); + CLEAN(var); + } + if (l->tk != ';') + l->match(','); + } l->match(';'); } else if (l->tk==LEX_R_IF) { l->match(LEX_R_IF); diff --git a/TinyJS.h b/TinyJS.h index f0c583c..a527771 100755 --- a/TinyJS.h +++ b/TinyJS.h @@ -26,6 +26,9 @@ #ifndef TINYJS_H #define TINYJS_H +// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging +#define TINYJS_CALL_STACK + #ifdef _WIN32 #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC @@ -40,6 +43,7 @@ #define TRACE printf #endif // TRACE + const int TINYJS_LOOP_MAX_ITERATIONS = 8192; enum LEX_TYPES { @@ -58,6 +62,7 @@ enum LEX_TYPES { LEX_LSHIFTEQUAL, LEX_GEQUAL, LEX_RSHIFT, + LEX_RSHIFTUNSIGNED, LEX_RSHIFTEQUAL, LEX_PLUSEQUAL, LEX_MINUSEQUAL, @@ -319,6 +324,10 @@ public: private: CScriptLex *l; /// current lexer std::vector scopes; /// stack of scopes when parsing +#ifdef TINYJS_CALL_STACK + std::vector call_stack; /// Names of places called so we can show when erroring +#endif + CScriptVar *stringClass; /// Built in string class CScriptVar *objectClass; /// Built in object class CScriptVar *arrayClass; /// Built in array class @@ -328,8 +337,10 @@ private: CScriptVarLink *unary(bool &execute); CScriptVarLink *term(bool &execute); CScriptVarLink *expression(bool &execute); + CScriptVarLink *shift(bool &execute); CScriptVarLink *condition(bool &execute); CScriptVarLink *logic(bool &execute); + CScriptVarLink *ternary(bool &execute); CScriptVarLink *base(bool &execute); void block(bool &execute); void statement(bool &execute); diff --git a/TinyJS_Functions.cpp b/TinyJS_Functions.cpp index b427425..9f30369 100755 --- a/TinyJS_Functions.cpp +++ b/TinyJS_Functions.cpp @@ -93,6 +93,15 @@ void scStringCharAt(CScriptVar *c, void *) { c->getReturnVar()->setString(""); } +void scStringCharCodeAt(CScriptVar *c, void *) { + string str = c->getParameter("this")->getString(); + int p = c->getParameter("pos")->getInt(); + if (p>=0 && p<(int)str.length()) + c->getReturnVar()->setInt(str.at(p)); + else + c->getReturnVar()->setInt(0); +} + void scStringSplit(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); string sep = c->getParameter("separator")->getString(); @@ -111,6 +120,13 @@ void scStringSplit(CScriptVar *c, void *) { result->setArrayIndex(length++, new CScriptVar(str)); } +void scStringFromCharCode(CScriptVar *c, void *) { + char str[2]; + str[0] = c->getParameter("char")->getInt(); + str[1] = 0; + c->getReturnVar()->setString(str); +} + void scIntegerParseInt(CScriptVar *c, void *) { string str = c->getParameter("str")->getString(); int val = strtol(str.c_str(),0,0); @@ -206,6 +222,8 @@ void registerFunctions(CTinyJS *tinyJS) { tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); + tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0); + tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0); tinyJS->addNative("function String.split(separator)", scStringSplit, 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 diff --git a/tests/test033.js b/tests/test033.js new file mode 100644 index 0000000..ade1179 --- /dev/null +++ b/tests/test033.js @@ -0,0 +1,5 @@ +// test for shift +var a = (2<<2); +var b = (16>>3); +var c = (-1 >>> 16); +result = a==8 && b==2 && c == 0xFFFF; diff --git a/tests/test034.js b/tests/test034.js new file mode 100644 index 0000000..a49fa29 --- /dev/null +++ b/tests/test034.js @@ -0,0 +1,3 @@ +// test for ternary + +result = (true?3:4)==3 && (false?5:6)==6;