/* * TinyJS * * A single-file Javascript-alike engine * * Authored By Gordon Williams * * Copyright (C) 2009 Pur3 Ltd * * 42TinyJS * * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine * * Authored / Changed By Armin Diedering * * Copyright (C) 2010-2013 ardisoft * * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifdef _DEBUG # ifndef _MSC_VER # define DEBUG_MEMORY 1 # endif #endif #include #include "TinyJS.h" #ifndef ASSERT # define ASSERT(X) assert(X) #endif #ifndef NO_REGEXP # if defined HAVE_TR1_REGEX # include using namespace std::tr1; # elif defined HAVE_BOOST_REGEX # include using namespace boost; # else # include # endif #endif #include /* printf, scanf, NULL */ #include #include #include #include #include #include #include "TinyJS_Functions.h" using namespace std; // ----------------------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// /// Memory Debug ////////////////////////////////////////////////////////////////////////// //#define DEBUG_MEMORY 1 #if DEBUG_MEMORY vector allocatedVars; vector allocatedLinks; void mark_allocated(CScriptVar *v) { allocatedVars.push_back(v); } void mark_deallocated(CScriptVar *v) { for (size_t i=0;igetRefs()); allocatedVars[i]->trace(" "); } for (size_t i=0;igetName().c_str(), allocatedLinks[i]->getVarPtr()->getRefs()); allocatedLinks[i]->getVarPtr()->trace(" "); } allocatedVars.clear(); allocatedLinks.clear(); } #endif ////////////////////////////////////////////////////////////////////////// /// Utils ////////////////////////////////////////////////////////////////////////// inline bool isWhitespace(char ch) { return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); } inline bool isNumeric(char ch) { return (ch>='0') && (ch<='9'); } uint32_t isArrayIndex(const string &str) { if(str.size()==0 || !isNumeric(str[0]) || (str.size()>1 && str[0]=='0') ) return -1; // empty or (more as 1 digit and beginning with '0') CNumber idx; const char *endptr; idx.parseInt(str.c_str(), 10, &endptr); if(*endptr || idx>uint32_t(0xFFFFFFFFUL)) return -1; return idx.toUInt32(); } inline bool isHexadecimal(char ch) { return ((ch>='0') && (ch<='9')) || ((ch>='a') && (ch<='f')) || ((ch>='A') && (ch<='F')); } inline bool isOctal(char ch) { return ((ch>='0') && (ch<='7')); } inline bool isAlpha(char ch) { return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_' || ch=='$'; } bool isIDString(const char *s) { if (!isAlpha(*s)) return false; while (*s) { if (!(isAlpha(*s) || isNumeric(*s))) return false; s++; } return true; } void replace(string &str, char textFrom, const char *textTo) { int sLen = strlen(textTo); size_t p = str.find(textFrom); while (p != string::npos) { str = str.substr(0, p) + textTo + str.substr(p+1); p = str.find(textFrom, p+sLen); } } string int2string(int32_t intData) { ostringstream str; str << intData; return str.str(); } string int2string(uint32_t intData) { ostringstream str; str << intData; return str.str(); } string float2string(const double &floatData) { ostringstream str; str.unsetf(ios::floatfield); #if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L str.precision(numeric_limits::max_digits10); #else str.precision(numeric_limits::digits10+2); #endif str << floatData; return str.str(); } /// convert the given string into a quoted string suitable for javascript string getJSString(const string &str) { char buffer[5] = "\\x00"; string nStr; nStr.reserve(str.length()); nStr.push_back('\"'); for (string::const_iterator i=str.begin();i!=str.end();i++) { const char *replaceWith = 0; switch (*i) { case '\\': replaceWith = "\\\\"; break; case '\n': replaceWith = "\\n"; break; case '\r': replaceWith = "\\r"; break; case '\a': replaceWith = "\\a"; break; case '\b': replaceWith = "\\b"; break; case '\f': replaceWith = "\\f"; break; case '\t': replaceWith = "\\t"; break; case '\v': replaceWith = "\\v"; break; case '"': replaceWith = "\\\""; break; default: { int nCh = ((unsigned char)*i) & 0xff; if(nCh<32 || nCh>127) { static char hex[] = "0123456789ABCDEF"; buffer[2] = hex[(nCh>>4)&0x0f]; buffer[3] = hex[nCh&0x0f]; replaceWith = buffer; }; } } if (replaceWith) nStr.append(replaceWith); else nStr.push_back(*i); } nStr.push_back('\"'); return nStr; } static inline string getIDString(const string& str) { if(isIDString(str.c_str()) && CScriptToken::isReservedWord(str)==LEX_ID) return str; return getJSString(str); } ////////////////////////////////////////////////////////////////////////// /// CScriptException ////////////////////////////////////////////////////////////////////////// string CScriptException::toString() { ostringstream msg; msg << ERROR_NAME[errorType] << ": " << message; if(lineNumber >= 0) msg << " at Line:" << lineNumber+1; if(column >=0) msg << " Column:" << column+1; if(fileName.length()) msg << " in " << fileName; return msg.str(); } ////////////////////////////////////////////////////////////////////////// /// CScriptLex ////////////////////////////////////////////////////////////////////////// CScriptLex::CScriptLex(const char *Code, const string &File, int Line, int Column) : data(Code) { currentFile = File; pos.currentLineStart = pos.tokenStart = data; pos.currentLine = Line; reset(pos); } void CScriptLex::reset(const POS &toPos) { ///< Reset this lex so we can start again dataPos = toPos.tokenStart; tk = last_tk = 0; tkStr = ""; pos = toPos; lineBreakBeforeToken = false; currCh = nextCh = 0; getNextCh(); // currCh getNextCh(); // nextCh getNextToken(); } void CScriptLex::check(int expected_tk, int alternate_tk/*=-1*/) { if (expected_tk==';' && tk==LEX_EOF) return; // ignore last missing ';' if (tk!=expected_tk && tk!=alternate_tk) { ostringstream errorString; if(expected_tk == LEX_EOF) errorString << "Got unexpected " << CScriptToken::getTokenStr(tk); else errorString << "Got '" << CScriptToken::getTokenStr(tk) << "' expected '" << CScriptToken::getTokenStr(expected_tk) << "'"; if(alternate_tk!=-1) errorString << " or '" << CScriptToken::getTokenStr(alternate_tk) << "'"; throw new CScriptException(SyntaxError, errorString.str(), currentFile, pos.currentLine, currentColumn()); } } void CScriptLex::match(int expected_tk1, int alternate_tk/*=-1*/) { check(expected_tk1, alternate_tk); int line = pos.currentLine; getNextToken(); lineBreakBeforeToken = line != pos.currentLine; } void CScriptLex::getNextCh() { if(currCh == '\n') { // Windows or Linux pos.currentLine++; pos.tokenStart = pos.currentLineStart = dataPos - (nextCh == LEX_EOF ? 0 : 1); } currCh = nextCh; if ( (nextCh = *dataPos) != LEX_EOF ) dataPos++; // stay on EOF if(currCh == '\r') { // Windows or Mac if(nextCh == '\n') getNextCh(); // Windows '\r\n\' --> skip '\r' else currCh = '\n'; // Mac (only '\r') --> convert '\r' to '\n' } } static uint16_t not_allowed_tokens_befor_regexp[] = {LEX_ID, LEX_INT, LEX_FLOAT, LEX_STR, LEX_R_TRUE, LEX_R_FALSE, LEX_R_NULL, ']', ')', '.', LEX_EOF}; void CScriptLex::getNextToken() { while (currCh && isWhitespace(currCh)) getNextCh(); // newline comments if (currCh=='/' && nextCh=='/') { while (currCh && currCh!='\n') getNextCh(); getNextCh(); getNextToken(); return; } // block comments if (currCh=='/' && nextCh=='*') { while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); getNextCh(); getNextCh(); getNextToken(); return; } last_tk = tk; tk = LEX_EOF; tkStr.clear(); // record beginning of this token pos.tokenStart = dataPos - (nextCh == LEX_EOF ? (currCh == LEX_EOF ? 0 : 1) : 2); // tokens if (isAlpha(currCh)) { // IDs while (isAlpha(currCh) || isNumeric(currCh)) { tkStr += currCh; getNextCh(); } tk = CScriptToken::isReservedWord(tkStr); } else if (isNumeric(currCh) || (currCh=='.' && isNumeric(nextCh))) { // Numbers if(currCh=='.') tkStr+='0'; bool isHex = false, isOct=false; if (currCh=='0') { tkStr += currCh; getNextCh(); if(isOctal(currCh)) isOct = true; } if (currCh=='x' || currCh=='X') { isHex = true; tkStr += currCh; getNextCh(); } tk = LEX_INT; while (isOctal(currCh) || (!isOct && isNumeric(currCh)) || (isHex && isHexadecimal(currCh))) { tkStr += currCh; getNextCh(); } if (!isHex && !isOct && currCh=='.') { tk = LEX_FLOAT; tkStr += '.'; getNextCh(); while (isNumeric(currCh)) { tkStr += currCh; getNextCh(); } } // do fancy e-style floating point if (!isHex && !isOct && (currCh=='e' || currCh=='E')) { tk = LEX_FLOAT; tkStr += currCh; getNextCh(); if (currCh=='-') { tkStr += currCh; getNextCh(); } while (isNumeric(currCh)) { tkStr += currCh; getNextCh(); } } } else if (currCh=='"' || currCh=='\'') { // strings... char endCh = currCh; getNextCh(); while (currCh && currCh!=endCh && currCh!='\n') { if (currCh == '\\') { getNextCh(); switch (currCh) { case '\n' : break; // ignore newline after '\' case 'n': tkStr += '\n'; break; case 'r': tkStr += '\r'; break; case 'a': tkStr += '\a'; break; case 'b': tkStr += '\b'; break; case 'f': tkStr += '\f'; break; case 't': tkStr += '\t'; break; case 'v': tkStr += '\v'; break; case 'x': { // hex digits getNextCh(); if(isHexadecimal(currCh)) { char buf[3]="\0\0"; buf[0] = currCh; for(int i=0; i<2 && isHexadecimal(nextCh); i++) { getNextCh(); buf[i] = currCh; } tkStr += (char)strtol(buf, 0, 16); } else throw new CScriptException(SyntaxError, "malformed hexadezimal character escape sequence", currentFile, pos.currentLine, currentColumn()); } default: { if(isOctal(currCh)) { char buf[4]="\0\0\0"; buf[0] = currCh; for(int i=1; i<3 && isOctal(nextCh); i++) { getNextCh(); buf[i] = currCh; } tkStr += (char)strtol(buf, 0, 8); } else tkStr += currCh; } } } else { tkStr += currCh; } getNextCh(); } if(currCh != endCh) throw new CScriptException(SyntaxError, "unterminated string literal", currentFile, pos.currentLine, currentColumn()); getNextCh(); tk = LEX_STR; } else { // single chars tk = currCh; if (currCh) getNextCh(); if (tk=='=' && currCh=='=') { // == tk = LEX_EQUAL; getNextCh(); if (currCh=='=') { // === tk = LEX_TYPEEQUAL; getNextCh(); } } else if (tk=='!' && currCh=='=') { // != tk = LEX_NEQUAL; getNextCh(); if (currCh=='=') { // !== tk = LEX_NTYPEEQUAL; getNextCh(); } } else if (tk=='<') { if (currCh=='=') { // <= tk = LEX_LEQUAL; getNextCh(); } else if (currCh=='<') { // << tk = LEX_LSHIFT; getNextCh(); if (currCh=='=') { // <<= tk = LEX_LSHIFTEQUAL; getNextCh(); } } } else if (tk=='>') { if (currCh=='=') { // >= tk = LEX_GEQUAL; getNextCh(); } else if (currCh=='>') { // >> tk = LEX_RSHIFT; getNextCh(); if (currCh=='=') { // >>= tk = LEX_RSHIFTEQUAL; getNextCh(); } else if (currCh=='>') { // >>> tk = LEX_RSHIFTU; getNextCh(); if (currCh=='=') { // >>>= tk = LEX_RSHIFTUEQUAL; getNextCh(); } } } } else if (tk=='+') { if (currCh=='=') { // += tk = LEX_PLUSEQUAL; getNextCh(); } else if (currCh=='+') { // ++ tk = LEX_PLUSPLUS; getNextCh(); } } else if (tk=='-') { if (currCh=='=') { // -= tk = LEX_MINUSEQUAL; getNextCh(); } else if (currCh=='-') { // -- tk = LEX_MINUSMINUS; getNextCh(); } } else if (tk=='&') { if (currCh=='=') { // &= tk = LEX_ANDEQUAL; getNextCh(); } else if (currCh=='&') { // && tk = LEX_ANDAND; getNextCh(); } } else if (tk=='|') { if (currCh=='=') { // |= tk = LEX_OREQUAL; getNextCh(); } else if (currCh=='|') { // || tk = LEX_OROR; getNextCh(); } } else if (tk=='^' && currCh=='=') { tk = LEX_XOREQUAL; getNextCh(); } else if (tk=='*' && currCh=='=') { tk = LEX_ASTERISKEQUAL; getNextCh(); } else if (tk=='/') { // check if it's a RegExp-Literal tk = LEX_REGEXP; for(uint16_t *p = not_allowed_tokens_befor_regexp; *p; p++) { if(*p==last_tk) { tk = '/'; break; } } if(tk == LEX_REGEXP) { #ifdef NO_REGEXP throw new CScriptException(Error, "42TinyJS was built without support for regular expressions", currentFile, pos.currentLine, currentColumn()); #endif tkStr = "/"; while (currCh && currCh!='/' && currCh!='\n') { if (currCh == '\\' && nextCh == '/') { tkStr.append(1, currCh); getNextCh(); } tkStr.append(1, currCh); getNextCh(); } if(currCh == '/') { #ifndef NO_REGEXP try { regex(tkStr.substr(1), regex_constants::ECMAScript); } catch(regex_error e) { throw new CScriptException(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code()), currentFile, pos.currentLine, currentColumn()); } #endif /* NO_REGEXP */ do { tkStr.append(1, currCh); getNextCh(); } while (currCh=='g' || currCh=='i' || currCh=='m' || currCh=='y'); } else throw new CScriptException(SyntaxError, "unterminated regular expression literal", currentFile, pos.currentLine, currentColumn()); } else if(currCh=='=') { tk = LEX_SLASHEQUAL; getNextCh(); } } else if (tk=='%' && currCh=='=') { tk = LEX_PERCENTEQUAL; getNextCh(); } } /* This isn't quite right yet */ } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataForwards ////////////////////////////////////////////////////////////////////////// bool CScriptTokenDataForwards::compare_fnc_token_by_name::operator()(const CScriptToken& lhs, const CScriptToken& rhs) const { return lhs.Fnc().name < rhs.Fnc().name; } bool CScriptTokenDataForwards::checkRedefinition(const string &Str, bool checkVarsInLetScope) { STRING_SET_it it = varNames[LETS].find(Str); if(it!=varNames[LETS].end()) return false; else if(checkVarsInLetScope) { STRING_SET_it it = vars_in_letscope.find(Str); if(it!=vars_in_letscope.end()) return false; } return true; } void CScriptTokenDataForwards::addVars( STRING_VECTOR_t &Vars ) { varNames[VARS].insert(Vars.begin(), Vars.end()); } void CScriptTokenDataForwards::addConsts( STRING_VECTOR_t &Vars ) { varNames[CONSTS].insert(Vars.begin(), Vars.end()); } std::string CScriptTokenDataForwards::addVarsInLetscope( STRING_VECTOR_t &Vars ) { for(STRING_VECTOR_it it=Vars.begin(); it!=Vars.end(); ++it) { if(!checkRedefinition(*it, false)) return *it; vars_in_letscope.insert(*it); } return ""; } std::string CScriptTokenDataForwards::addLets( STRING_VECTOR_t &Lets ) { for(STRING_VECTOR_it it=Lets.begin(); it!=Lets.end(); ++it) { if(!checkRedefinition(*it, true)) return *it; varNames[LETS].insert(*it); } return ""; } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataLoop ////////////////////////////////////////////////////////////////////////// std::string CScriptTokenDataLoop::getParsableString(const string &IndentString/*=""*/, const string &Indent/*=""*/ ) { static const char *heads[] = {"for each(", "for(", "for(", "for(", "while(", "do "}; static const char *ops[] = {" in ", " in ", " of ", "; "}; string out = heads[type]; if(init.size() && type==FOR)out.append(CScriptToken::getParsableString(init)); if(type<=WHILE) out.append(CScriptToken::getParsableString(condition.begin(), condition.end()-(type>=FOR ? 0 : 7))); if(type<=FOR) out.append(ops[type]); if(iter.size()) out.append(CScriptToken::getParsableString(iter)); out.append(")"); out.append(CScriptToken::getParsableString(body)); if(type==DO)out.append(" while(").append(CScriptToken::getParsableString(condition)).append(");"); return out; } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataTry ////////////////////////////////////////////////////////////////////////// std::string CScriptTokenDataTry::getParsableString( const string &IndentString/*=""*/, const string &Indent/*=""*/ ) { string out = "try "; string nl = Indent.size() ? "\n"+IndentString : " "; out.append(CScriptToken::getParsableString(tryBlock, IndentString, Indent)); for(CScriptTokenDataTry::CatchBlock_it catchBlock = catchBlocks.begin(); catchBlock!=catchBlocks.end(); catchBlock++) { out.append(nl).append("catch(").append(catchBlock->indentifiers->getParsableString()); if(catchBlock->condition.size()>1) { out.append(" if ").append(CScriptToken::getParsableString(catchBlock->condition.begin()+1, catchBlock->condition.end())); } out.append(") ").append(CScriptToken::getParsableString(catchBlock->block, IndentString, Indent)); } if(finallyBlock.size()) out.append(nl).append("finally ").append(CScriptToken::getParsableString(finallyBlock, IndentString, Indent)); return out; } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataFnc ////////////////////////////////////////////////////////////////////////// string CScriptTokenDataFnc::getArgumentsString() { ostringstream destination; destination << "("; if(arguments.size()) { const char *comma = ""; for(TOKEN_VECT_it argument=arguments.begin(); argument!=arguments.end(); ++argument, comma=", ") { if(argument->token == LEX_ID) destination << comma << argument->String(); else { vector isObject(1, false); for(DESTRUCTURING_VARS_it it=argument->DestructuringVar().vars.begin(); it!=argument->DestructuringVar().vars.end(); ++it) { if(it->second == "}" || it->second == "]") { destination << it->second; isObject.pop_back(); } else { destination << comma; if(it->second == "[" || it->second == "{") { comma = ""; if(isObject.back() && it->first.length()) destination << getIDString(it->first) << ":"; destination << it->second; isObject.push_back(it->second == "{"); } else { comma = ", "; if(it->second.empty()) continue; // skip empty entries if(isObject.back() && it->first!=it->second) destination << getIDString(it->first) << ":"; destination << it->second; } } } } } } destination << ") "; return destination.str(); } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataDestructuringVar ////////////////////////////////////////////////////////////////////////// void CScriptTokenDataDestructuringVar::getVarNames(STRING_VECTOR_t Names) { for(DESTRUCTURING_VARS_it it = vars.begin(); it != vars.end(); ++it) { if(it->second.size() && it->second.find_first_of("{[]}") == string::npos) Names.push_back(it->second); } } std::string CScriptTokenDataDestructuringVar::getParsableString() { string out; const char *comma = ""; vector isObject(1, false); for(DESTRUCTURING_VARS_it it=vars.begin(); it!=vars.end(); ++it) { if(it->second == "}" || it->second == "]") { out.append(it->second); isObject.pop_back(); } else { out.append(comma); if(it->second == "[" || it->second == "{") { comma = ""; if(isObject.back() && it->first.length()) out.append(getIDString(it->first)).append(":"); out.append(it->second); isObject.push_back(it->second == "{"); } else { comma = ", "; if(it->second.empty()) continue; // skip empty entries if(isObject.back() && it->first!=it->second) out.append(getIDString(it->first)).append(":"); out.append(it->second); } } } return out; } ////////////////////////////////////////////////////////////////////////// // CScriptTokenDataObjectLiteral ////////////////////////////////////////////////////////////////////////// void CScriptTokenDataObjectLiteral::setMode(bool Destructuring) { structuring = !(destructuring = Destructuring); for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) { if(it->value.size() && it->value.front().token == LEX_T_OBJECT_LITERAL) { CScriptTokenDataObjectLiteral& e = it->value.front().Object(); if(e.destructuring && e.structuring) e.setMode(Destructuring); } } } string CScriptTokenDataObjectLiteral::getParsableString() { string out = type == OBJECT ? "{ " : "[ "; const char *comma = ""; for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) { out.append(comma); comma=", "; if(it->value.empty()) continue; if(type == OBJECT) out.append(getIDString(it->id)).append(" : "); out.append(CScriptToken::getParsableString(it->value)); } out.append(type == OBJECT ? " }" : " ]"); return out; } ////////////////////////////////////////////////////////////////////////// // CScriptToken ////////////////////////////////////////////////////////////////////////// typedef struct { int id; const char *str; bool need_space; } token2str_t; static token2str_t reserved_words_begin[] ={ // reserved words { LEX_R_IF, "if", true }, { LEX_R_ELSE, "else", true }, { LEX_R_DO, "do", true }, { LEX_R_WHILE, "while", true }, { LEX_R_FOR, "for", true }, { LEX_R_IN, "in", true }, { LEX_R_BREAK, "break", true }, { LEX_R_CONTINUE, "continue", true }, { LEX_R_FUNCTION, "function", true }, { LEX_R_RETURN, "return", true }, { LEX_R_VAR, "var", true }, { LEX_R_LET, "let", true }, { LEX_R_CONST, "const", true }, { LEX_R_WITH, "with", true }, { LEX_R_TRUE, "true", true }, { LEX_R_FALSE, "false", true }, { LEX_R_NULL, "null", true }, { LEX_R_NEW, "new", true }, { LEX_R_TRY, "try", true }, { LEX_R_CATCH, "catch", true }, { LEX_R_FINALLY, "finally", true }, { LEX_R_THROW, "throw", true }, { LEX_R_TYPEOF, "typeof", true }, { LEX_R_VOID, "void", true }, { LEX_R_DELETE, "delete", true }, { LEX_R_INSTANCEOF, "instanceof", true }, { LEX_R_SWITCH, "switch", true }, { LEX_R_CASE, "case", true }, { LEX_R_DEFAULT, "default", true }, }; #define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0])) #define ARRAY_END(array) (&array[ARRAY_LENGTH(array)]) static token2str_t *reserved_words_end = ARRAY_END(reserved_words_begin);//&reserved_words_begin[ARRAY_LENGTH(reserved_words_begin)]; static token2str_t *str2reserved_begin[sizeof(reserved_words_begin)/sizeof(reserved_words_begin[0])]; static token2str_t **str2reserved_end = &str2reserved_begin[sizeof(str2reserved_begin)/sizeof(str2reserved_begin[0])]; static token2str_t tokens2str_begin[] = { { LEX_EOF, "EOF", false }, { LEX_ID, "ID", true }, { LEX_INT, "INT", true }, { LEX_FLOAT, "FLOAT", true }, { LEX_STR, "STRING", true }, { LEX_REGEXP, "REGEXP", true }, { LEX_EQUAL, "==", false }, { LEX_TYPEEQUAL, "===", false }, { LEX_NEQUAL, "!=", false }, { LEX_NTYPEEQUAL, "!==", false }, { LEX_LEQUAL, "<=", false }, { LEX_LSHIFT, "<<", false }, { LEX_LSHIFTEQUAL, "<<=", false }, { LEX_GEQUAL, ">=", false }, { LEX_RSHIFT, ">>", false }, { LEX_RSHIFTEQUAL, ">>=", false }, { LEX_RSHIFTU, ">>>", false }, { LEX_RSHIFTUEQUAL, ">>>=", false }, { LEX_PLUSEQUAL, "+=", false }, { LEX_MINUSEQUAL, "-=", false }, { LEX_PLUSPLUS, "++", false }, { LEX_MINUSMINUS, "--", false }, { LEX_ANDEQUAL, "&=", false }, { LEX_ANDAND, "&&", false }, { LEX_OREQUAL, "|=", false }, { LEX_OROR, "||", false }, { LEX_XOREQUAL, "^=", false }, { LEX_ASTERISKEQUAL, "*=", false }, { LEX_SLASHEQUAL, "/=", false }, { LEX_PERCENTEQUAL, "%=", false }, // special tokens { LEX_T_OF, "of", true }, { LEX_T_FUNCTION_OPERATOR, "function", true }, { LEX_T_GET, "get", true }, { LEX_T_SET, "set", true }, { LEX_T_EXCEPTION_VAR, "LEX_T_EXCEPTION_VAR", false }, { LEX_T_SKIP, "LEX_SKIP", false }, { LEX_T_DUMMY_LABEL, "LABEL", true }, { LEX_T_LABEL, "LABEL", true }, { LEX_T_LOOP, "LEX_LOOP", true }, { LEX_T_FOR_IN, "LEX_FOR_IN", true }, { LEX_T_FORWARD, "LEX_T_FORWARD", false }, { LEX_T_OBJECT_LITERAL, "LEX_OBJECT_LITERAL", false }, { LEX_T_DESTRUCTURING_VAR, "Destructuring Var", false }, }; static token2str_t *tokens2str_end = &tokens2str_begin[sizeof(tokens2str_begin)/sizeof(tokens2str_begin[0])]; struct token2str_cmp_t { bool operator()(const token2str_t &lhs, const token2str_t &rhs) { return lhs.id < rhs.id; } bool operator()(const token2str_t &lhs, int rhs) { return lhs.id < rhs; } bool operator()(const token2str_t *lhs, const token2str_t *rhs) { return strcmp(lhs->str, rhs->str)<0; } bool operator()(const token2str_t *lhs, const char *rhs) { return strcmp(lhs->str, rhs)<0; } }; static bool tokens2str_sort() { // printf("tokens2str_sort called\n"); sort(tokens2str_begin, tokens2str_end, token2str_cmp_t()); sort(reserved_words_begin, reserved_words_end, token2str_cmp_t()); for(unsigned int i=0; icurrentLine()), column(l->currentColumn()), token(l->tk), intData(0) { if(token == LEX_INT || LEX_TOKEN_DATA_FLOAT(token)) { CNumber number(l->tkStr); if(number.isInfinity()) token=LEX_ID, (tokenData=new CScriptTokenDataString("Infinity"))->ref(); else if(number.isInt32()) token=LEX_INT, intData=number.toInt32(); else token=LEX_FLOAT, floatData=new double(number.toDouble()); } else if(LEX_TOKEN_DATA_STRING(token)) (tokenData = new CScriptTokenDataString(l->tkStr))->ref(); else if(LEX_TOKEN_DATA_FUNCTION(token)) (tokenData = new CScriptTokenDataFnc)->ref(); else if (LEX_TOKEN_DATA_LOOP(token)) (tokenData = new CScriptTokenDataLoop)->ref(); else if (LEX_TOKEN_DATA_TRY(token)) (tokenData = new CScriptTokenDataTry)->ref(); if(Match>=0) l->match(Match, Alternate); else l->match(l->tk); #ifdef _DEBUG token_str = getTokenStr(token); #endif } CScriptToken::CScriptToken(uint16_t Tk, int IntData) : line(0), column(0), token(Tk), intData(0) { if (LEX_TOKEN_DATA_SIMPLE(token)) intData = IntData; else if (LEX_TOKEN_DATA_FUNCTION(token)) (tokenData = new CScriptTokenDataFnc)->ref(); else if (LEX_TOKEN_DATA_DESTRUCTURING_VAR(token)) (tokenData = new CScriptTokenDataDestructuringVar)->ref(); else if (LEX_TOKEN_DATA_OBJECT_LITERAL(token)) (tokenData = new CScriptTokenDataObjectLiteral)->ref(); else if (LEX_TOKEN_DATA_LOOP(token)) (tokenData = new CScriptTokenDataLoop)->ref(); else if (LEX_TOKEN_DATA_TRY(token)) (tokenData = new CScriptTokenDataTry)->ref(); else if (LEX_TOKEN_DATA_FORWARDER(token)) (tokenData = new CScriptTokenDataForwards)->ref(); else ASSERT(0); #ifdef _DEBUG token_str = getTokenStr(token); #endif } CScriptToken::CScriptToken(uint16_t Tk, const string &TkStr) : line(0), column(0), token(Tk), intData(0) { ASSERT(LEX_TOKEN_DATA_STRING(token)); (tokenData = new CScriptTokenDataString(TkStr))->ref(); #ifdef _DEBUG token_str = getTokenStr(token); #endif } CScriptToken &CScriptToken::operator =(const CScriptToken &Copy) { if(this == &Copy) return *this; clear(); #ifdef _DEBUG token_str = Copy.token_str; #endif line = Copy.line; column = Copy.column; token = Copy.token; if(LEX_TOKEN_DATA_FLOAT(token)) floatData = new double(*Copy.floatData); else if(!LEX_TOKEN_DATA_SIMPLE(token)) (tokenData = Copy.tokenData)->ref(); else intData = Copy.intData; return *this; } string CScriptToken::getParsableString(TOKEN_VECT &Tokens, const string &IndentString, const string &Indent) { return getParsableString(Tokens.begin(), Tokens.end(), IndentString, Indent); } string CScriptToken::getParsableString(TOKEN_VECT_it Begin, TOKEN_VECT_it End, const string &IndentString, const string &Indent) { ostringstream destination; string nl = Indent.size() ? "\n" : " "; string my_indentString = IndentString; bool add_nl=false, block_start=false, need_space=false; int skip_collon = 0; for(TOKEN_VECT_it it=Begin; it != End; ++it) { string OutString; if(add_nl) OutString.append(nl).append(my_indentString); bool old_block_start = block_start; bool old_need_space = need_space; add_nl = block_start = need_space =false; if(it->token == LEX_STR) OutString.append(getJSString(it->String())), need_space=true; else if(LEX_TOKEN_DATA_STRING(it->token)) OutString.append(it->String()), need_space=true; else if(LEX_TOKEN_DATA_FLOAT(it->token)) OutString.append(CNumber(it->Float()).toString()), need_space=true; else if(it->token == LEX_INT) OutString.append(CNumber(it->Int()).toString()), need_space=true; else if(LEX_TOKEN_DATA_FUNCTION(it->token)) { OutString.append("function "); if(it->Fnc().name.size() ) OutString.append(it->Fnc().name); OutString.append(it->Fnc().getArgumentsString()); OutString.append(getParsableString(it->Fnc().body, my_indentString, Indent)); if(it->Fnc().body.front().token != '{') { OutString.append(";"); } } else if(LEX_TOKEN_DATA_LOOP(it->token)) { OutString.append(it->Loop().getParsableString(my_indentString, Indent)); } else if(LEX_TOKEN_DATA_TRY(it->token)) { OutString.append(it->Try().getParsableString(my_indentString, Indent)); } else if(LEX_TOKEN_DATA_DESTRUCTURING_VAR(it->token)) { OutString.append(it->DestructuringVar().getParsableString()); } else if(LEX_TOKEN_DATA_OBJECT_LITERAL(it->token)) { OutString.append(it->Object().getParsableString()); } else if(it->token == '{') { OutString.append("{"); my_indentString.append(Indent); add_nl = block_start = true; } else if(it->token == '}') { my_indentString.resize(my_indentString.size() - min(my_indentString.size(),Indent.size())); if(old_block_start) OutString = "}"; else OutString = nl + my_indentString + "}"; add_nl = true; } else if(it->token == LEX_T_SKIP) { // ignore SKIP-Token } else if(it->token == LEX_T_FORWARD) { // ignore Forwarder-Token } else if(it->token == LEX_R_FOR) { OutString.append(CScriptToken::getTokenStr(it->token)); skip_collon=2; } else { OutString.append(CScriptToken::getTokenStr(it->token,&need_space)); if(it->token==';') { if(skip_collon) { --skip_collon; } else add_nl=true; } } if(need_space && old_need_space) destination << " "; destination << OutString; } return destination.str(); } void CScriptToken::clear() { if(LEX_TOKEN_DATA_FLOAT(token)) delete floatData; else if(!LEX_TOKEN_DATA_SIMPLE(token)) tokenData->unref(); token = 0; } string CScriptToken::getTokenStr( int token, bool *need_space/*=0*/ ) { if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, token, token2str_cmp_t()); if(found != reserved_words_end && found->id==token) { if(need_space) *need_space=found->need_space; return found->str; } found = lower_bound(tokens2str_begin, tokens2str_end, token, token2str_cmp_t()); if(found != tokens2str_end && found->id==token) { if(need_space) *need_space=found->need_space; return found->str; } if(need_space) *need_space=false; if (token>32 && token<128) { char buf[2] = " "; buf[0] = (char)token; return buf; } ostringstream msg; msg << "?[" << token << "]"; return msg.str(); } const char *CScriptToken::isReservedWord(int Token) { if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, Token, token2str_cmp_t()); if(found != reserved_words_end && found->id==Token) { return found->str; } return 0; } int CScriptToken::isReservedWord(const string &Str) { const char *str = Str.c_str(); if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); token2str_t **found = lower_bound(str2reserved_begin, str2reserved_end, str, token2str_cmp_t()); if(found != str2reserved_end && strcmp((*found)->str, str)==0) { return (*found)->id; } return LEX_ID; } ////////////////////////////////////////////////////////////////////////// /// CScriptTokenizer ////////////////////////////////////////////////////////////////////////// CScriptTokenizer::CScriptTokenizer() : l(0), prevPos(&tokens) { } CScriptTokenizer::CScriptTokenizer(CScriptLex &Lexer) : l(0), prevPos(&tokens) { tokenizeCode(Lexer); } CScriptTokenizer::CScriptTokenizer(const char *Code, const string &File, int Line, int Column) : l(0), prevPos(&tokens) { CScriptLex lexer(Code, File, Line, Column); tokenizeCode(lexer); } void CScriptTokenizer::tokenizeCode(CScriptLex &Lexer) { try { l=&Lexer; tokens.clear(); tokenScopeStack.clear(); ScriptTokenState state; pushForwarder(state); if(l->tk == '§') { // special-Token at Start means the code begins not at Statement-Level l->match('§'); tokenizeLiteral(state, 0); } else do { tokenizeStatement(state, 0); } while (l->tk!=LEX_EOF); pushToken(state.Tokens, LEX_EOF); // add LEX_EOF-Token removeEmptyForwarder(state); // TOKEN_VECT(tokens).swap(tokens);// tokens.shrink_to_fit(); tokens.swap(state.Tokens); pushTokenScope(tokens); currentFile = l->currentFile; tk = getToken().token; } catch (...) { l=0; throw; } } void CScriptTokenizer::getNextToken() { prevPos = tokenScopeStack.back(); if(getToken().token == LEX_EOF) return; ScriptTokenPosition &_TokenPos = tokenScopeStack.back(); _TokenPos.pos++; if(_TokenPos.pos == _TokenPos.tokens->end()) tokenScopeStack.pop_back(); // ScriptTokenPosition &TokenPos = tokenScopeStack.back(); tk = getToken().token; } void CScriptTokenizer::match(int ExpectedToken, int AlternateToken/*=-1*/) { if(check(ExpectedToken, AlternateToken)) getNextToken(); } bool CScriptTokenizer::check(int ExpectedToken, int AlternateToken/*=-1*/) { int currentToken = getToken().token; if (ExpectedToken==';' && (currentToken==LEX_EOF || currentToken=='}')) return false; // ignore last missing ';' if (currentToken!=ExpectedToken && currentToken!=AlternateToken) { ostringstream errorString; if(ExpectedToken == LEX_EOF) errorString << "Got unexpected " << CScriptToken::getTokenStr(currentToken); else { errorString << "Got '" << CScriptToken::getTokenStr(currentToken) << "' expected '" << CScriptToken::getTokenStr(ExpectedToken) << "'"; if(AlternateToken!=-1) errorString << " or '" << CScriptToken::getTokenStr(AlternateToken) << "'"; } throw new CScriptException(SyntaxError, errorString.str(), currentFile, currentLine(), currentColumn()); } return true; } void CScriptTokenizer::pushTokenScope(TOKEN_VECT &Tokens) { tokenScopeStack.push_back(ScriptTokenPosition(&Tokens)); tk = getToken().token; } void CScriptTokenizer::setPos(ScriptTokenPosition &TokenPos) { ASSERT( TokenPos.tokens == tokenScopeStack.back().tokens); tokenScopeStack.back().pos = TokenPos.pos; tk = getToken().token; } void CScriptTokenizer::skip(int Tokens) { ASSERT(tokenScopeStack.back().tokens->end()-tokenScopeStack.back().pos-Tokens>=0); tokenScopeStack.back().pos+=Tokens-1; getNextToken(); } static inline void setTokenSkip(CScriptTokenizer::ScriptTokenState &State) { int tokenBeginIdx = State.Marks.back(); State.Marks.pop_back(); State.Tokens[tokenBeginIdx].Int() = State.Tokens.size()-tokenBeginIdx; } enum { TOKENIZE_FLAGS_canLabel = 1<<0, TOKENIZE_FLAGS_canBreak = 1<<1, TOKENIZE_FLAGS_canContinue = 1<<2, TOKENIZE_FLAGS_canReturn = 1<<3, TOKENIZE_FLAGS_asStatement = 1<<4, TOKENIZE_FLAGS_noIn = 1<<5, TOKENIZE_FLAGS_isAccessor = 1<<6, TOKENIZE_FLAGS_callForNew = 1<<7, TOKENIZE_FLAGS_noBlockStart = 1<<8, TOKENIZE_FLAGS_nestedObject = 1<<9, }; void CScriptTokenizer::tokenizeTry(ScriptTokenState &State, int Flags) { l->match(LEX_R_TRY); CScriptToken TryToken(LEX_T_TRY); CScriptTokenDataTry &TryData = TryToken.Try(); pushToken(State.Tokens, TryToken); TOKEN_VECT mainTokens; State.Tokens.swap(mainTokens); // try-block tokenizeBlock(State, Flags); State.Tokens.swap(TryData.tryBlock); // catch-blocks l->check(LEX_R_CATCH, LEX_R_FINALLY); bool unconditionalCatch = false; while(l->tk == LEX_R_CATCH) { if(unconditionalCatch) throw new CScriptException(SyntaxError, "catch after unconditional catch", l->currentFile, l->currentLine(), l->currentColumn()); // vars & condition l->match(LEX_R_CATCH); l->match('('); TryData.catchBlocks.resize(TryData.catchBlocks.size()+1); CScriptTokenDataTry::CatchBlock &catchBlock = TryData.catchBlocks.back(); pushForwarder(State, true); STRING_VECTOR_t vars; catchBlock.indentifiers = tokenizeVarIdentifier(&vars).DestructuringVar(); State.Forwarders.back()->addLets(vars); if(l->tk == LEX_R_IF) { l->match(LEX_R_IF); tokenizeExpression(State, Flags); } else unconditionalCatch = true; State.Tokens.swap(catchBlock.condition); l->match(')'); // catch-block tokenizeBlock(State, Flags | TOKENIZE_FLAGS_noBlockStart); State.Tokens.swap(catchBlock.block); State.Forwarders.pop_back(); } // finally-block if(l->tk == LEX_R_FINALLY) { l->match(LEX_R_FINALLY); tokenizeBlock(State, Flags); State.Tokens.swap(TryData.finallyBlock); } State.Tokens.swap(mainTokens); } void CScriptTokenizer::tokenizeSwitch(ScriptTokenState &State, int Flags) { State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx pushToken(State.Tokens, '('); tokenizeExpression(State, Flags); pushToken(State.Tokens, ')'); State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push blockBeginIdx pushForwarder(State); vector::size_type MarksSize = State.Marks.size(); Flags |= TOKENIZE_FLAGS_canBreak; for(bool hasDefault=false;;) { if( l->tk == LEX_R_CASE || l->tk == LEX_R_DEFAULT) { if(l->tk == LEX_R_CASE) { State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx State.Marks.push_back(pushToken(State.Tokens,CScriptToken(LEX_T_SKIP))); // skipper to skip case-expression tokenizeExpression(State, Flags); setTokenSkip(State); } else { // default State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx if(hasDefault) throw new CScriptException(SyntaxError, "more than one switch default", l->currentFile, l->currentLine(), l->currentColumn()); hasDefault = true; } State.Marks.push_back(pushToken(State.Tokens, ':')); while(l->tk != '}' && l->tk != LEX_R_CASE && l->tk != LEX_R_DEFAULT && l->tk != LEX_EOF ) tokenizeStatement(State, Flags); setTokenSkip(State); } else if(l->tk == '}') break; else throw new CScriptException(SyntaxError, "invalid switch statement", l->currentFile, l->currentLine(), l->currentColumn()); } while(MarksSize < State.Marks.size()) setTokenSkip(State); removeEmptyForwarder(State); // remove Forwarder if empty pushToken(State.Tokens, '}'); setTokenSkip(State); // switch-block setTokenSkip(State); // switch-statement } void CScriptTokenizer::tokenizeWith(ScriptTokenState &State, int Flags) { State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx pushToken(State.Tokens, '('); tokenizeExpression(State, Flags); pushToken(State.Tokens, ')'); tokenizeStatementNoLet(State, Flags); setTokenSkip(State); } static inline uint32_t GetLoopLabels(CScriptTokenizer::ScriptTokenState &State, CScriptTokenDataLoop &LoopData) { uint32_t label_count = 0; if(State.Tokens.size()>=2) { for(TOKEN_VECT::reverse_iterator it = State.Tokens.rbegin(); it!=State.Tokens.rend(); ++it) { if(it->token == ':' && (++it)->token == LEX_T_LABEL) { ++label_count; LoopData.labels.push_back(it->String()); State.LoopLabels.push_back(it->String()); it->token = LEX_T_DUMMY_LABEL; } else break; } } return label_count; } static inline void PopLoopLabels(uint32_t label_count, STRING_VECTOR_t &LoopLabels) { ASSERT(label_count <= LoopLabels.size()); LoopLabels.resize(LoopLabels.size()-label_count); } void CScriptTokenizer::tokenizeWhileAndDo(ScriptTokenState &State, int Flags) { bool do_while = l->tk==LEX_R_DO; CScriptToken LoopToken(LEX_T_LOOP); CScriptTokenDataLoop &LoopData = LoopToken.Loop(); LoopData.type = do_while ? CScriptTokenDataLoop::DO : CScriptTokenDataLoop::WHILE; // get loop-labels uint32_t label_count = GetLoopLabels(State, LoopData); l->match(l->tk); // match while or do pushToken(State.Tokens, LoopToken); TOKEN_VECT mainTokens; State.Tokens.swap(mainTokens); if(!do_while) { l->match('('); tokenizeExpression(State, Flags); State.Tokens.swap(LoopData.condition); l->match(')'); } tokenizeStatementNoLet(State, Flags | TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue); State.Tokens.swap(LoopData.body); if(do_while) { l->match(LEX_R_WHILE); l->match('('); tokenizeExpression(State, Flags); State.Tokens.swap(LoopData.condition); l->match(')'); l->match(';'); } State.Tokens.swap(mainTokens); PopLoopLabels(label_count, State.LoopLabels); } void CScriptTokenizer::tokenizeIf(ScriptTokenState &State, int Flags) { State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx pushToken(State.Tokens, '('); tokenizeExpression(State, Flags); pushToken(State.Tokens, ')'); State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx tokenizeStatementNoLet(State, Flags); setTokenSkip(State); if(l->tk == LEX_R_ELSE) { State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx tokenizeStatementNoLet(State, Flags); setTokenSkip(State); } setTokenSkip(State); } void CScriptTokenizer::tokenizeFor(ScriptTokenState &State, int Flags) { bool for_in=false, for_of=false, for_each_in=false; CScriptToken LoopToken(LEX_T_LOOP); CScriptTokenDataLoop &LoopData = LoopToken.Loop(); // get loop-labels uint32_t label_count = GetLoopLabels(State, LoopData); l->match(LEX_R_FOR); if((for_in = for_each_in = (l->tk == LEX_ID && l->tkStr == "each"))) l->match(LEX_ID); // match "each" pushToken(State.Tokens, LoopToken); l->match('('); TOKEN_VECT mainTokens; State.Tokens.swap(mainTokens); bool haveLetScope = false; if(l->tk == LEX_R_VAR || l->tk == LEX_R_LET) { if(l->tk == LEX_R_VAR) tokenizeVarNoConst(State, Flags | TOKENIZE_FLAGS_noIn); else { //if(l->tk == LEX_R_LET) haveLetScope = true; pushForwarder(State, true); // no clean up empty tokenizer tokenizeLet(State, Flags | TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_asStatement); } } else if(l->tk!=';') { tokenizeExpression(State, Flags | TOKENIZE_FLAGS_noIn); } if((for_in=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) { if(!State.LeftHand) throw new CScriptException(ReferenceError, "invalid for/in left-hand side", l->currentFile, l->currentLine(), l->currentColumn()); if(l->tk==LEX_ID && l->tkStr=="of") l->tk = LEX_T_OF; // fake token if((for_of = (!for_each_in && l->tk==LEX_T_OF))) { l->match(LEX_T_OF); LoopData.type = CScriptTokenDataLoop::FOR_OF; } else { l->match(LEX_R_IN); LoopData.type = for_each_in ? CScriptTokenDataLoop::FOR_EACH : CScriptTokenDataLoop::FOR_IN; } State.Tokens.swap(LoopData.condition); if(LoopData.condition.front().token == LEX_T_FORWARD) { LoopData.init.push_back(LoopData.condition.front()); LoopData.condition.erase(LoopData.condition.begin()); } mainTokens.back().token = LEX_T_FOR_IN; } else { l->check(';'); // no automatic ;-injection pushToken(State.Tokens, ';'); State.Tokens.swap(LoopData.init); if(l->tk != ';') tokenizeExpression(State, Flags); l->check(';'); // no automatic ;-injection l->match(';'); // no automatic ;-injection State.Tokens.swap(LoopData.condition); } if(for_in || l->tk != ')') tokenizeExpression(State, Flags); l->match(')'); State.Tokens.swap(LoopData.iter); Flags = TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue; if(haveLetScope) Flags |= TOKENIZE_FLAGS_noBlockStart; tokenizeStatementNoLet(State, Flags); if(haveLetScope) State.Forwarders.pop_back(); State.Tokens.swap(LoopData.body); State.Tokens.swap(mainTokens); if(for_in) { LoopData.condition.push_back('='); LoopData.condition.push_back(LEX_T_EXCEPTION_VAR); LoopData.condition.push_back('.'); LoopData.condition.push_back(CScriptToken(LEX_ID, "next")); LoopData.condition.push_back('('); LoopData.condition.push_back(')'); LoopData.condition.push_back(';'); } PopLoopLabels(label_count, State.LoopLabels); } static void tokenizeVarIdentifierDestructuring( CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames ); static void tokenizeVarIdentifierDestructuringObject(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) { Lexer->match('{'); while(Lexer->tk != '}') { CScriptLex::POS prev_pos = Lexer->pos; string Path = Lexer->tkStr; Lexer->match(LEX_ID, LEX_STR); if(Lexer->tk == ':') { Lexer->match(':'); tokenizeVarIdentifierDestructuring(Lexer, Vars, Path, VarNames); } else { Lexer->reset(prev_pos); if(VarNames) VarNames->push_back(Lexer->tkStr); Vars.push_back(DESTRUCTURING_VAR_t(Lexer->tkStr, Lexer->tkStr)); Lexer->match(LEX_ID); } if (Lexer->tk!='}') Lexer->match(',', '}'); } Lexer->match('}'); } static void tokenizeVarIdentifierDestructuringArray(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) { int idx = 0; Lexer->match('['); while(Lexer->tk != ']') { if(Lexer->tk == ',') Vars.push_back(DESTRUCTURING_VAR_t("", "")); // empty else tokenizeVarIdentifierDestructuring(Lexer, Vars, int2string(idx), VarNames); ++idx; if (Lexer->tk!=']') Lexer->match(',',']'); } Lexer->match(']'); } static void tokenizeVarIdentifierDestructuring(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames ) { if(Lexer->tk == '[') { Vars.push_back(DESTRUCTURING_VAR_t(Path, "[")); // marks array begin tokenizeVarIdentifierDestructuringArray(Lexer, Vars, VarNames); Vars.push_back(DESTRUCTURING_VAR_t("", "]")); // marks array end } else if(Lexer->tk == '{') { Vars.push_back(DESTRUCTURING_VAR_t(Path, "{")); // marks object begin tokenizeVarIdentifierDestructuringObject(Lexer, Vars, VarNames); Vars.push_back(DESTRUCTURING_VAR_t("", "}")); // marks object end } else { if(VarNames) VarNames->push_back(Lexer->tkStr); Vars.push_back(DESTRUCTURING_VAR_t(Path, Lexer->tkStr)); Lexer->match(LEX_ID); } } CScriptToken CScriptTokenizer::tokenizeVarIdentifier( STRING_VECTOR_t *VarNames/*=0*/, bool *NeedAssignment/*=0*/ ) { CScriptToken token(LEX_T_DESTRUCTURING_VAR); if(NeedAssignment) *NeedAssignment=(l->tk == '[' || l->tk=='{'); token.column = l->currentColumn(); token.line = l->currentLine(); tokenizeVarIdentifierDestructuring(l, token.DestructuringVar().vars, "", VarNames); return token; } void CScriptTokenizer::tokenizeFunction(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) { bool forward = false; bool Statement = (Flags & TOKENIZE_FLAGS_asStatement) != 0; bool Accessor = (Flags & TOKENIZE_FLAGS_isAccessor) != 0; int tk = l->tk; if(Accessor) { tk = State.Tokens.back().String()=="get"?LEX_T_GET:LEX_T_SET; State.Tokens.pop_back(); } else { l->match(LEX_R_FUNCTION); if(!Statement) tk = LEX_T_FUNCTION_OPERATOR; } if(tk == LEX_R_FUNCTION) // only forward functions forward = !noLetDef && State.Forwarders.front() == State.Forwarders.back(); CScriptToken FncToken(tk); CScriptTokenDataFnc &FncData = FncToken.Fnc(); if(l->tk == LEX_ID || Accessor) { FncData.name = l->tkStr; l->match(LEX_ID, LEX_STR); } else if(Statement) throw new CScriptException(SyntaxError, "Function statement requires a name.", l->currentFile, l->currentLine(), l->currentColumn()); l->match('('); while(l->tk != ')') { FncData.arguments.push_back(tokenizeVarIdentifier()); if (l->tk!=')') l->match(',',')'); } // l->match(')'); // to allow regexp at the beginning of a lambda-function fake last token l->tk = '{'; l->match('{'); FncData.file = l->currentFile; FncData.line = l->currentLine(); ScriptTokenState functionState; if(l->tk == '{' || tk==LEX_T_GET || tk==LEX_T_SET) tokenizeBlock(functionState, TOKENIZE_FLAGS_canReturn); else { tokenizeExpression(functionState, 0); l->match(';'); } functionState.Tokens.swap(FncData.body); if(forward) { State.Forwarders.front()->functions.insert(FncToken); FncToken.token = LEX_T_FUNCTION_PLACEHOLDER; } State.Tokens.push_back(FncToken); } void CScriptTokenizer::tokenizeLet(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) { bool Definition = (Flags & TOKENIZE_FLAGS_asStatement)!=0; bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0; bool Statement = Definition & !noIN; bool Expression = !Definition; Flags &= ~(TOKENIZE_FLAGS_asStatement); if(!Definition) noIN=false, Flags &= ~TOKENIZE_FLAGS_noIn; bool foundIN = false; bool leftHand = true; int currLine = l->currentLine(), currColumn = l->currentColumn(); State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx if(l->tk == '(' || !Definition) { // no definition needs statement or expression leftHand = false; Expression = true; pushToken(State.Tokens, '('); pushForwarder(State); } else if(noLetDef) throw new CScriptException(SyntaxError, "let declaration not directly within block", l->currentFile, currLine, currColumn); STRING_VECTOR_t vars; for(;;) { bool needAssignment = false; State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment)); if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) break; if(needAssignment || l->tk=='=') { leftHand = false; pushToken(State.Tokens, '='); tokenizeAssignment(State, Flags); if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) break; } if(l->tk==',') { leftHand = false; pushToken(State.Tokens); } else break; } if(Expression) { string redeclared = State.Forwarders.back()->addLets(vars); if(redeclared.size()) throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); if(!foundIN) { pushToken(State.Tokens, ')'); if(Statement) { if(l->tk == '{') // no extra BlockStart by expression tokenizeBlock(State, Flags|=TOKENIZE_FLAGS_noBlockStart); else tokenizeStatementNoLet(State, Flags); } else tokenizeAssignment(State, Flags); } // never remove Forwarder-token here -- popForwarder(Tokens, BlockStart, Marks); State.Forwarders.back()->vars_in_letscope.clear(); // only clear vars_in_letscope State.Marks.pop_back(); } else { if(!noIN) pushToken(State.Tokens, ';'); string redeclared; if(State.Forwarders.size()<=1) { // Currently it is allowed in javascript, to redeclare "let"-declared vars // in root- or function-scopes. In this case, "let" handled like "var" // To prevent redeclaration in root- or function-scopes define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES #ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES redeclared = State.Forwarders.front()->addLets(vars); #else State.Forwarders.front()->addVars(vars); #endif } else redeclared = State.Forwarders.back()->addLets(vars); if(redeclared.size()) throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); } setTokenSkip(State); if(leftHand) State.LeftHand = true; } void CScriptTokenizer::tokenizeVarNoConst( ScriptTokenState &State, int Flags) { l->check(LEX_R_VAR); tokenizeVarAndConst(State, Flags); } void CScriptTokenizer::tokenizeVarAndConst( ScriptTokenState &State, int Flags) { bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0; int currLine = l->currentLine(), currColumn = l->currentColumn(); bool leftHand = true; int tk = l->tk; State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx STRING_VECTOR_t vars; for(;;) { bool needAssignment = false; State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment)); if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))) break; if(needAssignment || l->tk=='=') { leftHand = false; pushToken(State.Tokens, '='); tokenizeAssignment(State, Flags); if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))) break; } if(l->tk==',') { leftHand = false; pushToken(State.Tokens); } else break; } if(!noIN) pushToken(State.Tokens, ';'); setTokenSkip(State); if(tk==LEX_R_VAR) State.Forwarders.front()->addVars(vars); else State.Forwarders.front()->addConsts(vars); string redeclared; if(State.Forwarders.size()>1) // have let-scope redeclared = State.Forwarders.back()->addVarsInLetscope(vars); #ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES else redeclared = State.Forwarders.front()->addVarsInLetscope(vars); #endif if(redeclared.size()) throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); if(leftHand) State.LeftHand = true; } void CScriptTokenizer::_tokenizeLiteralObject(ScriptTokenState &State, int Flags) { bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0; bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0; Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject); CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL); CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object(); Objc.type = CScriptTokenDataObjectLiteral::OBJECT; Objc.destructuring = Objc.structuring = true; string msg, msgFile; int msgLine=0, msgColumn=0; l->match('{'); while (l->tk != '}') { CScriptTokenDataObjectLiteral::ELEMENT element; bool assign = false; if(CScriptToken::isReservedWord(l->tk)) l->tk = LEX_ID; // fake reserved-word as member.ID if(l->tk == LEX_ID) { element.id = l->tkStr; CScriptToken Token(l, LEX_ID); if((l->tk==LEX_ID || l->tk==LEX_STR ) && (element.id=="get" || element.id=="set")) { element.id = l->tkStr; element.value.push_back(Token); State.Tokens.swap(element.value); tokenizeFunction(State, Flags|TOKENIZE_FLAGS_isAccessor); State.Tokens.swap(element.value); Objc.destructuring = false; } else { if(Objc.destructuring && (l->tk == ',' || l->tk == '}')) { if(!msg.size()) { Objc.structuring = false; msg.append("Got '").append(CScriptToken::getTokenStr(l->tk)).append("' expected ':'"); msgFile = l->currentFile; msgLine = l->currentLine(); msgColumn = l->currentColumn(); ; } element.value.push_back(Token); } else assign = true; } } else if(l->tk == LEX_INT) { element.id = int2string((int32_t)strtol(l->tkStr.c_str(),0,0)); l->match(LEX_INT); assign = true; } else if(l->tk == LEX_FLOAT) { element.id = float2string(strtod(l->tkStr.c_str(),0)); l->match(LEX_FLOAT); assign = true; } else if(LEX_TOKEN_DATA_STRING(l->tk) && l->tk != LEX_REGEXP) { element.id = l->tkStr; l->match(l->tk); assign = true; } else l->match(LEX_ID, LEX_STR); if(assign) { l->match(':'); int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0; State.pushLeftHandState(); State.Tokens.swap(element.value); tokenizeAssignment(State, dFlags); State.Tokens.swap(element.value); if(Objc.destructuring) Objc.destructuring = State.LeftHand; State.popLeftHandeState(); } if(!Objc.destructuring && msg.size()) throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn); Objc.elements.push_back(element); if (l->tk != '}') l->match(',', '}'); } l->match('}'); if(Objc.destructuring && Objc.structuring) { if(nestedObject) { if(l->tk!=',' && l->tk!='}' && l->tk!='=') Objc.destructuring = false; } else Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of")))); } else { if(!Objc.destructuring && msg.size()) throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn); if(!nestedObject) Objc.setMode(Objc.destructuring); } if(Objc.destructuring) State.LeftHand = true; State.Tokens.push_back(ObjectToken); } void CScriptTokenizer::_tokenizeLiteralArray(ScriptTokenState &State, int Flags) { bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0; bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0; Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject); CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL); CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object(); Objc.type = CScriptTokenDataObjectLiteral::ARRAY; Objc.destructuring = Objc.structuring = true; int idx = 0; l->match('['); while (l->tk != ']') { CScriptTokenDataObjectLiteral::ELEMENT element; element.id = int2string(idx++); if(l->tk != ',') { int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0; State.pushLeftHandState(); State.Tokens.swap(element.value); tokenizeAssignment(State, dFlags); State.Tokens.swap(element.value); if(Objc.destructuring) Objc.destructuring = State.LeftHand; State.popLeftHandeState(); } Objc.elements.push_back(element); if (l->tk != ']') l->match(',', ']'); } l->match(']'); if(Objc.destructuring && Objc.structuring) { if(nestedObject) { if(l->tk!=',' && l->tk!=']' && l->tk!='=') Objc.destructuring = false; } else Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of")))); } else if(!nestedObject) Objc.setMode(Objc.destructuring); if(Objc.destructuring) State.LeftHand = true; State.Tokens.push_back(ObjectToken); } void CScriptTokenizer::tokenizeLiteral(ScriptTokenState &State, int Flags) { State.LeftHand = 0; bool canLabel = Flags & TOKENIZE_FLAGS_canLabel; Flags &= ~TOKENIZE_FLAGS_canLabel; int ObjectLiteralFlags = Flags; Flags &= ~TOKENIZE_FLAGS_noIn; switch(l->tk) { case LEX_ID: { string label = l->tkStr; pushToken(State.Tokens); if(l->tk==':' && canLabel) { if(find(State.Labels.begin(), State.Labels.end(), label) != State.Labels.end()) throw new CScriptException(SyntaxError, "dublicate label '"+label+"'", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); State.Tokens[State.Tokens.size()-1].token = LEX_T_LABEL; // change LEX_ID to LEX_T_LABEL State.Labels.push_back(label); } else if(label=="this") { if( l->tk == '=' || (l->tk >= LEX_ASSIGNMENTS_BEGIN && l->tk <= LEX_ASSIGNMENTS_END) ) throw new CScriptException(SyntaxError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); if( l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS ) throw new CScriptException(SyntaxError, l->tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); } else State.LeftHand = true; } break; case LEX_INT: case LEX_FLOAT: case LEX_STR: case LEX_REGEXP: case LEX_R_TRUE: case LEX_R_FALSE: case LEX_R_NULL: pushToken(State.Tokens); break; case '{': _tokenizeLiteralObject(State, ObjectLiteralFlags); break; case '[': _tokenizeLiteralArray(State, ObjectLiteralFlags); break; case LEX_R_LET: // let as expression tokenizeLet(State, Flags); break; case LEX_R_FUNCTION: tokenizeFunction(State, Flags); break; case LEX_R_NEW: State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx { tokenizeFunctionCall(State, (Flags | TOKENIZE_FLAGS_callForNew) & ~TOKENIZE_FLAGS_noIn); State.LeftHand = 0; } setTokenSkip(State); break; case '(': State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn); State.LeftHand = 0; pushToken(State.Tokens, ')'); setTokenSkip(State); break; default: l->check(LEX_EOF); } } void CScriptTokenizer::tokenizeMember(ScriptTokenState &State, int Flags) { while(l->tk == '.' || l->tk == '[') { if(l->tk == '.') { pushToken(State.Tokens); if(CScriptToken::isReservedWord(l->tk)) l->tk = LEX_ID; // fake reserved-word as member.ID pushToken(State.Tokens , LEX_ID); } else { State.Marks.push_back(pushToken(State.Tokens)); State.pushLeftHandState(); tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn); State.popLeftHandeState(); pushToken(State.Tokens, ']'); setTokenSkip(State); } State.LeftHand = true; } } void CScriptTokenizer::tokenizeFunctionCall(ScriptTokenState &State, int Flags) { bool for_new = (Flags & TOKENIZE_FLAGS_callForNew)!=0; Flags &= ~TOKENIZE_FLAGS_callForNew; tokenizeLiteral(State, Flags); tokenizeMember(State, Flags); while(l->tk == '(') { State.LeftHand = false; State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx State.pushLeftHandState(); while(l->tk!=')') { tokenizeAssignment(State, Flags & ~TOKENIZE_FLAGS_noIn); if (l->tk!=')') pushToken(State.Tokens, ',', ')'); } State.popLeftHandeState(); pushToken(State.Tokens); setTokenSkip(State); if(for_new) break; tokenizeMember(State, Flags); } } void CScriptTokenizer::tokenizeSubExpression(ScriptTokenState &State, int Flags) { static int Left2Right_begin[] = { /* Precedence 5 */ '*', '/', '%', /* Precedence 6 */ '+', '-', /* Precedence 7 */ LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU, /* Precedence 8 */ LEX_EQUAL, LEX_NEQUAL, LEX_TYPEEQUAL, LEX_NTYPEEQUAL, /* Precedence 9 */ '<', LEX_LEQUAL, '>', LEX_GEQUAL, LEX_R_IN, LEX_R_INSTANCEOF, /* Precedence 10-12 */ '&', '^', '|', }; static int *Left2Right_end = &Left2Right_begin[sizeof(Left2Right_begin)/sizeof(Left2Right_begin[0])]; static bool Left2Right_sorted = false; if(!Left2Right_sorted) Left2Right_sorted = (sort(Left2Right_begin, Left2Right_end), true); bool noLeftHand = false; for(;;) { bool right2left_end = false; while(!right2left_end) { switch(l->tk) { case '-': case '+': case '!': case '~': case LEX_R_TYPEOF: case LEX_R_VOID: case LEX_R_DELETE: Flags &= ~TOKENIZE_FLAGS_canLabel; noLeftHand = true; pushToken(State.Tokens); // Precedence 3 break; case LEX_PLUSPLUS: // pre-increment case LEX_MINUSMINUS: // pre-decrement { int tk = l->tk; Flags &= ~TOKENIZE_FLAGS_canLabel; noLeftHand = true; pushToken(State.Tokens); // Precedence 4 if(l->tk == LEX_ID && l->tkStr == "this") throw new CScriptException(SyntaxError, tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn()); } default: right2left_end = true; } } tokenizeFunctionCall(State, Flags); if (!l->lineBreakBeforeToken && (l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS)) { // post-in-/de-crement noLeftHand = true;; pushToken(State.Tokens); // Precedence 4 } if(Flags&TOKENIZE_FLAGS_noIn && l->tk==LEX_R_IN) break; int *found = lower_bound(Left2Right_begin, Left2Right_end, l->tk); if(found != Left2Right_end && *found == l->tk) { noLeftHand = true; pushToken(State.Tokens); // Precedence 5-14 } else break; } if(noLeftHand) State.LeftHand = false; } void CScriptTokenizer::tokenizeLogic(ScriptTokenState &State, int Flags, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) { op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags); if(l->tk==op) { unsigned int marks_count = State.Marks.size(); while(l->tk==op) { State.Marks.push_back(pushToken(State.Tokens)); op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags); } while(State.Marks.size()>marks_count) setTokenSkip(State); State.LeftHand = false; } } void CScriptTokenizer::tokenizeCondition(ScriptTokenState &State, int Flags) { tokenizeLogic(State, Flags); if(l->tk == '?') { Flags &= ~TOKENIZE_FLAGS_noIn; State.Marks.push_back(pushToken(State.Tokens)); tokenizeCondition(State, Flags); setTokenSkip(State); State.Marks.push_back(pushToken(State.Tokens, ':')); tokenizeCondition(State, Flags); setTokenSkip(State); State.LeftHand = false; } } void CScriptTokenizer::tokenizeAssignment(ScriptTokenState &State, int Flags) { tokenizeCondition(State, Flags); if (l->tk=='=' || (l->tk>=LEX_ASSIGNMENTS_BEGIN && l->tk<=LEX_ASSIGNMENTS_END) ) { if(!State.LeftHand) throw new CScriptException(ReferenceError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn()); pushToken(State.Tokens); tokenizeAssignment(State, Flags); State.LeftHand = false; } } void CScriptTokenizer::tokenizeExpression(ScriptTokenState &State, int Flags) { tokenizeAssignment(State, Flags); while(l->tk == ',') { pushToken(State.Tokens); tokenizeAssignment(State, Flags); State.LeftHand = false; } } void CScriptTokenizer::tokenizeBlock(ScriptTokenState &State, int Flags) { bool addBlockStart = (Flags&TOKENIZE_FLAGS_noBlockStart)==0; Flags&=~(TOKENIZE_FLAGS_noBlockStart); State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push BeginIdx if(addBlockStart) pushForwarder(State); while(l->tk != '}' && l->tk != LEX_EOF) tokenizeStatement(State, Flags); pushToken(State.Tokens, '}'); if(addBlockStart) removeEmptyForwarder(State); // clean-up BlockStarts setTokenSkip(State); } void CScriptTokenizer::tokenizeStatementNoLet(ScriptTokenState &State, int Flags) { if(l->tk == LEX_R_LET) tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement, true); else if(l->tk==LEX_R_FUNCTION) tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement, true); else tokenizeStatement(State, Flags); } void CScriptTokenizer::tokenizeStatement(ScriptTokenState &State, int Flags) { switch(l->tk) { case '{': tokenizeBlock(State, Flags); break; case ';': pushToken(State.Tokens); break; case LEX_R_CONST: case LEX_R_VAR: tokenizeVarAndConst(State, Flags); break; case LEX_R_LET: tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement); break; case LEX_R_WITH: tokenizeWith(State, Flags); break; case LEX_R_IF: tokenizeIf(State, Flags); break; case LEX_R_SWITCH: tokenizeSwitch(State, Flags); break; case LEX_R_DO: case LEX_R_WHILE: tokenizeWhileAndDo(State, Flags); break; case LEX_R_FOR: tokenizeFor(State, Flags); break; case LEX_R_FUNCTION: tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement); break; case LEX_R_TRY: tokenizeTry(State, Flags); break; case LEX_R_RETURN: if( (Flags & TOKENIZE_FLAGS_canReturn)==0) throw new CScriptException(SyntaxError, "'return' statement, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn()); case LEX_R_THROW: State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx if(l->tk != ';' && !l->lineBreakBeforeToken) { tokenizeExpression(State, Flags); } pushToken(State.Tokens, ';'); // push ';' setTokenSkip(State); break; case LEX_R_BREAK: case LEX_R_CONTINUE: { bool isBreak = l->tk == LEX_R_BREAK; State.Marks.push_back(pushToken(State.Tokens)); // push Token if(l->tk != ';' && !l->lineBreakBeforeToken) { l->check(LEX_ID); STRING_VECTOR_t &L = isBreak ? State.Labels : State.LoopLabels; if(find(L.begin(), L.end(), l->tkStr) == L.end()) throw new CScriptException(SyntaxError, "label '"+l->tkStr+"' not found", l->currentFile, l->currentLine(), l->currentColumn()); pushToken(State.Tokens); // push 'Label' } else if((Flags & (isBreak ? TOKENIZE_FLAGS_canBreak : TOKENIZE_FLAGS_canContinue) )==0) throw new CScriptException(SyntaxError, isBreak ? "'break' must be inside loop, switch or labeled statement" : "'continue' must be inside loop", l->currentFile, l->currentLine(), l->currentColumn()); pushToken(State.Tokens, ';'); // push ';' setTokenSkip(State); } break; case LEX_ID: { State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx STRING_VECTOR_t::size_type label_count = State.Labels.size(); tokenizeExpression(State, Flags | TOKENIZE_FLAGS_canLabel); if(label_count < State.Labels.size() && l->tk == ':') { State.Tokens.erase(State.Tokens.begin()+State.Marks.back()); // remove skip State.Marks.pop_back(); pushToken(State.Tokens); // push ':' tokenizeStatement(State, Flags); State.Labels.pop_back(); } else { pushToken(State.Tokens, ';'); setTokenSkip(State); } } break; default: State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx tokenizeExpression(State, Flags); pushToken(State.Tokens, ';'); setTokenSkip(State); break; } } int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, int Match, int Alternate) { if(Match == ';' && l->tk != ';' && (l->lineBreakBeforeToken || l->tk=='}' || l->tk==LEX_EOF)) Tokens.push_back(CScriptToken(';')); // inject ';' else Tokens.push_back(CScriptToken(l, Match, Alternate)); return Tokens.size()-1; } int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, const CScriptToken &Token) { int ret = Tokens.size(); Tokens.push_back(Token); return ret; } void CScriptTokenizer::pushForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks) { Marks.push_back(Tokens.size()); CScriptToken token(LEX_T_FORWARD); Tokens.push_back(token); Forwarders.push_back(token.Forwarder()); } void CScriptTokenizer::pushForwarder(ScriptTokenState &State, bool noMarks/*=false*/) { if(!noMarks) State.Marks.push_back(State.Tokens.size()); CScriptToken token(LEX_T_FORWARD); State.Tokens.push_back(token); State.Forwarders.push_back(token.Forwarder()); } void CScriptTokenizer::removeEmptyForwarder(ScriptTokenState &State) { CScriptTokenDataForwardsPtr &forwarder = State.Forwarders.back(); forwarder->vars_in_letscope.clear(); if(forwarder->empty()) State.Tokens.erase(State.Tokens.begin()+State.Marks.back()); State.Forwarders.pop_back(); State.Marks.pop_back(); } void CScriptTokenizer::removeEmptyForwarder( TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks ) { CScriptTokenDataForwardsPtr &forwarder = Forwarders.back(); forwarder->vars_in_letscope.clear(); if(forwarder->empty()) Tokens.erase(Tokens.begin()+Marks.back()); Forwarders.pop_back(); Marks.pop_back(); } void CScriptTokenizer::throwTokenNotExpected() { throw new CScriptException(SyntaxError, "'"+CScriptToken::getTokenStr(l->tk)+"' was not expected", l->currentFile, l->currentLine(), l->currentColumn()); } ////////////////////////////////////////////////////////////////////////// /// CScriptVar ////////////////////////////////////////////////////////////////////////// CScriptVar::CScriptVar(CTinyJS *Context, const CScriptVarPtr &Prototype) { extensible = true; context = Context; temporaryID = 0; if(context->first) { next = context->first; next->prev = this; } else { next = 0; } context->first = this; prev = 0; refs = 0; if(Prototype) addChild(TINYJS___PROTO___VAR, Prototype, SCRIPTVARLINK_WRITABLE); #if DEBUG_MEMORY mark_allocated(this); #endif } CScriptVar::CScriptVar(const CScriptVar &Copy) { extensible = Copy.extensible; context = Copy.context; temporaryID = 0; if(context->first) { next = context->first; next->prev = this; } else { next = 0; } context->first = this; prev = 0; refs = 0; SCRIPTVAR_CHILDS_cit it; for(it = Copy.Childs.begin(); it!= Copy.Childs.end(); ++it) { addChild((*it)->getName(), (*it)->getVarPtr(), (*it)->getFlags()); } #if DEBUG_MEMORY mark_allocated(this); #endif } CScriptVar::~CScriptVar(void) { #if DEBUG_MEMORY mark_deallocated(this); #endif for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) (*it)->setOwner(0); removeAllChildren(); if(prev) prev->next = next; else context->first = next; if(next) next->prev = prev; } /// Type bool CScriptVar::isObject() {return false;} bool CScriptVar::isError() {return false;} bool CScriptVar::isArray() {return false;} bool CScriptVar::isRegExp() {return false;} bool CScriptVar::isAccessor() {return false;} bool CScriptVar::isNull() {return false;} bool CScriptVar::isUndefined() {return false;} bool CScriptVar::isNaN() {return false;} bool CScriptVar::isString() {return false;} bool CScriptVar::isInt() {return false;} bool CScriptVar::isBool() {return false;} int CScriptVar::isInfinity() { return 0; } ///< +1==POSITIVE_INFINITY, -1==NEGATIVE_INFINITY, 0==is not an InfinityVar bool CScriptVar::isDouble() {return false;} bool CScriptVar::isRealNumber() {return false;} bool CScriptVar::isNumber() {return false;} bool CScriptVar::isPrimitive() {return false;} bool CScriptVar::isFunction() {return false;} bool CScriptVar::isNative() {return false;} bool CScriptVar::isBounded() {return false;} bool CScriptVar::isIterator() {return false;} ////////////////////////////////////////////////////////////////////////// /// Value ////////////////////////////////////////////////////////////////////////// CScriptVarPrimitivePtr CScriptVar::getRawPrimitive() { return CScriptVarPrimitivePtr(); // default NULL-Ptr } CScriptVarPrimitivePtr CScriptVar::toPrimitive() { return toPrimitive_hintNumber(); } CScriptVarPrimitivePtr CScriptVar::toPrimitive(CScriptResult &execute) { return toPrimitive_hintNumber(execute); } CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(int32_t radix) { CScriptResult execute; CScriptVarPrimitivePtr var = toPrimitive_hintString(execute, radix); execute.cThrow(); return var; } CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(CScriptResult &execute, int32_t radix) { if(execute) { if(!isPrimitive()) { CScriptVarPtr ret = callJS_toString(execute, radix); if(execute && !ret->isPrimitive()) { ret = callJS_valueOf(execute); if(execute && !ret->isPrimitive()) context->throwError(execute, TypeError, "can't convert b to primitive type"); } return ret; } return this; } return constScriptVar(Undefined); } CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber() { CScriptResult execute; CScriptVarPrimitivePtr var = toPrimitive_hintNumber(execute); execute.cThrow(); return var; } CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber(CScriptResult &execute) { if(execute) { if(!isPrimitive()) { CScriptVarPtr ret = callJS_valueOf(execute); if(execute && !ret->isPrimitive()) { ret = callJS_toString(execute); if(execute && !ret->isPrimitive()) context->throwError(execute, TypeError, "can't convert to primitive type"); } return ret; } return this; } return constScriptVar(Undefined); } CScriptVarPtr CScriptVar::callJS_valueOf(CScriptResult &execute) { if(execute) { CScriptVarPtr FncValueOf = findChildWithPrototypeChain("valueOf").getter(execute); if(FncValueOf != context->objectPrototype_valueOf) { // custom valueOf in JavaScript if(FncValueOf->isFunction()) { // no Error if toString not callable vector Params; return context->callFunction(execute, FncValueOf, Params, this); } } else return valueOf_CallBack(); } return this; } CScriptVarPtr CScriptVar::valueOf_CallBack() { return this; } CScriptVarPtr CScriptVar::callJS_toString(CScriptResult &execute, int radix/*=0*/) { if(execute) { CScriptVarPtr FncToString = findChildWithPrototypeChain("toString").getter(execute); if(FncToString != context->objectPrototype_toString) { // custom valueOf in JavaScript if(FncToString->isFunction()) { // no Error if toString not callable vector Params; Params.push_back(newScriptVar(radix)); return context->callFunction(execute, FncToString, Params, this); } } else return toString_CallBack(execute, radix); } return this; } CScriptVarPtr CScriptVar::toString_CallBack(CScriptResult &execute, int radix/*=0*/) { return this; } CNumber CScriptVar::toNumber() { return toPrimitive_hintNumber()->toNumber_Callback(); }; CNumber CScriptVar::toNumber(CScriptResult &execute) { return toPrimitive_hintNumber(execute)->toNumber_Callback(); }; bool CScriptVar::toBoolean() { return true; } string CScriptVar::toString(int32_t radix) { return toPrimitive_hintString(radix)->toCString(radix); } string CScriptVar::toString(CScriptResult &execute, int32_t radix) { return toPrimitive_hintString(execute, radix)->toCString(radix); } int CScriptVar::getInt() { return toNumber().toInt32(); } double CScriptVar::getDouble() { return toNumber().toDouble(); } bool CScriptVar::getBool() { return toBoolean(); } string CScriptVar::getString() { return toPrimitive_hintString()->toCString(); } CScriptTokenDataFnc *CScriptVar::getFunctionData() { return 0; } CScriptVarPtr CScriptVar::toIterator(int Mode/*=3*/) { CScriptResult execute; CScriptVarPtr var = toIterator(execute, Mode); execute.cThrow(); return var; } CScriptVarPtr CScriptVar::toIterator(CScriptResult &execute, int Mode/*=3*/) { if(!execute) return constScriptVar(Undefined); if(isIterator()) return this; CScriptVarFunctionPtr Generator(findChildWithPrototypeChain("__iterator__").getter(execute)); vector args; if(Generator) return context->callFunction(execute, Generator, args, this); return newScriptVarDefaultIterator(context, this, Mode); } string CScriptVar::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { getParsableStringRecursionsCheck(); return toString(); } CScriptVarPtr CScriptVar::getNumericVar() { return newScriptVar(toNumber()); } ////// Flags void CScriptVar::seal() { preventExtensions(); for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) (*it)->setConfigurable(false); } bool CScriptVar::isSealed() const { if(isExtensible()) return false; for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it) if((*it)->isConfigurable()) return false; return true; } void CScriptVar::freeze() { preventExtensions(); for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) (*it)->setConfigurable(false), (*it)->setWritable(false); } bool CScriptVar::isFrozen() const { if(isExtensible()) return false; for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it) if((*it)->isConfigurable() || (*it)->isWritable()) return false; return true; } ////// Childs CScriptVarPtr CScriptVar::getOwnPropertyDescriptor(const string &Name) { CScriptVarLinkPtr child = findChild(Name); if(!child) { CScriptVarStringPtr strVar = getRawPrimitive(); uint32_t Idx; if (strVar && (Idx=isArrayIndex(Name))!=uint32_t(-1) && IdxstringLength()) { int Char = strVar->getChar(Idx); CScriptVarPtr ret = newScriptVar(Object); ret->addChild("value", newScriptVar(string(1, (char)Char))); ret->addChild("writable", constScriptVar(false)); ret->addChild("enumerable", constScriptVar(true)); ret->addChild("configurable", constScriptVar(false)); return ret; } } if(!child || child->getVarPtr()->isUndefined()) return constScriptVar(Undefined); CScriptVarPtr ret = newScriptVar(Object); if(child->getVarPtr()->isAccessor()) { CScriptVarLinkPtr value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR); ret->addChild("get", value ? value->getVarPtr() : constScriptVar(Undefined)); value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR); ret->addChild("set", value ? value->getVarPtr() : constScriptVar(Undefined)); } else { ret->addChild("value", child->getVarPtr()->valueOf_CallBack()); ret->addChild("writable", constScriptVar(child->isWritable())); } ret->addChild("enumerable", constScriptVar(child->isEnumerable())); ret->addChild("configurable", constScriptVar(child->isConfigurable())); return ret; } const char *CScriptVar::defineProperty(const string &Name, CScriptVarPtr Attributes) { CScriptVarPtr attr; CScriptVarLinkPtr child = findChildWithStringChars(Name); CScriptVarPtr attr_value = Attributes->findChild("value"); CScriptVarPtr attr_writable = Attributes->findChild("writable"); CScriptVarPtr attr_get = Attributes->findChild("get"); CScriptVarPtr attr_set = Attributes->findChild("set"); CScriptVarPtr attr_enumerable = Attributes->findChild("enumerable"); CScriptVarPtr attr_configurable = Attributes->findChild("configurable"); bool attr_isDataDescriptor = !attr_get && !attr_set; if(!attr_isDataDescriptor && (attr_value || attr_writable)) return "property descriptors must not specify a value or be writable when a getter or setter has been specified"; if(attr_isDataDescriptor) { if(attr_get && (!attr_get->isUndefined() || !attr_get->isFunction())) return "property descriptor's getter field is neither undefined nor a function"; if(attr_set && (!attr_set->isUndefined() || !attr_set->isFunction())) return "property descriptor's setter field is neither undefined nor a function"; } if(!child) { if(!isExtensible()) return "is not extensible"; if(attr_isDataDescriptor) { child = addChild(Name, attr_value?attr_value:constScriptVar(Undefined), 0); if(attr_writable) child->setWritable(attr_writable->toBoolean()); } else { child = addChild(Name, newScriptVarAccessor(context, attr_get, attr_set), SCRIPTVARLINK_WRITABLE); } } else { if(!child->isConfigurable()) { if(attr_configurable && attr_configurable->toBoolean()) goto cant_redefine; if(attr_enumerable && attr_enumerable->toBoolean() != child->isEnumerable()) goto cant_redefine; if(child->getVarPtr()->isAccessor()) { if(attr_isDataDescriptor) goto cant_redefine; if(attr_get && attr_get != child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR)) goto cant_redefine; if(attr_set && attr_set != child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR)) goto cant_redefine; } else if(!attr_isDataDescriptor) goto cant_redefine; else if(!child->isWritable()) { if(attr_writable && attr_writable->toBoolean()) goto cant_redefine; if(attr_value && !attr_value->mathsOp(child, LEX_EQUAL)->toBoolean()) goto cant_redefine; } } if(attr_isDataDescriptor) { if(child->getVarPtr()->isAccessor()) child->setWritable(false); child->setVarPtr(attr_value?attr_value:constScriptVar(Undefined)); if(attr_writable) child->setWritable(attr_writable->toBoolean()); } else { if(child->getVarPtr()->isAccessor()) { if(!attr_get) attr_get = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR); if(!attr_set) attr_set = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR); } child->setVarPtr(newScriptVarAccessor(context, attr_get, attr_set)); child->setWritable(true); } } if(attr_enumerable) child->setEnumerable(attr_enumerable->toBoolean()); if(attr_configurable) child->setConfigurable(attr_configurable->toBoolean()); return 0; cant_redefine: return "can't redefine non-configurable property"; } CScriptVarLinkPtr CScriptVar::findChild(const string &childName) { if(Childs.empty()) return 0; SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); if(it != Childs.end() && (*it)->getName() == childName) return *it; return 0; } CScriptVarLinkWorkPtr CScriptVar::findChildWithStringChars(const string &childName) { CScriptVarLinkWorkPtr child = findChild(childName); if(child) return child; CScriptVarStringPtr strVar = getRawPrimitive(); uint32_t Idx; if (strVar && (Idx=isArrayIndex(childName))!=uint32_t(-1) && IdxstringLength()) { int Char = strVar->getChar(Idx); child(newScriptVar(string(1, (char)Char)), childName, SCRIPTVARLINK_ENUMERABLE); child.setReferencedOwner(this); // fake referenced Owner return child; } return 0; } CScriptVarLinkPtr CScriptVar::findChildInPrototypeChain(const string &childName) { unsigned int uniqueID = context->getUniqueID(); // Look for links to actual parent classes CScriptVarPtr object = this; CScriptVarLinkPtr __proto__; while( object->getTempraryID() != uniqueID && (__proto__ = object->findChild(TINYJS___PROTO___VAR)) ) { CScriptVarLinkPtr implementation = __proto__->getVarPtr()->findChild(childName); if (implementation) return implementation; object->setTemporaryID(uniqueID); // prevents recursions object = __proto__; } return 0; } CScriptVarLinkWorkPtr CScriptVar::findChildWithPrototypeChain(const string &childName) { CScriptVarLinkWorkPtr child = findChildWithStringChars(childName); if(child) return child; child = findChildInPrototypeChain(childName); if(child) { child(child->getVarPtr(), child->getName(), child->getFlags()); // recreate implementation child.setReferencedOwner(this); // fake referenced Owner } return child; } CScriptVarLinkPtr CScriptVar::findChildByPath(const string &path) { string::size_type p = path.find('.'); CScriptVarLinkPtr child; if (p == string::npos) return findChild(path); if( (child = findChild(path.substr(0,p))) ) return child->getVarPtr()->findChildByPath(path.substr(p+1)); return 0; } CScriptVarLinkPtr CScriptVar::findChildOrCreate(const string &childName/*, int varFlags*/) { CScriptVarLinkPtr l = findChild(childName); if (l) return l; return addChild(childName, constScriptVar(Undefined)); // return addChild(childName, new CScriptVar(context, TINYJS_BLANK_DATA, varFlags)); } CScriptVarLinkPtr CScriptVar::findChildOrCreateByPath(const string &path) { string::size_type p = path.find('.'); if (p == string::npos) return findChildOrCreate(path); string childName(path, 0, p); CScriptVarLinkPtr l = findChild(childName); if (!l) l = addChild(childName, newScriptVar(Object)); return l->getVarPtr()->findChildOrCreateByPath(path.substr(p+1)); } void CScriptVar::keys(set &Keys, bool OnlyEnumerable/*=true*/, uint32_t ID/*=0*/) { setTemporaryID(ID); for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { if(!OnlyEnumerable || (*it)->isEnumerable()) Keys.insert((*it)->getName()); } CScriptVarStringPtr isStringObj = this->getRawPrimitive(); if(isStringObj) { uint32_t length = isStringObj->stringLength(); for(uint32_t i=0; igetVarPtr()->getTempraryID() != ID ) __proto__->getVarPtr()->keys(Keys, OnlyEnumerable, ID); } /// add & remove CScriptVarLinkPtr CScriptVar::addChild(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { CScriptVarLinkPtr link; SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); if(it == Childs.end() || (*it)->getName() != childName) { link = CScriptVarLinkPtr(child?child:constScriptVar(Undefined), childName, linkFlags); link->setOwner(this); Childs.insert(it, 1, link); #ifdef _DEBUG } else { ASSERT(0); // addChild - the child exists #endif } return link; } CScriptVarLinkPtr CScriptVar::addChildNoDup(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { return addChildOrReplace(childName, child, linkFlags); } CScriptVarLinkPtr CScriptVar::addChildOrReplace(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); if(it == Childs.end() || (*it)->getName() != childName) { CScriptVarLinkPtr link(child, childName, linkFlags); link->setOwner(this); Childs.insert(it, 1, link); return link; } else { (*it)->setVarPtr(child); return (*it); } } bool CScriptVar::removeLink(CScriptVarLinkPtr &link) { if (!link) return false; SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), link->getName()); if(it != Childs.end() && (*it) == link) { Childs.erase(it); #ifdef _DEBUG } else { ASSERT(0); // removeLink - the link is not atached to this var #endif } link.clear(); return true; } void CScriptVar::removeAllChildren() { Childs.clear(); } CScriptVarPtr CScriptVar::getArrayIndex(uint32_t idx) { CScriptVarLinkPtr link = findChild(int2string(idx)); if (link) return link; else return constScriptVar(Undefined); // undefined } void CScriptVar::setArrayIndex(uint32_t idx, const CScriptVarPtr &value) { string sIdx = int2string(idx); CScriptVarLinkPtr link = findChild(sIdx); if (link) { link->setVarPtr(value); } else { addChild(sIdx, value); } } uint32_t CScriptVar::getArrayLength() { if (!isArray() || Childs.size()==0) return 0; return isArrayIndex(Childs.back()->getName())+1; } CScriptVarPtr CScriptVar::mathsOp(const CScriptVarPtr &b, int op) { CScriptResult execute; return context->mathsOp(execute, this, b, op); } void CScriptVar::trace(const string &name) { string indentStr; uint32_t uniqueID = context->getUniqueID(); trace(indentStr, uniqueID, name); } void CScriptVar::trace(string &indentStr, uint32_t uniqueID, const string &name) { string indent = " "; const char *extra=""; if(temporaryID == uniqueID) extra = " recursion detected"; TRACE("%s'%s' = '%s' %s%s\n", indentStr.c_str(), name.c_str(), toString().c_str(), getFlagsAsString().c_str(), extra); if(temporaryID != uniqueID) { temporaryID = uniqueID; indentStr+=indent; for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { if((*it)->isEnumerable()) (*it)->getVarPtr()->trace(indentStr, uniqueID, (*it)->getName()); } indentStr = indentStr.substr(0, indentStr.length()-2); } } string CScriptVar::getFlagsAsString() { string flagstr = ""; if (isFunction()) flagstr = flagstr + "FUNCTION "; if (isObject()) flagstr = flagstr + "OBJECT "; if (isArray()) flagstr = flagstr + "ARRAY "; if (isNative()) flagstr = flagstr + "NATIVE "; if (isDouble()) flagstr = flagstr + "DOUBLE "; if (isInt()) flagstr = flagstr + "INTEGER "; if (isBool()) flagstr = flagstr + "BOOLEAN "; if (isString()) flagstr = flagstr + "STRING "; if (isRegExp()) flagstr = flagstr + "REGEXP "; if (isNaN()) flagstr = flagstr + "NaN "; if (isInfinity()) flagstr = flagstr + "INFINITY "; return flagstr; } CScriptVar *CScriptVar::ref() { refs++; return this; } void CScriptVar::unref() { refs--; ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n"); if (refs==0) delete this; } int CScriptVar::getRefs() { return refs; } void CScriptVar::setTemporaryID_recursive(uint32_t ID) { if(temporaryID != ID) { temporaryID = ID; for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { (*it)->getVarPtr()->setTemporaryID_recursive(ID); } } } ////////////////////////////////////////////////////////////////////////// /// CScriptVarLink ////////////////////////////////////////////////////////////////////////// CScriptVarLink::CScriptVarLink(const CScriptVarPtr &Var, const string &Name /*=TINYJS_TEMP_NAME*/, int Flags /*=SCRIPTVARLINK_DEFAULT*/) : name(Name), owner(0), flags(Flags), refs(0) { #if DEBUG_MEMORY mark_allocated(this); #endif var = Var; } CScriptVarLink::~CScriptVarLink() { #if DEBUG_MEMORY mark_deallocated(this); #endif } CScriptVarLink *CScriptVarLink::ref() { refs++; return this; } void CScriptVarLink::unref() { refs--; ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n"); if (refs==0) delete this; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarLinkPtr ////////////////////////////////////////////////////////////////////////// CScriptVarLinkPtr & CScriptVarLinkPtr::operator()( const CScriptVarPtr &var, const std::string &name /*= TINYJS_TEMP_NAME*/, int flags /*= SCRIPTVARLINK_DEFAULT*/ ) { if(link && link->refs == 1) { // the link is only refered by this link->name = name; link->owner = 0; link->flags = flags; link->var = var; } else { if(link) link->unref(); link = (new CScriptVarLink(var, name, flags))->ref(); } return *this; } CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter() { return CScriptVarLinkWorkPtr(*this).getter(); } CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter( CScriptResult &execute ) { return CScriptVarLinkWorkPtr(*this).getter(execute); } CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( const CScriptVarPtr &Var ) { return CScriptVarLinkWorkPtr(*this).setter(Var); } CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) { return CScriptVarLinkWorkPtr(*this).setter(execute, Var); } bool CScriptVarLinkPtr::operator <(const string &rhs) const { uint32_t lhs_int = isArrayIndex(link->getName()); uint32_t rhs_int = isArrayIndex(rhs); if(lhs_int==uint32_t(-1)) { if(rhs_int==uint32_t(-1)) return link->getName() < rhs; else return true; } else if(rhs_int==uint32_t(-1)) return false; return lhs_int < rhs_int; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarLinkWorkPtr ////////////////////////////////////////////////////////////////////////// CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter() { if(link && link->getVarPtr()) { CScriptResult execute; CScriptVarPtr ret = getter(execute); execute.cThrow(); return ret; } return *this; } CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter(CScriptResult &execute) { if(execute && link && link->getVarPtr() && link->getVarPtr()->isAccessor()) { const CScriptVarPtr &var = link->getVarPtr(); CScriptVarLinkPtr getter = var->findChild(TINYJS_ACCESSOR_GET_VAR); if(getter) { vector Params; ASSERT(getReferencedOwner()); return getter->getVarPtr()->getContext()->callFunction(execute, getter->getVarPtr(), Params, getReferencedOwner()); } else return var->constScriptVar(Undefined); } else return *this; } CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( const CScriptVarPtr &Var ) { if(link && link->getVarPtr()) { CScriptResult execute; CScriptVarPtr ret = setter(execute, Var); execute.cThrow(); return ret; } return *this; } CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) { if(execute) { if(link) { if(link->getVarPtr() && link->getVarPtr()->isAccessor()) { const CScriptVarPtr &var = link->getVarPtr(); CScriptVarLinkPtr setter = var->findChild(TINYJS_ACCESSOR_SET_VAR); if(setter) { vector Params; Params.push_back(Var); ASSERT(getReferencedOwner()); setter->getVarPtr()->getContext()->callFunction(execute, setter->getVarPtr(), Params, getReferencedOwner()); } } else link->setVarPtr(Var); } } return *this; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarPrimitive ////////////////////////////////////////////////////////////////////////// CScriptVarPrimitive::~CScriptVarPrimitive(){} bool CScriptVarPrimitive::isPrimitive() { return true; } CScriptVarPrimitivePtr CScriptVarPrimitive::getRawPrimitive() { return this; } bool CScriptVarPrimitive::toBoolean() { return false; } CScriptVarPtr CScriptVarPrimitive::toObject() { return this; } CScriptVarPtr CScriptVarPrimitive::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { return newScriptVar(toCString(radix)); } ////////////////////////////////////////////////////////////////////////// // CScriptVarUndefined ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Undefined); CScriptVarUndefined::CScriptVarUndefined(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { } CScriptVarUndefined::~CScriptVarUndefined() {} CScriptVarPtr CScriptVarUndefined::clone() { return new CScriptVarUndefined(*this); } bool CScriptVarUndefined::isUndefined() { return true; } CNumber CScriptVarUndefined::toNumber_Callback() { return NaN; } string CScriptVarUndefined::toCString(int radix/*=0*/) { return "undefined"; } string CScriptVarUndefined::getVarType() { return "undefined"; } ////////////////////////////////////////////////////////////////////////// // CScriptVarNull ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Null); CScriptVarNull::CScriptVarNull(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { } CScriptVarNull::~CScriptVarNull() {} CScriptVarPtr CScriptVarNull::clone() { return new CScriptVarNull(*this); } bool CScriptVarNull::isNull() { return true; } CNumber CScriptVarNull::toNumber_Callback() { return 0; } string CScriptVarNull::toCString(int radix/*=0*/) { return "null"; } string CScriptVarNull::getVarType() { return "null"; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarString ////////////////////////////////////////////////////////////////////////// CScriptVarString::CScriptVarString(CTinyJS *Context, const string &Data) : CScriptVarPrimitive(Context, Context->stringPrototype), data(Data) { addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT); /* CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0); CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarString::native_Length, 0)); getter->setFunctionData(new CScriptTokenDataFnc); acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); */ } CScriptVarString::~CScriptVarString() {} CScriptVarPtr CScriptVarString::clone() { return new CScriptVarString(*this); } bool CScriptVarString::isString() { return true; } bool CScriptVarString::toBoolean() { return data.length()!=0; } CNumber CScriptVarString::toNumber_Callback() { return data.c_str(); } string CScriptVarString::toCString(int radix/*=0*/) { return data; } string CScriptVarString::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return getJSString(data); } string CScriptVarString::getVarType() { return "string"; } CScriptVarPtr CScriptVarString::toObject() { CScriptVarPtr ret = newScriptVar(CScriptVarPrimitivePtr(this), context->stringPrototype); ret->addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT); return ret; } CScriptVarPtr CScriptVarString::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { return this; } int CScriptVarString::getChar(uint32_t Idx) { if((string::size_type)Idx >= data.length()) return -1; else return (unsigned char)data[Idx]; } ////////////////////////////////////////////////////////////////////////// /// CNumber ////////////////////////////////////////////////////////////////////////// NegativeZero_t NegativeZero; declare_dummy_t(NaN); Infinity InfinityPositive(1); Infinity InfinityNegative(-1); #if 1 static inline bool _isNaN(volatile double *Value1, volatile double *Value2) { return !(*Value1==*Value2); } static inline bool isNaN(double Value) { return _isNaN(&Value, &Value); } inline bool isNegZero(double d) { double x=-0.0; return memcmp(&d, &x, sizeof(double))==0; } CNumber &CNumber::operator=(double Value) { double integral; if(isNegZero(Value)) type=tnNULL, Int32=0; else if(numeric_limits::has_infinity && Value == numeric_limits::infinity()) type=tInfinity, Int32=1; else if(numeric_limits::has_infinity && Value == -numeric_limits::infinity()) type=tInfinity, Int32=-1; else if(::isNaN(Value) || Value == numeric_limits::quiet_NaN() || Value == std::numeric_limits::signaling_NaN()) type=tNaN, Int32=0; else if(modf(Value, &integral)==0.0 && numeric_limits::min()<=integral && integral<=numeric_limits::max()) type=tInt32, Int32=int32_t(integral); else type=tDouble, Double=Value; return *this; } CNumber &CNumber::operator=(const char *str) { while(isWhitespace(*str)) str++; const char *start = str, *endptr; if(*str == '-' || *str == '+') str++; if(*str == '0' && ( str[1]=='x' || str[1]=='X')) parseInt(start, 16, &endptr); else if(*str == '0' && str[1]>='0' && str[1]<='7') parseInt(start, 8, &endptr); else parseFloat(start, &endptr); while(isWhitespace(*endptr)) endptr++; if(*endptr != '\0') type=tNaN, Int32=0; return *this; } int32_t CNumber::parseInt(const char * str, int32_t radix/*=0*/, const char **endptr/*=0*/) { type=tInt32, Int32=0; if(endptr) *endptr = str; bool stripPrefix = false; //< is true if radix==0 or radix==16 if(radix == 0) { radix=10; stripPrefix = true; } else if(radix < 2 || radix > 36) { type = tNaN; return 0; } else stripPrefix = radix == 16; while(isWhitespace(*str)) str++; int sign=1; if(*str=='-') sign=-1,str++; else if(*str=='+') str++; if(stripPrefix && *str=='0' && (str[1]=='x' || str[1]=='X')) str+=2, radix=16; else if(stripPrefix && *str=='0' && str[1]>='0' && str[1]<='7') str+=1, radix=8; int32_t max = 0x7fffffff/radix; const char *start = str; for( ; *str; str++) { if(*str >= '0' && *str <= '0'-1+radix) Int32 = Int32*radix+*str-'0'; else if(*str>='a' && *str<='a'-11+radix) Int32 = Int32*radix+*str-'a'+10; else if(*str>='A' && *str<='A'-11+radix) Int32 = Int32*radix+*str-'A'+10; else break; if(Int32 >= max) { type=tDouble, Double=double(Int32); for(str++ ; *str; str++) { if(*str >= '0' && *str <= '0'-1+radix) Double = Double *radix+*str-'0'; else if(*str>='a' && *str<='a'-11+radix) Double = Double *radix+*str-'a'+10; else if(*str>='A' && *str<='A'-11+radix) Double = Double *radix+*str-'A'+10; else break; } break; } } if(str == start) { type= tNaN; return 0; } if(sign<0 && ((type==tInt32 && Int32==0) || (type==tDouble && Double==0.0))) { type=tnNULL,Int32=0; return radix; } if(type==tInt32) operator=(sign<0 ? -Int32 : Int32); else operator=(sign<0 ? -Double : Double); if(endptr) *endptr = (char*)str; return radix; } void CNumber::parseFloat(const char * str, const char **endptr/*=0*/) { type=tInt32, Int32=0; if(endptr) *endptr = str; while(isWhitespace(*str)) str++; int sign=1; if(*str=='-') sign=-1,str++; else if(*str=='+') str++; if(strncmp(str, "Infinity", 8) == 0) { type=tInfinity, Int32=sign; return; } double d = strtod(str, (char**)endptr); operator=(sign>0 ? d : -d); return; } CNumber CNumber::add(const CNumber &Value) const { if(type==tNaN || Value.type==tNaN) return CNumber(tNaN); else if(type==tInfinity || Value.type==tInfinity) { if(type!=tInfinity) return Value; else if(Value.type!=tInfinity || sign()==Value.sign()) return *this; else return CNumber(tNaN); } else if(type==tnNULL) return Value; else if(Value.type==tnNULL) return *this; else if(type==tDouble || Value.type==tDouble) return CNumber(toDouble()+Value.toDouble()); else { int32_t range_max = numeric_limits::max(); int32_t range_min = numeric_limits::min(); if(Int32>0) range_max-=Int32; else if(Int32<0) range_min-=Int32; if(range_min<=Value.Int32 && Value.Int32<=range_max) return CNumber(Int32+Value.Int32); else return CNumber(double(Int32)+double(Value.Int32)); } } CNumber CNumber::operator-() const { switch(type) { case tInt32: if(Int32==0) return CNumber(NegativeZero); case tnNULL: return CNumber(-Int32); case tDouble: return CNumber(-Double); case tInfinity: return CNumber(tInfinity, -Int32); default: return CNumber(tNaN); } } static inline int bits(uint32_t Value) { uint32_t b=0, mask=0xFFFF0000UL; for(int shift=16; shift>0 && Value!=0; shift>>=1, mask>>=shift) { if(Value & mask) { b += shift; Value>>=shift; } } return b; } static inline int bits(int32_t Value) { return bits(uint32_t(Value<0?-Value:Value)); } CNumber CNumber::multi(const CNumber &Value) const { if(type==tNaN || Value.type==tNaN) return CNumber(tNaN); else if(type==tInfinity || Value.type==tInfinity) { if(isZero() || Value.isZero()) return CNumber(tNaN); else return CNumber(tInfinity, sign()==Value.sign()?1:-1); } else if(isZero() || Value.isZero()) { if(sign()==Value.sign()) return CNumber(0); else return CNumber(NegativeZero); } else if(type==tDouble || Value.type==tDouble) return CNumber(toDouble()*Value.toDouble()); else { // Int32*Int32 if(bits(Int32)+bits(Value.Int32) <= 29) return CNumber(Int32*Value.Int32); else return CNumber(double(Int32)*double(Value.Int32)); } } CNumber CNumber::div( const CNumber &Value ) const { if(type==tNaN || Value.type==tNaN) return CNumber(tNaN); int Sign = sign()*Value.sign(); if(type==tInfinity) { if(Value.type==tInfinity) return CNumber(tNaN); else return CNumber(tInfinity, Sign); } if(Value.type==tInfinity) { if(Sign<0) return CNumber(NegativeZero); else return CNumber(0); } else if(Value.isZero()) { if(isZero()) return CNumber(tNaN); else return CNumber(tInfinity, Sign); } else return CNumber(toDouble() / Value.toDouble()); } CNumber CNumber::modulo( const CNumber &Value ) const { if(type==tNaN || type==tInfinity || Value.type==tNaN || Value.isZero()) return CNumber(tNaN); if(Value.type==tInfinity) return CNumber(*this); if(isZero()) return CNumber(0); if(type==tDouble || Value.type==tDouble) { double n = toDouble(), d = Value.toDouble(), q; modf(n/d, &q); return CNumber(n - (d * q)); } else return CNumber(Int32 % Value.Int32); } CNumber CNumber::round() const { if(type != tDouble) return CNumber(*this); if(Double < 0.0 && Double >= -0.5) return CNumber(NegativeZero); return CNumber(::floor(Double+0.5)); } CNumber CNumber::floor() const { if(type != tDouble) return CNumber(*this); return CNumber(::floor(Double)); } CNumber CNumber::ceil() const { if(type != tDouble) return CNumber(*this); return CNumber(::ceil(Double)); } CNumber CNumber::abs() const { if(sign()<0) return -CNumber(*this); else return CNumber(*this); } CNumber CNumber::shift(const CNumber &Value, bool Right) const { int32_t lhs = toInt32(); uint32_t rhs = Value.toUInt32() & 0x1F; return CNumber(Right ? lhs>>rhs : lhs<>rhs : lhs< 9 ? ('a'-10) : '0')); if(p==buf_end) { char *new_buf = (char *)realloc(buf, buf_end-buf+16+1); // for '\0' if(!new_buf) { free(buf); return 0; } p = new_buf + (buf_end - buf); buf_end = p + 16; buf = new_buf; } } while (val > 0); // We now have the digit of the number in the buffer, but in reverse // order. Thus we reverse them now. *p-- = '\0'; firstdig = buf; if(*firstdig=='-') firstdig++; do { temp = *p; *p = *firstdig; *firstdig = temp; p--; firstdig++; } while (firstdig < p); return buf; } static char *tiny_dtoa(double val, unsigned radix) { char *buf, *buf_end, *p, temp; unsigned digval; buf = (char*)malloc(64); if(!buf) return 0; buf_end = buf+64-2; // -1 for '.' , -1 for '\0' p = buf; if (val < 0.0) { *p++ = '-'; val = -val; } double val_1 = floor(val); double val_2 = val - val_1; do { double tmp = val_1 / radix; val_1 = floor(tmp); digval = (unsigned)((tmp - val_1) * radix); *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0')); if(p==buf_end) { char *new_buf = (char *)realloc(buf, buf_end-buf+16+2); // +2 for '.' + '\0' if(!new_buf) { free(buf); return 0; } p = new_buf + (buf_end - buf); buf_end = p + 16; buf = new_buf; } } while (val_1 > 0.0); // We now have the digit of the number in the buffer, but in reverse // order. Thus we reverse them now. char *p1 = buf; char *p2 = p-1; do { temp = *p2; *p2-- = *p1; *p1++ = temp; } while (p1 < p2); if(val_2) { *p++ = '.'; do { val_2 *= radix; digval = (unsigned)(val_2); val_2 -= digval; *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0')); if(p==buf_end) { char *new_buf = (char *)realloc(buf, buf_end-buf+16); if(!new_buf) { free(buf); return 0; } p = new_buf + (buf_end - buf); buf_end = p + 16; buf = new_buf; } } while (val_2 > 0.0); } *p = '\0'; return buf; } std::string CNumber::toString( uint32_t Radix/*=10*/ ) const { char *str; if(2 > Radix || Radix > 36) Radix = 10; // todo error; switch(type) { case tInt32: if( (str = tiny_ltoa(Int32, Radix)) ) { string ret(str); free(str); return ret; } break; case tnNULL: return "0"; case tDouble: if(Radix==10) { ostringstream str; str.unsetf(ios::floatfield); #if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L str.precision(numeric_limits::max_digits10); #else str.precision(numeric_limits::digits10+2); #endif str << Double; return str.str(); } else if( (str = tiny_dtoa(Double, Radix)) ) { string ret(str); free(str); return ret; } break; case tInfinity: return Int32<0?"-Infinity":"Infinity"; case tNaN: return "NaN"; } return ""; } double CNumber::toDouble() const { switch(type) { case tnNULL: return -0.0; case tInt32: return double(Int32); case tDouble: return Double; case tNaN: return std::numeric_limits::quiet_NaN(); case tInfinity: return Int32<0 ? -std::numeric_limits::infinity():std::numeric_limits::infinity(); } return 0.0; } #endif ////////////////////////////////////////////////////////////////////////// /// CScriptVarNumber ////////////////////////////////////////////////////////////////////////// CScriptVarNumber::CScriptVarNumber(CTinyJS *Context, const CNumber &Data) : CScriptVarPrimitive(Context, Context->numberPrototype), data(Data) {} CScriptVarNumber::~CScriptVarNumber() {} CScriptVarPtr CScriptVarNumber::clone() { return new CScriptVarNumber(*this); } bool CScriptVarNumber::isNumber() { return true; } bool CScriptVarNumber::isInt() { return data.isInt32(); } bool CScriptVarNumber::isDouble() { return data.isDouble(); } bool CScriptVarNumber::isRealNumber() { return isInt() || isDouble(); } bool CScriptVarNumber::isNaN() { return data.isNaN(); } int CScriptVarNumber::isInfinity() { return data.isInfinity(); } bool CScriptVarNumber::toBoolean() { return data.toBoolean(); } CNumber CScriptVarNumber::toNumber_Callback() { return data; } string CScriptVarNumber::toCString(int radix/*=0*/) { return data.toString(radix); } string CScriptVarNumber::getVarType() { return "number"; } CScriptVarPtr CScriptVarNumber::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->numberPrototype); } inline define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &Obj) { if(!Obj.isInt32() && !Obj.isDouble()) { if(Obj.isNaN()) return Context->constScriptVar(NaN); if(Obj.isInfinity()) return Context->constScriptVar(Infinity(Obj.sign())); if(Obj.isNegativeZero()) return Context->constScriptVar(NegativeZero); } return new CScriptVarNumber(Context, Obj); } ////////////////////////////////////////////////////////////////////////// // CScriptVarBool ////////////////////////////////////////////////////////////////////////// CScriptVarBool::CScriptVarBool(CTinyJS *Context, bool Data) : CScriptVarPrimitive(Context, Context->booleanPrototype), data(Data) {} CScriptVarBool::~CScriptVarBool() {} CScriptVarPtr CScriptVarBool::clone() { return new CScriptVarBool(*this); } bool CScriptVarBool::isBool() { return true; } bool CScriptVarBool::toBoolean() { return data; } CNumber CScriptVarBool::toNumber_Callback() { return data?1:0; } string CScriptVarBool::toCString(int radix/*=0*/) { return data ? "true" : "false"; } string CScriptVarBool::getVarType() { return "boolean"; } CScriptVarPtr CScriptVarBool::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->booleanPrototype); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarObject ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Object); declare_dummy_t(StopIteration); CScriptVarObject::CScriptVarObject(CTinyJS *Context) : CScriptVar(Context, Context->objectPrototype) { } CScriptVarObject::~CScriptVarObject() {} CScriptVarPtr CScriptVarObject::clone() { return new CScriptVarObject(*this); } CScriptVarPrimitivePtr CScriptVarObject::getRawPrimitive() { return value; } bool CScriptVarObject::isObject() { return true; } string CScriptVarObject::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { getParsableStringRecursionsCheck(); string destination; const char *nl = indent.size() ? "\n" : " "; const char *comma = ""; destination.append("{"); if(Childs.size()) { string new_indentString = indentString + indent; for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { if((*it)->isEnumerable()) { destination.append(comma); comma=","; destination.append(nl).append(new_indentString).append(getIDString((*it)->getName())); destination.append(" : "); destination.append((*it)->getVarPtr()->getParsableString(new_indentString, indent, uniqueID, hasRecursion)); } } destination.append(nl).append(indentString); } destination.append("}"); return destination; } string CScriptVarObject::getVarType() { return "object"; } CScriptVarPtr CScriptVarObject::toObject() { return this; } CScriptVarPtr CScriptVarObject::valueOf_CallBack() { if(value) return value->valueOf_CallBack(); return CScriptVar::valueOf_CallBack(); } CScriptVarPtr CScriptVarObject::toString_CallBack(CScriptResult &execute, int radix) { if(value) return value->toString_CallBack(execute, radix); return newScriptVar("[object Object]"); }; void CScriptVarObject::setTemporaryID_recursive( uint32_t ID ) { CScriptVar::setTemporaryID_recursive(ID); if(value) value->setTemporaryID_recursive(ID); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarError ////////////////////////////////////////////////////////////////////////// const char *ERROR_NAME[] = {"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError"}; CScriptVarError::CScriptVarError(CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column) : CScriptVarObject(Context, Context->getErrorPrototype(type)) { if(message && *message) addChild("message", newScriptVar(message)); if(file && *file) addChild("fileName", newScriptVar(file)); if(line>=0) addChild("lineNumber", newScriptVar(line+1)); if(column>=0) addChild("column", newScriptVar(column+1)); } CScriptVarError::~CScriptVarError() {} CScriptVarPtr CScriptVarError::clone() { return new CScriptVarError(*this); } bool CScriptVarError::isError() { return true; } CScriptVarPtr CScriptVarError::toString_CallBack(CScriptResult &execute, int radix) { CScriptVarLinkPtr link; string name = ERROR_NAME[Error]; link = findChildWithPrototypeChain("name"); if(link) name = link->toString(execute); string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString(execute); string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString(execute); int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32(); int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32(); ostringstream msg; msg << name << ": " << message; if(lineNumber >= 0) msg << " at Line:" << lineNumber+1; if(column >=0) msg << " Column:" << column+1; if(fileName.length()) msg << " in " << fileName; return newScriptVar(msg.str()); } CScriptException *CScriptVarError::toCScriptException() { CScriptVarLinkPtr link; string name = ERROR_NAME[Error]; link = findChildWithPrototypeChain("name"); if(link) name = link->toString(); int ErrorCode; for(ErrorCode=(sizeof(ERROR_NAME)/sizeof(ERROR_NAME[0]))-1; ErrorCode>0; ErrorCode--) { if(name == ERROR_NAME[ErrorCode]) break; } string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString(); string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString(); int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32(); int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32(); return new CScriptException((enum ERROR_TYPES)ErrorCode, message, fileName, lineNumber, column); } ////////////////////////////////////////////////////////////////////////// // CScriptVarArray ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Array); CScriptVarArray::CScriptVarArray(CTinyJS *Context) : CScriptVarObject(Context, Context->arrayPrototype) { CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0); CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarArray::native_Length, 0)); getter->setFunctionData(new CScriptTokenDataFnc); acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); } CScriptVarArray::~CScriptVarArray() {} CScriptVarPtr CScriptVarArray::clone() { return new CScriptVarArray(*this); } bool CScriptVarArray::isArray() { return true; } string CScriptVarArray::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { getParsableStringRecursionsCheck(); string destination; const char *nl = indent.size() ? "\n" : " "; const char *comma = ""; destination.append("["); int len = getArrayLength(); if(len) { string new_indentString = indentString + indent; for (int i=0;igetParsableString(new_indentString, indent, uniqueID, hasRecursion)); } destination.append(nl).append(indentString); } destination.append("]"); return destination; } CScriptVarPtr CScriptVarArray::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { ostringstream destination; int len = getArrayLength(); for (int i=0;itoString(execute); if (isetReturnVar(newScriptVar(c->getArgument("this")->getArrayLength())); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarRegExp ////////////////////////////////////////////////////////////////////////// #ifndef NO_REGEXP CScriptVarRegExp::CScriptVarRegExp(CTinyJS *Context, const string &Regexp, const string &Flags) : CScriptVarObject(Context, Context->regexpPrototype), regexp(Regexp), flags(Flags) { addChild("global", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Global, 0, 0, 0), 0); addChild("ignoreCase", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_IgnoreCase, 0, 0, 0), 0); addChild("multiline", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Multiline, 0, 0, 0), 0); addChild("sticky", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Sticky, 0, 0, 0), 0); addChild("regexp", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Source, 0, 0, 0), 0); addChild("lastIndex", newScriptVar(0)); } CScriptVarRegExp::~CScriptVarRegExp() {} CScriptVarPtr CScriptVarRegExp::clone() { return new CScriptVarRegExp(*this); } bool CScriptVarRegExp::isRegExp() { return true; } //int CScriptVarRegExp::getInt() {return strtol(regexp.c_str(),0,0); } //bool CScriptVarRegExp::getBool() {return regexp.length()!=0;} //double CScriptVarRegExp::getDouble() {return strtod(regexp.c_str(),0);} //string CScriptVarRegExp::getString() { return "/"+regexp+"/"+flags; } //string CScriptVarRegExp::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return getString(); } CScriptVarPtr CScriptVarRegExp::toString_CallBack(CScriptResult &execute, int radix) { return newScriptVar("/"+regexp+"/"+flags); } void CScriptVarRegExp::native_Global(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(Global())); } void CScriptVarRegExp::native_IgnoreCase(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(IgnoreCase())); } void CScriptVarRegExp::native_Multiline(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(Multiline())); } void CScriptVarRegExp::native_Sticky(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(Sticky())); } void CScriptVarRegExp::native_Source(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(newScriptVar(regexp)); } unsigned int CScriptVarRegExp::LastIndex() { CScriptVarPtr lastIndex = findChild("lastIndex"); if(lastIndex) return lastIndex->toNumber().toInt32(); return 0; } void CScriptVarRegExp::LastIndex(unsigned int Idx) { addChildOrReplace("lastIndex", newScriptVar((int)Idx)); } CScriptVarPtr CScriptVarRegExp::exec( const string &Input, bool Test /*= false*/ ) { regex::flag_type flags = regex_constants::ECMAScript; if(IgnoreCase()) flags |= regex_constants::icase; bool global = Global(), sticky = Sticky(); unsigned int lastIndex = LastIndex(); int offset = 0; if(global || sticky) { if(lastIndex > Input.length()) goto failed; offset=lastIndex; } { regex_constants::match_flag_type mflag = sticky?regex_constants::match_continuous:regex_constants::match_default; if(offset) mflag |= regex_constants::match_prev_avail; smatch match; if(regex_search(Input.begin()+offset, Input.end(), match, regex(regexp, flags), mflag) ) { LastIndex(offset+match.position()+match.str().length()); if(Test) return constScriptVar(true); CScriptVarArrayPtr retVar = newScriptVar(Array); retVar->addChild("input", newScriptVar(Input)); retVar->addChild("index", newScriptVar(match.position())); for(smatch::size_type idx=0; idxaddChild(int2string(idx), newScriptVar(match[idx].str())); return retVar; } } failed: if(global || sticky) LastIndex(0); if(Test) return constScriptVar(false); return constScriptVar(Null); } const char * CScriptVarRegExp::ErrorStr( int Error ) { switch(Error) { case regex_constants::error_badbrace: return "the expression contained an invalid count in a { } expression"; case regex_constants::error_badrepeat: return "a repeat expression (one of '*', '?', '+', '{' in most contexts) was not preceded by an expression"; case regex_constants::error_brace: return "the expression contained an unmatched '{' or '}'"; case regex_constants::error_brack: return "the expression contained an unmatched '[' or ']'"; case regex_constants::error_collate: return "the expression contained an invalid collating element name"; case regex_constants::error_complexity: return "an attempted match failed because it was too complex"; case regex_constants::error_ctype: return "the expression contained an invalid character class name"; case regex_constants::error_escape: return "the expression contained an invalid escape sequence"; case regex_constants::error_paren: return "the expression contained an unmatched '(' or ')'"; case regex_constants::error_range: return "the expression contained an invalid character range specifier"; case regex_constants::error_space: return "parsing a regular expression failed because there were not enough resources available"; case regex_constants::error_stack: return "an attempted match failed because there was not enough memory available"; case regex_constants::error_backref: return "the expression contained an invalid back reference"; default: return ""; } } #endif /* NO_REGEXP */ ////////////////////////////////////////////////////////////////////////// /// CScriptVarDefaultIterator ////////////////////////////////////////////////////////////////////////// //declare_dummy_t(DefaultIterator); CScriptVarDefaultIterator::CScriptVarDefaultIterator(CTinyJS *Context, const CScriptVarPtr &Object, int Mode) : CScriptVarObject(Context, Context->iteratorPrototype), mode(Mode), object(Object) { object->keys(keys, true); pos = keys.begin(); addChild("next", ::newScriptVar(context, this, &CScriptVarDefaultIterator::native_next, 0)); } CScriptVarDefaultIterator::~CScriptVarDefaultIterator() {} CScriptVarPtr CScriptVarDefaultIterator::clone() { return new CScriptVarDefaultIterator(*this); } bool CScriptVarDefaultIterator::isIterator() {return true;} void CScriptVarDefaultIterator::native_next(const CFunctionsScopePtr &c, void *data) { if(pos==keys.end()) throw constScriptVar(StopIteration); CScriptVarPtr ret, ret0, ret1; if(mode&1) ret0 = newScriptVar(*pos); if(mode&2) ret1 = object->findChildWithStringChars(*pos); pos++; if(mode==3) { ret = newScriptVar(Array); ret->setArrayIndex(0, ret0); ret->setArrayIndex(1, ret1); } else if(mode==1) ret = ret0; else ret = ret1; c->setReturnVar(ret); } ////////////////////////////////////////////////////////////////////////// // CScriptVarFunction ////////////////////////////////////////////////////////////////////////// CScriptVarFunction::CScriptVarFunction(CTinyJS *Context, CScriptTokenDataFnc *Data) : CScriptVarObject(Context, Context->functionPrototype), data(0) { setFunctionData(Data); } CScriptVarFunction::~CScriptVarFunction() { setFunctionData(0); } CScriptVarPtr CScriptVarFunction::clone() { return new CScriptVarFunction(*this); } bool CScriptVarFunction::isObject() { return true; } bool CScriptVarFunction::isFunction() { return true; } bool CScriptVarFunction::isPrimitive() { return false; } //string CScriptVarFunction::getString() {return "[ Function ]";} string CScriptVarFunction::getVarType() { return "function"; } string CScriptVarFunction::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { getParsableStringRecursionsCheck(); string destination; destination.append("function ").append(data->name); // get list of arguments destination.append(data->getArgumentsString()); if(isNative() || isBounded()) destination.append("{ [native code] }"); else { destination.append(CScriptToken::getParsableString(data->body, indentString, indent)); if(!data->body.size() || data->body.front().token != '{') destination.append(";"); } return destination; } CScriptVarPtr CScriptVarFunction::toString_CallBack(CScriptResult &execute, int radix){ bool hasRecursion; return newScriptVar(getParsableString("", " ", 0, hasRecursion)); } CScriptTokenDataFnc *CScriptVarFunction::getFunctionData() { return data; } void CScriptVarFunction::setFunctionData(CScriptTokenDataFnc *Data) { if(data) { data->unref(); data = 0; } if(Data) { data = Data; data->ref(); addChildOrReplace("length", newScriptVar((int)data->arguments.size()), 0); // can not add "name" here because name is a StingVar with length as getter // length-getter is a function with a function name -> endless recursion //addChildNoDup("name", newScriptVar(data->name), 0); } } ////////////////////////////////////////////////////////////////////////// /// CScriptVarFunctionBounded ////////////////////////////////////////////////////////////////////////// CScriptVarFunctionBounded::CScriptVarFunctionBounded(CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments) : CScriptVarFunction(BoundedFunction->getContext(), new CScriptTokenDataFnc) , boundedFunction(BoundedFunction), boundedThis(BoundedThis), boundedArguments(BoundedArguments) { getFunctionData()->name = BoundedFunction->getFunctionData()->name; } CScriptVarFunctionBounded::~CScriptVarFunctionBounded(){} CScriptVarPtr CScriptVarFunctionBounded::clone() { return new CScriptVarFunctionBounded(*this); } bool CScriptVarFunctionBounded::isBounded() { return true; } void CScriptVarFunctionBounded::setTemporaryID_recursive( uint32_t ID ) { CScriptVarFunction::setTemporaryID_recursive(ID); boundedThis->setTemporaryID_recursive(ID); for(vector::iterator it=boundedArguments.begin(); it!=boundedArguments.end(); ++it) (*it)->setTemporaryID_recursive(ID); } CScriptVarPtr CScriptVarFunctionBounded::callFunction( CScriptResult &execute, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis/*=0*/ ) { vector newArgs=boundedArguments; newArgs.insert(newArgs.end(), Arguments.begin(), Arguments.end()); return context->callFunction(execute, boundedFunction, newArgs, newThis ? This : boundedThis, newThis); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarFunctionNative ////////////////////////////////////////////////////////////////////////// CScriptVarFunctionNative::~CScriptVarFunctionNative() {} bool CScriptVarFunctionNative::isNative() { return true; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarFunctionNativeCallback ////////////////////////////////////////////////////////////////////////// CScriptVarFunctionNativeCallback::~CScriptVarFunctionNativeCallback() {} CScriptVarPtr CScriptVarFunctionNativeCallback::clone() { return new CScriptVarFunctionNativeCallback(*this); } void CScriptVarFunctionNativeCallback::callFunction(const CFunctionsScopePtr &c) { jsCallback(c, jsUserData); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarAccessor ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Accessor); CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context) : CScriptVarObject(Context, Context->objectPrototype) { } CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context, JSCallback getterFnc, void *getterData, JSCallback setterFnc, void *setterData) : CScriptVarObject(Context) { if(getterFnc) addChild(TINYJS_ACCESSOR_GET_VAR, ::newScriptVar(Context, getterFnc, getterData), 0); if(setterFnc) addChild(TINYJS_ACCESSOR_SET_VAR, ::newScriptVar(Context, setterFnc, setterData), 0); } CScriptVarAccessor::CScriptVarAccessor( CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter) : CScriptVarObject(Context, Context->objectPrototype) { if(getter) addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); if(setter) addChild(TINYJS_ACCESSOR_SET_VAR, setter, 0); } CScriptVarAccessor::~CScriptVarAccessor() {} CScriptVarPtr CScriptVarAccessor::clone() { return new CScriptVarAccessor(*this); } bool CScriptVarAccessor::isAccessor() { return true; } bool CScriptVarAccessor::isPrimitive() { return false; } string CScriptVarAccessor::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return ""; } string CScriptVarAccessor::getVarType() { return "accessor"; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarScope ////////////////////////////////////////////////////////////////////////// declare_dummy_t(Scope); CScriptVarScope::~CScriptVarScope() {} CScriptVarPtr CScriptVarScope::clone() { return CScriptVarPtr(); } bool CScriptVarScope::isObject() { return false; } CScriptVarPtr CScriptVarScope::scopeVar() { return this; } ///< to create var like: var a = ... CScriptVarPtr CScriptVarScope::scopeLet() { return this; } ///< to create var like: let a = ... CScriptVarLinkWorkPtr CScriptVarScope::findInScopes(const string &childName) { return CScriptVar::findChild(childName); } CScriptVarScopePtr CScriptVarScope::getParent() { return CScriptVarScopePtr(); } ///< no Parent ////////////////////////////////////////////////////////////////////////// /// CScriptVarScopeFnc ////////////////////////////////////////////////////////////////////////// declare_dummy_t(ScopeFnc); CScriptVarScopeFnc::~CScriptVarScopeFnc() {} CScriptVarLinkWorkPtr CScriptVarScopeFnc::findInScopes(const string &childName) { CScriptVarLinkWorkPtr ret = findChild(childName); if( !ret ) { if(closure) ret = CScriptVarScopePtr(closure)->findInScopes(childName); else ret = context->getRoot()->findChild(childName); } return ret; } void CScriptVarScopeFnc::setReturnVar(const CScriptVarPtr &var) { addChildOrReplace(TINYJS_RETURN_VAR, var); } CScriptVarPtr CScriptVarScopeFnc::getParameter(const string &name) { return getArgument(name); } CScriptVarPtr CScriptVarScopeFnc::getParameter(int Idx) { return getArgument(Idx); } CScriptVarPtr CScriptVarScopeFnc::getArgument(const string &name) { return findChildOrCreate(name); } CScriptVarPtr CScriptVarScopeFnc::getArgument(int Idx) { CScriptVarLinkPtr arguments = findChildOrCreate(TINYJS_ARGUMENTS_VAR); if(arguments) arguments = arguments->getVarPtr()->findChild(int2string(Idx)); return arguments ? arguments->getVarPtr() : constScriptVar(Undefined); } int CScriptVarScopeFnc::getParameterLength() { return getArgumentsLength(); } int CScriptVarScopeFnc::getArgumentsLength() { CScriptVarLinkPtr arguments = findChild(TINYJS_ARGUMENTS_VAR); if(arguments) arguments = arguments->getVarPtr()->findChild("length"); return arguments ? arguments.getter()->toNumber().toInt32() : 0; } void CScriptVarScopeFnc::throwError( ERROR_TYPES ErrorType, const string &message ) { throw newScriptVarError(context, ErrorType, message.c_str()); } ////////////////////////////////////////////////////////////////////////// /// CScriptVarScopeLet ////////////////////////////////////////////////////////////////////////// declare_dummy_t(ScopeLet); CScriptVarScopeLet::CScriptVarScopeLet(const CScriptVarScopePtr &Parent) // constructor for LetScope : CScriptVarScope(Parent->getContext()), parent(addChild(TINYJS_SCOPE_PARENT_VAR, Parent, 0)) , letExpressionInitMode(false) {} CScriptVarScopeLet::~CScriptVarScopeLet() {} CScriptVarPtr CScriptVarScopeLet::scopeVar() { // to create var like: var a = ... return getParent()->scopeVar(); } CScriptVarScopePtr CScriptVarScopeLet::getParent() { return (CScriptVarPtr)parent; } CScriptVarLinkWorkPtr CScriptVarScopeLet::findInScopes(const string &childName) { CScriptVarLinkWorkPtr ret; if(letExpressionInitMode) { return getParent()->findInScopes(childName); } else { ret = findChild(childName); if( !ret ) ret = getParent()->findInScopes(childName); } return ret; } ////////////////////////////////////////////////////////////////////////// /// CScriptVarScopeWith ////////////////////////////////////////////////////////////////////////// declare_dummy_t(ScopeWith); CScriptVarScopeWith::~CScriptVarScopeWith() {} CScriptVarPtr CScriptVarScopeWith::scopeLet() { // to create var like: let a = ... return getParent()->scopeLet(); } CScriptVarLinkWorkPtr CScriptVarScopeWith::findInScopes(const string &childName) { if(childName == "this") return with; CScriptVarLinkWorkPtr ret = with->getVarPtr()->findChild(childName); if( !ret ) { ret = with->getVarPtr()->findChildInPrototypeChain(childName); if(ret) { ret(ret->getVarPtr(), ret->getName()); // recreate ret ret.setReferencedOwner(with->getVarPtr()); // fake referenced Owner } } if( !ret ) ret = getParent()->findInScopes(childName); return ret; } ////////////////////////////////////////////////////////////////////////// /// CTinyJS ////////////////////////////////////////////////////////////////////////// extern "C" void _registerFunctions(CTinyJS *tinyJS); extern "C" void _registerStringFunctions(CTinyJS *tinyJS); extern "C" void _registerMathFunctions(CTinyJS *tinyJS); CTinyJS::CTinyJS() { CScriptVarPtr var; t = 0; haveTry = false; first = 0; uniqueID = 0; ////////////////////////////////////////////////////////////////////////// // Object-Prototype // must be created as first object because this prototype is the base of all objects objectPrototype = newScriptVar(Object); // all objects have a prototype. Also the prototype of prototypes objectPrototype->addChild(TINYJS___PROTO___VAR, objectPrototype, 0); ////////////////////////////////////////////////////////////////////////// // Function-Prototype // must be created as second object because this is the base of all functions (also constructors) functionPrototype = newScriptVar(Object); ////////////////////////////////////////////////////////////////////////// // Scopes root = ::newScriptVar(this, Scope); scopes.push_back(root); ////////////////////////////////////////////////////////////////////////// // Add built-in classes ////////////////////////////////////////////////////////////////////////// // Object var = addNative("function Object()", this, &CTinyJS::native_Object, 0, SCRIPTVARLINK_CONSTANT); objectPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); addNative("function Object.getPrototypeOf(obj)", this, &CTinyJS::native_Object_getPrototypeOf); addNative("function Object.preventExtensions(obj)", this, &CTinyJS::native_Object_setObjectSecure); addNative("function Object.isExtensible(obj)", this, &CTinyJS::native_Object_isSecureObject); addNative("function Object.seel(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)1); addNative("function Object.isSealed(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)1); addNative("function Object.freeze(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)2); addNative("function Object.isFrozen(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)2); addNative("function Object.keys(obj)", this, &CTinyJS::native_Object_keys); addNative("function Object.getOwnPropertyNames(obj)", this, &CTinyJS::native_Object_keys, (void*)1); addNative("function Object.getOwnPropertyDescriptor(obj,name)", this, &CTinyJS::native_Object_getOwnPropertyDescriptor); addNative("function Object.defineProperty(obj,name,attributes)", this, &CTinyJS::native_Object_defineProperty); addNative("function Object.defineProperties(obj,properties)", this, &CTinyJS::native_Object_defineProperties); addNative("function Object.create(obj,properties)", this, &CTinyJS::native_Object_defineProperties, (void*)1); addNative("function Object.prototype.hasOwnProperty(prop)", this, &CTinyJS::native_Object_prototype_hasOwnProperty); objectPrototype_valueOf = addNative("function Object.prototype.valueOf()", this, &CTinyJS::native_Object_prototype_valueOf); objectPrototype_toString = addNative("function Object.prototype.toString(radix)", this, &CTinyJS::native_Object_prototype_toString); pseudo_refered.push_back(&objectPrototype); pseudo_refered.push_back(&objectPrototype_valueOf); pseudo_refered.push_back(&objectPrototype_toString); ////////////////////////////////////////////////////////////////////////// // Array var = addNative("function Array()", this, &CTinyJS::native_Array, 0, SCRIPTVARLINK_CONSTANT); arrayPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); arrayPrototype->addChild("valueOf", objectPrototype_valueOf); arrayPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(&arrayPrototype); var = addNative("function Array.__constructor__()", this, &CTinyJS::native_Array, (void*)1, SCRIPTVARLINK_CONSTANT); var->getFunctionData()->name = "Array"; ////////////////////////////////////////////////////////////////////////// // String var = addNative("function String()", this, &CTinyJS::native_String, 0, SCRIPTVARLINK_CONSTANT); stringPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); stringPrototype->addChild("valueOf", objectPrototype_valueOf); stringPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(&stringPrototype); var = addNative("function String.__constructor__()", this, &CTinyJS::native_String, (void*)1, SCRIPTVARLINK_CONSTANT); var->getFunctionData()->name = "String"; ////////////////////////////////////////////////////////////////////////// // RegExp #ifndef NO_REGEXP var = addNative("function RegExp()", this, &CTinyJS::native_RegExp, 0, SCRIPTVARLINK_CONSTANT); regexpPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); regexpPrototype->addChild("valueOf", objectPrototype_valueOf); regexpPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(®expPrototype); #endif /* NO_REGEXP */ ////////////////////////////////////////////////////////////////////////// // Number var = addNative("function Number()", this, &CTinyJS::native_Number, 0, SCRIPTVARLINK_CONSTANT); var->addChild("NaN", constNaN = newScriptVarNumber(this, NaN), SCRIPTVARLINK_CONSTANT); var->addChild("MAX_VALUE", constInfinityPositive = newScriptVarNumber(this, numeric_limits::max()), SCRIPTVARLINK_CONSTANT); var->addChild("MIN_VALUE", constInfinityPositive = newScriptVarNumber(this, numeric_limits::min()), SCRIPTVARLINK_CONSTANT); var->addChild("POSITIVE_INFINITY", constInfinityPositive = newScriptVarNumber(this, InfinityPositive), SCRIPTVARLINK_CONSTANT); var->addChild("NEGATIVE_INFINITY", constInfinityNegative = newScriptVarNumber(this, InfinityNegative), SCRIPTVARLINK_CONSTANT); numberPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); numberPrototype->addChild("valueOf", objectPrototype_valueOf); numberPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(&numberPrototype); pseudo_refered.push_back(&constNaN); pseudo_refered.push_back(&constInfinityPositive); pseudo_refered.push_back(&constInfinityNegative); var = addNative("function Number.__constructor__()", this, &CTinyJS::native_Number, (void*)1, SCRIPTVARLINK_CONSTANT); var->getFunctionData()->name = "Number"; ////////////////////////////////////////////////////////////////////////// // Boolean var = addNative("function Boolean()", this, &CTinyJS::native_Boolean, 0, SCRIPTVARLINK_CONSTANT); booleanPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); booleanPrototype->addChild("valueOf", objectPrototype_valueOf); booleanPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(&booleanPrototype); var = addNative("function Boolean.__constructor__()", this, &CTinyJS::native_Boolean, (void*)1, SCRIPTVARLINK_CONSTANT); var->getFunctionData()->name = "Boolean"; ////////////////////////////////////////////////////////////////////////// // Iterator var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT); iteratorPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); pseudo_refered.push_back(&iteratorPrototype); ////////////////////////////////////////////////////////////////////////// // Function var = addNative("function Function(params, body)", this, &CTinyJS::native_Function, 0, SCRIPTVARLINK_CONSTANT); var->addChildOrReplace(TINYJS_PROTOTYPE_CLASS, functionPrototype); addNative("function Function.prototype.call(objc)", this, &CTinyJS::native_Function_prototype_call); addNative("function Function.prototype.apply(objc, args)", this, &CTinyJS::native_Function_prototype_apply); addNative("function Function.prototype.bind(objc, args)", this, &CTinyJS::native_Function_prototype_bind); functionPrototype->addChild("valueOf", objectPrototype_valueOf); functionPrototype->addChild("toString", objectPrototype_toString); pseudo_refered.push_back(&functionPrototype); ////////////////////////////////////////////////////////////////////////// // Error var = addNative("function Error(message, fileName, lineNumber, column)", this, &CTinyJS::native_Error, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[Error] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[Error]->addChild("message", newScriptVar("")); errorPrototypes[Error]->addChild("name", newScriptVar("Error")); errorPrototypes[Error]->addChild("fileName", newScriptVar("")); errorPrototypes[Error]->addChild("lineNumber", newScriptVar(-1)); // -1 means not viable errorPrototypes[Error]->addChild("column", newScriptVar(-1)); // -1 means not viable var = addNative("function EvalError(message, fileName, lineNumber, column)", this, &CTinyJS::native_EvalError, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[EvalError] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[EvalError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); errorPrototypes[EvalError]->addChild("name", newScriptVar("EvalError")); var = addNative("function RangeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_RangeError, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[RangeError] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[RangeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); errorPrototypes[RangeError]->addChild("name", newScriptVar("RangeError")); var = addNative("function ReferenceError(message, fileName, lineNumber, column)", this, &CTinyJS::native_ReferenceError, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[ReferenceError] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[ReferenceError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); errorPrototypes[ReferenceError]->addChild("name", newScriptVar("ReferenceError")); var = addNative("function SyntaxError(message, fileName, lineNumber, column)", this, &CTinyJS::native_SyntaxError, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[SyntaxError] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[SyntaxError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); errorPrototypes[SyntaxError]->addChild("name", newScriptVar("SyntaxError")); var = addNative("function TypeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_TypeError, 0, SCRIPTVARLINK_CONSTANT); errorPrototypes[TypeError] = var->findChild(TINYJS_PROTOTYPE_CLASS); errorPrototypes[TypeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); errorPrototypes[TypeError]->addChild("name", newScriptVar("TypeError")); ////////////////////////////////////////////////////////////////////////// // add global built-in vars & constants root->addChild("undefined", constUndefined = newScriptVarUndefined(this), SCRIPTVARLINK_CONSTANT); pseudo_refered.push_back(&constUndefined); constNull = newScriptVarNull(this); pseudo_refered.push_back(&constNull); root->addChild("NaN", constNaN, SCRIPTVARLINK_CONSTANT); root->addChild("Infinity", constInfinityPositive, SCRIPTVARLINK_CONSTANT); root->addChild("StopIteration", constStopIteration=newScriptVar(Object, var=newScriptVar(Object)), SCRIPTVARLINK_CONSTANT); constStopIteration->addChild(TINYJS_PROTOTYPE_CLASS, var, SCRIPTVARLINK_CONSTANT); pseudo_refered.push_back(&constStopIteration); constNegativZero = newScriptVarNumber(this, NegativeZero); pseudo_refered.push_back(&constNegativZero); constFalse = newScriptVarBool(this, false); pseudo_refered.push_back(&constFalse); constTrue = newScriptVarBool(this, true); pseudo_refered.push_back(&constTrue); ////////////////////////////////////////////////////////////////////////// // add global functions addNative("function eval(jsCode)", this, &CTinyJS::native_eval); addNative("function isNaN(objc)", this, &CTinyJS::native_isNAN); addNative("function isFinite(objc)", this, &CTinyJS::native_isFinite); addNative("function parseInt(string, radix)", this, &CTinyJS::native_parseInt); addNative("function parseFloat(string)", this, &CTinyJS::native_parseFloat); addNative("function JSON.parse(text, reviver)", this, &CTinyJS::native_JSON_parse); _registerFunctions(this); _registerStringFunctions(this); _registerMathFunctions(this); } CTinyJS::~CTinyJS() { ASSERT(!t); for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it) **it = CScriptVarPtr(); for(int i=Error; iremoveAllChildren(); scopes.clear(); ClearUnreferedVars(); root = CScriptVarPtr(); #ifdef _DEBUG for(CScriptVar *p = first; p; p=p->next) printf("%p\n", p); #endif #if DEBUG_MEMORY show_allocated(); #endif } ////////////////////////////////////////////////////////////////////////// /// throws an Error & Exception ////////////////////////////////////////////////////////////////////////// void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message ) { if(execute && haveTry) { execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), t->currentLine(), t->currentColumn())); return; } throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn()); } void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message ) { throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn()); } void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){ if(execute && haveTry) { execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), Pos.currentLine(), Pos.currentColumn())); return; } throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn()); } void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){ throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn()); } ////////////////////////////////////////////////////////////////////////// void CTinyJS::trace() { root->trace(); } void CTinyJS::execute(CScriptTokenizer &Tokenizer) { evaluateComplex(Tokenizer); } void CTinyJS::execute(const char *Code, const string &File, int Line, int Column) { evaluateComplex(Code, File, Line, Column); } void CTinyJS::execute(const string &Code, const string &File, int Line, int Column) { evaluateComplex(Code, File, Line, Column); } CScriptVarLinkPtr CTinyJS::evaluateComplex(CScriptTokenizer &Tokenizer) { t = &Tokenizer; CScriptResult execute; try { do { execute_statement(execute); while (t->tk==';') t->match(';'); // skip empty statements } while (t->tk!=LEX_EOF); } catch (...) { haveTry = false; t=0; // clean up Tokenizer throw; // } t=0; ClearUnreferedVars(execute.value); uint32_t UniqueID = getUniqueID(); setTemporaryID_recursive(UniqueID); if(execute.value) execute.value->setTemporaryID_recursive(UniqueID); for(CScriptVar *p = first; p; p=p->next) { if(p->temporaryID != UniqueID) printf("%p\n", p); } if (execute.value) return CScriptVarLinkPtr(execute.value); // return undefined... return CScriptVarLinkPtr(constScriptVar(Undefined)); } CScriptVarLinkPtr CTinyJS::evaluateComplex(const char *Code, const string &File, int Line, int Column) { CScriptTokenizer Tokenizer(Code, File, Line, Column); return evaluateComplex(Tokenizer); } CScriptVarLinkPtr CTinyJS::evaluateComplex(const string &Code, const string &File, int Line, int Column) { CScriptTokenizer Tokenizer(Code.c_str(), File, Line, Column); return evaluateComplex(Tokenizer); } string CTinyJS::evaluate(CScriptTokenizer &Tokenizer) { return evaluateComplex(Tokenizer)->toString(); } string CTinyJS::evaluate(const char *Code, const string &File, int Line, int Column) { return evaluateComplex(Code, File, Line, Column)->toString(); } string CTinyJS::evaluate(const string &Code, const string &File, int Line, int Column) { return evaluate(Code.c_str(), File, Line, Column); } CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata, int LinkFlags) { return addNative(funcDesc, ::newScriptVar(this, ptr, userdata), LinkFlags); } CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, CScriptVarFunctionNativePtr Var, int LinkFlags) { CScriptLex lex(funcDesc.c_str()); CScriptVarPtr base = root; lex.match(LEX_R_FUNCTION); string funcName = lex.tkStr; lex.match(LEX_ID); /* Check for dots, we might want to do something like function String.substring ... */ while (lex.tk == '.') { lex.match('.'); CScriptVarLinkPtr link = base->findChild(funcName); // if it doesn't exist, make an object class if (!link) link = base->addChild(funcName, newScriptVar(Object)); base = link->getVarPtr(); funcName = lex.tkStr; lex.match(LEX_ID); } auto_ptr pFunctionData(new CScriptTokenDataFnc); pFunctionData->name = funcName; lex.match('('); while (lex.tk!=')') { pFunctionData->arguments.push_back(CScriptToken(LEX_ID, lex.tkStr)); lex.match(LEX_ID); if (lex.tk!=')') lex.match(',',')'); } lex.match(')'); Var->setFunctionData(pFunctionData.release()); Var->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE); base->addChild(funcName, Var, LinkFlags); return Var; } CScriptVarLinkWorkPtr CTinyJS::parseFunctionDefinition(const CScriptToken &FncToken) { const CScriptTokenDataFnc &Fnc = FncToken.Fnc(); // string fncName = (FncToken.token == LEX_T_FUNCTION_OPERATOR) ? TINYJS_TEMP_NAME : Fnc.name; CScriptVarLinkWorkPtr funcVar(newScriptVar((CScriptTokenDataFnc*)&Fnc), Fnc.name); if(scope() != root) funcVar->getVarPtr()->addChild(TINYJS_FUNCTION_CLOSURE_VAR, scope(), 0); funcVar->getVarPtr()->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE); return funcVar; } CScriptVarLinkWorkPtr CTinyJS::parseFunctionsBodyFromString(const string &ArgumentList, const string &FncBody) { string Fnc = "function ("+ArgumentList+"){"+FncBody+"}"; CScriptTokenizer tokenizer(Fnc.c_str()); return parseFunctionDefinition(tokenizer.getToken()); } CScriptVarPtr CTinyJS::callFunction(const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) { CScriptResult execute; CScriptVarPtr retVar = callFunction(execute, Function, Arguments, This, newThis); execute.cThrow(); return retVar; } CScriptVarPtr CTinyJS::callFunction(CScriptResult &execute, const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) { ASSERT(Function && Function->isFunction()); if(Function->isBounded()) return CScriptVarFunctionBoundedPtr(Function)->callFunction(execute, Arguments, This, newThis); CScriptTokenDataFnc *Fnc = Function->getFunctionData(); CScriptVarScopeFncPtr functionRoot(::newScriptVar(this, ScopeFnc, CScriptVarPtr(Function->findChild(TINYJS_FUNCTION_CLOSURE_VAR)))); if(Fnc->name.size()) functionRoot->addChild(Fnc->name, Function); functionRoot->addChild("this", This); CScriptVarPtr arguments = functionRoot->addChild(TINYJS_ARGUMENTS_VAR, newScriptVar(Object)); CScriptResult function_execute; int length_proto = Fnc->arguments.size(); int length_arguments = Arguments.size(); int length = max(length_proto, length_arguments); for(int arguments_idx = 0; arguments_idxaddChild(arguments_idx_str, Arguments[arguments_idx]); } else { value = constScriptVar(Undefined); } if(arguments_idx < length_proto) { CScriptToken &FncArguments = Fnc->arguments[arguments_idx]; if(FncArguments.token == LEX_ID) functionRoot->addChildOrReplace(FncArguments.String(), value); else assign_destructuring_var(functionRoot, FncArguments.DestructuringVar(), value, function_execute); } } arguments->addChild("length", newScriptVar(length_arguments)); // execute function! // add the function's execute space to the symbol table so we can recurse CScopeControl ScopeControl(this); ScopeControl.addFncScope(functionRoot); if (Function->isNative()) { try { CScriptVarFunctionNativePtr(Function)->callFunction(functionRoot); CScriptVarLinkPtr ret = functionRoot->findChild(TINYJS_RETURN_VAR); function_execute.set(CScriptResult::Return, ret ? CScriptVarPtr(ret) : constUndefined); } catch (CScriptVarPtr v) { if(haveTry) { function_execute.set(CScriptResult::Throw, v); } else if(v->isError()) { CScriptException *err = CScriptVarErrorPtr(v)->toCScriptException(); if(err->fileName.empty()) err->fileName = "native function '"+Function->getFunctionData()->name+"'"; throw err; } else throw new CScriptException(Error, v->toString(function_execute)+"' in: native function '"+Function->getFunctionData()->name+"'"); } } 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... */ string oldFile = t->currentFile; t->currentFile = Fnc->file; t->pushTokenScope(Fnc->body); if(Fnc->body.front().token == '{') execute_block(function_execute); else { CScriptVarPtr ret = execute_base(function_execute); if(function_execute) function_execute.set(CScriptResult::Return, ret); } t->currentFile = oldFile; // because return will probably have called this, and set execute to false } if(function_execute.isReturnNormal()) { if(newThis) *newThis = functionRoot->findChild("this"); if(function_execute.isReturn()) { CScriptVarPtr ret = function_execute.value; return ret; } } else execute = function_execute; return constScriptVar(Undefined); } CScriptVarPtr CTinyJS::mathsOp(CScriptResult &execute, const CScriptVarPtr &A, const CScriptVarPtr &B, int op) { if(!execute) return constUndefined; if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) { // check type first if( (A->getVarType() == B->getVarType()) ^ (op == LEX_TYPEEQUAL)) return constFalse; // check value second return mathsOp(execute, A, B, op == LEX_TYPEEQUAL ? LEX_EQUAL : LEX_NEQUAL); } if (!A->isPrimitive() && !B->isPrimitive()) { // Objects both // check pointers switch (op) { case LEX_EQUAL: return constScriptVar(A==B); case LEX_NEQUAL: return constScriptVar(A!=B); } } CScriptVarPtr a = A->toPrimitive_hintNumber(execute); CScriptVarPtr b = B->toPrimitive_hintNumber(execute); if(!execute) return constUndefined; // do maths... bool a_isString = a->isString(); bool b_isString = b->isString(); // both a String or one a String and op='+' if( (a_isString && b_isString) || ((a_isString || b_isString) && op == '+')) { string da = a->isNull() ? "" : a->toString(execute); string db = b->isNull() ? "" : b->toString(execute); switch (op) { case '+': return newScriptVar(da+db); case LEX_EQUAL: return constScriptVar(da==db); case LEX_NEQUAL: return constScriptVar(da!=db); case '<': return constScriptVar(da': return constScriptVar(da>db); case LEX_GEQUAL: return constScriptVar(da>=db); } } // special for undefined and null --> every true: undefined==undefined, undefined==null, null==undefined and null=null else if( (a->isUndefined() || a->isNull()) && (b->isUndefined() || b->isNull()) ) { switch (op) { case LEX_EQUAL: return constScriptVar(true); case LEX_NEQUAL: case LEX_GEQUAL: case LEX_LEQUAL: case '<': case '>': return constScriptVar(false); } } CNumber da = a->toNumber(); CNumber db = b->toNumber(); switch (op) { case '+': return a->newScriptVar(da+db); case '-': return a->newScriptVar(da-db); case '*': return a->newScriptVar(da*db); case '/': return a->newScriptVar(da/db); case '%': return a->newScriptVar(da%db); case '&': return a->newScriptVar(da.toInt32()&db.toInt32()); case '|': return a->newScriptVar(da.toInt32()|db.toInt32()); case '^': return a->newScriptVar(da.toInt32()^db.toInt32()); case '~': return a->newScriptVar(~da); case LEX_LSHIFT: return a->newScriptVar(da<newScriptVar(da>>db); case LEX_RSHIFTU: return a->newScriptVar(da.ushift(db)); case LEX_EQUAL: return a->constScriptVar(da==db); case LEX_NEQUAL: return a->constScriptVar(da!=db); case '<': return a->constScriptVar(daconstScriptVar(da<=db); case '>': return a->constScriptVar(da>db); case LEX_GEQUAL: return a->constScriptVar(da>=db); default: throw new CScriptException("This operation not supported on the int datatype"); } } void CTinyJS::assign_destructuring_var(const CScriptVarPtr &Scope, const CScriptTokenDataDestructuringVar &Objc, const CScriptVarPtr &Val, CScriptResult &execute) { if(!execute) return; if(Objc.vars.size() == 1) { if(Scope) Scope->addChildOrReplace(Objc.vars.front().second, Val); else { CScriptVarLinkWorkPtr v(findInScopes(Objc.vars.front().second)); ASSERT(v==true); if(v) v->setVarPtr(Val); } } else { vector Path(1, Val); for(DESTRUCTURING_VARS_cit it=Objc.vars.begin()+1; it!=Objc.vars.end(); ++it) { if(it->second == "}" || it->second == "]") Path.pop_back(); else { if(it->second.empty()) continue; // skip empty entries CScriptVarLinkWorkPtr var = Path.back()->findChildWithStringChars(it->first); if(var) var = var.getter(execute); else var = constUndefined; if(!execute) return; if(it->second == "{" || it->second == "[") { Path.push_back(var); } else if(Scope) Scope->addChildOrReplace(it->second, var); else { CScriptVarLinkWorkPtr v(findInScopes(it->second)); ASSERT(v==true); if(v) v->setVarPtr(var); } } } } } void CTinyJS::execute_var_init( bool hideLetScope, CScriptResult &execute ) { for(;;) { t->check(LEX_T_DESTRUCTURING_VAR); CScriptTokenDataDestructuringVar &Objc = t->getToken().DestructuringVar(); t->match(LEX_T_DESTRUCTURING_VAR); if(t->tk == '=') { t->match('='); if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(true); CScriptVarPtr Val = execute_assignment(execute); if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(false); assign_destructuring_var(0, Objc, Val, execute); } if (t->tk == ',') t->match(','); else break; } } void CTinyJS::execute_destructuring(CScriptTokenDataObjectLiteral &Objc, const CScriptVarPtr &Val, CScriptResult &execute) { for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) { if(it->value.empty()) continue; CScriptVarPtr rhs = Val->findChildWithStringChars(it->id).getter(execute); if(!rhs) rhs=constUndefined; if(it->value.front().token == LEX_T_OBJECT_LITERAL && it->value.front().Object().destructuring) { execute_destructuring(it->value.front().Object(), rhs, execute); } else { t->pushTokenScope(it->value); CScriptVarLinkWorkPtr lhs = execute_condition(execute); if(lhs->isWritable()) { if (!lhs->isOwned()) { CScriptVarPtr fakedOwner = lhs.getReferencedOwner(); if(fakedOwner) { if(!fakedOwner->isExtensible()) continue; lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs); } else lhs = root->addChildOrReplace(lhs->getName(), lhs); } lhs.setter(execute, rhs); } } } } CScriptVarLinkWorkPtr CTinyJS::execute_literals(CScriptResult &execute) { switch(t->tk) { case LEX_ID: if(execute) { CScriptVarLinkWorkPtr a(findInScopes(t->tkStr())); if (!a) { /* Variable doesn't exist! JavaScript says we should create it * (we won't add it here. This is done in the assignment operator)*/ if(t->tkStr() == "this") a = root; // fake this else a = CScriptVarLinkPtr(constScriptVar(Undefined), t->tkStr()); } /* prvention for assignment to this is now done by the tokenizer else if(t->tkStr() == "this") a(a->getVarPtr()); // prevent assign to this */ t->match(LEX_ID); return a; } t->match(LEX_ID); break; case LEX_INT: { CScriptVarPtr a = newScriptVar(t->getToken().Int()); a->setExtensible(false); t->match(LEX_INT); return a; } break; case LEX_FLOAT: { CScriptVarPtr a = newScriptVar(t->getToken().Float()); t->match(LEX_FLOAT); return a; } break; case LEX_STR: { CScriptVarPtr a = newScriptVar(t->getToken().String()); t->match(LEX_STR); return a; } break; #ifndef NO_REGEXP case LEX_REGEXP: { string::size_type pos = t->getToken().String().find_last_of('/'); string source = t->getToken().String().substr(1, pos-1); string flags = t->getToken().String().substr(pos+1); CScriptVarPtr a = newScriptVar(source, flags); t->match(LEX_REGEXP); return a; } break; #endif /* NO_REGEXP */ case LEX_T_OBJECT_LITERAL: if(execute) { CScriptTokenDataObjectLiteral &Objc = t->getToken().Object(); t->match(LEX_T_OBJECT_LITERAL); if(Objc.destructuring) { t->match('='); CScriptVarPtr a = execute_assignment(execute); if(execute) execute_destructuring(Objc, a, execute); return a; } else { CScriptVarPtr a = Objc.type==CScriptTokenDataObjectLiteral::OBJECT ? newScriptVar(Object) : newScriptVar(Array); for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) { if(it->value.empty()) continue; CScriptToken &tk = it->value.front(); if(tk.token==LEX_T_GET || tk.token==LEX_T_SET) { CScriptTokenDataFnc &Fnc = tk.Fnc(); if((tk.token == LEX_T_GET && Fnc.arguments.size()==0) || (tk.token == LEX_T_SET && Fnc.arguments.size()==1)) { CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(tk); CScriptVarLinkWorkPtr child = a->findChild(Fnc.name); if(child && !child->getVarPtr()->isAccessor()) child.clear(); if(!child) child = a->addChildOrReplace(Fnc.name, newScriptVar(Accessor)); child->getVarPtr()->addChildOrReplace((tk.token==LEX_T_GET?TINYJS_ACCESSOR_GET_VAR:TINYJS_ACCESSOR_SET_VAR), funcVar->getVarPtr()); } } else { t->pushTokenScope(it->value); a->addChildOrReplace(it->id, execute_assignment(execute)); while(0); } } return a; } } else t->match(LEX_T_OBJECT_LITERAL); break; case LEX_R_LET: // let as expression if(execute) { CScopeControl ScopeControl(this); t->match(LEX_R_LET); t->match('('); t->check(LEX_T_FORWARD); ScopeControl.addLetScope(); execute_statement(execute); // execute forwarder execute_var_init(true, execute); t->match(')'); return execute_assignment(execute); } else { t->skip(t->getToken().Int()); } break; case LEX_T_FUNCTION_OPERATOR: if(execute) { CScriptVarLinkWorkPtr a = parseFunctionDefinition(t->getToken()); t->match(LEX_T_FUNCTION_OPERATOR); return a; } t->match(LEX_T_FUNCTION_OPERATOR); break; case LEX_R_NEW: // new -> create a new object if (execute) { t->match(LEX_R_NEW); CScriptVarLinkWorkPtr parent = execute_literals(execute); CScriptVarLinkWorkPtr objClass = execute_member(parent, execute).getter(execute); if (execute) { if(objClass->getVarPtr()->isFunction()) { CScriptVarPtr obj(newScriptVar(Object)); CScriptVarLinkPtr prototype = objClass->getVarPtr()->findChild(TINYJS_PROTOTYPE_CLASS); if(!prototype || prototype->getVarPtr()->isUndefined() || prototype->getVarPtr()->isNull()) { prototype = objClass->getVarPtr()->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE); obj->addChildOrReplace(TINYJS___PROTO___VAR, prototype, SCRIPTVARLINK_WRITABLE); } CScriptVarLinkPtr constructor = objClass->getVarPtr()->findChild("__constructor__"); if(constructor && constructor->getVarPtr()->isFunction()) objClass = constructor; vector arguments; if (t->tk == '(') { t->match('('); while(t->tk!=')') { CScriptVarPtr value = execute_assignment(execute).getter(execute); if (execute) arguments.push_back(value); if (t->tk!=')') t->match(',', ')'); } t->match(')'); } if(execute) { CScriptVarPtr returnVar = callFunction(execute, objClass->getVarPtr(), arguments, obj, &obj); if(returnVar->isObject()) return CScriptVarLinkWorkPtr(returnVar); return CScriptVarLinkWorkPtr(obj); } } else throwError(execute, TypeError, objClass->getName() + " is not a constructor"); } else if (t->tk == '(') t->skip(t->getToken().Int()); } else t->skip(t->getToken().Int()); break; case LEX_R_TRUE: t->match(LEX_R_TRUE); return constScriptVar(true); case LEX_R_FALSE: t->match(LEX_R_FALSE); return constScriptVar(false); case LEX_R_NULL: t->match(LEX_R_NULL); return constScriptVar(Null); case '(': if(execute) { t->match('('); CScriptVarLinkWorkPtr a = execute_base(execute).getter(execute); t->match(')'); return a; } else t->skip(t->getToken().Int()); break; case LEX_T_EXCEPTION_VAR: t->match(LEX_T_EXCEPTION_VAR); if(execute.value) return execute.value; break; default: t->match(LEX_EOF); break; } return constScriptVar(Undefined); } CScriptVarLinkWorkPtr CTinyJS::execute_member(CScriptVarLinkWorkPtr &parent, CScriptResult &execute) { CScriptVarLinkWorkPtr a; parent.swap(a); if(t->tk == '.' || t->tk == '[') { while(t->tk == '.' || t->tk == '[') { parent.swap(a); a = parent.getter(execute); // a is now the "getted" var if(execute && (a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull())) { throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute)); } string name; if(t->tk == '.') { t->match('.'); name = t->tkStr(); t->match(LEX_ID); } else { if(execute) { t->match('['); name = execute_expression(execute)->toString(execute); t->match(']'); } else t->skip(t->getToken().Int()); } if (execute) { CScriptVarPtr aVar = a; a = aVar->findChildWithPrototypeChain(name); if(!a) { a(constScriptVar(Undefined), name); a.setReferencedOwner(aVar); } } } } return a; } CScriptVarLinkWorkPtr CTinyJS::execute_function_call(CScriptResult &execute) { CScriptVarLinkWorkPtr parent = execute_literals(execute); CScriptVarLinkWorkPtr a = execute_member(parent, execute); while (t->tk == '(') { if (execute) { if(a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull()) throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute)); CScriptVarLinkWorkPtr fnc = a.getter(execute); if (!fnc->getVarPtr()->isFunction()) throwError(execute, TypeError, a->getName() + " is not a function"); t->match('('); // path += '('; // grab in all parameters vector arguments; while(t->tk!=')') { CScriptVarLinkWorkPtr value = execute_assignment(execute).getter(execute); // path += (*value)->getString(); if (execute) { arguments.push_back(value); } if (t->tk!=')') { t->match(','); /*path+=',';*/ } } t->match(')'); //path+=')'; // setup a return variable CScriptVarLinkWorkPtr returnVar; if(execute) { if (!parent) parent = findInScopes("this"); // if no parent use the root-scope CScriptVarPtr This(parent ? parent->getVarPtr() : (CScriptVarPtr )root); a = callFunction(execute, a->getVarPtr(), arguments, This); } } else { // function, but not executing - just parse args and be done t->match('('); while (t->tk != ')') { CScriptVarLinkWorkPtr value = execute_base(execute); // if (t->tk!=')') t->match(','); } t->match(')'); } a = execute_member(parent = a, execute); } return a; } // R->L: Precedence 3 (in-/decrement) ++ -- // R<-L: Precedence 4 (unary) ! ~ + - typeof void delete bool CTinyJS::execute_unary_rhs(CScriptResult &execute, CScriptVarLinkWorkPtr& a) { t->match(t->tk); a = execute_unary(execute).getter(execute); if(execute) CheckRightHandVar(execute, a); return execute; }; CScriptVarLinkWorkPtr CTinyJS::execute_unary(CScriptResult &execute) { CScriptVarLinkWorkPtr a; switch(t->tk) { case '-': if(execute_unary_rhs(execute, a)) a(newScriptVar(-a->getVarPtr()->toNumber(execute))); break; case '+': if(execute_unary_rhs(execute, a)) a = newScriptVar(a->getVarPtr()->toNumber(execute)); break; case '!': if(execute_unary_rhs(execute, a)) a = constScriptVar(!a->getVarPtr()->toBoolean()); break; case '~': if(execute_unary_rhs(execute, a)) a = newScriptVar(~a->getVarPtr()->toNumber(execute)); break; case LEX_R_TYPEOF: if(execute_unary_rhs(execute, a)) a = newScriptVar(a->getVarPtr()->getVarType()); break; case LEX_R_VOID: if(execute_unary_rhs(execute, a)) a = constScriptVar(Undefined); break; case LEX_R_DELETE: t->match(LEX_R_DELETE); // delete a = execute_unary(execute); // no getter - delete can remove the accessor if (execute) { // !!! no right-hand-check by delete if(a->isOwned() && a->isConfigurable()) { a->getOwner()->removeLink(a); // removes the link from owner a = constScriptVar(true); } else a = constScriptVar(false); } break; case LEX_PLUSPLUS: case LEX_MINUSMINUS: { int op = t->tk; t->match(op); // pre increment/decrement CScriptTokenizer::ScriptTokenPosition ErrorPos = t->getPos(); a = execute_function_call(execute); if (execute) { if(a->getName().empty()) throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", ErrorPos); else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty()) throwError(execute, ReferenceError, a->getName() + " is not defined", ErrorPos); CScriptVarPtr res = newScriptVar(a.getter(execute)->getVarPtr()->toNumber(execute).add(op==LEX_PLUSPLUS ? 1 : -1)); if(a->isWritable()) { if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible()) a.getReferencedOwner()->addChildOrReplace(a->getName(), res); else a.setter(execute, res); } a = res; } } break; default: a = execute_function_call(execute); break; } // post increment/decrement if (t->tk==LEX_PLUSPLUS || t->tk==LEX_MINUSMINUS) { int op = t->tk; t->match(op); if (execute) { if(a->getName().empty()) throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", t->getPrevPos()); else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty()) throwError(execute, ReferenceError, a->getName() + " is not defined", t->getPrevPos()); CNumber num = a.getter(execute)->getVarPtr()->toNumber(execute); CScriptVarPtr res = newScriptVar(num.add(op==LEX_PLUSPLUS ? 1 : -1)); if(a->isWritable()) { if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible()) a.getReferencedOwner()->addChildOrReplace(a->getName(), res); else a.setter(execute, res); } a = newScriptVar(num); } } return a; } // L->R: Precedence 5 (term) * / % CScriptVarLinkWorkPtr CTinyJS::execute_term(CScriptResult &execute) { CScriptVarLinkWorkPtr a = execute_unary(execute); if (t->tk=='*' || t->tk=='/' || t->tk=='%') { CheckRightHandVar(execute, a); while (t->tk=='*' || t->tk=='/' || t->tk=='%') { int op = t->tk; t->match(t->tk); CScriptVarLinkWorkPtr b = execute_unary(execute); // L->R if (execute) { CheckRightHandVar(execute, b); a = mathsOp(execute, a.getter(execute), b.getter(execute), op); } } } return a; } // L->R: Precedence 6 (addition/subtraction) + - CScriptVarLinkWorkPtr CTinyJS::execute_expression(CScriptResult &execute) { CScriptVarLinkWorkPtr a = execute_term(execute); if (t->tk=='+' || t->tk=='-') { CheckRightHandVar(execute, a); while (t->tk=='+' || t->tk=='-') { int op = t->tk; t->match(t->tk); CScriptVarLinkWorkPtr b = execute_term(execute); // L->R if (execute) { CheckRightHandVar(execute, b); a = mathsOp(execute, a.getter(execute), b.getter(execute), op); } } } return a; } // L->R: Precedence 7 (bitwise shift) << >> >>> CScriptVarLinkWorkPtr CTinyJS::execute_binary_shift(CScriptResult &execute) { CScriptVarLinkWorkPtr a = execute_expression(execute); if (t->tk==LEX_LSHIFT || t->tk==LEX_RSHIFT || t->tk==LEX_RSHIFTU) { CheckRightHandVar(execute, a); while (t->tk>=LEX_SHIFTS_BEGIN && t->tk<=LEX_SHIFTS_END) { int op = t->tk; t->match(t->tk); CScriptVarLinkWorkPtr b = execute_expression(execute); // L->R if (execute) { CheckRightHandVar(execute, a); // not in-place, so just replace a = mathsOp(execute, a.getter(execute), b.getter(execute), op); } } } return a; } // L->R: Precedence 8 (relational) < <= > <= in instanceof // L->R: Precedence 9 (equality) == != === !=== CScriptVarLinkWorkPtr CTinyJS::execute_relation(CScriptResult &execute, int set, int set_n) { CScriptVarLinkWorkPtr a = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute); if ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END) || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) { CheckRightHandVar(execute, a); a = a.getter(execute); while ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END) || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) { int op = t->tk; t->match(t->tk); CScriptVarLinkWorkPtr b = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute); // L->R if (execute) { CheckRightHandVar(execute, b); string nameOf_b = b->getName(); b = b.getter(execute); if(op == LEX_R_IN) { if(!b->getVarPtr()->isObject()) throwError(execute, TypeError, "invalid 'in' operand "+nameOf_b); a(constScriptVar( (bool)b->getVarPtr()->findChildWithPrototypeChain(a->toString(execute)))); } else if(op == LEX_R_INSTANCEOF) { CScriptVarLinkPtr prototype = b->getVarPtr()->findChild(TINYJS_PROTOTYPE_CLASS); if(!prototype) throwError(execute, TypeError, "invalid 'instanceof' operand "+nameOf_b); else { unsigned int uniqueID = getUniqueID(); CScriptVarPtr object = a->getVarPtr()->findChild(TINYJS___PROTO___VAR); while( object && object!=prototype->getVarPtr() && object->getTempraryID() != uniqueID) { object->setTemporaryID(uniqueID); // prevents recursions object = object->findChild(TINYJS___PROTO___VAR); } a(constScriptVar(object && object==prototype->getVarPtr())); } } else a = mathsOp(execute, a, b, op); } } } return a; } // L->R: Precedence 10 (bitwise-and) & // L->R: Precedence 11 (bitwise-xor) ^ // L->R: Precedence 12 (bitwise-or) | CScriptVarLinkWorkPtr CTinyJS::execute_binary_logic(CScriptResult &execute, int op, int op_n1, int op_n2) { CScriptVarLinkWorkPtr a = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute); if (t->tk==op) { CheckRightHandVar(execute, a); a = a.getter(execute); while (t->tk==op) { t->match(t->tk); CScriptVarLinkWorkPtr b = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute); // L->R if (execute) { CheckRightHandVar(execute, b); a = mathsOp(execute, a, b.getter(execute), op); } } } return a; } // L->R: Precedence 13 ==> (logical-and) && // L->R: Precedence 14 ==> (logical-or) || CScriptVarLinkWorkPtr CTinyJS::execute_logic(CScriptResult &execute, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) { CScriptVarLinkWorkPtr a = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute); if (t->tk==op) { if(execute) { CScriptVarLinkWorkPtr b; CheckRightHandVar(execute, a); a(a.getter(execute)); // rebuild a do { if(execute && (op==LEX_ANDAND ? a->toBoolean() : !a->toBoolean())) { t->match(t->tk); b = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute); CheckRightHandVar(execute, b); a(b.getter(execute)); // rebuild a } else t->skip(t->getToken().Int()); } while(t->tk==op); } else t->skip(t->getToken().Int()); } return a; } // L<-R: Precedence 15 (condition) ?: CScriptVarLinkWorkPtr CTinyJS::execute_condition(CScriptResult &execute) { CScriptVarLinkWorkPtr a = execute_logic(execute); if (t->tk=='?') { CheckRightHandVar(execute, a); bool cond = execute && a.getter(execute)->toBoolean(); if(execute) { if(cond) { t->match('?'); a = execute_condition(execute); t->check(':'); t->skip(t->getToken().Int()); } else { CScriptVarLinkWorkPtr b; t->skip(t->getToken().Int()); t->match(':'); return execute_condition(execute); } } else { t->skip(t->getToken().Int()); t->skip(t->getToken().Int()); } } return a; } // L<-R: Precedence 16 (assignment) = += -= *= /= %= <<= >>= >>>= &= |= ^= // now we can return CScriptVarLinkPtr execute_assignment returns always no setters/getters // force life of the Owner is no more needed CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptResult &execute) { return execute_assignment(execute_condition(execute), execute); } CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptVarLinkWorkPtr lhs, CScriptResult &execute) { if (t->tk=='=' || (t->tk>=LEX_ASSIGNMENTS_BEGIN && t->tk<=LEX_ASSIGNMENTS_END) ) { int op = t->tk; CScriptTokenizer::ScriptTokenPosition leftHandPos = t->getPos(); t->match(t->tk); CScriptVarLinkWorkPtr rhs = execute_assignment(execute).getter(execute); // L<-R if (execute) { if (!lhs->isOwned() && !lhs.hasReferencedOwner() && lhs->getName().empty()) { throw new CScriptException(ReferenceError, "invalid assignment left-hand side (at runtime)", t->currentFile, leftHandPos.currentLine(), leftHandPos.currentColumn()); } else if (op != '=' && !lhs->isOwned()) { throwError(execute, ReferenceError, lhs->getName() + " is not defined"); } else if(lhs->isWritable()) { if (op=='=') { if (!lhs->isOwned()) { CScriptVarPtr fakedOwner = lhs.getReferencedOwner(); if(fakedOwner) { if(!fakedOwner->isExtensible()) return rhs->getVarPtr(); lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs); } else lhs = root->addChildOrReplace(lhs->getName(), lhs); } lhs.setter(execute, rhs); return rhs->getVarPtr(); } else { CScriptVarPtr result; static int assignments[] = {'+', '-', '*', '/', '%', LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU, '&', '|', '^'}; result = mathsOp(execute, lhs, rhs, assignments[op-LEX_PLUSEQUAL]); lhs.setter(execute, result); return result; } } else { // lhs is not writable we ignore lhs & use rhs return rhs->getVarPtr(); } } } else CheckRightHandVar(execute, lhs); return lhs.getter(execute); } // L->R: Precedence 17 (comma) , CScriptVarLinkPtr CTinyJS::execute_base(CScriptResult &execute) { CScriptVarLinkPtr a; for(;;) { a = execute_assignment(execute); // L->R if (t->tk == ',') { t->match(','); } else break; } return a; } void CTinyJS::execute_block(CScriptResult &execute) { if(execute) { t->match('{'); CScopeControl ScopeControl(this); if(t->tk==LEX_T_FORWARD) // add a LetScope only if needed ScopeControl.addLetScope(); while (t->tk && t->tk!='}') execute_statement(execute); t->match('}'); // scopes.pop_back(); } else t->skip(t->getToken().Int()); } void CTinyJS::execute_statement(CScriptResult &execute) { switch(t->tk) { case '{': /* A block of code */ execute_block(execute); break; case ';': /* Empty statement - to allow things like ;;; */ t->match(';'); break; case LEX_T_FORWARD: { CScriptVarPtr in_scope = scope()->scopeLet(); STRING_SET_t *varNames = t->getToken().Forwarder().varNames; for(int i=0; ifindChild(*it); if(!a) in_scope->addChild(*it, constScriptVar(Undefined), i==CScriptTokenDataForwards::CONSTS ? SCRIPTVARLINK_CONSTDEFAULT : SCRIPTVARLINK_VARDEFAULT); } in_scope = scope()->scopeVar(); } CScriptTokenDataForwards::FNC_SET_t &functions = t->getToken().Forwarder().functions; for(CScriptTokenDataForwards::FNC_SET_it it=functions.begin(); it!=functions.end(); ++it) { CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(*it); in_scope->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT); } t->match(LEX_T_FORWARD); } break; case LEX_R_VAR: case LEX_R_LET: case LEX_R_CONST: if(execute) { CScopeControl ScopeControl(this); bool isLet = t->tk==LEX_R_LET, let_ext=false; t->match(t->tk); if(isLet && t->tk=='(') { let_ext = true; t->match('('); t->check(LEX_T_FORWARD); ScopeControl.addLetScope(); execute_statement(execute); // forwarder } execute_var_init(let_ext, execute); if(let_ext) { t->match(')'); execute_statement(execute); } else t->match(';'); } else t->skip(t->getToken().Int()); break; case LEX_R_WITH: if(execute) { t->match(LEX_R_WITH); t->match('('); CScriptVarLinkPtr var = execute_base(execute); t->match(')'); CScopeControl ScopeControl(this); ScopeControl.addWithScope(var); execute_statement(execute); } else t->skip(t->getToken().Int()); break; case LEX_R_IF: if(execute) { t->match(LEX_R_IF); t->match('('); bool cond = execute_base(execute)->toBoolean(); t->match(')'); if(cond && execute) { t->match(LEX_T_SKIP); execute_statement(execute); } else { t->check(LEX_T_SKIP); t->skip(t->getToken().Int()); } if (t->tk==LEX_R_ELSE) { if(!cond && execute) { t->match(LEX_R_ELSE); execute_statement(execute); } else t->skip(t->getToken().Int()); } } else t->skip(t->getToken().Int()); break; case LEX_T_FOR_IN: { CScriptTokenDataLoop &LoopData = t->getToken().Loop(); t->match(LEX_T_FOR_IN); if(!execute) break; CScopeControl ScopeControl(this); if(LoopData.init.size()) { t->pushTokenScope(LoopData.init); ScopeControl.addLetScope(); execute_statement(execute); // forwarder } if(!execute) break; t->pushTokenScope(LoopData.iter); CScriptVarPtr for_in_var = execute_base(execute); if(!execute) break; CScriptVarPtr Iterator(for_in_var->toIterator(execute, LoopData.type!=CScriptTokenDataLoop::FOR_IN ? 2:1)); CScriptVarFunctionPtr Iterator_next(Iterator->findChildWithPrototypeChain("next").getter(execute)); if(execute && !Iterator_next) throwError(execute, TypeError, "'" + for_in_var->toString(execute) + "' is not iterable", t->getPrevPos()); if(!execute) break; CScriptResult tmp_execute; for(;;) { bool old_haveTry = haveTry; haveTry = true; tmp_execute.set(CScriptResult::Normal, Iterator); t->pushTokenScope(LoopData.condition); execute_statement(tmp_execute); haveTry = old_haveTry; if(tmp_execute.isThrow()){ if(tmp_execute.value != constStopIteration) { if(!haveTry) throw new CScriptException("uncaught exception: ", t->currentFile, t->currentLine(), t->currentColumn()); else execute = tmp_execute; } break; } t->pushTokenScope(LoopData.body); execute_statement(execute); if(!execute) { bool Continue = false; if(execute.isBreakContinue() && (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) { Continue = execute.isContinue(); execute.set(CScriptResult::Normal, false); } if(!Continue) break; } } } break; case LEX_T_LOOP: { CScriptTokenDataLoop &LoopData = t->getToken().Loop(); t->match(LEX_T_LOOP); if(!execute) break; CScopeControl ScopeControl(this); if(LoopData.type == CScriptTokenDataLoop::FOR) { CScriptResult tmp_execute; t->pushTokenScope(LoopData.init); if(t->tk == LEX_T_FORWARD) { ScopeControl.addLetScope(); execute_statement(tmp_execute); // forwarder } if(t->tk==LEX_R_VAR || t->tk==LEX_R_LET) execute_statement(tmp_execute); // initialisation else execute_base(tmp_execute); // initialisation if(!execute(tmp_execute)) break; } bool loopCond = true; // Empty Condition -->always true if(LoopData.type != CScriptTokenDataLoop::DO && LoopData.condition.size()) { t->pushTokenScope(LoopData.condition); loopCond = execute_base(execute)->toBoolean(); if(!execute) break; } while (loopCond && execute) { t->pushTokenScope(LoopData.body); execute_statement(execute); if(!execute) { bool Continue = false; if(execute.isBreakContinue() && (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) { Continue = execute.isContinue(); execute.set(CScriptResult::Normal, false); } if(!Continue) break; } if(LoopData.type == CScriptTokenDataLoop::FOR && execute && LoopData.iter.size()) { t->pushTokenScope(LoopData.iter); execute_base(execute); } if(execute && LoopData.condition.size()) { t->pushTokenScope(LoopData.condition); loopCond = execute_base(execute)->toBoolean(); } } } break; case LEX_R_BREAK: case LEX_R_CONTINUE: if (execute) { CScriptResult::TYPE type = t->tk==LEX_R_BREAK ? CScriptResult::Break : CScriptResult::Continue; string label; t->match(t->tk); if(t->tk == LEX_ID) { label = t->tkStr(); t->match(LEX_ID); } t->match(';'); execute.set(type, label); } else t->skip(t->getToken().Int()); break; case LEX_R_RETURN: if (execute) { t->match(LEX_R_RETURN); CScriptVarPtr result; if (t->tk != ';') result = execute_base(execute); t->match(';'); execute.set(CScriptResult::Return, result); } else t->skip(t->getToken().Int()); break; case LEX_R_FUNCTION: if(execute) { CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(t->getToken()); scope()->scopeVar()->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT); } case LEX_T_FUNCTION_PLACEHOLDER: t->match(t->tk); break; case LEX_T_TRY: if(execute) { CScriptTokenDataTry &TryData = t->getToken().Try(); bool old_haveTry = haveTry; haveTry = true; // execute try-block t->pushTokenScope(TryData.tryBlock); execute_block(execute); bool isThrow = execute.isThrow(); if(isThrow) { // execute catch-blocks for(CScriptTokenDataTry::CatchBlock_it catchBlock = TryData.catchBlocks.begin(); catchBlock!=TryData.catchBlocks.end(); catchBlock++) { CScriptResult catch_execute; CScopeControl ScopeControl(this); ScopeControl.addLetScope(); t->pushTokenScope(catchBlock->condition); // condition; execute_statement(catch_execute); // forwarder assign_destructuring_var(0, *catchBlock->indentifiers, execute.value, catch_execute); bool condition = true; if(catchBlock->condition.size()>1) condition = execute_base(catch_execute)->toBoolean(); if(!catch_execute) { execute = catch_execute; break; } else if(condition) { t->pushTokenScope(catchBlock->block); // condition; execute_block(catch_execute); execute = catch_execute; break; } } } if(TryData.finallyBlock.size()) { CScriptResult finally_execute; // alway execute finally-block t->pushTokenScope(TryData.finallyBlock); // finally; execute_block(finally_execute); execute(finally_execute); } // restore haveTry haveTry = old_haveTry; if(execute.isThrow() && !haveTry) { // (exception in catch or finally or no catch-clause found) and no parent try-block if(execute.value->isError()) throw CScriptVarErrorPtr(execute.value)->toCScriptException(); throw new CScriptException("uncaught exception: '"+execute.value->toString()+"'", t->currentFile, t->currentLine(), t->currentColumn()); } } t->match(LEX_T_TRY); break; case LEX_R_THROW: if(execute) { CScriptTokenizer::ScriptTokenPosition tokenPos = t->getPos(); // int tokenStart = t->getToken().pos; t->match(LEX_R_THROW); CScriptVarPtr a = execute_base(execute); if(execute) { if(haveTry) execute.set(CScriptResult::Throw, a); else throw new CScriptException("uncaught exception: '"+a->toString(execute)+"'", t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn()); } } else t->skip(t->getToken().Int()); break; case LEX_R_SWITCH: if(execute) { t->match(LEX_R_SWITCH); t->match('('); CScriptVarPtr SwitchValue = execute_base(execute); t->match(')'); if(execute) { t->match('{'); CScopeControl ScopeControl(this); if(t->tk == LEX_T_FORWARD) { ScopeControl.addLetScope(); // add let-scope only if needed execute_statement(execute); // execute forwarder } CScriptTokenizer::ScriptTokenPosition defaultStart = t->getPos(); bool hasDefault = false, found = false; while (t->tk) { switch(t->tk) { case LEX_R_CASE: if(!execute) t->skip(t->getToken().Int()); // skip up to'}' else if(found) { // execute && found t->match(LEX_R_CASE); t->skip(t->getToken().Int()); // skip up to ':' t->match(':'); // skip ':' and execute all after ':' } else { // execute && !found t->match(LEX_R_CASE); t->match(LEX_T_SKIP); // skip 'L_T_SKIP' CScriptVarLinkPtr CaseValue = execute_base(execute); CaseValue = mathsOp(execute, CaseValue, SwitchValue, LEX_TYPEEQUAL); if(execute) { found = CaseValue->toBoolean(); if(found) t->match(':'); // skip ':' and execute all after ':' else t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}' } else t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}' } break; case LEX_R_DEFAULT: if(!execute) t->skip(t->getToken().Int()); // skip up to'}' NOTE: no extra 'L_T_SKIP' for skipping tp ':' else { t->match(LEX_R_DEFAULT); if(found) t->match(':'); // skip ':' and execute all after ':' else { hasDefault = true; // in fist pass: skip default-area defaultStart = t->getPos(); // remember pos of default t->skip(t->getToken().Int()); // skip up to next 'case' or '}' } } break; case '}': if(execute && !found && hasDefault) { // if not found & have default -> execute default found = true; t->setPos(defaultStart); t->match(':'); } else goto end_while; // goto isn't fine but C supports no "break lable;" break; default: ASSERT(found); execute_statement(execute); break; } } end_while: t->match('}'); if(execute.isBreak() && execute.target.empty()) { execute.set(CScriptResult::Normal); } } else t->skip(t->getToken().Int()); } else t->skip(t->getToken().Int()); break; case LEX_T_DUMMY_LABEL: t->match(LEX_T_DUMMY_LABEL); t->match(':'); break; case LEX_T_LABEL: { STRING_VECTOR_t Labels; while(t->tk == LEX_T_LABEL) { Labels.push_back(t->tkStr()); t->match(LEX_T_LABEL); t->match(':'); } if(execute) { execute_statement(execute); if(execute.isBreak() && find(Labels.begin(), Labels.end(), execute.target) != Labels.end()) { // break this label execute.set(CScriptResult::Normal, false); } } else execute_statement(execute); } break; case LEX_EOF: t->match(LEX_EOF); break; default: if(execute) { t->match(LEX_T_SKIP); /* Execute a simple statement that only contains basic arithmetic... */ CScriptVarPtr ret = execute_base(execute); if(execute) execute.set(CScriptResult::Normal, CScriptVarPtr(ret)); t->match(';'); } else t->skip(t->getToken().Int()); break; } } /// Finds a child, looking recursively up the scopes CScriptVarLinkPtr CTinyJS::findInScopes(const string &childName) { return scope()->findInScopes(childName); } ////////////////////////////////////////////////////////////////////////// /// Object ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Object(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(c->getArgument(0)->toObject()); } void CTinyJS::native_Object_getPrototypeOf(const CFunctionsScopePtr &c, void *data) { if(c->getArgumentsLength()>=1) { CScriptVarPtr obj = c->getArgument(0); if(obj->isObject()) { c->setReturnVar(obj->findChild(TINYJS___PROTO___VAR)); return; } } c->throwError(TypeError, "argument is not an object"); } void CTinyJS::native_Object_setObjectSecure(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr obj = c->getArgument(0); if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); if(data==(void*)2) obj->freeze(); else if(data==(void*)1) obj->seal(); else obj->preventExtensions(); c->setReturnVar(obj); } void CTinyJS::native_Object_isSecureObject(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr obj = c->getArgument(0); if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); bool ret; if(data==(void*)2) ret = obj->isFrozen(); else if(data==(void*)1) ret = obj->isSealed(); else ret = obj->isExtensible(); c->setReturnVar(constScriptVar(ret)); } void CTinyJS::native_Object_keys(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr obj = c->getArgument(0); if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); CScriptVarPtr returnVar = c->newScriptVar(Array); c->setReturnVar(returnVar); STRING_SET_t keys; obj->keys(keys, data==0); uint32_t idx=0; for(STRING_SET_it it=keys.begin(); it!=keys.end(); ++it) returnVar->setArrayIndex(idx++, newScriptVar(*it)); } void CTinyJS::native_Object_getOwnPropertyDescriptor(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr obj = c->getArgument(0); if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); c->setReturnVar(obj->getOwnPropertyDescriptor(c->getArgument(1)->toString())); } void CTinyJS::native_Object_defineProperty(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr obj = c->getArgument(0); if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); string name = c->getArgument(1)->toString(); CScriptVarPtr attributes = c->getArgument(2); if(!attributes->isObject()) c->throwError(TypeError, "attributes is not an object"); const char *err = obj->defineProperty(name, attributes); if(err) c->throwError(TypeError, err); c->setReturnVar(obj); } void CTinyJS::native_Object_defineProperties(const CFunctionsScopePtr &c, void *data) { bool ObjectCreate = data!=0; CScriptVarPtr obj = c->getArgument(0); if(ObjectCreate) { if(!obj->isObject() && !obj->isNull()) c->throwError(TypeError, "argument is not an object or null"); obj = newScriptVar(Object, obj); } else if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); c->setReturnVar(obj); if(c->getArrayLength()<2) { if(ObjectCreate) return; c->throwError(TypeError, "Object.defineProperties requires 2 arguments"); } CScriptVarPtr properties = c->getArgument(1); STRING_SET_t names; properties->keys(names, true); for(STRING_SET_it it=names.begin(); it!=names.end(); ++it) { CScriptVarPtr attributes = properties->findChildWithStringChars(*it).getter(); if(!attributes->isObject()) c->throwError(TypeError, "descriptor for "+*it+" is not an object"); const char *err = obj->defineProperty(*it, attributes); if(err) c->throwError(TypeError, err); } } ////////////////////////////////////////////////////////////////////////// /// Object.prototype ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Object_prototype_hasOwnProperty(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr This = c->getArgument("this"); string PropStr = c->getArgument("prop")->toString(); CScriptVarLinkPtr Prop = This->findChild(PropStr); bool res = Prop && !Prop->getVarPtr()->isUndefined(); if(!res) { CScriptVarStringPtr This_asString = This->getRawPrimitive(); if(This_asString) { uint32_t Idx = isArrayIndex(PropStr); res = Idx!=uint32_t(-1) && IdxstringLength(); } } c->setReturnVar(c->constScriptVar(res)); } void CTinyJS::native_Object_prototype_valueOf(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(c->getArgument("this")->valueOf_CallBack()); } void CTinyJS::native_Object_prototype_toString(const CFunctionsScopePtr &c, void *data) { CScriptResult execute; int radix = 10; if(c->getArgumentsLength()>=1) radix = c->getArgument("radix")->toNumber().toInt32(); c->setReturnVar(c->getArgument("this")->toString_CallBack(execute, radix)); if(!execute) { // TODO } } ////////////////////////////////////////////////////////////////////////// /// Array ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Array(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr returnVar = c->newScriptVar(Array); c->setReturnVar(returnVar); int length = c->getArgumentsLength(); CScriptVarPtr Argument_0_Var = c->getArgument(0); if(data!=0 && length == 1 && Argument_0_Var->isNumber()) { CNumber Argument_0 = Argument_0_Var->toNumber(); uint32_t new_size = Argument_0.toUInt32(); if(Argument_0.isFinite() && Argument_0 == new_size) returnVar->setArrayIndex(new_size-1, constScriptVar(Undefined)); else c->throwError(RangeError, "invalid array length"); } else for(int i=0; isetArrayIndex(i, c->getArgument(i)); } ////////////////////////////////////////////////////////////////////////// /// String ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_String(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr arg; if(c->getArgumentsLength()==0) arg = newScriptVar(""); else arg = newScriptVar(c->getArgument(0)->toString()); if(data) c->setReturnVar(arg->toObject()); else c->setReturnVar(arg); } ////////////////////////////////////////////////////////////////////////// /// RegExp ////////////////////////////////////////////////////////////////////////// #ifndef NO_REGEXP void CTinyJS::native_RegExp(const CFunctionsScopePtr &c, void *data) { int arglen = c->getArgumentsLength(); string RegExp, Flags; if(arglen>=1) { RegExp = c->getArgument(0)->toString(); try { regex(RegExp, regex_constants::ECMAScript); } catch(regex_error e) { c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); } if(arglen>=2) { Flags = c->getArgument(1)->toString(); string::size_type pos = Flags.find_first_not_of("gimy"); if(pos != string::npos) { c->throwError(SyntaxError, string("invalid regular expression flag ")+Flags[pos]); } } } c->setReturnVar(newScriptVar(RegExp, Flags)); } #endif /* NO_REGEXP */ ////////////////////////////////////////////////////////////////////////// /// Number ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Number(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr arg; if(c->getArgumentsLength()==0) arg = newScriptVar(0); else arg = newScriptVar(c->getArgument(0)->toNumber()); if(data) c->setReturnVar(arg->toObject()); else c->setReturnVar(arg); } ////////////////////////////////////////////////////////////////////////// /// Boolean ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Boolean(const CFunctionsScopePtr &c, void *data) { CScriptVarPtr arg; if(c->getArgumentsLength()==0) arg = constScriptVar(false); else arg = constScriptVar(c->getArgument(0)->toBoolean()); if(data) c->setReturnVar(arg->toObject()); else c->setReturnVar(arg); } ////////////////////////////////////////////////////////////////////////// /// Iterator ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Iterator(const CFunctionsScopePtr &c, void *data) { if(c->getArgumentsLength()<1) c->throwError(TypeError, "missing argument 0 when calling function Iterator"); c->setReturnVar(c->getArgument(0)->toIterator(c->getArgument(1)->toBoolean()?1:3)); } ////////////////////////////////////////////////////////////////////////// /// Function ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_Function(const CFunctionsScopePtr &c, void *data) { int length = c->getArgumentsLength(); string params, body; if(length>=1) body = c->getArgument(length-1)->toString(); if(length>=2) { params = c->getArgument(0)->toString(); for(int i=1; igetArgument(i)->toString()); } } c->setReturnVar(parseFunctionsBodyFromString(params,body)); } void CTinyJS::native_Function_prototype_call(const CFunctionsScopePtr &c, void *data) { int length = c->getArgumentsLength(); CScriptVarPtr Fnc = c->getArgument("this"); if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.call called on incompatible Object"); CScriptVarPtr This = c->getArgument(0); vector Args; for(int i=1; igetArgument(i)); c->setReturnVar(callFunction(Fnc, Args, This)); } void CTinyJS::native_Function_prototype_apply(const CFunctionsScopePtr &c, void *data) { int length=0; CScriptVarPtr Fnc = c->getArgument("this"); if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.apply called on incompatible Object"); // Argument_0 CScriptVarPtr This = c->getArgument(0)->toObject(); if(This->isNull() || This->isUndefined()) This=root; // Argument_1 CScriptVarPtr Array = c->getArgument(1); if(!Array->isNull() && !Array->isUndefined()) { CScriptVarLinkWorkPtr Length = Array->findChild("length"); if(!Length) c->throwError(TypeError, "second argument to Function.prototype.apply must be an array or an array like object"); length = Length.getter()->toNumber().toInt32(); } vector Args; for(int i=0; ifindChild(int2string(i)); if(value) Args.push_back(value); else Args.push_back(constScriptVar(Undefined)); } c->setReturnVar(callFunction(Fnc, Args, This)); } void CTinyJS::native_Function_prototype_bind(const CFunctionsScopePtr &c, void *data) { int length = c->getArgumentsLength(); CScriptVarPtr Fnc = c->getArgument("this"); if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.bind called on incompatible Object"); CScriptVarPtr This = c->getArgument(0); if(This->isUndefined() || This->isNull()) This = root; vector Args; for(int i=1; igetArgument(i)); c->setReturnVar(newScriptVarFunctionBounded(Fnc, This, Args)); } ////////////////////////////////////////////////////////////////////////// /// Error ////////////////////////////////////////////////////////////////////////// static CScriptVarPtr _newError(CTinyJS *context, ERROR_TYPES type, const CFunctionsScopePtr &c) { int i = c->getArgumentsLength(); string message, fileName; int line=-1, column=-1; if(i>0) message = c->getArgument(0)->toString(); if(i>1) fileName = c->getArgument(1)->toString(); if(i>2) line = c->getArgument(2)->toNumber().toInt32(); if(i>3) column = c->getArgument(3)->toNumber().toInt32(); return ::newScriptVarError(context, type, message.c_str(), fileName.c_str(), line, column); } void CTinyJS::native_Error(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, Error,c)); } void CTinyJS::native_EvalError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, EvalError,c)); } void CTinyJS::native_RangeError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, RangeError,c)); } void CTinyJS::native_ReferenceError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, ReferenceError,c)); } void CTinyJS::native_SyntaxError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, SyntaxError,c)); } void CTinyJS::native_TypeError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, TypeError,c)); } ////////////////////////////////////////////////////////////////////////// /// global functions ////////////////////////////////////////////////////////////////////////// void CTinyJS::native_eval(const CFunctionsScopePtr &c, void *data) { string Code = c->getArgument("jsCode")->toString(); CScriptVarScopePtr scEvalScope = scopes.back(); // save scope scopes.pop_back(); // go back to the callers scope CScriptResult execute; CScriptTokenizer *oldTokenizer = t; t=0; try { CScriptTokenizer Tokenizer(Code.c_str(), "eval"); t = &Tokenizer; do { execute_statement(execute); while (t->tk==';') t->match(';'); // skip empty statements } while (t->tk!=LEX_EOF); } catch (CScriptException *e) { // script exceptions t = oldTokenizer; // restore tokenizer scopes.push_back(scEvalScope); // restore Scopes; if(haveTry) { // an Error in eval is always catchable CScriptVarPtr E = newScriptVarError(this, e->errorType, e->message.c_str(), e->fileName.c_str(), e->lineNumber, e->column); delete e; throw E; } else throw e; } catch (...) { // all other exceptions t = oldTokenizer; // restore tokenizer scopes.push_back(scEvalScope); // restore Scopes; throw; // re-throw } t = oldTokenizer; // restore tokenizer scopes.push_back(scEvalScope); // restore Scopes; if(execute.value) c->setReturnVar(execute.value); } void CTinyJS::native_isNAN(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isNaN())); } void CTinyJS::native_isFinite(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isFinite())); } void CTinyJS::native_parseInt(const CFunctionsScopePtr &c, void *) { CNumber result; result.parseInt(c->getArgument("string")->toString(), c->getArgument("radix")->toNumber().toInt32()); c->setReturnVar(c->newScriptVar(result)); } void CTinyJS::native_parseFloat(const CFunctionsScopePtr &c, void *) { CNumber result; result.parseFloat(c->getArgument("string")->toString()); c->setReturnVar(c->newScriptVar(result)); } void CTinyJS::native_JSON_parse(const CFunctionsScopePtr &c, void *data) { string Code = "§" + c->getArgument("text")->toString(); // "§" is a spezal-token - it's for the tokenizer and means the code begins not in Statement-level CScriptVarLinkWorkPtr returnVar; CScriptTokenizer *oldTokenizer = t; t=0; try { CScriptTokenizer Tokenizer(Code.c_str(), "JSON.parse", 0, -1); t = &Tokenizer; CScriptResult execute; returnVar = execute_literals(execute); t->match(LEX_EOF); } catch (CScriptException *e) { t = oldTokenizer; throw e; } t = oldTokenizer; if(returnVar) c->setReturnVar(returnVar); } void CTinyJS::setTemporaryID_recursive(uint32_t ID) { for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it) if(**it) (**it)->setTemporaryID_recursive(ID); for(int i=Error; isetTemporaryID_recursive(ID); root->setTemporaryID_recursive(ID); } void CTinyJS::ClearUnreferedVars(const CScriptVarPtr &extra/*=CScriptVarPtr()*/) { uint32_t UniqueID = getUniqueID(); setTemporaryID_recursive(UniqueID); if(extra) extra->setTemporaryID_recursive(UniqueID); CScriptVar *p = first; while(p) { if(p->temporaryID != UniqueID) { CScriptVarPtr var = p; var->removeAllChildren(); p = var->next; } else p = p->next; } }