#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