An implementation of Q floating point format over C++
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

6556 lines
236 KiB

/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* 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 <armin@diedering.de>
*
* Copyright (C) 2010-2014 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 <errno.h>
#include <sstream>
#include <fstream>
#include "TinyJS/TinyJS.h"
#ifndef ASSERT
# define ASSERT(X) assert(X)
#endif
#ifndef NO_REGEXP
# if defined HAVE_TR1_REGEX
# include <tr1/regex>
using namespace std::tr1;
# elif defined HAVE_BOOST_REGEX
# include <boost/regex.hpp>
using namespace boost;
# else
# include <regex>
# endif
#else
# include <algorithm>
# include <cmath>
# include <memory>
#endif
using namespace std;
// -----------------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
/// Memory Debug
//////////////////////////////////////////////////////////////////////////
//#define DEBUG_MEMORY 1
#if DEBUG_MEMORY
vector<CScriptVar*> allocatedVars;
vector<CScriptVarLink*> allocatedLinks;
void mark_allocated(CScriptVar *v) {
allocatedVars.push_back(v);
}
void mark_deallocated(CScriptVar *v) {
for (size_t i=0;i<allocatedVars.size();i++) {
if (allocatedVars[i] == v) {
allocatedVars.erase(allocatedVars.begin()+i);
break;
}
}
}
void mark_allocated(CScriptVarLink *v) {
allocatedLinks.push_back(v);
}
void mark_deallocated(CScriptVarLink *v) {
for (size_t i=0;i<allocatedLinks.size();i++) {
if (allocatedLinks[i] == v) {
allocatedLinks.erase(allocatedLinks.begin()+i);
break;
}
}
}
void show_allocated() {
for (size_t i=0;i<allocatedVars.size();i++) {
printf("ALLOCATED, %d refs\n", allocatedVars[i]->getRefs());
allocatedVars[i]->trace(" ");
}
for (size_t i=0;i<allocatedLinks.size();i++) {
printf("ALLOCATED LINK %s, allocated[%d] to \n", allocatedLinks[i]->getName().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<double>::max_digits10);
#else
str.precision(numeric_limits<double>::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(nextCh == -124) nextCh = '\n'; //
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_PLUSPLUS, LEX_MINUSMINUS, 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);
#ifdef NO_GENERATORS
if(tk == LEX_R_YIELD)
throw new CScriptException(Error, "42TinyJS was built without support of generators (yield expression)", currentFile, pos.currentLine, currentColumn());
#endif
} 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 of 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<bool> 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<bool> 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<ELEMENT>::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<ELEMENT>::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 },
{ LEX_R_YIELD, "yield", 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; i<ARRAY_LENGTH(str2reserved_begin); i++)
str2reserved_begin[i] = &reserved_words_begin[i];
sort(str2reserved_begin, str2reserved_end, token2str_cmp_t());
return true;
}
static bool tokens2str_sorted = tokens2str_sort();
CScriptToken::CScriptToken(CScriptLex *l, int Match, int Alternate) : line(l->currentLine()), 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_canYield = 1<<4,
TOKENIZE_FLAGS_asStatement = 1<<5,
TOKENIZE_FLAGS_noIn = 1<<6,
TOKENIZE_FLAGS_isAccessor = 1<<7,
TOKENIZE_FLAGS_callForNew = 1<<8,
TOKENIZE_FLAGS_noBlockStart = 1<<9,
TOKENIZE_FLAGS_nestedObject = 1<<10,
};
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<int>::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 = (Flags & (TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield)) | 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;
CScriptLex::POS functionPos = l->pos;
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;
functionState.HaveReturnValue = functionState.FunctionIsGenerator = false;
if(l->tk == '{' || tk==LEX_T_GET || tk==LEX_T_SET)
tokenizeBlock(functionState, TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield);
else {
tokenizeExpression(functionState, TOKENIZE_FLAGS_canYield);
l->match(';');
functionState.HaveReturnValue = true;
}
if(functionState.HaveReturnValue == true && functionState.FunctionIsGenerator == true)
throw new CScriptException(TypeError, "generator function returns a value.", l->currentFile, functionPos.currentLine, functionPos.currentColumn());
FncData.isGenerator = functionState.FunctionIsGenerator;
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;
#ifndef NO_GENERATORS
case LEX_R_YIELD:
if( (Flags & TOKENIZE_FLAGS_canYield)==0)
throw new CScriptException(SyntaxError, "'yield' expression, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn());
pushToken(State.Tokens);
if(l->tk != ';' && l->tk != '}' && !l->lineBreakBeforeToken) {
tokenizeExpression(State, Flags);
}
State.FunctionIsGenerator = true;
break;
#endif
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 | TOKENIZE_FLAGS_canLabel);
State.Marks.push_back(pushToken(State.Tokens));
tokenizeAssignment(State, Flags);
setTokenSkip(State);
State.Marks.push_back(pushToken(State.Tokens, ':'));
tokenizeAssignment(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) {
int tk = l->tk;
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->tk != '}' && !l->lineBreakBeforeToken) {
if(tk==LEX_R_RETURN) State.HaveReturnValue = true;
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;
case 0:
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<int> &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<int> &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;
memset(temporaryMark, 0, sizeof(temporaryMark));
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;
memset(temporaryMark, 0, sizeof(temporaryMark));
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;}
bool CScriptVar::isGenerator() {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 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 && FncValueOf != context->objectPrototype_valueOf) { // custom valueOf in JavaScript
if(FncValueOf->isFunction()) { // no Error if toString not callable
vector<CScriptVarPtr> 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 && FncToString != context->objectPrototype_toString) { // custom valueOf in JavaScript
if(FncToString->isFunction()) { // no Error if toString not callable
vector<CScriptVarPtr> 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/*=0*/) { 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<CScriptVarPtr> args;
if(Generator) return context->callFunction(execute, Generator, args, this);
return newScriptVarDefaultIterator(context, this, Mode);
}
string CScriptVar::getParsableString() {
uint32_t UniqueID = context->allocUniqueID();
bool hasRecursion=false;
string ret = getParsableString("", " ", UniqueID, hasRecursion);
context->freeUniqueID();
return ret;
}
string CScriptVar::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) {
getParsableStringRecursionsCheck();
return indentString+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) && Idx<strVar->stringLength()) {
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)) {
if (Idx<strVar->stringLength()) {
int Char = strVar->getChar(Idx);
child(newScriptVar(string(1, (char)Char)), childName, SCRIPTVARLINK_ENUMERABLE);
} else {
child(constScriptVar(Undefined), childName, SCRIPTVARLINK_ENUMERABLE);
}
child.setReferencedOwner(this); // fake referenced Owner
return child;
}
return 0;
}
CScriptVarLinkPtr CScriptVar::findChildInPrototypeChain(const string &childName) {
unsigned int uniqueID = context->allocUniqueID();
// Look for links to actual parent classes
CScriptVarPtr object = this;
CScriptVarLinkPtr __proto__;
while( object->getTemporaryMark() != uniqueID && (__proto__ = object->findChild(TINYJS___PROTO___VAR)) ) {
CScriptVarLinkPtr implementation = __proto__->getVarPtr()->findChild(childName);
if (implementation) {
context->freeUniqueID();
return implementation;
}
object->setTemporaryMark(uniqueID); // prevents recursions
object = __proto__;
}
context->freeUniqueID();
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<string> &Keys, bool OnlyEnumerable/*=true*/, uint32_t ID/*=0*/)
{
if(ID) setTemporaryMark(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; i<length; ++i)
Keys.insert(int2string(i));
}
CScriptVarLinkPtr __proto__;
if( ID && (__proto__ = findChild(TINYJS___PROTO___VAR)) && __proto__->getVarPtr()->getTemporaryMark() != 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->allocUniqueID();
trace(indentStr, uniqueID, name);
context->freeUniqueID();
}
void CScriptVar::trace(string &indentStr, uint32_t uniqueID, const string &name) {
string indent = " ";
const char *extra="";
if(getTemporaryMark() == 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(getTemporaryMark() != uniqueID) {
setTemporaryMark(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::setTemporaryMark_recursive(uint32_t ID)
{
if(getTemporaryMark() != ID) {
setTemporaryMark(ID);
for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) {
(*it)->getVarPtr()->setTemporaryMark_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<CScriptVarPtr> 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<CScriptVarPtr> 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 indentString+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<double>::has_infinity && Value == numeric_limits<double>::infinity())
type=tInfinity, Int32=1;
else if(numeric_limits<double>::has_infinity && Value == -numeric_limits<double>::infinity())
type=tInfinity, Int32=-1;
else if(::isNaN(Value) || Value == numeric_limits<double>::quiet_NaN() || Value == std::numeric_limits<double>::signaling_NaN())
type=tNaN, Int32=0;
else if(modf(Value, &integral)==0.0 && numeric_limits<int32_t>::min()<=integral && integral<=numeric_limits<int32_t>::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<int32_t>::max();
int32_t range_min = numeric_limits<int32_t>::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 || (Int32==numeric_limits<int32_t>::min() && Value == -1) /* use double to prevent integer overflow */ ) {
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);
}
CNumber CNumber::ushift(const CNumber &Value, bool Right) const {
uint32_t lhs = toUInt32();
uint32_t rhs = Value.toUInt32() & 0x1F;
return CNumber(Right ? lhs>>rhs : lhs<<rhs);
}
CNumber CNumber::binary(const CNumber &Value, char Mode) const {
int32_t lhs = toInt32();
int32_t rhs = Value.toInt32();
switch(Mode) {
case '&': lhs&=rhs; break;
case '|': lhs|=rhs; break;
case '^': lhs^=rhs; break;
}
return CNumber(lhs);
}
int CNumber::less( const CNumber &Value ) const {
if(type==tNaN || Value.type==tNaN) return 0;
else if(type==tInfinity) {
if(Value.type==tInfinity) return Int32<Value.Int32 ? 1 : -1;
return -Int32;
} else if(Value.type==tInfinity)
return Value.Int32;
else if(isZero() && Value.isZero()) return -1;
else if(type==tDouble || Value.type==tDouble) return toDouble() < Value.toDouble() ? 1 : -1;
return toInt32() < Value.toInt32() ? 1 : -1;
}
bool CNumber::equal( const CNumber &Value ) const {
if(type==tNaN || Value.type==tNaN) return false;
else if(type==tInfinity) {
if(Value.type==tInfinity) return Int32==Value.Int32;
return false;
} else if(Value.type==tInfinity)
return false;
else if(isZero() && Value.isZero()) return true;
else if(type==tDouble || Value.type==tDouble) return toDouble() == Value.toDouble();
return toInt32() == Value.toInt32();
}
bool CNumber::isZero() const
{
switch(type) {
case tInt32:
return Int32==0;
case tnNULL:
return true;
case tDouble:
return Double==0.0;
default:
return false;
}
}
bool CNumber::isInteger() const
{
double integral;
switch(type) {
case tInt32:
case tnNULL:
return true;
case tDouble:
return modf(Double, &integral)==0.0;
default:
return false;
}
}
int CNumber::sign() const {
switch(type) {
case tInt32:
case tInfinity:
return Int32<0?-1:1;
case tnNULL:
return -1;
case tDouble:
return Double<0.0?-1:1;
default:
return 1;
}
}
char *tiny_ltoa(int32_t val, unsigned radix) {
char *buf, *buf_end, *p, *firstdig, temp;
unsigned digval;
buf = (char*)malloc(64);
if(!buf) return 0;
buf_end = buf+64-1; // -1 for '\0'
p = buf;
if (val < 0) {
*p++ = '-';
val = -val;
}
do {
digval = (unsigned) (val % radix);
val /= radix;
*p++ = (char) (digval + (digval > 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<double>::max_digits10);
#else
str.precision(numeric_limits<double>::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<double>::quiet_NaN();
case tInfinity:
return Int32<0 ? -std::numeric_limits<double>::infinity():std::numeric_limits<double>::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);
CScriptVarObject::CScriptVarObject(CTinyJS *Context) : CScriptVar(Context, Context->objectPrototype) { }
CScriptVarObject::~CScriptVarObject() {}
CScriptVarPtr CScriptVarObject::clone() { return new CScriptVarObject(*this); }
void CScriptVarObject::removeAllChildren()
{
CScriptVar::removeAllChildren();
value.clear();
}
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"; }
string CScriptVarObject::getVarTypeTagName() { 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 "+getVarTypeTagName()+"]");
}
void CScriptVarObject::setTemporaryMark_recursive( uint32_t ID) {
CScriptVar::setTemporaryMark_recursive(ID);
if(value) value->setTemporaryMark_recursive(ID);
}
//////////////////////////////////////////////////////////////////////////
/// CScriptVarObjectTyped (simple Object with Typename
//////////////////////////////////////////////////////////////////////////
declare_dummy_t(StopIteration);
CScriptVarObjectTypeTagged::~CScriptVarObjectTypeTagged() {}
CScriptVarPtr CScriptVarObjectTypeTagged::clone() { return new CScriptVarObjectTypeTagged(*this); }
std::string CScriptVarObjectTypeTagged::getVarTypeTagName() { return typeTagName; }
//////////////////////////////////////////////////////////////////////////
/// 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()-1;
int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32()-1;
return new CScriptException((enum ERROR_TYPES)ErrorCode, message, fileName, lineNumber, column);
}
//////////////////////////////////////////////////////////////////////////
// CScriptVarArray
//////////////////////////////////////////////////////////////////////////
declare_dummy_t(Array);
CScriptVarArray::CScriptVarArray(CTinyJS *Context) : CScriptVarObject(Context, Context->arrayPrototype), toStringRecursion(false) {
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;i<len;i++) {
destination.append(comma); comma = ",";
destination.append(nl).append(new_indentString).append(getArrayIndex(i)->getParsableString(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;
if(toStringRecursion) {
return newScriptVar("");
}
toStringRecursion = true;
try {
int len = getArrayLength();
for (int i=0;i<len;i++) {
destination << getArrayIndex(i)->toString(execute);
if (i<len-1) destination << ", ";
}
} catch(...) {
toStringRecursion = false;
throw; // rethrow
}
toStringRecursion = false;
return newScriptVar(destination.str());
}
void CScriptVarArray::native_Length(const CFunctionsScopePtr &c, void *data) {
c->setReturnVar(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<CScriptVarRegExp>(Context, this, &CScriptVarRegExp::native_Global, 0, 0, 0), 0);
addChild("ignoreCase", ::newScriptVarAccessor<CScriptVarRegExp>(Context, this, &CScriptVarRegExp::native_IgnoreCase, 0, 0, 0), 0);
addChild("multiline", ::newScriptVarAccessor<CScriptVarRegExp>(Context, this, &CScriptVarRegExp::native_Multiline, 0, 0, 0), 0);
addChild("sticky", ::newScriptVarAccessor<CScriptVarRegExp>(Context, this, &CScriptVarRegExp::native_Sticky, 0, 0, 0), 0);
addChild("regexp", ::newScriptVarAccessor<CScriptVarRegExp>(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; idx<match.size(); idx++)
retVar->addChild(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);
}
#ifndef NO_GENERATORS
//////////////////////////////////////////////////////////////////////////
/// CScriptVarGenerator
//////////////////////////////////////////////////////////////////////////
//declare_dummy_t(Generator);
CScriptVarGenerator::CScriptVarGenerator(CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function)
: CScriptVarObject(Context, Context->generatorPrototype), functionRoot(FunctionRoot), function(Function),
closed(false), yieldVarIsException(false), coroutine(this) {
// addChild("next", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, 0, "Generator.next"));
// addChild("send", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, (void*)1, "Generator.send"));
//addChild("close", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)0, "Generator.close"));
//addChild("throw", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)1, "Generator.throw"));
}
CScriptVarGenerator::~CScriptVarGenerator() {
if(coroutine.isStarted() && coroutine.isRunning()) {
coroutine.Stop(false);
coroutine.next();
coroutine.Stop();
}
}
CScriptVarPtr CScriptVarGenerator::clone() { return new CScriptVarGenerator(*this); }
bool CScriptVarGenerator::isIterator() {return true;}
bool CScriptVarGenerator::isGenerator() {return true;}
string CScriptVarGenerator::getVarType() { return "generator"; }
string CScriptVarGenerator::getVarTypeTagName() { return "Generator"; }
void CScriptVarGenerator::setTemporaryMark_recursive( uint32_t ID ) {
CScriptVarObject::setTemporaryMark_recursive(ID);
functionRoot->setTemporaryMark_recursive(ID);
function->setTemporaryMark_recursive(ID);
if(yieldVar) yieldVar->setTemporaryMark_recursive(ID);
for(std::vector<CScriptVarScopePtr>::iterator it=generatorScopes.begin(); it != generatorScopes.end(); ++it)
(*it)->setTemporaryMark_recursive(ID);
}
void CScriptVarGenerator::native_send(const CFunctionsScopePtr &c, void *data) {
// data == 0 ==> next()
// data != 0 ==> send(...)
if(closed)
throw constScriptVar(StopIteration);
yieldVar = data ? c->getArgument(0) : constScriptVar(Undefined);
yieldVarIsException = false;
if(!coroutine.isStarted() && data && !yieldVar->isUndefined())
c->throwError(TypeError, "attempt to send value to newborn generator");
if(coroutine.next()) {
c->setReturnVar(yieldVar);
return;
}
closed = true;
throw yieldVar;
}
void CScriptVarGenerator::native_throw(const CFunctionsScopePtr &c, void *data) {
// data == 0 ==> close()
// data != 0 ==> throw(...)
if(closed || !coroutine.isStarted()) {
closed = true;
if(data)
throw c->getArgument(0);
else
return;
}
yieldVar = data ? c->getArgument(0) : CScriptVarPtr();
yieldVarIsException = true;
closed = data==0;
if(coroutine.next()) {
c->setReturnVar(yieldVar);
return;
}
closed = true;
/*
* from http://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
* Generators have a close() method that forces the generator to close itself. The effects of closing a generator are:
* - Any finally clauses active in the generator function are run.
* - If a finally clause throws any exception other than StopIteration, the exception is propagated to the caller of the close() method.
* - The generator terminates.
*
* but in Firefox is also "StopIteration" propagated to the caller
* define GENERATOR_CLOSE_LIKE_IN_FIREFOX to enable this behavior
*/
//#define GENERATOR_CLOSE_LIKE_IN_FIREFOX
#ifdef GENERATOR_CLOSE_LIKE_IN_FIREFOX
if(data || yieldVar)
throw yieldVar;
#else
if(data || (yieldVar && yieldVar != constScriptVar(StopIteration)))
throw yieldVar;
#endif
}
int CScriptVarGenerator::Coroutine()
{
context->generator_start(this);
return 0;
}
CScriptVarPtr CScriptVarGenerator::yield( CScriptResult &execute, CScriptVar *YieldIn )
{
yieldVar = YieldIn;
coroutine.yield();
if(yieldVarIsException) {
execute.set(CScriptResult::Throw, yieldVar);
return constScriptVar(Undefined);
}
return yieldVar;
}
#endif /*NO_GENERATORS*/
//////////////////////////////////////////////////////////////////////////
// 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<CScriptVarPtr> &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::setTemporaryMark_recursive(uint32_t ID) {
CScriptVarFunction::setTemporaryMark_recursive(ID);
boundedThis->setTemporaryMark_recursive(ID);
for(vector<CScriptVarPtr>::iterator it=boundedArguments.begin(); it!=boundedArguments.end(); ++it)
(*it)->setTemporaryMark_recursive(ID);
}
CScriptVarPtr CScriptVarFunctionBounded::callFunction( CScriptResult &execute, vector<CScriptVarPtr> &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis/*=0*/ )
{
vector<CScriptVarPtr> 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;
currentMarkSlot = -1;
stackBase = 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);
objectPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
arrayPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
arrayPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
stringPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
stringPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
regexpPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
regexpPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
pseudo_refered.push_back(&regexpPrototype);
#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", newScriptVarNumber(this, numeric_limits<double>::max()), SCRIPTVARLINK_CONSTANT);
var->addChild("MIN_VALUE", newScriptVarNumber(this, numeric_limits<double>::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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
numberPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
numberPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
booleanPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT);
booleanPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
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);
iteratorPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
pseudo_refered.push_back(&iteratorPrototype);
//////////////////////////////////////////////////////////////////////////
// Generator
// var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT);
#ifndef NO_GENERATORS
generatorPrototype = newScriptVar(Object);
generatorPrototype->addChild("next", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)0, "Generator.next"), SCRIPTVARLINK_BUILDINDEFAULT);
generatorPrototype->addChild("send", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)1, "Generator.send"), SCRIPTVARLINK_BUILDINDEFAULT);
generatorPrototype->addChild("close", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)2, "Generator.close"), SCRIPTVARLINK_BUILDINDEFAULT);
generatorPrototype->addChild("throw", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)3, "Generator.throw"), SCRIPTVARLINK_BUILDINDEFAULT);
pseudo_refered.push_back(&generatorPrototype);
#endif /*NO_GENERATORS*/
//////////////////////////////////////////////////////////////////////////
// Function
var = addNative("function Function(params, body)", this, &CTinyJS::native_Function, 0, SCRIPTVARLINK_CONSTANT);
var->addChildOrReplace(TINYJS_PROTOTYPE_CLASS, functionPrototype);
functionPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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, SCRIPTVARLINK_BUILDINDEFAULT);
functionPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT);
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(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT);
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), "StopIteration"), 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);
native_require_read = 0;
addNative("function require(jsFile)", this, &CTinyJS::native_require);
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);
root->addChild("JSON", newScriptVar(Object), SCRIPTVARLINK_BUILDINDEFAULT);
addNative("function JSON.parse(text, reviver)", this, &CTinyJS::native_JSON_parse);
_registerFunctions(this);
_registerStringFunctions(this);
_registerMathFunctions(this);
}
CTinyJS::~CTinyJS() {
ASSERT(!t);
for(vector<CScriptVarPtr*>::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it)
**it = CScriptVarPtr();
for(int i=Error; i<ERROR_COUNT; i++)
errorPrototypes[i] = CScriptVarPtr();
root->removeAllChildren();
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 = allocUniqueID();
setTemporaryID_recursive(UniqueID);
if(execute.value) execute.value->setTemporaryMark_recursive(UniqueID);
for(CScriptVar *p = first; p; p=p->next)
{
if(p->getTemporaryMark() != UniqueID)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
printf("%s %p\n", p->getVarType().c_str(), p);
#pragma GCC diagnostic pop
}
freeUniqueID();
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);
}
unique_ptr<CScriptTokenDataFnc> 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)->getVarPtr()->addChild(TINYJS_CONSTRUCTOR_VAR, funcVar->getVarPtr(), 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<CScriptVarPtr> &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<CScriptVarPtr> &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_idx<length; ++arguments_idx) {
string arguments_idx_str = int2string(arguments_idx);
CScriptVarLinkWorkPtr value;
if(arguments_idx < length_arguments) {
value = arguments->addChild(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));
#ifndef NO_GENERATORS
if(Fnc->isGenerator) {
return ::newScriptVarCScriptVarGenerator(this, functionRoot, Function);
}
#endif /*NO_GENERATORS*/
// 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.setThrow(v, "native function '"+Fnc->name+"'");
} else if(v->isError()) {
CScriptException *err = CScriptVarErrorPtr(v)->toCScriptException();
if(err->fileName.empty()) err->fileName = "native function '"+Fnc->name+"'";
throw err;
}
else
throw new CScriptException(Error, "uncaught exception: '"+v->toString(function_execute)+"' in native function '"+Fnc->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);
}
#ifndef NO_GENERATORS
void CTinyJS::generator_start(CScriptVarGenerator *Generator)
{
// push current Generator
generatorStack.push_back(Generator);
// safe callers stackBase & set generators one
Generator->callersStackBase = stackBase;
stackBase = 0;
// safe callers ScopeSize
Generator->callersScopeSize = scopes.size();
// safe callers Tokenizer & set generators one
Generator->callersTokenizer = t;
CScriptTokenizer generatorTokenizer;
t = &generatorTokenizer;
// safe callers haveTry
Generator->callersHaveTry = haveTry;
haveTry = true;
// push generator's FunctionRoot
CScopeControl ScopeControl(this);
ScopeControl.addFncScope(Generator->getFunctionRoot());
// call generator-function
CScriptTokenDataFnc *Fnc = Generator->getFunction()->getFunctionData();
CScriptResult function_execute;
TOKEN_VECT eof(1, CScriptToken());
t->pushTokenScope(eof);
t->pushTokenScope(Fnc->body);
t->currentFile = Fnc->file;
try {
if(Fnc->body.front().token == '{')
execute_block(function_execute);
else {
execute_base(function_execute);
}
if(function_execute.isThrow())
Generator->setException(function_execute.value);
else
Generator->setException(constStopIteration);
// } catch(CScriptVarPtr &e) {
// Generator->setException(e);
} catch(CScriptCoroutine::StopIteration_t &) {
Generator->setException(CScriptVarPtr());
// } catch(CScriptException *e) {
// Generator->setException(newScriptVarError(this, *e));
} catch(...) {
// pop current Generator
generatorStack.pop_back();
// restore callers stackBase
stackBase = Generator->callersStackBase;
// restore callers Scope (restored by ScopeControl
// scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
// restore callers Tokenizer
t = Generator->callersTokenizer;
// restore callers haveTry
haveTry = Generator->callersHaveTry;
Generator->setException(constStopIteration);
// re-throw
throw;
}
// pop current Generator
generatorStack.pop_back();
// restore callers stackBase
stackBase = Generator->callersStackBase;
// restore callers Scope (restored by ScopeControl
// scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
// restore callers Tokenizer
t = Generator->callersTokenizer;
// restore callers haveTry
haveTry = Generator->callersHaveTry;
}
CScriptVarPtr CTinyJS::generator_yield(CScriptResult &execute, CScriptVar *YieldIn)
{
if(!execute) return constUndefined;
CScriptVarGenerator *Generator = generatorStack.back();
if(Generator->isClosed()) {
throwError(execute, TypeError, "yield from closing generator function");
return constUndefined;
}
// pop current Generator
generatorStack.pop_back();
// safe generators and restore callers stackBase
void *generatorStckBase = stackBase;
stackBase = Generator->callersStackBase;
// safe generators and restore callers scopes
Generator->generatorScopes.assign(scopes.begin()+Generator->callersScopeSize, scopes.end());
scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end());
// safe generators and restore callers Tokenizer
CScriptTokenizer *generatorTokenizer = t;
t = Generator->callersTokenizer;
// safe generators and restore callers haveTry
bool generatorsHaveTry = haveTry;
haveTry = Generator->callersHaveTry;
CScriptVarPtr ret;
try {
ret = Generator->yield(execute, YieldIn);
} catch(...) {
// normaly catch(CScriptCoroutine::CScriptCoroutineFinish_t &)
// force StopIteration with call CScriptCoroutine::Stop() before CScriptCoroutine::next()
// but catch(...) is for paranoia
// push current Generator
generatorStack.push_back(Generator);
// safe callers and restore generators stackBase
Generator->callersStackBase = stackBase;
stackBase = generatorStckBase;
// safe callers and restore generator Scopes
Generator->callersScopeSize = scopes.size();
scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end());
Generator->generatorScopes.clear();
// safe callers and restore generator Tokenizer
Generator->callersTokenizer = t;
t = generatorTokenizer;
// safe callers and restore generator haveTry
Generator->callersHaveTry = haveTry;
haveTry = generatorsHaveTry;
// re-throw
throw;
}
// push current Generator
generatorStack.push_back(Generator);
// safe callers and restore generators stackBase
Generator->callersStackBase = stackBase;
stackBase = generatorStckBase;
// safe callers and restore generator Scopes
Generator->callersScopeSize = scopes.size();
scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end());
Generator->generatorScopes.clear();
// safe callers and restore generator Tokenizer
Generator->callersTokenizer = t;
t = generatorTokenizer;
// safe callers and restore generator haveTry
Generator->callersHaveTry = haveTry;
haveTry = generatorsHaveTry;
return ret;
}
#endif /*NO_GENERATORS*/
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 '+':
try{
return newScriptVar(da+db);
} catch(exception& e) {
throwError(execute, Error, e.what());
return constUndefined;
}
case LEX_EQUAL: return constScriptVar(da==db);
case LEX_NEQUAL: return constScriptVar(da!=db);
case '<': return constScriptVar(da<db);
case LEX_LEQUAL: return constScriptVar(da<=db);
case '>': 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<<db);
case LEX_RSHIFT: return a->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(da<db);
case LEX_LEQUAL: return a->constScriptVar(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<CScriptVarPtr> 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<CScriptTokenDataObjectLiteral::ELEMENT>::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<CScriptTokenDataObjectLiteral::ELEMENT>::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;
#ifndef NO_GENERATORS
case LEX_R_YIELD:
if (execute) {
t->match(LEX_R_YIELD);
CScriptVarPtr result = constUndefined;
if (t->tk != ';')
result = execute_base(execute);
if(execute)
return generator_yield(execute, result.getVar());
else
return constUndefined;
} else
t->skip(t->getToken().Int());
break;
#endif /*NO_GENERATORS*/
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) {
CScriptVarPtr Constructor = objClass->getVarPtr();
if(Constructor->isFunction()) {
CScriptVarPtr obj(newScriptVar(Object));
CScriptVarLinkPtr prototype = Constructor->findChild(TINYJS_PROTOTYPE_CLASS);
if(!prototype || prototype->getVarPtr()->isUndefined() || prototype->getVarPtr()->isNull()) {
prototype = Constructor->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE);
obj->addChildOrReplace(TINYJS___PROTO___VAR, prototype, SCRIPTVARLINK_WRITABLE);
}
CScriptVarLinkPtr __constructor__ = Constructor->findChild("__constructor__");
if(__constructor__ && __constructor__->getVarPtr()->isFunction())
Constructor = __constructor__;
if (stackBase) {
int dummy;
if(&dummy < stackBase)
throwError(execute, Error, "too much recursion");
}
vector<CScriptVarPtr> 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, Constructor, 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_base(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));
CScriptVarPtr fnc = a.getter(execute)->getVarPtr();
if (!fnc->isFunction())
throwError(execute, TypeError, a->getName() + " is not a function");
if (stackBase) {
int dummy;
if(&dummy < stackBase)
throwError(execute, Error, "too much recursion");
}
t->match('('); // path += '(';
// grab in all parameters
vector<CScriptVarPtr> 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, fnc, 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->getName() != "this") {
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 = allocUniqueID();
CScriptVarPtr object = a->getVarPtr()->findChild(TINYJS___PROTO___VAR);
while( object && object!=prototype->getVarPtr() && object->getTemporaryMark() != uniqueID) {
object->setTemporaryMark(uniqueID); // prevents recursions
object = object->findChild(TINYJS___PROTO___VAR);
}
freeUniqueID();
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);
a = execute_assignment(execute);
t->check(':');
t->skip(t->getToken().Int());
} else {
CScriptVarLinkWorkPtr b;
t->skip(t->getToken().Int());
t->match(':');
return execute_assignment(execute);
// 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; i<CScriptTokenDataForwards::END; ++i) {
for(STRING_SET_it it=varNames[i].begin(); it!=varNames[i].end(); ++it) {
CScriptVarLinkPtr a = in_scope->findChild(*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: '"+tmp_execute.value->toString(CScriptResult())+"'", 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 if (t->tk != ';')
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 = constUndefined;
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.value) {
// execute catch-blocks only if value set (spezial case Generator.close() -> only finally-blocks are executed)
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()+"'", execute.throw_at_file, execute.throw_at_line, execute.throw_at_column);
}
}
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.setThrow(a, t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn());
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(t->tk!=LEX_T_SKIP || execute) {
if(t->tk==LEX_T_SKIP) 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) && Idx<This_asString->stringLength();
}
}
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; i<length; i++)
returnVar->setArrayIndex(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));
}
//////////////////////////////////////////////////////////////////////////
/// Generator
//////////////////////////////////////////////////////////////////////////
#ifndef NO_GENERATORS
void CTinyJS::native_Generator_prototype_next(const CFunctionsScopePtr &c, void *data) {
CScriptVarGeneratorPtr Generator(c->getArgument("this"));
if(!Generator) {
static const char *fnc[] = {"next","send","close","throw"};
c->throwError(TypeError, string(fnc[(int)data])+" method called on incompatible Object");
}
if((int)data >=2)
Generator->native_throw(c, (void*)(((int)data)-2));
else
Generator->native_send(c, data);
}
#endif /*NO_GENERATORS*/
//////////////////////////////////////////////////////////////////////////
/// 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; i<length-1; i++)
{
params.append(",");
params.append(c->getArgument(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<CScriptVarPtr> Args;
for(int i=1; i<length; i++)
Args.push_back(c->getArgument(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<CScriptVarPtr> Args;
for(int i=0; i<length; i++) {
CScriptVarLinkPtr value = Array->findChild(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<CScriptVarPtr> Args;
for(int i=1; i<length; i++) Args.push_back(c->getArgument(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);
}
static int _native_require_read(const string &Fname, std::string &Data) {
std::ifstream in(Fname.c_str(), std::ios::in | std::ios::binary);
if (in) {
in.seekg(0, std::ios::end);
Data.resize((string::size_type)in.tellg());
in.seekg(0, std::ios::beg);
in.read(&Data[0], Data.size());
in.close();
return(0);
}
return errno;
}
void CTinyJS::native_require(const CFunctionsScopePtr &c, void *data) {
string File = c->getArgument("jsFile")->toString();
string Code;
int ErrorNo;
if(!native_require_read)
native_require_read = _native_require_read; // use builtin if no callback
if((ErrorNo = native_require_read(File, Code))) {
ostringstream msg;
msg << "can't read \"" << File << "\" (Error=" << ErrorNo << ")";
c->throwError(Error, msg.str());
}
c->addChildOrReplace("jsCode", c->newScriptVar(Code));
native_eval(c, data);
}
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<CScriptVarPtr*>::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it)
if(**it) (**it)->setTemporaryMark_recursive(ID);
for(int i=Error; i<ERROR_COUNT; i++)
if(errorPrototypes[i]) errorPrototypes[i]->setTemporaryMark_recursive(ID);
root->setTemporaryMark_recursive(ID);
}
void CTinyJS::ClearUnreferedVars(const CScriptVarPtr &extra/*=CScriptVarPtr()*/) {
uint32_t UniqueID = allocUniqueID();
setTemporaryID_recursive(UniqueID);
if(extra) extra->setTemporaryMark_recursive(UniqueID);
CScriptVar *p = first;
while(p)
{
if(p->getTemporaryMark() != UniqueID)
{
CScriptVarPtr var = p;
var->removeAllChildren();
p = var->next;
}
else
p = p->next;
}
freeUniqueID();
}