#include "TinyJS.h" #include <assert.h> #define ASSERT(X) assert(X) #define CLEAN(x) { CScriptVarLink *__v = x; if (__v && !__v->owned) { delete __v; } } #define CREATE_LINK(LINK, VAR) { if (!LINK || LINK->owned) LINK = new CScriptVarLink(VAR); else LINK->replaceWith(VAR); } #include <string> #include <string.h> #include <sstream> #include <cstdlib> #include <stdio.h> using namespace std; #ifdef _WIN32 #ifdef _DEBUG #ifndef DBG_NEW #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) #define new DBG_NEW #endif #endif #endif #ifdef __GNUC__ #define vsprintf_s vsnprintf #define sprintf_s snprintf #define _strdup strdup #endif #define DEBUG_MEMORY 0 #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]->name.c_str(), allocatedLinks[i]->var->getRefs()); allocatedLinks[i]->var->trace(" "); } allocatedVars.clear(); allocatedLinks.clear(); } #endif bool isWhitespace(char ch) { return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); } bool isNumeric(char ch) { return (ch>='0') && (ch<='9'); } bool isNumber(const string &str) { for (size_t i=0;i<str.size();i++) if (!isNumeric(str[i])) return false; return true; } bool isHexadecimal(char ch) { return ((ch>='0') && (ch<='9')) || ((ch>='a') && (ch<='f')) || ((ch>='A') && (ch<='F')); } bool isAlpha(char ch) { return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || 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); } } std::string getJSString(const std::string &str) { std::string nStr = str; for (size_t i=0;i<nStr.size();i++) { const char *replaceWith = ""; bool replace = true; switch (nStr[i]) { case '\\': replaceWith = "\\\\"; break; case '\n': replaceWith = "\\n"; break; case '\r': replaceWith = "\\r"; break; case '\a': replaceWith = "\\a"; break; case '"': replaceWith = "\\\""; break; default: { int nCh = ((int)nStr[i]) &0xFF; if (nCh<32 || nCh>127) { char buffer[5]; sprintf_s(buffer, 5, "\\x%02X", nCh); replaceWith = buffer; } else replace=false; } } if (replace) { nStr = nStr.substr(0, i) + replaceWith + nStr.substr(i+1); i += strlen(replaceWith)-1; } } return "\"" + nStr + "\""; } bool isAlphaNum(const std::string &str) { if (str.size()==0) return true; if (!isAlpha(str[0])) return false; for (size_t i=0;i<str.size();i++) if (!(isAlpha(str[i]) || isNumeric(str[i]))) return false; return true; } CScriptException::CScriptException(const string &exceptionText) { text = exceptionText; } CScriptLex::CScriptLex(const string &input) { data = _strdup(input.c_str()); dataOwned = true; dataStart = 0; dataEnd = strlen(data); reset(); } CScriptLex::CScriptLex(CScriptLex *owner, int startChar, int endChar) { data = owner->data; dataOwned = false; dataStart = startChar; dataEnd = endChar; reset(); } CScriptLex::~CScriptLex(void) { if (dataOwned) free((void*)data); } void CScriptLex::reset() { dataPos = dataStart; tokenStart = 0; tokenEnd = 0; tokenLastEnd = 0; tk = 0; tkStr = ""; getNextCh(); getNextCh(); getNextToken(); } void CScriptLex::match(int expected_tk) { if (tk!=expected_tk) { ostringstream errorString; errorString << "Got " << getTokenStr(tk) << " expected " << getTokenStr(expected_tk) << " at " << getPosition(tokenStart); throw new CScriptException(errorString.str()); } getNextToken(); } string CScriptLex::getTokenStr(int token) { if (token>32 && token<128) { char buf[4] = "' '"; buf[1] = (char)token; return buf; } switch (token) { case LEX_EOF : return "EOF"; case LEX_ID : return "ID"; case LEX_INT : return "INT"; case LEX_FLOAT : return "FLOAT"; case LEX_STR : return "STRING"; case LEX_EQUAL : return "=="; case LEX_TYPEEQUAL : return "==="; case LEX_NEQUAL : return "!="; case LEX_NTYPEEQUAL : return "!=="; case LEX_LEQUAL : return "<="; case LEX_LSHIFT : return "<<"; case LEX_LSHIFTEQUAL : return "<<="; case LEX_GEQUAL : return ">="; case LEX_RSHIFT : return ">>"; case LEX_RSHIFTUNSIGNED : return ">>"; case LEX_RSHIFTEQUAL : return ">>="; case LEX_PLUSEQUAL : return "+="; case LEX_MINUSEQUAL : return "-="; case LEX_PLUSPLUS : return "++"; case LEX_MINUSMINUS : return "--"; case LEX_ANDEQUAL : return "&="; case LEX_ANDAND : return "&&"; case LEX_OREQUAL : return "|="; case LEX_OROR : return "||"; case LEX_XOREQUAL : return "^="; case LEX_R_IF : return "if"; case LEX_R_ELSE : return "else"; case LEX_R_DO : return "do"; case LEX_R_WHILE : return "while"; case LEX_R_FOR : return "for"; case LEX_R_BREAK : return "break"; case LEX_R_CONTINUE : return "continue"; case LEX_R_FUNCTION : return "function"; case LEX_R_RETURN : return "return"; case LEX_R_VAR : return "var"; case LEX_R_TRUE : return "true"; case LEX_R_FALSE : return "false"; case LEX_R_NULL : return "null"; case LEX_R_UNDEFINED : return "undefined"; case LEX_R_NEW : return "new"; } ostringstream msg; msg << "?[" << token << "]"; return msg.str(); } void CScriptLex::getNextCh() { currCh = nextCh; if (dataPos < dataEnd) nextCh = data[dataPos]; else nextCh = 0; dataPos++; } void CScriptLex::getNextToken() { tk = LEX_EOF; tkStr.clear(); while (currCh && isWhitespace(currCh)) getNextCh(); if (currCh=='/' && nextCh=='/') { while (currCh && currCh!='\n') getNextCh(); getNextCh(); getNextToken(); return; } if (currCh=='/' && nextCh=='*') { while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); getNextCh(); getNextCh(); getNextToken(); return; } tokenStart = dataPos-2; if (isAlpha(currCh)) { while (isAlpha(currCh) || isNumeric(currCh)) { tkStr += currCh; getNextCh(); } tk = LEX_ID; if (tkStr=="if") tk = LEX_R_IF; else if (tkStr=="else") tk = LEX_R_ELSE; else if (tkStr=="do") tk = LEX_R_DO; else if (tkStr=="while") tk = LEX_R_WHILE; else if (tkStr=="for") tk = LEX_R_FOR; else if (tkStr=="break") tk = LEX_R_BREAK; else if (tkStr=="continue") tk = LEX_R_CONTINUE; else if (tkStr=="function") tk = LEX_R_FUNCTION; else if (tkStr=="return") tk = LEX_R_RETURN; else if (tkStr=="var") tk = LEX_R_VAR; else if (tkStr=="true") tk = LEX_R_TRUE; else if (tkStr=="false") tk = LEX_R_FALSE; else if (tkStr=="null") tk = LEX_R_NULL; else if (tkStr=="undefined") tk = LEX_R_UNDEFINED; else if (tkStr=="new") tk = LEX_R_NEW; } else if (isNumeric(currCh)) { bool isHex = false; if (currCh=='0') { tkStr += currCh; getNextCh(); } if (currCh=='x') { isHex = true; tkStr += currCh; getNextCh(); } tk = LEX_INT; while (isNumeric(currCh) || (isHex && isHexadecimal(currCh))) { tkStr += currCh; getNextCh(); } if (!isHex && currCh=='.') { tk = LEX_FLOAT; tkStr += '.'; getNextCh(); while (isNumeric(currCh)) { tkStr += currCh; getNextCh(); } } if (!isHex && (currCh=='e'||currCh=='E')) { tk = LEX_FLOAT; tkStr += currCh; getNextCh(); if (currCh=='-') { tkStr += currCh; getNextCh(); } while (isNumeric(currCh)) { tkStr += currCh; getNextCh(); } } } else if (currCh=='"') { getNextCh(); while (currCh && currCh!='"') { if (currCh == '\\') { getNextCh(); switch (currCh) { case 'n' : tkStr += '\n'; break; case '"' : tkStr += '"'; break; case '\\' : tkStr += '\\'; break; default: tkStr += currCh; } } else { tkStr += currCh; } getNextCh(); } getNextCh(); tk = LEX_STR; } else if (currCh=='\'') { getNextCh(); while (currCh && currCh!='\'') { if (currCh == '\\') { getNextCh(); switch (currCh) { case 'n' : tkStr += '\n'; break; case 'a' : tkStr += '\a'; break; case 'r' : tkStr += '\r'; break; case 't' : tkStr += '\t'; break; case '\'' : tkStr += '\''; break; case '\\' : tkStr += '\\'; break; case 'x' : { char buf[3] = "??"; getNextCh(); buf[0] = currCh; getNextCh(); buf[1] = currCh; tkStr += (char)strtol(buf,0,16); } break; default: if (currCh>='0' && currCh<='7') { char buf[4] = "???"; buf[0] = currCh; getNextCh(); buf[1] = currCh; getNextCh(); buf[2] = currCh; tkStr += (char)strtol(buf,0,8); } else tkStr += currCh; } } else { tkStr += currCh; } getNextCh(); } getNextCh(); tk = LEX_STR; } else { 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=='<' && currCh=='=') { tk = LEX_LEQUAL; getNextCh(); } else if (tk=='<' && currCh=='<') { tk = LEX_LSHIFT; getNextCh(); if (currCh=='=') { tk = LEX_LSHIFTEQUAL; getNextCh(); } } else if (tk=='>' && currCh=='=') { tk = LEX_GEQUAL; getNextCh(); } else if (tk=='>' && currCh=='>') { tk = LEX_RSHIFT; getNextCh(); if (currCh=='=') { tk = LEX_RSHIFTEQUAL; getNextCh(); } else if (currCh=='>') { tk = LEX_RSHIFTUNSIGNED; getNextCh(); } } else if (tk=='+' && currCh=='=') { tk = LEX_PLUSEQUAL; getNextCh(); } else if (tk=='-' && currCh=='=') { tk = LEX_MINUSEQUAL; getNextCh(); } else if (tk=='+' && currCh=='+') { tk = LEX_PLUSPLUS; getNextCh(); } else if (tk=='-' && currCh=='-') { tk = LEX_MINUSMINUS; getNextCh(); } else if (tk=='&' && currCh=='=') { tk = LEX_ANDEQUAL; getNextCh(); } else if (tk=='&' && currCh=='&') { tk = LEX_ANDAND; getNextCh(); } else if (tk=='|' && currCh=='=') { tk = LEX_OREQUAL; getNextCh(); } else if (tk=='|' && currCh=='|') { tk = LEX_OROR; getNextCh(); } else if (tk=='^' && currCh=='=') { tk = LEX_XOREQUAL; getNextCh(); } } tokenLastEnd = tokenEnd; tokenEnd = dataPos-3; } string CScriptLex::getSubString(int lastPosition) { int lastCharIdx = tokenLastEnd+1; if (lastCharIdx < dataEnd) { char old = data[lastCharIdx]; data[lastCharIdx] = 0; std::string value = &data[lastPosition]; data[lastCharIdx] = old; return value; } else { return std::string(&data[lastPosition]); } } CScriptLex *CScriptLex::getSubLex(int lastPosition) { int lastCharIdx = tokenLastEnd+1; if (lastCharIdx < dataEnd) return new CScriptLex(this, lastPosition, lastCharIdx); else return new CScriptLex(this, lastPosition, dataEnd ); } string CScriptLex::getPosition(int pos) { if (pos<0) pos=tokenLastEnd; int line = 1,col = 1; for (int i=0;i<pos;i++) { char ch; if (i < dataEnd) ch = data[i]; else ch = 0; col++; if (ch=='\n') { line++; col = 0; } } char buf[256]; sprintf_s(buf, 256, "(line: %d, col: %d)", line, col); return buf; } CScriptVarLink::CScriptVarLink(CScriptVar *var, const std::string &name) { #if DEBUG_MEMORY mark_allocated(this); #endif this->name = name; this->nextSibling = 0; this->prevSibling = 0; this->var = var->ref(); this->owned = false; } CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) { #if DEBUG_MEMORY mark_allocated(this); #endif this->name = link.name; this->nextSibling = 0; this->prevSibling = 0; this->var = link.var->ref(); this->owned = false; } CScriptVarLink::~CScriptVarLink() { #if DEBUG_MEMORY mark_deallocated(this); #endif var->unref(); } void CScriptVarLink::replaceWith(CScriptVar *newVar) { CScriptVar *oldVar = var; var = newVar->ref(); oldVar->unref(); } void CScriptVarLink::replaceWith(CScriptVarLink *newVar) { if (newVar) replaceWith(newVar->var); else replaceWith(new CScriptVar()); } int CScriptVarLink::getIntName() { return atoi(name.c_str()); } void CScriptVarLink::setIntName(int n) { char sIdx[64]; sprintf_s(sIdx, sizeof(sIdx), "%d", n); name = sIdx; } CScriptVar::CScriptVar() { refs = 0; #if DEBUG_MEMORY mark_allocated(this); #endif init(); flags = SCRIPTVAR_UNDEFINED; } CScriptVar::CScriptVar(const string &str) { refs = 0; #if DEBUG_MEMORY mark_allocated(this); #endif init(); flags = SCRIPTVAR_STRING; data = str; } CScriptVar::CScriptVar(const string &varData, int varFlags) { refs = 0; #if DEBUG_MEMORY mark_allocated(this); #endif init(); flags = varFlags; if (varFlags & SCRIPTVAR_INTEGER) { intData = strtol(varData.c_str(),0,0); } else if (varFlags & SCRIPTVAR_DOUBLE) { doubleData = strtod(varData.c_str(),0); } else data = varData; } CScriptVar::CScriptVar(double val) { refs = 0; #if DEBUG_MEMORY mark_allocated(this); #endif init(); setDouble(val); } CScriptVar::CScriptVar(int val) { refs = 0; #if DEBUG_MEMORY mark_allocated(this); #endif init(); setInt(val); } CScriptVar::~CScriptVar(void) { #if DEBUG_MEMORY mark_deallocated(this); #endif removeAllChildren(); } void CScriptVar::init() { firstChild = 0; lastChild = 0; flags = 0; jsCallback = 0; jsCallbackUserData = 0; data = TINYJS_BLANK_DATA; intData = 0; doubleData = 0; } CScriptVar *CScriptVar::getReturnVar() { return getParameter(TINYJS_RETURN_VAR); } void CScriptVar::setReturnVar(CScriptVar *var) { findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var); } CScriptVar *CScriptVar::getParameter(const std::string &name) { return findChildOrCreate(name)->var; } CScriptVarLink *CScriptVar::findChild(const string &childName) { CScriptVarLink *v = firstChild; while (v) { if (v->name.compare(childName)==0) return v; v = v->nextSibling; } return 0; } CScriptVarLink *CScriptVar::findChildOrCreate(const string &childName, int varFlags) { CScriptVarLink *l = findChild(childName); if (l) return l; return addChild(childName, new CScriptVar(TINYJS_BLANK_DATA, varFlags)); } CScriptVarLink *CScriptVar::findChildOrCreateByPath(const std::string &path) { size_t p = path.find('.'); if (p == string::npos) return findChildOrCreate(path); return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var-> findChildOrCreateByPath(path.substr(p+1)); } CScriptVarLink *CScriptVar::addChild(const std::string &childName, CScriptVar *child) { if (isUndefined()) { flags = SCRIPTVAR_OBJECT; } if (!child) child = new CScriptVar(); CScriptVarLink *link = new CScriptVarLink(child, childName); link->owned = true; if (lastChild) { lastChild->nextSibling = link; link->prevSibling = lastChild; lastChild = link; } else { firstChild = link; lastChild = link; } return link; } CScriptVarLink *CScriptVar::addChildNoDup(const std::string &childName, CScriptVar *child) { if (!child) child = new CScriptVar(); CScriptVarLink *v = findChild(childName); if (v) { v->replaceWith(child); } else { v = addChild(childName, child); } return v; } void CScriptVar::removeChild(CScriptVar *child) { CScriptVarLink *link = firstChild; while (link) { if (link->var == child) break; link = link->nextSibling; } ASSERT(link); removeLink(link); } void CScriptVar::removeLink(CScriptVarLink *link) { if (!link) return; if (link->nextSibling) link->nextSibling->prevSibling = link->prevSibling; if (link->prevSibling) link->prevSibling->nextSibling = link->nextSibling; if (lastChild == link) lastChild = link->prevSibling; if (firstChild == link) firstChild = link->nextSibling; delete link; } void CScriptVar::removeAllChildren() { CScriptVarLink *c = firstChild; while (c) { CScriptVarLink *t = c->nextSibling; delete c; c = t; } firstChild = 0; lastChild = 0; } CScriptVar *CScriptVar::getArrayIndex(int idx) { char sIdx[64]; sprintf_s(sIdx, sizeof(sIdx), "%d", idx); CScriptVarLink *link = findChild(sIdx); if (link) return link->var; else return new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_NULL); } void CScriptVar::setArrayIndex(int idx, CScriptVar *value) { char sIdx[64]; sprintf_s(sIdx, sizeof(sIdx), "%d", idx); CScriptVarLink *link = findChild(sIdx); if (link) { if (value->isUndefined()) removeLink(link); else link->replaceWith(value); } else { if (!value->isUndefined()) addChild(sIdx, value); } } int CScriptVar::getArrayLength() { int highest = -1; if (!isArray()) return 0; CScriptVarLink *link = firstChild; while (link) { if (isNumber(link->name)) { int val = atoi(link->name.c_str()); if (val > highest) highest = val; } link = link->nextSibling; } return highest+1; } int CScriptVar::getChildren() { int n = 0; CScriptVarLink *link = firstChild; while (link) { n++; link = link->nextSibling; } return n; } int CScriptVar::getInt() { if (isInt()) return intData; if (isNull()) return 0; if (isUndefined()) return 0; if (isDouble()) return (int)doubleData; return 0; } double CScriptVar::getDouble() { if (isDouble()) return doubleData; if (isInt()) return intData; if (isNull()) return 0; if (isUndefined()) return 0; return 0; } const string &CScriptVar::getString() { static string s_null = "null"; static string s_undefined = "undefined"; if (isInt()) { char buffer[32]; sprintf_s(buffer, sizeof(buffer), "%ld", intData); data = buffer; return data; } if (isDouble()) { char buffer[32]; sprintf_s(buffer, sizeof(buffer), "%f", doubleData); data = buffer; return data; } if (isNull()) return s_null; if (isUndefined()) return s_undefined; return data; } void CScriptVar::setInt(int val) { flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER; intData = val; doubleData = 0; data = TINYJS_BLANK_DATA; } void CScriptVar::setDouble(double val) { flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE; doubleData = val; intData = 0; data = TINYJS_BLANK_DATA; } void CScriptVar::setString(const string &str) { flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING; data = str; intData = 0; doubleData = 0; } void CScriptVar::setUndefined() { flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED; data = TINYJS_BLANK_DATA; intData = 0; doubleData = 0; removeAllChildren(); } void CScriptVar::setArray() { flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_ARRAY; data = TINYJS_BLANK_DATA; intData = 0; doubleData = 0; removeAllChildren(); } bool CScriptVar::equals(CScriptVar *v) { CScriptVar *resV = mathsOp(v, LEX_EQUAL); bool res = resV->getBool(); delete resV; return res; } CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) { CScriptVar *a = this; if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) { bool eql = ((a->flags & SCRIPTVAR_VARTYPEMASK) == (b->flags & SCRIPTVAR_VARTYPEMASK)); if (eql) { CScriptVar *contents = a->mathsOp(b, LEX_EQUAL); if (!contents->getBool()) eql = false; if (!contents->refs) delete contents; } ; if (op == LEX_TYPEEQUAL) return new CScriptVar(eql); else return new CScriptVar(!eql); } if (a->isUndefined() && b->isUndefined()) { if (op == LEX_EQUAL) return new CScriptVar(true); else if (op == LEX_NEQUAL) return new CScriptVar(false); else return new CScriptVar(); } else if ((a->isNumeric() || a->isUndefined()) && (b->isNumeric() || b->isUndefined())) { if (!a->isDouble() && !b->isDouble()) { int da = a->getInt(); int db = b->getInt(); switch (op) { case '+': return new CScriptVar(da+db); case '-': return new CScriptVar(da-db); case '*': return new CScriptVar(da*db); case '/': return new CScriptVar(da/db); case '&': return new CScriptVar(da&db); case '|': return new CScriptVar(da|db); case '^': return new CScriptVar(da^db); case '%': return new CScriptVar(da%db); case LEX_EQUAL: return new CScriptVar(da==db); case LEX_NEQUAL: return new CScriptVar(da!=db); case '<': return new CScriptVar(da<db); case LEX_LEQUAL: return new CScriptVar(da<=db); case '>': return new CScriptVar(da>db); case LEX_GEQUAL: return new CScriptVar(da>=db); default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Int datatype"); } } else { double da = a->getDouble(); double db = b->getDouble(); switch (op) { case '+': return new CScriptVar(da+db); case '-': return new CScriptVar(da-db); case '*': return new CScriptVar(da*db); case '/': return new CScriptVar(da/db); case LEX_EQUAL: return new CScriptVar(da==db); case LEX_NEQUAL: return new CScriptVar(da!=db); case '<': return new CScriptVar(da<db); case LEX_LEQUAL: return new CScriptVar(da<=db); case '>': return new CScriptVar(da>db); case LEX_GEQUAL: return new CScriptVar(da>=db); default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Double datatype"); } } } else if (a->isArray()) { switch (op) { case LEX_EQUAL: return new CScriptVar(a==b); case LEX_NEQUAL: return new CScriptVar(a!=b); default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Array datatype"); } } else if (a->isObject()) { switch (op) { case LEX_EQUAL: return new CScriptVar(a==b); case LEX_NEQUAL: return new CScriptVar(a!=b); default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Object datatype"); } } else { string da = a->getString(); string db = b->getString(); switch (op) { case '+': return new CScriptVar(da+db, SCRIPTVAR_STRING); case LEX_EQUAL: return new CScriptVar(da==db); case LEX_NEQUAL: return new CScriptVar(da!=db); case '<': return new CScriptVar(da<db); case LEX_LEQUAL: return new CScriptVar(da<=db); case '>': return new CScriptVar(da>db); case LEX_GEQUAL: return new CScriptVar(da>=db); default: throw new CScriptException("Operation "+CScriptLex::getTokenStr(op)+" not supported on the string datatype"); } } ASSERT(0); return 0; } void CScriptVar::copySimpleData(CScriptVar *val) { data = val->data; intData = val->intData; doubleData = val->doubleData; flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK); } void CScriptVar::copyValue(CScriptVar *val) { if (val) { copySimpleData(val); removeAllChildren(); CScriptVarLink *child = val->firstChild; while (child) { CScriptVar *copied; if (child->name != TINYJS_PROTOTYPE_CLASS) copied = child->var->deepCopy(); else copied = child->var; addChild(child->name, copied); child = child->nextSibling; } } else { setUndefined(); } } CScriptVar *CScriptVar::deepCopy() { CScriptVar *newVar = new CScriptVar(); newVar->copySimpleData(this); CScriptVarLink *child = firstChild; while (child) { CScriptVar *copied; if (child->name != TINYJS_PROTOTYPE_CLASS) copied = child->var->deepCopy(); else copied = child->var; newVar->addChild(child->name, copied); child = child->nextSibling; } return newVar; } void CScriptVar::trace(string indentStr, const string &name) { TRACE("%s'%s' = '%s' %s\n", indentStr.c_str(), name.c_str(), getString().c_str(), getFlagsAsString().c_str()); string indent = indentStr+" "; CScriptVarLink *link = firstChild; while (link) { link->var->trace(indent, link->name); link = link->nextSibling; } } string CScriptVar::getFlagsAsString() { string flagstr = ""; if (flags&SCRIPTVAR_FUNCTION) flagstr = flagstr + "FUNCTION "; if (flags&SCRIPTVAR_OBJECT) flagstr = flagstr + "OBJECT "; if (flags&SCRIPTVAR_ARRAY) flagstr = flagstr + "ARRAY "; if (flags&SCRIPTVAR_NATIVE) flagstr = flagstr + "NATIVE "; if (flags&SCRIPTVAR_DOUBLE) flagstr = flagstr + "DOUBLE "; if (flags&SCRIPTVAR_INTEGER) flagstr = flagstr + "INTEGER "; if (flags&SCRIPTVAR_STRING) flagstr = flagstr + "STRING "; return flagstr; } string CScriptVar::getParsableString() { if (isNumeric()) return getString(); if (isFunction()) { ostringstream funcStr; funcStr << "function ("; CScriptVarLink *link = firstChild; while (link) { funcStr << link->name; if (link->nextSibling) funcStr << ","; link = link->nextSibling; } funcStr << ") " << getString(); return funcStr.str(); } if (isString()) return getJSString(getString()); if (isNull()) return "null"; return "undefined"; } void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) { if (isObject()) { string indentedLinePrefix = linePrefix+" "; destination << "{ \n"; CScriptVarLink *link = firstChild; while (link) { destination << indentedLinePrefix; destination << getJSString(link->name); destination << " : "; link->var->getJSON(destination, indentedLinePrefix); link = link->nextSibling; if (link) { destination << ",\n"; } } destination << "\n" << linePrefix << "}"; } else if (isArray()) { string indentedLinePrefix = linePrefix+" "; destination << "[\n"; int len = getArrayLength(); if (len>10000) len=10000; for (int i=0;i<len;i++) { getArrayIndex(i)->getJSON(destination, indentedLinePrefix); if (i<len-1) destination << ",\n"; } destination << "\n" << linePrefix << "]"; } else { destination << getParsableString(); } } void CScriptVar::setCallback(JSCallback callback, void *userdata) { jsCallback = callback; jsCallbackUserData = userdata; } CScriptVar *CScriptVar::ref() { refs++; return this; } void CScriptVar::unref() { if (refs<=0) printf("OMFG, we have unreffed too far!\n"); if ((--refs)==0) { delete this; } } int CScriptVar::getRefs() { return refs; } CTinyJS::CTinyJS() { l = 0; root = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); stringClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); arrayClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); objectClass = (new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); root->addChild("String", stringClass); root->addChild("Array", arrayClass); root->addChild("Object", objectClass); } CTinyJS::~CTinyJS() { ASSERT(!l); scopes.clear(); stringClass->unref(); arrayClass->unref(); objectClass->unref(); root->unref(); #if DEBUG_MEMORY show_allocated(); #endif } void CTinyJS::trace() { root->trace(); } void CTinyJS::execute(const string &code) { CScriptLex *oldLex = l; vector<CScriptVar*> oldScopes = scopes; l = new CScriptLex(code); #ifdef TINYJS_CALL_STACK call_stack.clear(); #endif scopes.clear(); scopes.push_back(root); try { bool execute = true; while (l->tk) statement(execute); } catch (CScriptException *e) { ostringstream msg; msg << "Error " << e->text; #ifdef TINYJS_CALL_STACK for (int i=(int)call_stack.size()-1;i>=0;i--) msg << "\n" << i << ": " << call_stack.at(i); #endif msg << " at " << l->getPosition(); delete l; l = oldLex; throw new CScriptException(msg.str()); } delete l; l = oldLex; scopes = oldScopes; } CScriptVarLink CTinyJS::evaluateComplex(const string &code) { CScriptLex *oldLex = l; vector<CScriptVar*> oldScopes = scopes; l = new CScriptLex(code); #ifdef TINYJS_CALL_STACK call_stack.clear(); #endif scopes.clear(); scopes.push_back(root); CScriptVarLink *v = 0; try { bool execute = true; do { CLEAN(v); v = base(execute); if (l->tk!=LEX_EOF) l->match(';'); } while (l->tk!=LEX_EOF); } catch (CScriptException *e) { ostringstream msg; msg << "Error " << e->text; #ifdef TINYJS_CALL_STACK for (int i=(int)call_stack.size()-1;i>=0;i--) msg << "\n" << i << ": " << call_stack.at(i); #endif msg << " at " << l->getPosition(); delete l; l = oldLex; throw new CScriptException(msg.str()); } delete l; l = oldLex; scopes = oldScopes; if (v) { CScriptVarLink r = *v; CLEAN(v); return r; } return CScriptVarLink(new CScriptVar()); } string CTinyJS::evaluate(const string &code) { return evaluateComplex(code).var->getString(); } void CTinyJS::parseFunctionArguments(CScriptVar *funcVar) { l->match('('); while (l->tk!=')') { funcVar->addChildNoDup(l->tkStr); l->match(LEX_ID); if (l->tk!=')') l->match(','); } l->match(')'); } void CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata) { CScriptLex *oldLex = l; l = new CScriptLex(funcDesc); CScriptVar *base = root; l->match(LEX_R_FUNCTION); string funcName = l->tkStr; l->match(LEX_ID); while (l->tk == '.') { l->match('.'); CScriptVarLink *link = base->findChild(funcName); if (!link) link = base->addChild(funcName, new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT)); base = link->var; funcName = l->tkStr; l->match(LEX_ID); } CScriptVar *funcVar = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE); funcVar->setCallback(ptr, userdata); parseFunctionArguments(funcVar); delete l; l = oldLex; base->addChild(funcName, funcVar); } CScriptVarLink *CTinyJS::parseFunctionDefinition() { l->match(LEX_R_FUNCTION); string funcName = TINYJS_TEMP_NAME; if (l->tk==LEX_ID) { funcName = l->tkStr; l->match(LEX_ID); } CScriptVarLink *funcVar = new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION), funcName); parseFunctionArguments(funcVar->var); int funcBegin = l->tokenStart; bool noexecute = false; block(noexecute); funcVar->var->data = l->getSubString(funcBegin); return funcVar; } CScriptVarLink *CTinyJS::functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent) { if (execute) { if (!function->var->isFunction()) { string errorMsg = "Expecting '"; errorMsg = errorMsg + function->name + "' to be a function"; throw new CScriptException(errorMsg.c_str()); } l->match('('); CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); if (parent) functionRoot->addChildNoDup("this", parent); CScriptVarLink *v = function->var->firstChild; while (v) { CScriptVarLink *value = base(execute); if (execute) { if (value->var->isBasic()) { functionRoot->addChild(v->name, value->var->deepCopy()); } else { functionRoot->addChild(v->name, value->var); } } CLEAN(value); if (l->tk!=')') l->match(','); v = v->nextSibling; } l->match(')'); CScriptVarLink *returnVar = NULL; CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); scopes.push_back(functionRoot); #ifdef TINYJS_CALL_STACK call_stack.push_back(function->name + " from " + l->getPosition()); #endif if (function->var->isNative()) { ASSERT(function->var->jsCallback); function->var->jsCallback(functionRoot, function->var->jsCallbackUserData); } else { CScriptException *exception = 0; CScriptLex *oldLex = l; CScriptLex *newLex = new CScriptLex(function->var->getString()); l = newLex; try { block(execute); execute = true; } catch (CScriptException *e) { exception = e; } delete newLex; l = oldLex; if (exception) throw exception; } #ifdef TINYJS_CALL_STACK if (!call_stack.empty()) call_stack.pop_back(); #endif scopes.pop_back(); returnVar = new CScriptVarLink(returnVarLink->var); functionRoot->removeLink(returnVarLink); delete functionRoot; if (returnVar) return returnVar; else return new CScriptVarLink(new CScriptVar()); } else { l->match('('); while (l->tk != ')') { CScriptVarLink *value = base(execute); CLEAN(value); if (l->tk!=')') l->match(','); } l->match(')'); if (l->tk == '{') { block(execute); } return function; } } CScriptVarLink *CTinyJS::factor(bool &execute) { if (l->tk=='(') { l->match('('); CScriptVarLink *a = base(execute); l->match(')'); return a; } if (l->tk==LEX_R_TRUE) { l->match(LEX_R_TRUE); return new CScriptVarLink(new CScriptVar(1)); } if (l->tk==LEX_R_FALSE) { l->match(LEX_R_FALSE); return new CScriptVarLink(new CScriptVar(0)); } if (l->tk==LEX_R_NULL) { l->match(LEX_R_NULL); return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_NULL)); } if (l->tk==LEX_R_UNDEFINED) { l->match(LEX_R_UNDEFINED); return new CScriptVarLink(new CScriptVar(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED)); } if (l->tk==LEX_ID) { CScriptVarLink *a = execute ? findInScopes(l->tkStr) : new CScriptVarLink(new CScriptVar()); CScriptVar *parent = 0; if (execute && !a) { a = new CScriptVarLink(new CScriptVar(), l->tkStr); } l->match(LEX_ID); while (l->tk=='(' || l->tk=='.' || l->tk=='[') { if (l->tk=='(') { a = functionCall(execute, a, parent); } else if (l->tk == '.') { l->match('.'); if (execute) { const string &name = l->tkStr; CScriptVarLink *child = a->var->findChild(name); if (!child) child = findInParentClasses(a->var, name); if (!child) { if (a->var->isArray() && name == "length") { int l = a->var->getArrayLength(); child = new CScriptVarLink(new CScriptVar(l)); } else if (a->var->isString() && name == "length") { int l = a->var->getString().size(); child = new CScriptVarLink(new CScriptVar(l)); } else { child = a->var->addChild(name); } } parent = a->var; a = child; } l->match(LEX_ID); } else if (l->tk == '[') { l->match('['); CScriptVarLink *index = base(execute); l->match(']'); if (execute) { CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString()); parent = a->var; a = child; } CLEAN(index); } else ASSERT(0); } return a; } if (l->tk==LEX_INT || l->tk==LEX_FLOAT) { CScriptVar *a = new CScriptVar(l->tkStr, ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:SCRIPTVAR_DOUBLE)); l->match(l->tk); return new CScriptVarLink(a); } if (l->tk==LEX_STR) { CScriptVar *a = new CScriptVar(l->tkStr, SCRIPTVAR_STRING); l->match(LEX_STR); return new CScriptVarLink(a); } if (l->tk=='{') { CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); l->match('{'); while (l->tk != '}') { string id = l->tkStr; if (l->tk==LEX_STR) l->match(LEX_STR); else l->match(LEX_ID); l->match(':'); if (execute) { CScriptVarLink *a = base(execute); contents->addChild(id, a->var); CLEAN(a); } if (l->tk != '}') l->match(','); } l->match('}'); return new CScriptVarLink(contents); } if (l->tk=='[') { CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY); l->match('['); int idx = 0; while (l->tk != ']') { if (execute) { char idx_str[16]; sprintf_s(idx_str, sizeof(idx_str), "%d",idx); CScriptVarLink *a = base(execute); contents->addChild(idx_str, a->var); CLEAN(a); } if (l->tk != ']') l->match(','); idx++; } l->match(']'); return new CScriptVarLink(contents); } if (l->tk==LEX_R_FUNCTION) { CScriptVarLink *funcVar = parseFunctionDefinition(); if (funcVar->name != TINYJS_TEMP_NAME) TRACE("Functions not defined at statement-level are not meant to have a name"); return funcVar; } if (l->tk==LEX_R_NEW) { l->match(LEX_R_NEW); const string &className = l->tkStr; if (execute) { CScriptVarLink *objClassOrFunc = findInScopes(className); if (!objClassOrFunc) { TRACE("%s is not a valid class name", className.c_str()); return new CScriptVarLink(new CScriptVar()); } l->match(LEX_ID); CScriptVar *obj = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); CScriptVarLink *objLink = new CScriptVarLink(obj); if (objClassOrFunc->var->isFunction()) { CLEAN(functionCall(execute, objClassOrFunc, obj)); } else { obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var); if (l->tk == '(') { l->match('('); l->match(')'); } } return objLink; } else { l->match(LEX_ID); if (l->tk == '(') { l->match('('); l->match(')'); } } } l->match(LEX_EOF); return 0; } CScriptVarLink *CTinyJS::unary(bool &execute) { CScriptVarLink *a; if (l->tk=='!') { l->match('!'); a = factor(execute); if (execute) { CScriptVar zero(0); CScriptVar *res = a->var->mathsOp(&zero, LEX_EQUAL); CREATE_LINK(a, res); } } else a = factor(execute); return a; } CScriptVarLink *CTinyJS::term(bool &execute) { CScriptVarLink *a = unary(execute); while (l->tk=='*' || l->tk=='/' || l->tk=='%') { int op = l->tk; l->match(l->tk); CScriptVarLink *b = unary(execute); if (execute) { CScriptVar *res = a->var->mathsOp(b->var, op); CREATE_LINK(a, res); } CLEAN(b); } return a; } CScriptVarLink *CTinyJS::expression(bool &execute) { bool negate = false; if (l->tk=='-') { l->match('-'); negate = true; } CScriptVarLink *a = term(execute); if (negate) { CScriptVar zero(0); CScriptVar *res = zero.mathsOp(a->var, '-'); CREATE_LINK(a, res); } while (l->tk=='+' || l->tk=='-' || l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS) { int op = l->tk; l->match(l->tk); if (op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) { if (execute) { CScriptVar one(1); CScriptVar *res = a->var->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-'); CScriptVarLink *oldValue = new CScriptVarLink(a->var); a->replaceWith(res); CLEAN(a); a = oldValue; } } else { CScriptVarLink *b = term(execute); if (execute) { CScriptVar *res = a->var->mathsOp(b->var, op); CREATE_LINK(a, res); } CLEAN(b); } } return a; } CScriptVarLink *CTinyJS::shift(bool &execute) { CScriptVarLink *a = expression(execute); if (l->tk==LEX_LSHIFT || l->tk==LEX_RSHIFT || l->tk==LEX_RSHIFTUNSIGNED) { int op = l->tk; l->match(op); CScriptVarLink *b = base(execute); int shift = execute ? b->var->getInt() : 0; CLEAN(b); if (execute) { if (op==LEX_LSHIFT) a->var->setInt(a->var->getInt() << shift); if (op==LEX_RSHIFT) a->var->setInt(a->var->getInt() >> shift); if (op==LEX_RSHIFTUNSIGNED) a->var->setInt(((unsigned int)a->var->getInt()) >> shift); } } return a; } CScriptVarLink *CTinyJS::condition(bool &execute) { CScriptVarLink *a = shift(execute); CScriptVarLink *b; while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL || l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL || l->tk=='<' || l->tk=='>') { int op = l->tk; l->match(l->tk); b = shift(execute); if (execute) { CScriptVar *res = a->var->mathsOp(b->var, op); CREATE_LINK(a,res); } CLEAN(b); } return a; } CScriptVarLink *CTinyJS::logic(bool &execute) { CScriptVarLink *a = condition(execute); CScriptVarLink *b; while (l->tk=='&' || l->tk=='|' || l->tk=='^' || l->tk==LEX_ANDAND || l->tk==LEX_OROR) { bool noexecute = false; int op = l->tk; l->match(l->tk); bool shortCircuit = false; bool boolean = false; if (op==LEX_ANDAND) { op = '&'; shortCircuit = !a->var->getBool(); boolean = true; } else if (op==LEX_OROR) { op = '|'; shortCircuit = a->var->getBool(); boolean = true; } b = condition(shortCircuit ? noexecute : execute); if (execute && !shortCircuit) { if (boolean) { CScriptVar *newa = new CScriptVar(a->var->getBool()); CScriptVar *newb = new CScriptVar(b->var->getBool()); CREATE_LINK(a, newa); CREATE_LINK(b, newb); } CScriptVar *res = a->var->mathsOp(b->var, op); CREATE_LINK(a, res); } CLEAN(b); } return a; } CScriptVarLink *CTinyJS::ternary(bool &execute) { CScriptVarLink *lhs = logic(execute); bool noexec = false; if (l->tk=='?') { l->match('?'); if (!execute) { CLEAN(lhs); CLEAN(base(noexec)); l->match(':'); CLEAN(base(noexec)); } else { bool first = lhs->var->getBool(); CLEAN(lhs); if (first) { lhs = base(execute); l->match(':'); CLEAN(base(noexec)); } else { CLEAN(base(noexec)); l->match(':'); lhs = base(execute); } } } return lhs; } CScriptVarLink *CTinyJS::base(bool &execute) { CScriptVarLink *lhs = ternary(execute); if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { if (execute && !lhs->owned) { if (lhs->name.length()>0) { CScriptVarLink *realLhs = root->addChildNoDup(lhs->name, lhs->var); CLEAN(lhs); lhs = realLhs; } else TRACE("Trying to assign to an un-named type\n"); } int op = l->tk; l->match(l->tk); CScriptVarLink *rhs = base(execute); if (execute) { if (op=='=') { lhs->replaceWith(rhs); } else if (op==LEX_PLUSEQUAL) { CScriptVar *res = lhs->var->mathsOp(rhs->var, '+'); lhs->replaceWith(res); } else if (op==LEX_MINUSEQUAL) { CScriptVar *res = lhs->var->mathsOp(rhs->var, '-'); lhs->replaceWith(res); } else ASSERT(0); } CLEAN(rhs); } return lhs; } void CTinyJS::block(bool &execute) { l->match('{'); if (execute) { while (l->tk && l->tk!='}') statement(execute); l->match('}'); } else { int brackets = 1; while (l->tk && brackets) { if (l->tk == '{') brackets++; if (l->tk == '}') brackets--; l->match(l->tk); } } } void CTinyJS::statement(bool &execute) { if (l->tk==LEX_ID || l->tk==LEX_INT || l->tk==LEX_FLOAT || l->tk==LEX_STR || l->tk=='-') { CLEAN(base(execute)); l->match(';'); } else if (l->tk=='{') { block(execute); } else if (l->tk==';') { l->match(';'); } else if (l->tk==LEX_R_VAR) { l->match(LEX_R_VAR); while (l->tk != ';') { CScriptVarLink *a = 0; if (execute) a = scopes.back()->findChildOrCreate(l->tkStr); l->match(LEX_ID); while (l->tk == '.') { l->match('.'); if (execute) { CScriptVarLink *lastA = a; a = lastA->var->findChildOrCreate(l->tkStr); } l->match(LEX_ID); } if (l->tk == '=') { l->match('='); CScriptVarLink *var = base(execute); if (execute) a->replaceWith(var); CLEAN(var); } if (l->tk != ';') l->match(','); } l->match(';'); } else if (l->tk==LEX_R_IF) { l->match(LEX_R_IF); l->match('('); CScriptVarLink *var = base(execute); l->match(')'); bool cond = execute && var->var->getBool(); CLEAN(var); bool noexecute = false; statement(cond ? execute : noexecute); if (l->tk==LEX_R_ELSE) { l->match(LEX_R_ELSE); statement(cond ? noexecute : execute); } } else if (l->tk==LEX_R_WHILE) { l->match(LEX_R_WHILE); l->match('('); int whileCondStart = l->tokenStart; bool noexecute = false; CScriptVarLink *cond = base(execute); bool loopCond = execute && cond->var->getBool(); CLEAN(cond); CScriptLex *whileCond = l->getSubLex(whileCondStart); l->match(')'); int whileBodyStart = l->tokenStart; statement(loopCond ? execute : noexecute); CScriptLex *whileBody = l->getSubLex(whileBodyStart); CScriptLex *oldLex = l; int loopCount = TINYJS_LOOP_MAX_ITERATIONS; while (loopCond && loopCount-->0) { whileCond->reset(); l = whileCond; cond = base(execute); loopCond = execute && cond->var->getBool(); CLEAN(cond); if (loopCond) { whileBody->reset(); l = whileBody; statement(execute); } } l = oldLex; delete whileCond; delete whileBody; if (loopCount<=0) { root->trace(); TRACE("WHILE Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str()); throw new CScriptException("LOOP_ERROR"); } } else if (l->tk==LEX_R_FOR) { l->match(LEX_R_FOR); l->match('('); statement(execute); int forCondStart = l->tokenStart; bool noexecute = false; CScriptVarLink *cond = base(execute); bool loopCond = execute && cond->var->getBool(); CLEAN(cond); CScriptLex *forCond = l->getSubLex(forCondStart); l->match(';'); int forIterStart = l->tokenStart; CLEAN(base(noexecute)); CScriptLex *forIter = l->getSubLex(forIterStart); l->match(')'); int forBodyStart = l->tokenStart; statement(loopCond ? execute : noexecute); CScriptLex *forBody = l->getSubLex(forBodyStart); CScriptLex *oldLex = l; if (loopCond) { forIter->reset(); l = forIter; CLEAN(base(execute)); } int loopCount = TINYJS_LOOP_MAX_ITERATIONS; while (execute && loopCond && loopCount-->0) { forCond->reset(); l = forCond; cond = base(execute); loopCond = cond->var->getBool(); CLEAN(cond); if (execute && loopCond) { forBody->reset(); l = forBody; statement(execute); } if (execute && loopCond) { forIter->reset(); l = forIter; CLEAN(base(execute)); } } l = oldLex; delete forCond; delete forIter; delete forBody; if (loopCount<=0) { root->trace(); TRACE("FOR Loop exceeded %d iterations at %s\n", TINYJS_LOOP_MAX_ITERATIONS, l->getPosition().c_str()); throw new CScriptException("LOOP_ERROR"); } } else if (l->tk==LEX_R_RETURN) { l->match(LEX_R_RETURN); CScriptVarLink *result = 0; if (l->tk != ';') result = base(execute); if (execute) { CScriptVarLink *resultVar = scopes.back()->findChild(TINYJS_RETURN_VAR); if (resultVar) resultVar->replaceWith(result); else TRACE("RETURN statement, but not in a function.\n"); execute = false; } CLEAN(result); l->match(';'); } else if (l->tk==LEX_R_FUNCTION) { CScriptVarLink *funcVar = parseFunctionDefinition(); if (execute) { if (funcVar->name == TINYJS_TEMP_NAME) TRACE("Functions defined at statement-level are meant to have a name\n"); else scopes.back()->addChildNoDup(funcVar->name, funcVar->var); } CLEAN(funcVar); } else l->match(LEX_EOF); } CScriptVar *CTinyJS::getScriptVariable(const string &path) { size_t prevIdx = 0; size_t thisIdx = path.find('.'); if (thisIdx == string::npos) thisIdx = path.length(); CScriptVar *var = root; while (var && prevIdx<path.length()) { string el = path.substr(prevIdx, thisIdx-prevIdx); CScriptVarLink *varl = var->findChild(el); var = varl?varl->var:0; prevIdx = thisIdx+1; thisIdx = path.find('.', prevIdx); if (thisIdx == string::npos) thisIdx = path.length(); } return var; } const string *CTinyJS::getVariable(const string &path) { CScriptVar *var = getScriptVariable(path); if (var) return &var->getString(); else return 0; } bool CTinyJS::setVariable(const std::string &path, const std::string &varData) { CScriptVar *var = getScriptVariable(path); if (var) { if (var->isInt()) var->setInt((int)strtol(varData.c_str(),0,0)); else if (var->isDouble()) var->setDouble(strtod(varData.c_str(),0)); else var->setString(varData.c_str()); return true; } else return false; } CScriptVarLink *CTinyJS::findInScopes(const std::string &childName) { for (int s=scopes.size()-1;s>=0;s--) { CScriptVarLink *v = scopes[s]->findChild(childName); if (v) return v; } return NULL; } CScriptVarLink *CTinyJS::findInParentClasses(CScriptVar *object, const std::string &name) { CScriptVarLink *parentClass = object->findChild(TINYJS_PROTOTYPE_CLASS); while (parentClass) { CScriptVarLink *implementation = parentClass->var->findChild(name); if (implementation) return implementation; parentClass = parentClass->var->findChild(TINYJS_PROTOTYPE_CLASS); } if (object->isString()) { CScriptVarLink *implementation = stringClass->findChild(name); if (implementation) return implementation; } if (object->isArray()) { CScriptVarLink *implementation = arrayClass->findChild(name); if (implementation) return implementation; } CScriptVarLink *implementation = objectClass->findChild(name); if (implementation) return implementation; return 0; } #ifndef TINYJS_H #define TINYJS_H #define TINYJS_CALL_STACK #ifdef _WIN32 #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> #endif #endif #include <string> #include <vector> #ifndef TRACE #define TRACE printf #endif const int TINYJS_LOOP_MAX_ITERATIONS = 8192; enum LEX_TYPES { LEX_EOF = 0, LEX_ID = 256, LEX_INT, LEX_FLOAT, LEX_STR, LEX_EQUAL, LEX_TYPEEQUAL, LEX_NEQUAL, LEX_NTYPEEQUAL, LEX_LEQUAL, LEX_LSHIFT, LEX_LSHIFTEQUAL, LEX_GEQUAL, LEX_RSHIFT, LEX_RSHIFTUNSIGNED, LEX_RSHIFTEQUAL, LEX_PLUSEQUAL, LEX_MINUSEQUAL, LEX_PLUSPLUS, LEX_MINUSMINUS, LEX_ANDEQUAL, LEX_ANDAND, LEX_OREQUAL, LEX_OROR, LEX_XOREQUAL, #define LEX_R_LIST_START LEX_R_IF LEX_R_IF, LEX_R_ELSE, LEX_R_DO, LEX_R_WHILE, LEX_R_FOR, LEX_R_BREAK, LEX_R_CONTINUE, LEX_R_FUNCTION, LEX_R_RETURN, LEX_R_VAR, LEX_R_TRUE, LEX_R_FALSE, LEX_R_NULL, LEX_R_UNDEFINED, LEX_R_NEW, LEX_R_LIST_END }; enum SCRIPTVAR_FLAGS { SCRIPTVAR_UNDEFINED = 0, SCRIPTVAR_FUNCTION = 1, SCRIPTVAR_OBJECT = 2, SCRIPTVAR_ARRAY = 4, SCRIPTVAR_DOUBLE = 8, SCRIPTVAR_INTEGER = 16, SCRIPTVAR_STRING = 32, SCRIPTVAR_NULL = 64, SCRIPTVAR_NATIVE = 128, SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL | SCRIPTVAR_DOUBLE | SCRIPTVAR_INTEGER, SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE | SCRIPTVAR_INTEGER | SCRIPTVAR_STRING | SCRIPTVAR_FUNCTION | SCRIPTVAR_OBJECT | SCRIPTVAR_ARRAY | SCRIPTVAR_NULL, }; #define TINYJS_RETURN_VAR "return" #define TINYJS_PROTOTYPE_CLASS "prototype" #define TINYJS_TEMP_NAME "" #define TINYJS_BLANK_DATA "" std::string getJSString(const std::string &str); class CScriptException { public: std::string text; CScriptException(const std::string &exceptionText); }; class CScriptLex { public: CScriptLex(const std::string &input); CScriptLex(CScriptLex *owner, int startChar, int endChar); ~CScriptLex(void); char currCh, nextCh; int tk; int tokenStart; int tokenEnd; int tokenLastEnd; std::string tkStr; void match(int expected_tk); static std::string getTokenStr(int token); void reset(); std::string getSubString(int pos); CScriptLex *getSubLex(int lastPosition); std::string getPosition(int pos=-1); protected: char *data; int dataStart, dataEnd; bool dataOwned; int dataPos; void getNextCh(); void getNextToken(); }; class CScriptVar; typedef void (*JSCallback)(CScriptVar *var, void *userdata); class CScriptVarLink { public: std::string name; CScriptVarLink *nextSibling; CScriptVarLink *prevSibling; CScriptVar *var; bool owned; CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); CScriptVarLink(const CScriptVarLink &link); ~CScriptVarLink(); void replaceWith(CScriptVar *newVar); void replaceWith(CScriptVarLink *newVar); int getIntName(); void setIntName(int n); }; class CScriptVar { public: CScriptVar(); CScriptVar(const std::string &varData, int varFlags); CScriptVar(const std::string &str); CScriptVar(double varData); CScriptVar(int val); ~CScriptVar(void); CScriptVar *getReturnVar(); void setReturnVar(CScriptVar *var); CScriptVar *getParameter(const std::string &name); CScriptVarLink *findChild(const std::string &childName); CScriptVarLink *findChildOrCreate(const std::string &childName, int varFlags=SCRIPTVAR_UNDEFINED); CScriptVarLink *findChildOrCreateByPath(const std::string &path); CScriptVarLink *addChild(const std::string &childName, CScriptVar *child=NULL); CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child=NULL); void removeChild(CScriptVar *child); void removeLink(CScriptVarLink *link); void removeAllChildren(); CScriptVar *getArrayIndex(int idx); void setArrayIndex(int idx, CScriptVar *value); int getArrayLength(); int getChildren(); int getInt(); bool getBool() { return getInt() != 0; } double getDouble(); const std::string &getString(); std::string getParsableString(); void setInt(int num); void setDouble(double val); void setString(const std::string &str); void setUndefined(); void setArray(); bool equals(CScriptVar *v); bool isInt() { return (flags&SCRIPTVAR_INTEGER)!=0; } bool isDouble() { return (flags&SCRIPTVAR_DOUBLE)!=0; } bool isString() { return (flags&SCRIPTVAR_STRING)!=0; } bool isNumeric() { return (flags&SCRIPTVAR_NUMERICMASK)!=0; } bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; } bool isObject() { return (flags&SCRIPTVAR_OBJECT)!=0; } bool isArray() { return (flags&SCRIPTVAR_ARRAY)!=0; } bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; } bool isUndefined() { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; } bool isNull() { return (flags & SCRIPTVAR_NULL)!=0; } bool isBasic() { return firstChild==0; } CScriptVar *mathsOp(CScriptVar *b, int op); void copyValue(CScriptVar *val); CScriptVar *deepCopy(); void trace(std::string indentStr = "", const std::string &name = ""); std::string getFlagsAsString(); void getJSON(std::ostringstream &destination, const std::string linePrefix=""); void setCallback(JSCallback callback, void *userdata); CScriptVarLink *firstChild; CScriptVarLink *lastChild; CScriptVar *ref(); void unref(); int getRefs(); protected: int refs; std::string data; long intData; double doubleData; int flags; JSCallback jsCallback; void *jsCallbackUserData; void init(); void copySimpleData(CScriptVar *val); friend class CTinyJS; }; class CTinyJS { public: CTinyJS(); ~CTinyJS(); void execute(const std::string &code); CScriptVarLink evaluateComplex(const std::string &code); std::string evaluate(const std::string &code); void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); CScriptVar *getScriptVariable(const std::string &path); const std::string *getVariable(const std::string &path); bool setVariable(const std::string &path, const std::string &varData); void trace(); CScriptVar *root; private: CScriptLex *l; std::vector<CScriptVar*> scopes; #ifdef TINYJS_CALL_STACK std::vector<std::string> call_stack; #endif CScriptVar *stringClass; CScriptVar *objectClass; CScriptVar *arrayClass; CScriptVarLink *functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent); CScriptVarLink *factor(bool &execute); CScriptVarLink *unary(bool &execute); CScriptVarLink *term(bool &execute); CScriptVarLink *expression(bool &execute); CScriptVarLink *shift(bool &execute); CScriptVarLink *condition(bool &execute); CScriptVarLink *logic(bool &execute); CScriptVarLink *ternary(bool &execute); CScriptVarLink *base(bool &execute); void block(bool &execute); void statement(bool &execute); CScriptVarLink *parseFunctionDefinition(); void parseFunctionArguments(CScriptVar *funcVar); CScriptVarLink *findInScopes(const std::string &childName); CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name); }; #endif #include "TinyJS_Functions.h" #include <math.h> #include <cstdlib> #include <sstream> using namespace std; void scTrace(CScriptVar *c, void *userdata) { CTinyJS *js = (CTinyJS*)userdata; js->root->trace(); } void scObjectDump(CScriptVar *c, void *) { c->getParameter("this")->trace("> "); } void scObjectClone(CScriptVar *c, void *) { CScriptVar *obj = c->getParameter("this"); c->getReturnVar()->copyValue(obj); } void scMathRand(CScriptVar *c, void *) { c->getReturnVar()->setDouble((double)rand()/RAND_MAX); } void scMathRandInt(CScriptVar *c, void *) { int min = c->getParameter("min")->getInt(); int max = c->getParameter("max")->getInt(); int val = min + (int)(rand()%(1+max-min)); c->getReturnVar()->setInt(val); } void scCharToInt(CScriptVar *c, void *) { string str = c->getParameter("ch")->getString();; int val = 0; if (str.length()>0) val = (int)str.c_str()[0]; c->getReturnVar()->setInt(val); } void scStringIndexOf(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); string search = c->getParameter("search")->getString(); size_t p = str.find(search); int val = (p==string::npos) ? -1 : p; c->getReturnVar()->setInt(val); } void scStringSubstring(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); int lo = c->getParameter("lo")->getInt(); int hi = c->getParameter("hi")->getInt(); int l = hi-lo; if (l>0 && lo>=0 && lo+l<=(int)str.length()) c->getReturnVar()->setString(str.substr(lo, l)); else c->getReturnVar()->setString(""); } void scStringCharAt(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); int p = c->getParameter("pos")->getInt(); if (p>=0 && p<(int)str.length()) c->getReturnVar()->setString(str.substr(p, 1)); else c->getReturnVar()->setString(""); } void scStringCharCodeAt(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); int p = c->getParameter("pos")->getInt(); if (p>=0 && p<(int)str.length()) c->getReturnVar()->setInt(str.at(p)); else c->getReturnVar()->setInt(0); } void scStringSplit(CScriptVar *c, void *) { string str = c->getParameter("this")->getString(); string sep = c->getParameter("separator")->getString(); CScriptVar *result = c->getReturnVar(); result->setArray(); int length = 0; size_t pos = str.find(sep); while (pos != string::npos) { result->setArrayIndex(length++, new CScriptVar(str.substr(0,pos))); str = str.substr(pos+1); pos = str.find(sep); } if (str.size()>0) result->setArrayIndex(length++, new CScriptVar(str)); } void scStringFromCharCode(CScriptVar *c, void *) { char str[2]; str[0] = c->getParameter("char")->getInt(); str[1] = 0; c->getReturnVar()->setString(str); } void scIntegerParseInt(CScriptVar *c, void *) { string str = c->getParameter("str")->getString(); int val = strtol(str.c_str(),0,0); c->getReturnVar()->setInt(val); } void scIntegerValueOf(CScriptVar *c, void *) { string str = c->getParameter("str")->getString(); int val = 0; if (str.length()==1) val = str[0]; c->getReturnVar()->setInt(val); } void scJSONStringify(CScriptVar *c, void *) { std::ostringstream result; c->getParameter("obj")->getJSON(result); c->getReturnVar()->setString(result.str()); } void scExec(CScriptVar *c, void *data) { CTinyJS *tinyJS = (CTinyJS *)data; std::string str = c->getParameter("jsCode")->getString(); tinyJS->execute(str); } void scEval(CScriptVar *c, void *data) { CTinyJS *tinyJS = (CTinyJS *)data; std::string str = c->getParameter("jsCode")->getString(); c->setReturnVar(tinyJS->evaluateComplex(str).var); } void scArrayContains(CScriptVar *c, void *data) { CScriptVar *obj = c->getParameter("obj"); CScriptVarLink *v = c->getParameter("this")->firstChild; bool contains = false; while (v) { if (v->var->equals(obj)) { contains = true; break; } v = v->nextSibling; } c->getReturnVar()->setInt(contains); } void scArrayRemove(CScriptVar *c, void *data) { CScriptVar *obj = c->getParameter("obj"); vector<int> removedIndices; CScriptVarLink *v; v = c->getParameter("this")->firstChild; while (v) { if (v->var->equals(obj)) { removedIndices.push_back(v->getIntName()); } v = v->nextSibling; } v = c->getParameter("this")->firstChild; while (v) { int n = v->getIntName(); int newn = n; for (size_t i=0;i<removedIndices.size();i++) if (n>=removedIndices[i]) newn--; if (newn!=n) v->setIntName(newn); v = v->nextSibling; } } void scArrayJoin(CScriptVar *c, void *data) { string sep = c->getParameter("separator")->getString(); CScriptVar *arr = c->getParameter("this"); ostringstream sstr; int l = arr->getArrayLength(); for (int i=0;i<l;i++) { if (i>0) sstr << sep; sstr << arr->getArrayIndex(i)->getString(); } c->getReturnVar()->setString(sstr.str()); } void registerFunctions(CTinyJS *tinyJS) { tinyJS->addNative("function exec(jsCode)", scExec, tinyJS); tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); tinyJS->addNative("function trace()", scTrace, tinyJS); tinyJS->addNative("function Object.dump()", scObjectDump, 0); tinyJS->addNative("function Object.clone()", scObjectClone, 0); tinyJS->addNative("function Math.rand()", scMathRand, 0); tinyJS->addNative("function Math.randInt(min, max)", scMathRandInt, 0); tinyJS->addNative("function charToInt(ch)", scCharToInt, 0); tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0); tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0); tinyJS->addNative("function String.split(separator)", scStringSplit, 0); tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); tinyJS->addNative("function Array.contains(obj)", scArrayContains, 0); tinyJS->addNative("function Array.remove(obj)", scArrayRemove, 0); tinyJS->addNative("function Array.join(separator)", scArrayJoin, 0); } #ifndef TINYJS_FUNCTIONS_H #define TINYJS_FUNCTIONS_H #include "TinyJS.h" extern void registerFunctions(CTinyJS *tinyJS); #endif #include <math.h> #include <cstdlib> #include <sstream> #include "TinyJS_MathFunctions.h" using namespace std; #define k_E exp(1.0) #define k_PI 3.1415926535897932384626433832795 #define F_ABS(a) ((a)>=0 ? (a) : (-(a))) #define F_MIN(a,b) ((a)>(b) ? (b) : (a)) #define F_MAX(a,b) ((a)>(b) ? (a) : (b)) #define F_SGN(a) ((a)>0 ? 1 : ((a)<0 ? -1 : 0 )) #define F_RNG(a,min,max) ((a)<(min) ? min : ((a)>(max) ? max : a )) #define F_ROUND(a) ((a)>0 ? (int) ((a)+0.5) : (int) ((a)-0.5) ) #define scIsInt(a) ( c->getParameter(a)->isInt() ) #define scIsDouble(a) ( c->getParameter(a)->isDouble() ) #define scGetInt(a) ( c->getParameter(a)->getInt() ) #define scGetDouble(a) ( c->getParameter(a)->getDouble() ) #define scReturnInt(a) ( c->getReturnVar()->setInt(a) ) #define scReturnDouble(a) ( c->getReturnVar()->setDouble(a) ) #ifdef _MSC_VER namespace { double asinh( const double &value ) { double returned; if(value>0) returned = log(value + sqrt(value * value + 1)); else returned = -log(-value + sqrt(value * value + 1)); return(returned); } double acosh( const double &value ) { double returned; if(value>0) returned = log(value + sqrt(value * value - 1)); else returned = -log(-value + sqrt(value * value - 1)); return(returned); } } #endif void scMathAbs(CScriptVar *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_ABS( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { scReturnDouble( F_ABS( scGetDouble("a") ) ); } } void scMathRound(CScriptVar *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_ROUND( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { scReturnDouble( F_ROUND( scGetDouble("a") ) ); } } void scMathMin(CScriptVar *c, void *userdata) { if ( (scIsInt("a")) && (scIsInt("b")) ) { scReturnInt( F_MIN( scGetInt("a"), scGetInt("b") ) ); } else { scReturnDouble( F_MIN( scGetDouble("a"), scGetDouble("b") ) ); } } void scMathMax(CScriptVar *c, void *userdata) { if ( (scIsInt("a")) && (scIsInt("b")) ) { scReturnInt( F_MAX( scGetInt("a"), scGetInt("b") ) ); } else { scReturnDouble( F_MAX( scGetDouble("a"), scGetDouble("b") ) ); } } void scMathRange(CScriptVar *c, void *userdata) { if ( (scIsInt("x")) ) { scReturnInt( F_RNG( scGetInt("x"), scGetInt("a"), scGetInt("b") ) ); } else { scReturnDouble( F_RNG( scGetDouble("x"), scGetDouble("a"), scGetDouble("b") ) ); } } void scMathSign(CScriptVar *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_SGN( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { scReturnDouble( F_SGN( scGetDouble("a") ) ); } } void scMathPI(CScriptVar *c, void *userdata) { scReturnDouble(k_PI); } void scMathToDegrees(CScriptVar *c, void *userdata) { scReturnDouble( (180.0/k_PI)*( scGetDouble("a") ) ); } void scMathToRadians(CScriptVar *c, void *userdata) { scReturnDouble( (k_PI/180.0)*( scGetDouble("a") ) ); } void scMathSin(CScriptVar *c, void *userdata) { scReturnDouble( sin( scGetDouble("a") ) ); } void scMathASin(CScriptVar *c, void *userdata) { scReturnDouble( asin( scGetDouble("a") ) ); } void scMathCos(CScriptVar *c, void *userdata) { scReturnDouble( cos( scGetDouble("a") ) ); } void scMathACos(CScriptVar *c, void *userdata) { scReturnDouble( acos( scGetDouble("a") ) ); } void scMathTan(CScriptVar *c, void *userdata) { scReturnDouble( tan( scGetDouble("a") ) ); } void scMathATan(CScriptVar *c, void *userdata) { scReturnDouble( atan( scGetDouble("a") ) ); } void scMathSinh(CScriptVar *c, void *userdata) { scReturnDouble( sinh( scGetDouble("a") ) ); } void scMathASinh(CScriptVar *c, void *userdata) { scReturnDouble( asinh( (long double)scGetDouble("a") ) ); } void scMathCosh(CScriptVar *c, void *userdata) { scReturnDouble( cosh( scGetDouble("a") ) ); } void scMathACosh(CScriptVar *c, void *userdata) { scReturnDouble( acosh( (long double)scGetDouble("a") ) ); } void scMathTanh(CScriptVar *c, void *userdata) { scReturnDouble( tanh( scGetDouble("a") ) ); } void scMathATanh(CScriptVar *c, void *userdata) { scReturnDouble( atan( scGetDouble("a") ) ); } void scMathE(CScriptVar *c, void *userdata) { scReturnDouble(k_E); } void scMathLog(CScriptVar *c, void *userdata) { scReturnDouble( log( scGetDouble("a") ) ); } void scMathLog10(CScriptVar *c, void *userdata) { scReturnDouble( log10( scGetDouble("a") ) ); } void scMathExp(CScriptVar *c, void *userdata) { scReturnDouble( exp( scGetDouble("a") ) ); } void scMathPow(CScriptVar *c, void *userdata) { scReturnDouble( pow( scGetDouble("a"), scGetDouble("b") ) ); } void scMathSqr(CScriptVar *c, void *userdata) { scReturnDouble( ( scGetDouble("a") * scGetDouble("a") ) ); } void scMathSqrt(CScriptVar *c, void *userdata) { scReturnDouble( sqrtf( scGetDouble("a") ) ); } void registerMathFunctions(CTinyJS *tinyJS) { tinyJS->addNative("function Math.abs(a)", scMathAbs, 0); tinyJS->addNative("function Math.round(a)", scMathRound, 0); tinyJS->addNative("function Math.min(a,b)", scMathMin, 0); tinyJS->addNative("function Math.max(a,b)", scMathMax, 0); tinyJS->addNative("function Math.range(x,a,b)", scMathRange, 0); tinyJS->addNative("function Math.sign(a)", scMathSign, 0); tinyJS->addNative("function Math.PI()", scMathPI, 0); tinyJS->addNative("function Math.toDegrees(a)", scMathToDegrees, 0); tinyJS->addNative("function Math.toRadians(a)", scMathToRadians, 0); tinyJS->addNative("function Math.sin(a)", scMathSin, 0); tinyJS->addNative("function Math.asin(a)", scMathASin, 0); tinyJS->addNative("function Math.cos(a)", scMathCos, 0); tinyJS->addNative("function Math.acos(a)", scMathACos, 0); tinyJS->addNative("function Math.tan(a)", scMathTan, 0); tinyJS->addNative("function Math.atan(a)", scMathATan, 0); tinyJS->addNative("function Math.sinh(a)", scMathSinh, 0); tinyJS->addNative("function Math.asinh(a)", scMathASinh, 0); tinyJS->addNative("function Math.cosh(a)", scMathCosh, 0); tinyJS->addNative("function Math.acosh(a)", scMathACosh, 0); tinyJS->addNative("function Math.tanh(a)", scMathTanh, 0); tinyJS->addNative("function Math.atanh(a)", scMathATanh, 0); tinyJS->addNative("function Math.E()", scMathE, 0); tinyJS->addNative("function Math.log(a)", scMathLog, 0); tinyJS->addNative("function Math.log10(a)", scMathLog10, 0); tinyJS->addNative("function Math.exp(a)", scMathExp, 0); tinyJS->addNative("function Math.pow(a,b)", scMathPow, 0); tinyJS->addNative("function Math.sqr(a)", scMathSqr, 0); tinyJS->addNative("function Math.sqrt(a)", scMathSqrt, 0); } #ifndef TINYJS_MATHFUNCTIONS_H #define TINYJS_MATHFUNCTIONS_H #include "TinyJS.h" extern void registerMathFunctions(CTinyJS *tinyJS); #endif