From f9e358d3b84ed6d3a932dfb71dbe964ce7659913 Mon Sep 17 00:00:00 2001 From: pur3mail Date: Sun, 6 Sep 2009 14:53:42 +0000 Subject: [PATCH] Version 0.12 : Added findChildOrCreate, changed string passing to use references Fixed broken string encoding in getJSString() Removed getInitCode and added getJSON instead Added nil Added rough JSON parsing Improved example app --- Script.cpp | 127 ++- TinyJS.cpp | 2504 ++++++++++++++++++++++-------------------- TinyJS.h | 459 ++++---- TinyJS_Functions.cpp | 172 +-- TinyJS_Functions.h | 68 +- 5 files changed, 1709 insertions(+), 1621 deletions(-) diff --git a/Script.cpp b/Script.cpp index b967728..91c8076 100755 --- a/Script.cpp +++ b/Script.cpp @@ -1,62 +1,71 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * This is a simple program showing how to use TinyJS + */ + +#include +#include "TinyJS.h" +#include "TinyJS_Functions.h" + +//const char *code = "var a = 5; if (a==5) a=4; else a=3;"; +//const char *code = "{ var a = 4; var b = 1; while (a>0) { b = b * 2; a = a - 1; } var c = 5; }"; +//const char *code = "{ var b = 1; for (var i=0;i<4;i=i+1) b = b * 2; }"; +const char *code = "function myfunc(x, y) { return x + y; } var a = myfunc(1,2); print(a);"; + +void js_print(CScriptVar *v) { + printf("> %s\n", v->findChild("text")->getString().c_str()); +} -/* - * This is a simple program showing how to use TinyJS - */ +void js_dump(CScriptVar *v) { + v->getRoot()->trace("> "); +} -#include -#include "TinyJS.h" -#include "TinyJS_Functions.h" - -//const char *code = "var a = 5; if (a==5) a=4; else a=3;"; -//const char *code = "{ var a = 4; var b = 1; while (a>0) { b = b * 2; a = a - 1; } var c = 5; }"; -//const char *code = "{ var b = 1; for (var i=0;i<4;i=i+1) b = b * 2; }"; -const char *code = "function myfunc(x, y) { return x + y; } var a = myfunc(1,2); print(a);"; - -void js_print(CScriptVar *v) { - printf(">%s\n", v->findChild("text")->getString().c_str()); -} - -int main(int argc, char **argv) + +int main(int argc, char **argv) { - try { - CTinyJS s; - - /* add the functions from TinyJS_Functions.cpp */ - registerFunctions(&s); - /* Add a native function */ - s.addNative("function print(text)", &js_print); - /* Execute out bit of code - we could call 'evaluate' here if - we wanted something returned */ - s.execute(code); - - printf("Symbol table contents:\n"); - s.trace(); - } catch (CScriptException *e) { - printf("ERROR: %s\n", e->text.c_str()); - } - return 0; -} + CTinyJS s; + /* add the functions from TinyJS_Functions.cpp */ + registerFunctions(&s); + /* Add a native function */ + s.addNative("function print(text)", &js_print); + s.addNative("function dump()", &js_dump); + /* Execute out bit of code - we could call 'evaluate' here if + we wanted something returned */ + s.execute("var lets_quit = 0; function quit() { lets_quit = 1; }"); + s.execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");"); + + while (s.evaluate("lets_quit") == "0") { + char buffer[2048]; + fgets ( buffer, sizeof(buffer), stdin ); + try { + s.execute(buffer); + } catch (CScriptException *e) { + printf("ERROR: %s\n", e->text.c_str()); + } + } + return 0; +} diff --git a/TinyJS.cpp b/TinyJS.cpp index 556d042..3d36310 100755 --- a/TinyJS.cpp +++ b/TinyJS.cpp @@ -1,1215 +1,1291 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -/* Version 0.1: (gw) First published on Google Code - * - */ - -#include "TinyJS.h" -#include - -#define ASSERT(X) assert(X) -#define CLEAN(x) { CScriptVar *__v = x; if (__v && !__v->parent) delete __v; } - -#include -#include -#include +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Version 0.1 : (gw) First published on Google Code + Version 0.11 : Making sure the 'root' variable never changes + 'symbol_base' added for the current base of the sybmbol table + Version 0.12 : Added findChildOrCreate, changed string passing to use references + Fixed broken string encoding in getJSString() + Removed getInitCode and added getJSON instead + Added nil + Added rough JSON parsing + Improved example app + */ + +#include "TinyJS.h" +#include + +#define ASSERT(X) assert(X) +#define CLEAN(x) { CScriptVar *__v = x; if (__v && !__v->parent) delete __v; } + +#include +#include +#include #include - -using namespace std; - -#ifdef __GNUC__ -#define vsprintf_s vsnprintf -#define sprintf_s snprintf -#endif - -// ----------------------------------------------------------------------------------- Utils -bool isWhitespace(char ch) { - return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); -} - -bool isNumeric(char ch) { - return (ch>='0') && (ch<='9'); -} -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); - } -} - -/// convert the given string into a quoted string suitable for javascript -std::string getJSString(const std::string &str) { - std::string nStr = str; - replace(nStr, '\\', "\\"); - replace(nStr, '\n', "\n"); - replace(nStr, '"', "\""); - return "\"" + str + "\""; -} - -// ----------------------------------------------------------------------------------- CSCRIPTEXCEPTION - -CScriptException::CScriptException(const string &exceptionText) { - text = exceptionText; -} - -// ----------------------------------------------------------------------------------- CSCRIPTLEX - -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; - tokenPosition = 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(tokenPosition) << "in '" << data << "'"; - throw new CScriptException(errorString.str()); - } - getNextToken(); -} - -string CScriptLex::getTokenStr(int token) { - if (token>32 && token<128) { - char buf[4] = "' '"; - buf[1] = 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_NEQUAL : return "!="; - case LEX_LEQUAL : return "<="; - case LEX_GEQUAL : return ">="; - case LEX_PLUSEQUAL : return "+="; - case LEX_MINUSEQUAL : return "-="; - case LEX_PLUSPLUS : return "++"; - case LEX_MINUSMINUS : return "--"; - case LEX_ANDAND : return "&&"; - case LEX_OROR : return "||"; - // reserved words - case LEX_R_IF : return "if"; - case LEX_R_ELSE : return "else"; - case LEX_R_WHILE : return "while"; - case LEX_R_FOR : return "for"; - 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"; - } - - ostringstream msg; - msg << "?[" << token << "]"; - return msg.str(); -} - -void CScriptLex::getNextCh() { - currCh = nextCh; - if (dataPos < dataEnd) - nextCh = data[dataPos++]; - else - nextCh = 0; -} - -void CScriptLex::getNextToken() { - tk = LEX_EOF; - tkStr.clear(); - while (currCh && isWhitespace(currCh)) getNextCh(); - // newline comments - if (currCh=='/' && nextCh=='/') { - while (currCh && currCh!='\n') getNextCh(); - getNextCh(); - getNextToken(); - return; - } - // block comments - if (currCh=='/' && nextCh=='*') { - while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); - getNextCh(); - getNextCh(); - getNextToken(); - return; - } - // record beginning of this token - tokenPosition = dataPos-2; - // tokens - if (isAlpha(currCh)) { // IDs - 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=="while") tk = LEX_R_WHILE; - else if (tkStr=="for") tk = LEX_R_FOR; - 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; - return; - } - if (isNumeric(currCh)) { // Numbers - tk = LEX_INT; - while (isNumeric(currCh)) { - tkStr += currCh; - getNextCh(); - } - if (currCh=='.') { - tk = LEX_FLOAT; - tkStr += '.'; - getNextCh(); - while (isNumeric(currCh)) { - tkStr += currCh; - getNextCh(); - } - } - return; - } - if (currCh=='"') { - getNextCh(); - while (currCh && currCh!='"') { - if (currCh == '\\') { - getNextCh(); - switch (currCh) { - case 'n' : tkStr += '\n'; break; - case '"' : tkStr += '"'; break; - default: tkStr += currCh; - } - } else { - tkStr += currCh; - } - getNextCh(); - } - getNextCh(); - tk = LEX_STR; - return; - } - // single chars - tk = currCh; - getNextCh(); - if (tk=='=' && currCh=='=') { - tk = LEX_EQUAL; - getNextCh(); - } else if (tk=='!' && currCh=='=') { - tk = LEX_NEQUAL; - getNextCh(); - } else if (tk=='<' && currCh=='=') { - tk = LEX_LEQUAL; - getNextCh(); - } else if (tk=='>' && currCh=='=') { - tk = LEX_GEQUAL; - 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_ANDAND; - getNextCh(); - } else if (tk=='|' && currCh=='|') { - tk = LEX_OROR; - getNextCh(); - } -} - -string CScriptLex::getSubString(int lastPosition) { - if (dataPos < dataEnd) { - /* save a memory alloc by using our data array to create the - substring */ - char old = data[tokenPosition]; - data[tokenPosition] = 0; - std::string value = &data[lastPosition]; - data[tokenPosition] = old; - return value; - } else { - return std::string(&data[lastPosition]); - } -} - - -CScriptLex *CScriptLex::getSubLex(int lastPosition) { - if (dataPos < dataEnd) - return new CScriptLex( this, lastPosition, tokenPosition); - else - return new CScriptLex( this, lastPosition, dataEnd ); -} - -string CScriptLex::getPosition(int pos) { - int line = 1,col = 1; - for (int i=0;iname.compare(childName)==0) - return v; - v = v->nextSibling; - } - return 0; -} - -CScriptVar *CScriptVar::getChild(string childName) { - CScriptVar *v = findChild(childName); - if (!v) { - ostringstream msg; - msg << "Node '" << childName << "' could notbe found"; - throw new CScriptException(msg.str()); - } - return v; -} - -CScriptVar *CScriptVar::findRecursive(string childName) { - CScriptVar *v = findChild(childName); - if (!v && parent) - v = parent->findRecursive(childName); - return v; -} - -void CScriptVar::addChild(CScriptVar *child) { - ASSERT(!child->parent && !child->nextSibling && !child->prevSibling); - child->parent = this; - if (lastChild) { - lastChild->nextSibling = child; - child->prevSibling = lastChild; - lastChild = child; - } else { - firstChild = child; - lastChild = child; - } -} - -CScriptVar *CScriptVar::addChildNoDup(CScriptVar *child) { - ASSERT(!child->parent); - CScriptVar *v = findChild(child->getName()); - if (v) { - v->copyValue(child); - delete child; - } else { - addChild(child); - v = child; - } - - return v; -} - -void CScriptVar::removeChild(CScriptVar *child) { - ASSERT(child->parent == this); - if (child->nextSibling) - child->nextSibling->prevSibling = child->prevSibling; - if (child->prevSibling) - child->prevSibling->nextSibling = child->nextSibling; - if (lastChild == child) - lastChild = child->prevSibling; - if (firstChild == child) - firstChild = child->nextSibling; - child->prevSibling = 0; - child->nextSibling = 0; - child->parent = 0; -} - -void CScriptVar::removeAllChildren() { - CScriptVar *c = firstChild; - while (c) { - CScriptVar *t = c->nextSibling; - delete c; - c = t; - } - firstChild = 0; - lastChild = 0; -} - -CScriptVar *CScriptVar::getRoot() { - CScriptVar *p = this; - while (p->parent) p = p->parent; - return p; -} - -string CScriptVar::getName() { - return name; -} - -int CScriptVar::getInt() { - return atoi(data.c_str()); -} - -double CScriptVar::getDouble() { - return atof(data.c_str()); -} - -string &CScriptVar::getString() { - return data; -} - -void CScriptVar::setInt(int val) { - char buf[256]; - sprintf(buf, "%d", val); - data = buf; - flags = SCRIPTVAR_NUMERIC | SCRIPTVAR_INTEGER; -} - -void CScriptVar::setDouble(double val) { - char buf[256]; - sprintf(buf, "%lf", val); - data = buf; - flags = SCRIPTVAR_NUMERIC; -} - -void CScriptVar::setString(string str) { - // name sure it's not still a number or integer - flags &= ~SCRIPTVAR_VARTYPEMASK; - data = str; -} - -void CScriptVar::setVoid() { - // name sure it's not still a number or integer - flags &= ~SCRIPTVAR_VARTYPEMASK; - data = TINYJS_BLANK_DATA; - removeAllChildren(); -} - -CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) { - CScriptVar *a = this; - // if these vars have nothing in them and we add a number... - if (a->data.empty() && b->isNumeric()) - a->setInt(0); - if (b->data.empty() && a->isNumeric()) - b->setInt(0); - // do maths... - if (a->isNumeric() && b->isNumeric()) { - if (a->isInt() && b->isInt()) { - // use ints - 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': return new CScriptVar(da>db); - case LEX_GEQUAL: return new CScriptVar(da>=db); - default: throw new CScriptException("This operation not supported on the int datatype"); - } - } else { - // use doubles - 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': return new CScriptVar(da>db); - case LEX_GEQUAL: return new CScriptVar(da>=db); - default: throw new CScriptException("This operation not supported on the double datatype"); - } - } - } else { - string da = a->getString(); - string db = b->getString(); - // use strings - switch (op) { - case '+': return new CScriptVar(TINYJS_TEMP_NAME, da+db); - case LEX_EQUAL: return new CScriptVar(da==db); - case LEX_NEQUAL: return new CScriptVar(da!=db); - case '<': return new CScriptVar(da': return new CScriptVar(da>db); - case LEX_GEQUAL: return new CScriptVar(da>=db); - default: throw new CScriptException("This operation not supported on the string datatype"); - } - } - ASSERT(0); - return 0; -} - -void CScriptVar::copyValue(CScriptVar *val) { - if (val) { - data = val->data; - flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK); - // copy children - removeAllChildren(); - CScriptVar *child = val->firstChild; - while (child) { - addChild(child->deepCopy()); - child = child->nextSibling; - } - } else { - setVoid(); - } -} - -CScriptVar *CScriptVar::deepCopy() { - CScriptVar *newVar = new CScriptVar(name, data, flags); - // copy children - CScriptVar *child = firstChild; - while (child) { - newVar->addChild(child->deepCopy()); - child = child->nextSibling; - } - return newVar; -} - -void CScriptVar::trace(string indentStr) { - TRACE("%s'%s' = '%s'(%d)\n", indentStr.c_str(), name.c_str(), data.c_str(), flags); - string indent = indentStr+" "; - CScriptVar *child = firstChild; - while (child) { - child->trace(indent); - child = child->nextSibling; - } -} -void CScriptVar::getInitialiseCode(ostringstream &destination, const string &prefix) { - bool isID = isIDString(name.c_str()); - // add this, so write the code to reference the var - if (isID) { - destination << "var "; - if (!prefix.empty()) - destination << prefix << '.'; - destination << name << " = "; - } else { - destination << prefix << "[" << getJSString(name) << "] = "; - } - // now write the value - if (isNumeric()) - destination << data; - else - destination << getJSString(data); - destination << ";\n";; - // work out new prefix - string newPrefix = name; - if (!prefix.empty()) - newPrefix = prefix + "." + newPrefix; - // add children - CScriptVar *child = firstChild; - while (child) { - child->getInitialiseCode(destination, newPrefix); - child = child->nextSibling; - } -} - - -void CScriptVar::setCallback(JSCallback callback) { - jsCallback = callback; -} - -// ----------------------------------------------------------------------------------- CSCRIPT - -CTinyJS::CTinyJS() { - l = 0; - root = symbol_base = new CScriptVar("root", ""); -} - -CTinyJS::~CTinyJS() { - ASSERT(!l); - symbol_base = 0; - delete root; -} - -void CTinyJS::trace() { - root->trace(); -} - -void CTinyJS::execute(const string &code) { - CScriptLex *oldLex = l; - l = new CScriptLex(code); - symbol_base = root; - try { - bool execute = true; - while (l->tk) statement(execute); - } catch (CScriptException *e) { - ostringstream msg; - msg << "Error " << e->text << " at " << l->getPosition(l->tokenPosition); - delete l; - l = oldLex; - throw new CScriptException(msg.str()); - } - delete l; - l = oldLex; -} - -string CTinyJS::evaluate(const string &code) { - CScriptLex *oldLex = l; - l = new CScriptLex(code); - symbol_base = root; - CScriptVar *v = 0; - try { - bool execute = true; - v = base(execute); - } catch (CScriptException *e) { - ostringstream msg; - msg << "Error " << e->text << " at " << l->getPosition(l->tokenPosition); - delete l; - l = oldLex; - throw new CScriptException(msg.str()); - } - delete l; - l = oldLex; - - string result = v ? v->getString() : ""; - CLEAN(v); - - return result; -} - -void CTinyJS::addNative(const string &funcDesc, JSCallback ptr) { - CScriptLex *oldLex = l; - l = new CScriptLex(funcDesc); - l->match(LEX_R_FUNCTION); - CScriptVar *funcVar = new CScriptVar(l->tkStr, "", SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE); - funcVar->setCallback(ptr); - l->match(LEX_ID); - l->match('('); - while (l->tk!=')') { - CScriptVar *funcParam = new CScriptVar(l->tkStr, "", SCRIPTVAR_PARAMETER); - funcVar->addChildNoDup(funcParam); - l->match(LEX_ID); - if (l->tk!=')') l->match(','); - } - l->match(')'); - delete l; - l = oldLex; - - root->addChild(funcVar); -} - -CScriptVar *CTinyJS::factor(bool &execute) { - if (l->tk=='(') { - l->match('('); - CScriptVar *a = base(execute); - l->match(')'); - return a; - } - if (l->tk==LEX_R_TRUE) { - l->match(LEX_R_TRUE); - return new CScriptVar(1); - } - if (l->tk==LEX_R_FALSE) { - l->match(LEX_R_FALSE); - return new CScriptVar(0); - } - if (l->tk==LEX_ID) { - CScriptVar *a = symbol_base->findRecursive(l->tkStr); - if (!a) { - if (execute) { - ostringstream msg; - msg << "Unknown ID '" << l->tkStr << "'"; - throw new CScriptException(msg.str()); - } - a = new CScriptVar(l->tkStr, ""); - } - l->match(LEX_ID); - while (l->tk=='(' || l->tk=='.' || l->tk=='[') { - if (l->tk=='(') { // ------------------------------------- Function Call - if (!a->isFunction()) { - string errorMsg = "Expecting '"; - errorMsg = errorMsg + a->getName() + "' to be a function"; - throw new CScriptException(errorMsg.c_str()); - } - l->match('('); - // create a new symbol table entry for execution of this function - CScriptVar *functionRoot = new CScriptVar("__EXECUTESTACK__", "", SCRIPTVAR_FUNCTION); - // grab in all parameters - CScriptVar *v = a->firstChild; - while (v) { - if (v->isParameter()) { - CScriptVar *value = base(execute); - if (execute) { - CScriptVar *param = new CScriptVar(v->getName(), "", SCRIPTVAR_PARAMETER); - param->copyValue(value); - functionRoot->addChild(param); - } - CLEAN(value); - if (l->tk!=')') l->match(','); - } - v = v->nextSibling; - } - l->match(')'); - // setup a return variable - CScriptVar *returnVar = new CScriptVar(TINYJS_RETURN_VAR, ""); - // execute function! - if (execute) { - // add the function's execute space to the symbol table so we can recurse - CScriptVar *old_symbol_base = symbol_base; - symbol_base->addChild(functionRoot); - functionRoot->addChild(returnVar); - symbol_base = functionRoot; - - if (a->isNative()) { - ASSERT(a->jsCallback); - a->jsCallback(functionRoot); - } else { - CScriptLex *oldLex = l; - fflush(stdout); - l = new CScriptLex(a->getString()); - block(execute); - // because return will probably have called this, and set execute to false - execute = true; - delete l; - l = oldLex; - } - symbol_base = old_symbol_base; - symbol_base->removeChild(functionRoot); - functionRoot->removeChild(returnVar); - } - delete functionRoot; - a = returnVar; - } else if (l->tk == '.') { // ------------------------------------- Record Access - l->match('.'); - CScriptVar *child = a->findRecursive(l->tkStr); - if (!child) { - // TODO: Error here if this doesn't exist?? - child = new CScriptVar(l->tkStr, ""); - a->addChild(child); - } - l->match(LEX_ID); - a = child; - } else if (l->tk == '[') { // ------------------------------------- Array Access - l->match('['); - CScriptVar *index = expression(execute); - l->match(']'); - CScriptVar *child = a->findRecursive(index->getString()); - if (!child) { - child = new CScriptVar(index->getString(), ""); - a->addChild(child); - } - CLEAN(index); - - a = child; - } else ASSERT(0); - - // return value? - } - return a; - } - if (l->tk==LEX_INT || l->tk==LEX_FLOAT) { - CScriptVar *a = new CScriptVar(TINYJS_TEMP_NAME, l->tkStr, SCRIPTVAR_NUMERIC | ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:0)); - l->match(l->tk); - return a; - } - if (l->tk==LEX_STR) { - CScriptVar *a = new CScriptVar(TINYJS_TEMP_NAME, l->tkStr, 0); - l->match(LEX_STR); - return a; - } - l->match(LEX_EOF); - return new CScriptVar(); -} - -CScriptVar *CTinyJS::unary(bool &execute) { - CScriptVar *a; - if (l->tk=='!') { - l->match('!'); // negation - a = factor(execute); - if (execute) { - CScriptVar zero(0); - CScriptVar *res = a->mathsOp(&zero, LEX_EQUAL); - CLEAN(a); - a = res; - } - } else - a = factor(execute); - return a; -} - -CScriptVar *CTinyJS::term(bool &execute) { - CScriptVar *a = unary(execute); - while (l->tk=='*' || l->tk=='/') { - int op = l->tk; - l->match(l->tk); - CScriptVar *b = unary(execute); - if (execute) { - CScriptVar *res = a->mathsOp(b, op); - CLEAN(a); - CLEAN(b); - a = res; - } else { - CLEAN(b); - } - } - return a; -} - -CScriptVar *CTinyJS::expression(bool &execute) { - bool negate = false; - if (l->tk=='-') { - l->match('-'); - negate = true; - } - CScriptVar *a = term(execute); - if (negate) { - CScriptVar *zero = new CScriptVar(0); - CScriptVar *res = zero->mathsOp(a, '-'); - CLEAN(a); - CLEAN(zero); - 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->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-'); - a->copyValue(res); - CLEAN(res); - } - } else { - CScriptVar *b = term(execute); - if (execute) { - CScriptVar *res = a->mathsOp(b, op); - CLEAN(a); - CLEAN(b); - a = res; - } else { - CLEAN(b); - } - } - } - return a; -} - -CScriptVar *CTinyJS::condition(bool &execute) { - CScriptVar *a = expression(execute); - CScriptVar *b; - while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || - l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL || - l->tk=='<' || l->tk=='>') { - int op = l->tk; - l->match(l->tk); - b = expression(execute); - if (execute) { - CScriptVar *res = a->mathsOp(b, op); - CLEAN(a); - CLEAN(b); - a = res; - } else { - CLEAN(b); - } - } - return a; -} - -CScriptVar *CTinyJS::logic(bool &execute) { - CScriptVar *a = condition(execute); - CScriptVar *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 we have short-circuit ops, then if we know the outcome - // we don't bother to execute the other op. Even if not - // we need to tell mathsOp it's an & or | - if (op==LEX_ANDAND) { - op = '&'; - shortCircuit = !a->getBool(); - boolean = true; - } else if (op==LEX_OROR) { - op = '|'; - shortCircuit = a->getBool(); - boolean = true; - } - b = condition(shortCircuit ? noexecute : execute); - if (execute && !shortCircuit) { - if (boolean) { - CScriptVar *newa = new CScriptVar(a->getBool()); - CScriptVar *newb = new CScriptVar(b->getBool()); - CLEAN(a); - CLEAN(b); - a = newa; - b = newb; - } - CScriptVar *res = a->mathsOp(b, op); - CLEAN(a); - CLEAN(b); - a = res; - } else { - CLEAN(b); - } - } - return a; -} - -CScriptVar *CTinyJS::base(bool &execute) { - CScriptVar *lhs = logic(execute); - if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { - int op = l->tk; - l->match(l->tk); - CScriptVar *rhs = base(execute); - if (execute) { - if (op=='=') { - lhs->copyValue(rhs); - } else if (op==LEX_PLUSEQUAL) { - CScriptVar *res = lhs->mathsOp(rhs, '+'); - lhs->copyValue(res); - CLEAN(res); - } else if (op==LEX_MINUSEQUAL) { - CScriptVar *res = lhs->mathsOp(rhs, '-'); - lhs->copyValue(res); - CLEAN(res); - } else ASSERT(0); - } - CLEAN(rhs); - } - return lhs; -} - -void CTinyJS::block(bool &execute) { - // TODO: fast skip of blocks - l->match('{'); - while (l->tk && l->tk!='}') - statement(execute); - l->match('}'); -} - -void CTinyJS::statement(bool &execute) { - if (l->tk=='{') { - block(execute); - } else if (l->tk==LEX_R_VAR) { - // variable creation - l->match(LEX_R_VAR); - CScriptVar *a = 0; - if (execute) { - a = symbol_base->findChild(l->tkStr); - if (!a) { - a = new CScriptVar(l->tkStr, ""); - symbol_base->addChild(a); - } - } - l->match(LEX_ID); - // now do stuff defined with dots - while (l->tk == '.') { - l->match('.'); - if (execute) { - CScriptVar *lastA = a; - a = lastA->findChild(l->tkStr); - if (!a) { - a = new CScriptVar(l->tkStr, ""); - lastA->addChild(a); - } - } - l->match(LEX_ID); - } - //. sort out initialiser - if (l->tk == '=') { - l->match('='); - CScriptVar *var = base(execute); - if (execute) - a->copyValue(var); - CLEAN(var); - } - l->match(';'); - } else if (l->tk==LEX_ID) { - CLEAN(base(execute)); - l->match(';'); - } else if (l->tk==LEX_R_IF) { - l->match(LEX_R_IF); - l->match('('); - CScriptVar *var = base(execute); - l->match(')'); - bool cond = execute && var->getBool(); - CLEAN(var); - bool noexecute = false; // because we need to be abl;e to write to it - 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) { - // We do repetition by pulling out the string representing our statement - // there's definitely some opportunity for optimisation here - l->match(LEX_R_WHILE); - l->match('('); - int whileCondStart = l->tokenPosition; - bool noexecute = false; - CScriptVar *cond = base(execute); - bool loopCond = execute && cond->getBool(); - CLEAN(cond); - CScriptLex *whileCond = l->getSubLex(whileCondStart); - l->match(')'); - int whileBodyStart = l->tokenPosition; - 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->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(l->tokenPosition).c_str()); - throw new CScriptException("LOOP_ERROR"); - } - } else if (l->tk==LEX_R_FOR) { - l->match(LEX_R_FOR); - l->match('('); - statement(execute); // initialisation - //l->match(';'); - int forCondStart = l->tokenPosition; - bool noexecute = false; - CScriptVar *cond = base(execute); // condition - bool loopCond = execute && cond->getBool(); - CLEAN(cond); - CScriptLex *forCond = l->getSubLex(forCondStart); - l->match(';'); - int forIterStart = l->tokenPosition; - base(noexecute); // iterator - CScriptLex *forIter = l->getSubLex(forIterStart); - l->match(')'); - int forBodyStart = l->tokenPosition; - statement(loopCond ? execute : noexecute); - CScriptLex *forBody = l->getSubLex(forBodyStart); - CScriptLex *oldLex = l; - if (loopCond) { - forIter->reset(); - l = forIter; - base(execute); - } - int loopCount = TINYJS_LOOP_MAX_ITERATIONS; - while (execute && loopCond && loopCount-->0) { - forCond->reset(); - l = forCond; - cond = base(execute); - loopCond = cond->getBool(); - CLEAN(cond); - if (execute && loopCond) { - forBody->reset(); - l = forBody; - statement(execute); - } - if (execute && loopCond) { - forIter->reset(); - l = forIter; - 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(l->tokenPosition).c_str()); - throw new CScriptException("LOOP_ERROR"); - } - } else if (l->tk==LEX_R_RETURN) { - l->match(LEX_R_RETURN); - CScriptVar *result = 0; - if (l->tk != ';') - result = base(execute); - if (execute) { - CScriptVar *resultVar = symbol_base->getChild(TINYJS_RETURN_VAR); - ASSERT(resultVar); - resultVar->copyValue(result); - execute = false; - } - CLEAN(result); - l->match(';'); - } else if (l->tk==LEX_R_FUNCTION) { - // actually parse a function... - l->match(LEX_R_FUNCTION); - string funcName = l->tkStr; - bool noexecute = false; - CScriptVar *funcVar = new CScriptVar(funcName, "", SCRIPTVAR_FUNCTION); - symbol_base->addChildNoDup(funcVar); - l->match(LEX_ID); - // add all parameters - l->match('('); - while (l->tk == LEX_ID) { - CScriptVar *param = new CScriptVar(l->tkStr, "", SCRIPTVAR_PARAMETER); - funcVar->addChildNoDup(param); - l->match(LEX_ID); - if (l->tk!=')') l->match(','); - } - l->match(')'); - int funcBegin = l->tokenPosition; - block(noexecute); - funcVar->setString(l->getSubString(funcBegin)); - } else l->match(LEX_EOF); -} - -/// Get the value of the given variable, or return 0 -string *CTinyJS::getVariable(const string &path) { - // traverse path - size_t prevIdx = 0; - size_t thisIdx = path.find('.'); - if (thisIdx == string::npos) thisIdx = path.length(); - CScriptVar *var = root; - while (var && prevIdxfindChild(el); - prevIdx = thisIdx+1; - thisIdx = path.find('.', prevIdx); - if (thisIdx == string::npos) thisIdx = path.length(); - } - // return result - if (var) - return &var->getString(); - else - return 0; -} + +using namespace std; + +#ifdef __GNUC__ +#define vsprintf_s vsnprintf +#define sprintf_s snprintf +#endif + +// ----------------------------------------------------------------------------------- Utils +bool isWhitespace(char ch) { + return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); +} + +bool isNumeric(char ch) { + return (ch>='0') && (ch<='9'); +} +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); + } +} + +/// convert the given string into a quoted string suitable for javascript +std::string getJSString(const std::string &str) { + std::string nStr = str; + for (size_t i=0;idata; + dataOwned = false; + dataStart = startChar; + dataEnd = endChar; + reset(); +} + +CScriptLex::~CScriptLex(void) +{ + if (dataOwned) + free((void*)data); +} + +void CScriptLex::reset() { + dataPos = dataStart; + tokenPosition = 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(tokenPosition) << "in '" << data << "'"; + throw new CScriptException(errorString.str()); + } + getNextToken(); +} + +string CScriptLex::getTokenStr(int token) { + if (token>32 && token<128) { + char buf[4] = "' '"; + buf[1] = 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_NEQUAL : return "!="; + case LEX_LEQUAL : return "<="; + case LEX_GEQUAL : return ">="; + case LEX_PLUSEQUAL : return "+="; + case LEX_MINUSEQUAL : return "-="; + case LEX_PLUSPLUS : return "++"; + case LEX_MINUSMINUS : return "--"; + case LEX_ANDAND : return "&&"; + case LEX_OROR : return "||"; + // reserved words + case LEX_R_IF : return "if"; + case LEX_R_ELSE : return "else"; + case LEX_R_WHILE : return "while"; + case LEX_R_FOR : return "for"; + 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_NIL : return "nil"; + } + + ostringstream msg; + msg << "?[" << token << "]"; + return msg.str(); +} + +void CScriptLex::getNextCh() { + currCh = nextCh; + if (dataPos < dataEnd) + nextCh = data[dataPos++]; + else + nextCh = 0; +} + +void CScriptLex::getNextToken() { + tk = LEX_EOF; + tkStr.clear(); + while (currCh && isWhitespace(currCh)) getNextCh(); + // newline comments + if (currCh=='/' && nextCh=='/') { + while (currCh && currCh!='\n') getNextCh(); + getNextCh(); + getNextToken(); + return; + } + // block comments + if (currCh=='/' && nextCh=='*') { + while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); + getNextCh(); + getNextCh(); + getNextToken(); + return; + } + // record beginning of this token + tokenPosition = dataPos-2; + // tokens + if (isAlpha(currCh)) { // IDs + 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=="while") tk = LEX_R_WHILE; + else if (tkStr=="for") tk = LEX_R_FOR; + 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=="nil") tk = LEX_R_NIL; + return; + } + if (isNumeric(currCh)) { // Numbers + tk = LEX_INT; + while (isNumeric(currCh)) { + tkStr += currCh; + getNextCh(); + } + if (currCh=='.') { + tk = LEX_FLOAT; + tkStr += '.'; + getNextCh(); + while (isNumeric(currCh)) { + tkStr += currCh; + getNextCh(); + } + } + return; + } + 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; + return; + } + // single chars + tk = currCh; + getNextCh(); + if (tk=='=' && currCh=='=') { + tk = LEX_EQUAL; + getNextCh(); + } else if (tk=='!' && currCh=='=') { + tk = LEX_NEQUAL; + getNextCh(); + } else if (tk=='<' && currCh=='=') { + tk = LEX_LEQUAL; + getNextCh(); + } else if (tk=='>' && currCh=='=') { + tk = LEX_GEQUAL; + 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_ANDAND; + getNextCh(); + } else if (tk=='|' && currCh=='|') { + tk = LEX_OROR; + getNextCh(); + } +} + +string CScriptLex::getSubString(int lastPosition) { + if (dataPos < dataEnd) { + /* save a memory alloc by using our data array to create the + substring */ + char old = data[tokenPosition]; + data[tokenPosition] = 0; + std::string value = &data[lastPosition]; + data[tokenPosition] = old; + return value; + } else { + return std::string(&data[lastPosition]); + } +} + + +CScriptLex *CScriptLex::getSubLex(int lastPosition) { + if (dataPos < dataEnd) + return new CScriptLex( this, lastPosition, tokenPosition); + else + return new CScriptLex( this, lastPosition, dataEnd ); +} + +string CScriptLex::getPosition(int pos) { + int line = 1,col = 1; + for (int i=0;iname.compare(childName)==0) + return v; + v = v->nextSibling; + } + return 0; +} + +CScriptVar *CScriptVar::findChildOrCreate(const string &childName) { + CScriptVar *v = findChild(childName); + if (!v) { + v = new CScriptVar(childName); + addChild(v); + } + return v; +} + +CScriptVar *CScriptVar::findRecursive(const string &childName) { + CScriptVar *v = findChild(childName); + if (!v && parent) + v = parent->findRecursive(childName); + return v; +} + +void CScriptVar::addChild(CScriptVar *child) { + ASSERT(!child->parent && !child->nextSibling && !child->prevSibling); + child->parent = this; + if (lastChild) { + lastChild->nextSibling = child; + child->prevSibling = lastChild; + lastChild = child; + } else { + firstChild = child; + lastChild = child; + } +} + +void CScriptVar::addNamedChild(const std::string &childName, CScriptVar *child) { + if (!child) { + // not given anything - just add empty child + addChild(new CScriptVar(childName)); + } else if (child->parent) { + // current element has an owner, so copy it + CScriptVar *c = new CScriptVar(childName); + c->copyValue(child); + addChild(c); + } else { + // no owners - just change name and add it + child->name = childName; + addChild(child); + } +} + +CScriptVar *CScriptVar::addChildNoDup(CScriptVar *child) { + ASSERT(!child->parent); + CScriptVar *v = findChild(child->getName()); + if (v) { + v->copyValue(child); + delete child; + } else { + addChild(child); + v = child; + } + + return v; +} + +void CScriptVar::removeChild(CScriptVar *child) { + ASSERT(child->parent == this); + if (child->nextSibling) + child->nextSibling->prevSibling = child->prevSibling; + if (child->prevSibling) + child->prevSibling->nextSibling = child->nextSibling; + if (lastChild == child) + lastChild = child->prevSibling; + if (firstChild == child) + firstChild = child->nextSibling; + child->prevSibling = 0; + child->nextSibling = 0; + child->parent = 0; +} + +void CScriptVar::removeAllChildren() { + CScriptVar *c = firstChild; + while (c) { + CScriptVar *t = c->nextSibling; + delete c; + c = t; + } + firstChild = 0; + lastChild = 0; +} + +CScriptVar *CScriptVar::getRoot() { + CScriptVar *p = this; + while (p->parent) p = p->parent; + return p; +} + +const string &CScriptVar::getName() { + return name; +} + +int CScriptVar::getInt() { + return atoi(data.c_str()); +} + +double CScriptVar::getDouble() { + return atof(data.c_str()); +} + +const string &CScriptVar::getString() { + return data; +} + +void CScriptVar::setInt(int val) { + char buf[256]; + sprintf(buf, "%d", val); + data = buf; + flags = SCRIPTVAR_NUMERIC | SCRIPTVAR_INTEGER; +} + +void CScriptVar::setDouble(double val) { + char buf[256]; + sprintf(buf, "%lf", val); + data = buf; + flags = SCRIPTVAR_NUMERIC; +} + +void CScriptVar::setString(const string &str) { + // name sure it's not still a number or integer + flags &= ~SCRIPTVAR_VARTYPEMASK; + data = str; +} + +void CScriptVar::setVoid() { + // name sure it's not still a number or integer + flags &= ~SCRIPTVAR_VARTYPEMASK; + data = TINYJS_BLANK_DATA; + removeAllChildren(); +} + +CScriptVar *CScriptVar::mathsOp(CScriptVar *b, int op) { + CScriptVar *a = this; + // if these vars have nothing in them and we add a number... + if (a->data.empty() && b->isNumeric()) + a->setInt(0); + if (b->data.empty() && a->isNumeric()) + b->setInt(0); + // do maths... + if (a->isNumeric() && b->isNumeric()) { + if (a->isInt() && b->isInt()) { + // use ints + 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': return new CScriptVar(da>db); + case LEX_GEQUAL: return new CScriptVar(da>=db); + default: throw new CScriptException("This operation not supported on the int datatype"); + } + } else { + // use doubles + 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': return new CScriptVar(da>db); + case LEX_GEQUAL: return new CScriptVar(da>=db); + default: throw new CScriptException("This operation not supported on the double datatype"); + } + } + } else { + string da = a->getString(); + string db = b->getString(); + // use strings + switch (op) { + case '+': return new CScriptVar(TINYJS_TEMP_NAME, da+db); + case LEX_EQUAL: return new CScriptVar(da==db); + case LEX_NEQUAL: return new CScriptVar(da!=db); + case '<': return new CScriptVar(da': return new CScriptVar(da>db); + case LEX_GEQUAL: return new CScriptVar(da>=db); + default: throw new CScriptException("This operation not supported on the string datatype"); + } + } + ASSERT(0); + return 0; +} + +void CScriptVar::copyValue(CScriptVar *val) { + if (val) { + data = val->data; + flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK); + // copy children + removeAllChildren(); + CScriptVar *child = val->firstChild; + while (child) { + addChild(child->deepCopy()); + child = child->nextSibling; + } + } else { + setVoid(); + } +} + +CScriptVar *CScriptVar::deepCopy() { + CScriptVar *newVar = new CScriptVar(name, data, flags); + // copy children + CScriptVar *child = firstChild; + while (child) { + newVar->addChild(child->deepCopy()); + child = child->nextSibling; + } + return newVar; +} + +void CScriptVar::trace(string indentStr) { + TRACE("%s'%s' = '%s'(%d)\n", indentStr.c_str(), name.c_str(), data.c_str(), flags); + string indent = indentStr+" "; + CScriptVar *child = firstChild; + while (child) { + child->trace(indent); + child = child->nextSibling; + } +} + +string CScriptVar::getParsableString() { + if (isNumeric()) + return getString(); + return getJSString(getString()); +} + +void CScriptVar::getJSON(ostringstream &destination) { + if (firstChild) { + // children - handle with bracketed list + destination << " { \n"; + CScriptVar *child = firstChild; + while (child) { + destination << getJSString(child->name) << " : "; + child->getJSON(destination); + child = child->nextSibling; + if (child) + destination << ",\n"; + else + destination << "\n"; + } + destination << " }\n"; + } else { + // no children, just write value directly + destination << getParsableString(); + } +} + + +void CScriptVar::setCallback(JSCallback callback) { + jsCallback = callback; +} + +// ----------------------------------------------------------------------------------- CSCRIPT + +CTinyJS::CTinyJS() { + l = 0; + root = symbol_base = new CScriptVar("root", ""); +} + +CTinyJS::~CTinyJS() { + ASSERT(!l); + symbol_base = 0; + delete root; +} + +void CTinyJS::trace() { + root->trace(); +} + +void CTinyJS::execute(const string &code) { + CScriptLex *oldLex = l; + l = new CScriptLex(code); + symbol_base = root; + try { + bool execute = true; + while (l->tk) statement(execute); + } catch (CScriptException *e) { + ostringstream msg; + msg << "Error " << e->text << " at " << l->getPosition(l->tokenPosition); + delete l; + l = oldLex; + throw new CScriptException(msg.str()); + } + delete l; + l = oldLex; +} + +string CTinyJS::evaluate(const string &code) { + CScriptLex *oldLex = l; + l = new CScriptLex(code); + symbol_base = root; + CScriptVar *v = 0; + try { + bool execute = true; + v = base(execute); + } catch (CScriptException *e) { + ostringstream msg; + msg << "Error " << e->text << " at " << l->getPosition(l->tokenPosition); + delete l; + l = oldLex; + throw new CScriptException(msg.str()); + } + delete l; + l = oldLex; + + string result = v ? v->getString() : ""; + CLEAN(v); + + return result; +} + +void CTinyJS::addNative(const string &funcDesc, JSCallback ptr) { + CScriptLex *oldLex = l; + l = new CScriptLex(funcDesc); + l->match(LEX_R_FUNCTION); + CScriptVar *funcVar = new CScriptVar(l->tkStr, "", SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE); + funcVar->setCallback(ptr); + l->match(LEX_ID); + l->match('('); + while (l->tk!=')') { + CScriptVar *funcParam = new CScriptVar(l->tkStr, "", SCRIPTVAR_PARAMETER); + funcVar->addChildNoDup(funcParam); + l->match(LEX_ID); + if (l->tk!=')') l->match(','); + } + l->match(')'); + delete l; + l = oldLex; + + root->addChild(funcVar); +} + +CScriptVar *CTinyJS::factor(bool &execute) { + if (l->tk=='(') { + l->match('('); + CScriptVar *a = base(execute); + l->match(')'); + return a; + } + if (l->tk==LEX_R_TRUE) { + l->match(LEX_R_TRUE); + return new CScriptVar(1); + } + if (l->tk==LEX_R_FALSE) { + l->match(LEX_R_FALSE); + return new CScriptVar(0); + } + if (l->tk==LEX_R_NIL) { + l->match(LEX_R_NIL); + return new CScriptVar(); + } + if (l->tk==LEX_ID) { + CScriptVar *a = symbol_base->findRecursive(l->tkStr); + if (!a) { + if (execute) { + ostringstream msg; + msg << "Unknown ID '" << l->tkStr << "'"; + throw new CScriptException(msg.str()); + } + a = new CScriptVar(l->tkStr, ""); + } + l->match(LEX_ID); + while (l->tk=='(' || l->tk=='.' || l->tk=='[') { + if (l->tk=='(') { // ------------------------------------- Function Call + if (!a->isFunction()) { + string errorMsg = "Expecting '"; + errorMsg = errorMsg + a->getName() + "' to be a function"; + throw new CScriptException(errorMsg.c_str()); + } + l->match('('); + // create a new symbol table entry for execution of this function + CScriptVar *functionRoot = new CScriptVar("__EXECUTESTACK__", "", SCRIPTVAR_FUNCTION); + // grab in all parameters + CScriptVar *v = a->firstChild; + while (v) { + if (v->isParameter()) { + CScriptVar *value = base(execute); + if (execute) { + CScriptVar *param = new CScriptVar(v->getName(), "", SCRIPTVAR_PARAMETER); + param->copyValue(value); + functionRoot->addChild(param); + } + CLEAN(value); + if (l->tk!=')') l->match(','); + } + v = v->nextSibling; + } + l->match(')'); + // setup a return variable + CScriptVar *returnVar = new CScriptVar(TINYJS_RETURN_VAR, ""); + // execute function! + if (execute) { + // add the function's execute space to the symbol table so we can recurse + CScriptVar *old_symbol_base = symbol_base; + symbol_base->addChild(functionRoot); + functionRoot->addChild(returnVar); + symbol_base = functionRoot; + + if (a->isNative()) { + ASSERT(a->jsCallback); + a->jsCallback(functionRoot); + } else { + CScriptLex *oldLex = l; + fflush(stdout); + l = new CScriptLex(a->getString()); + block(execute); + // because return will probably have called this, and set execute to false + execute = true; + delete l; + l = oldLex; + } + symbol_base = old_symbol_base; + symbol_base->removeChild(functionRoot); + functionRoot->removeChild(returnVar); + } + delete functionRoot; + a = returnVar; + } else if (l->tk == '.') { // ------------------------------------- Record Access + l->match('.'); + CScriptVar *child = a->findRecursive(l->tkStr); + if (!child) { + // TODO: Error here if this doesn't exist?? + child = new CScriptVar(l->tkStr, ""); + a->addChild(child); + } + l->match(LEX_ID); + a = child; + } else if (l->tk == '[') { // ------------------------------------- Array Access + l->match('['); + CScriptVar *index = expression(execute); + l->match(']'); + CScriptVar *child = a->findRecursive(index->getString()); + if (!child) { + child = new CScriptVar(index->getString(), ""); + a->addChild(child); + } + CLEAN(index); + + a = child; + } else ASSERT(0); + + // return value? + } + return a; + } + if (l->tk==LEX_INT || l->tk==LEX_FLOAT) { + CScriptVar *a = new CScriptVar(TINYJS_TEMP_NAME, l->tkStr, SCRIPTVAR_NUMERIC | ((l->tk==LEX_INT)?SCRIPTVAR_INTEGER:0)); + l->match(l->tk); + return a; + } + if (l->tk==LEX_STR) { + CScriptVar *a = new CScriptVar(TINYJS_TEMP_NAME, l->tkStr, 0); + l->match(LEX_STR); + return a; + } + if (l->tk=='{') { + CScriptVar *contents = new CScriptVar(); + /* JSON-style object definition */ + l->match('{'); + while (l->tk != '}') { + string id = l->tkStr; + l->match(LEX_STR); + l->match(':'); + CScriptVar *a = base(execute); + contents->addNamedChild(id, a); + // no need to clean here, as it will definitely be used + if (l->tk != '}') l->match(','); + } + + l->match('}'); + return contents; + } + if (l->tk=='[') { + CScriptVar *contents = new CScriptVar(); + /* JSON-style array */ + l->match('['); + int idx = 0; + while (l->tk != ']') { + char idx_str[16]; // big enough for 2^32 + sprintf(idx_str,"%d",idx); + + CScriptVar *a = base(execute); + contents->addNamedChild(idx_str, a); + // no need to clean here, as it will definitely be used + if (l->tk != ']') l->match(','); + idx++; + } + l->match(']'); + return contents; + } + l->match(LEX_EOF); + return new CScriptVar(); +} + +CScriptVar *CTinyJS::unary(bool &execute) { + CScriptVar *a; + if (l->tk=='!') { + l->match('!'); // negation + a = factor(execute); + if (execute) { + CScriptVar zero(0); + CScriptVar *res = a->mathsOp(&zero, LEX_EQUAL); + CLEAN(a); + a = res; + } + } else + a = factor(execute); + return a; +} + +CScriptVar *CTinyJS::term(bool &execute) { + CScriptVar *a = unary(execute); + while (l->tk=='*' || l->tk=='/') { + int op = l->tk; + l->match(l->tk); + CScriptVar *b = unary(execute); + if (execute) { + CScriptVar *res = a->mathsOp(b, op); + CLEAN(a); + CLEAN(b); + a = res; + } else { + CLEAN(b); + } + } + return a; +} + +CScriptVar *CTinyJS::expression(bool &execute) { + bool negate = false; + if (l->tk=='-') { + l->match('-'); + negate = true; + } + CScriptVar *a = term(execute); + if (negate) { + CScriptVar *zero = new CScriptVar(0); + CScriptVar *res = zero->mathsOp(a, '-'); + CLEAN(a); + CLEAN(zero); + 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->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-'); + a->copyValue(res); + CLEAN(res); + } + } else { + CScriptVar *b = term(execute); + if (execute) { + CScriptVar *res = a->mathsOp(b, op); + CLEAN(a); + CLEAN(b); + a = res; + } else { + CLEAN(b); + } + } + } + return a; +} + +CScriptVar *CTinyJS::condition(bool &execute) { + CScriptVar *a = expression(execute); + CScriptVar *b; + while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || + l->tk==LEX_LEQUAL || l->tk==LEX_GEQUAL || + l->tk=='<' || l->tk=='>') { + int op = l->tk; + l->match(l->tk); + b = expression(execute); + if (execute) { + CScriptVar *res = a->mathsOp(b, op); + CLEAN(a); + CLEAN(b); + a = res; + } else { + CLEAN(b); + } + } + return a; +} + +CScriptVar *CTinyJS::logic(bool &execute) { + CScriptVar *a = condition(execute); + CScriptVar *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 we have short-circuit ops, then if we know the outcome + // we don't bother to execute the other op. Even if not + // we need to tell mathsOp it's an & or | + if (op==LEX_ANDAND) { + op = '&'; + shortCircuit = !a->getBool(); + boolean = true; + } else if (op==LEX_OROR) { + op = '|'; + shortCircuit = a->getBool(); + boolean = true; + } + b = condition(shortCircuit ? noexecute : execute); + if (execute && !shortCircuit) { + if (boolean) { + CScriptVar *newa = new CScriptVar(a->getBool()); + CScriptVar *newb = new CScriptVar(b->getBool()); + CLEAN(a); + CLEAN(b); + a = newa; + b = newb; + } + CScriptVar *res = a->mathsOp(b, op); + CLEAN(a); + CLEAN(b); + a = res; + } else { + CLEAN(b); + } + } + return a; +} + +CScriptVar *CTinyJS::base(bool &execute) { + CScriptVar *lhs = logic(execute); + if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { + int op = l->tk; + l->match(l->tk); + CScriptVar *rhs = base(execute); + if (execute) { + if (op=='=') { + lhs->copyValue(rhs); + } else if (op==LEX_PLUSEQUAL) { + CScriptVar *res = lhs->mathsOp(rhs, '+'); + lhs->copyValue(res); + CLEAN(res); + } else if (op==LEX_MINUSEQUAL) { + CScriptVar *res = lhs->mathsOp(rhs, '-'); + lhs->copyValue(res); + CLEAN(res); + } else ASSERT(0); + } + CLEAN(rhs); + } + return lhs; +} + +void CTinyJS::block(bool &execute) { + // TODO: fast skip of blocks + l->match('{'); + while (l->tk && l->tk!='}') + statement(execute); + l->match('}'); +} + +void CTinyJS::statement(bool &execute) { + if (l->tk=='{') { + block(execute); + } else if (l->tk==LEX_R_VAR) { + // variable creation + l->match(LEX_R_VAR); + CScriptVar *a = 0; + if (execute) { + a = symbol_base->findChild(l->tkStr); + if (!a) { + a = new CScriptVar(l->tkStr, ""); + symbol_base->addChild(a); + } + } + l->match(LEX_ID); + // now do stuff defined with dots + while (l->tk == '.') { + l->match('.'); + if (execute) { + CScriptVar *lastA = a; + a = lastA->findChild(l->tkStr); + if (!a) { + a = new CScriptVar(l->tkStr, ""); + lastA->addChild(a); + } + } + l->match(LEX_ID); + } + //. sort out initialiser + if (l->tk == '=') { + l->match('='); + CScriptVar *var = base(execute); + if (execute) + a->copyValue(var); + CLEAN(var); + } + l->match(';'); + } else if (l->tk==LEX_ID) { + CLEAN(base(execute)); + l->match(';'); + } else if (l->tk==LEX_R_IF) { + l->match(LEX_R_IF); + l->match('('); + CScriptVar *var = base(execute); + l->match(')'); + bool cond = execute && var->getBool(); + CLEAN(var); + bool noexecute = false; // because we need to be abl;e to write to it + 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) { + // We do repetition by pulling out the string representing our statement + // there's definitely some opportunity for optimisation here + l->match(LEX_R_WHILE); + l->match('('); + int whileCondStart = l->tokenPosition; + bool noexecute = false; + CScriptVar *cond = base(execute); + bool loopCond = execute && cond->getBool(); + CLEAN(cond); + CScriptLex *whileCond = l->getSubLex(whileCondStart); + l->match(')'); + int whileBodyStart = l->tokenPosition; + 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->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(l->tokenPosition).c_str()); + throw new CScriptException("LOOP_ERROR"); + } + } else if (l->tk==LEX_R_FOR) { + l->match(LEX_R_FOR); + l->match('('); + statement(execute); // initialisation + //l->match(';'); + int forCondStart = l->tokenPosition; + bool noexecute = false; + CScriptVar *cond = base(execute); // condition + bool loopCond = execute && cond->getBool(); + CLEAN(cond); + CScriptLex *forCond = l->getSubLex(forCondStart); + l->match(';'); + int forIterStart = l->tokenPosition; + base(noexecute); // iterator + CScriptLex *forIter = l->getSubLex(forIterStart); + l->match(')'); + int forBodyStart = l->tokenPosition; + statement(loopCond ? execute : noexecute); + CScriptLex *forBody = l->getSubLex(forBodyStart); + CScriptLex *oldLex = l; + if (loopCond) { + forIter->reset(); + l = forIter; + base(execute); + } + int loopCount = TINYJS_LOOP_MAX_ITERATIONS; + while (execute && loopCond && loopCount-->0) { + forCond->reset(); + l = forCond; + cond = base(execute); + loopCond = cond->getBool(); + CLEAN(cond); + if (execute && loopCond) { + forBody->reset(); + l = forBody; + statement(execute); + } + if (execute && loopCond) { + forIter->reset(); + l = forIter; + 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(l->tokenPosition).c_str()); + throw new CScriptException("LOOP_ERROR"); + } + } else if (l->tk==LEX_R_RETURN) { + l->match(LEX_R_RETURN); + CScriptVar *result = 0; + if (l->tk != ';') + result = base(execute); + if (execute) { + CScriptVar *resultVar = symbol_base->findChild(TINYJS_RETURN_VAR); + ASSERT(resultVar); + resultVar->copyValue(result); + execute = false; + } + CLEAN(result); + l->match(';'); + } else if (l->tk==LEX_R_FUNCTION) { + // actually parse a function... + l->match(LEX_R_FUNCTION); + string funcName = l->tkStr; + bool noexecute = false; + CScriptVar *funcVar = new CScriptVar(funcName, "", SCRIPTVAR_FUNCTION); + symbol_base->addChildNoDup(funcVar); + l->match(LEX_ID); + // add all parameters + l->match('('); + while (l->tk == LEX_ID) { + CScriptVar *param = new CScriptVar(l->tkStr, "", SCRIPTVAR_PARAMETER); + funcVar->addChildNoDup(param); + l->match(LEX_ID); + if (l->tk!=')') l->match(','); + } + l->match(')'); + int funcBegin = l->tokenPosition; + block(noexecute); + funcVar->setString(l->getSubString(funcBegin)); + } else l->match(LEX_EOF); +} + +/// Get the value of the given variable, or return 0 +const string *CTinyJS::getVariable(const string &path) { + // traverse path + size_t prevIdx = 0; + size_t thisIdx = path.find('.'); + if (thisIdx == string::npos) thisIdx = path.length(); + CScriptVar *var = root; + while (var && prevIdxfindChild(el); + prevIdx = thisIdx+1; + thisIdx = path.find('.', prevIdx); + if (thisIdx == string::npos) thisIdx = path.length(); + } + // return result + if (var) + return &var->getString(); + else + return 0; +} diff --git a/TinyJS.h b/TinyJS.h index 2487d80..70907e8 100755 --- a/TinyJS.h +++ b/TinyJS.h @@ -1,228 +1,231 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef TINYJS_H -#define TINYJS_H - -#include - -#ifndef TRACE -#define TRACE printf -#endif // TRACE - -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_NEQUAL, - LEX_LEQUAL, - LEX_GEQUAL, - LEX_PLUSEQUAL, - LEX_MINUSEQUAL, - LEX_PLUSPLUS, - LEX_MINUSMINUS, - LEX_ANDAND, - LEX_OROR, - // reserved words - LEX_R_IF, - LEX_R_ELSE, - LEX_R_WHILE, - LEX_R_FOR, - LEX_R_FUNCTION, - LEX_R_RETURN, - LEX_R_VAR, - LEX_R_TRUE, - LEX_R_FALSE, -}; - -enum SCRIPTVAR_FLAGS { - SCRIPTVAR_FUNCTION = 1, - SCRIPTVAR_NATIVE = 2, - SCRIPTVAR_NUMERIC = 4, - SCRIPTVAR_INTEGER = 8, // eg. not floating point - SCRIPTVAR_VARTYPEMASK = 12, - SCRIPTVAR_PARAMETER = 16 -}; - -#define TINYJS_RETURN_VAR "return" -#define TINYJS_TEMP_NAME "" -#define TINYJS_BLANK_DATA "" - -/// convert the given string into a quoted string suitable for javascript -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; ///< The type of the token that we have - int tokenPosition; ///< Position in the data at the beginning of the token we have here - std::string tkStr; ///< Data contained in the token we have here - - void match(int expected_tk); ///< Lexical match wotsit - std::string getTokenStr(int token); ///< Get the string representation of the given token - void reset(); ///< Reset this lex so we can start again - - std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now - CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now - - std::string getPosition(int pos); ///< Return a string representing the position in lines and columns of the character pos given - -protected: - /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the - relevant string. This doesn't re-allocate and copy the string, but instead copies - the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */ - char *data; ///< Data string to get tokens from - int dataStart, dataEnd; ///< Start and end position in data string - bool dataOwned; ///< Do we own this data string? - - int dataPos; ///< Position in data - - void getNextCh(); - void getNextToken(); ///< Get the text token from our text string -}; - -class CScriptVar; - -typedef void (*JSCallback)(CScriptVar *var); - -/// Variable class (containing a doubly-linked list of children) -class CScriptVar -{ -public: - CScriptVar(void); - CScriptVar(std::string varName, std::string varData=TINYJS_BLANK_DATA, int varFlags = 0); - CScriptVar(double varData); - CScriptVar(int val); - ~CScriptVar(void); - - CScriptVar *findChild(std::string childName); ///< Tries to find a child with the given name, may return 0 - CScriptVar *getChild(std::string childName); ///< Gets a child with the given name, produces error on fail - CScriptVar *findRecursive(std::string childName); ///< Finds a child, looking recursively up the tree - void addChild(CScriptVar *child); - CScriptVar *addChildNoDup(CScriptVar *child); ///< add a child overwriting any with the same name - void removeChild(CScriptVar *child); - void removeAllChildren(); - CScriptVar *getRoot(); ///< Get the absolute root of the tree - - std::string getName(); - int getInt(); - bool getBool() { return getInt() != 0; } - double getDouble(); - std::string &getString(); - void setInt(int num); - void setDouble(double val); - void setString(std::string str); - void setVoid(); - - bool isInt() { return (flags&SCRIPTVAR_NUMERIC)!=0 && (flags&SCRIPTVAR_INTEGER)!=0; } - bool isDouble() { return (flags&SCRIPTVAR_NUMERIC)!=0 && (flags&SCRIPTVAR_INTEGER)==0; } - bool isNumeric() { return (flags&SCRIPTVAR_NUMERIC)!=0; } - bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; } - bool isParameter() { return (flags&SCRIPTVAR_PARAMETER)!=0; } - bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; } - - CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable - void copyValue(CScriptVar *val); ///< copy the value from the value given - CScriptVar *deepCopy(); ///< deep copy this node and return the result - - - void trace(std::string indentStr = ""); ///< Dump out the contents of this using trace - void getInitialiseCode(std::ostringstream &destination, const std::string &prefix = ""); ///< Write out all the JS code needed to recreate this script variable to the stream - void setCallback(JSCallback callback); - - CScriptVar *firstChild; - CScriptVar *lastChild; - CScriptVar *nextSibling; - CScriptVar *prevSibling; - CScriptVar *parent; -protected: - std::string name; - std::string data; - int flags; - JSCallback jsCallback; - - void init(); // initilisation of data members - - friend class CTinyJS; -}; - -class CTinyJS { -public: - CTinyJS(); - ~CTinyJS(); - - void execute(const std::string &code); - std::string evaluate(const std::string &code); - - /// add a function to the root scope - /** example: - \code - void scRandInt(CScriptVar *c) { ... } - tinyJS->addNative("function randInt(min, max)", scRandInt); - \endcode - */ - void addNative(const std::string &funcDesc, JSCallback ptr); - - /// Get the value of the given variable, or return 0 - std::string *getVariable(const std::string &path); - - /// Send all variables to stdout - void trace(); - - CScriptVar *root; /// root of symbol table -private: - CScriptLex *l; /// current lexer - CScriptVar *symbol_base; /// current symbol table base - - // parsing - in order of precedence - CScriptVar *factor(bool &execute); - CScriptVar *unary(bool &execute); - CScriptVar *term(bool &execute); - CScriptVar *expression(bool &execute); - CScriptVar *condition(bool &execute); - CScriptVar *logic(bool &execute); - CScriptVar *base(bool &execute); - void block(bool &execute); - void statement(bool &execute); -}; - -#endif +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef TINYJS_H +#define TINYJS_H + +#include + +#ifndef TRACE +#define TRACE printf +#endif // TRACE + +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_NEQUAL, + LEX_LEQUAL, + LEX_GEQUAL, + LEX_PLUSEQUAL, + LEX_MINUSEQUAL, + LEX_PLUSPLUS, + LEX_MINUSMINUS, + LEX_ANDAND, + LEX_OROR, + // reserved words + LEX_R_IF, + LEX_R_ELSE, + LEX_R_WHILE, + LEX_R_FOR, + LEX_R_FUNCTION, + LEX_R_RETURN, + LEX_R_VAR, + LEX_R_TRUE, + LEX_R_FALSE, + LEX_R_NIL, +}; + +enum SCRIPTVAR_FLAGS { + SCRIPTVAR_FUNCTION = 1, + SCRIPTVAR_NATIVE = 2, + SCRIPTVAR_NUMERIC = 4, + SCRIPTVAR_INTEGER = 8, // eg. not floating point + SCRIPTVAR_VARTYPEMASK = 12, + SCRIPTVAR_PARAMETER = 16 +}; + +#define TINYJS_RETURN_VAR "return" +#define TINYJS_TEMP_NAME "" +#define TINYJS_BLANK_DATA "" + +/// convert the given string into a quoted string suitable for javascript +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; ///< The type of the token that we have + int tokenPosition; ///< Position in the data at the beginning of the token we have here + std::string tkStr; ///< Data contained in the token we have here + + void match(int expected_tk); ///< Lexical match wotsit + std::string getTokenStr(int token); ///< Get the string representation of the given token + void reset(); ///< Reset this lex so we can start again + + std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now + CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now + + std::string getPosition(int pos); ///< Return a string representing the position in lines and columns of the character pos given + +protected: + /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the + relevant string. This doesn't re-allocate and copy the string, but instead copies + the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */ + char *data; ///< Data string to get tokens from + int dataStart, dataEnd; ///< Start and end position in data string + bool dataOwned; ///< Do we own this data string? + + int dataPos; ///< Position in data + + void getNextCh(); + void getNextToken(); ///< Get the text token from our text string +}; + +class CScriptVar; + +typedef void (*JSCallback)(CScriptVar *var); + +/// Variable class (containing a doubly-linked list of children) +class CScriptVar +{ +public: + CScriptVar(void); + CScriptVar(std::string varName, std::string varData=TINYJS_BLANK_DATA, int varFlags = 0); + CScriptVar(double varData); + CScriptVar(int val); + ~CScriptVar(void); + + CScriptVar *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 + CScriptVar *findChildOrCreate(const std::string &childName); ///< Tries to find a child with the given name, or will create it + CScriptVar *findRecursive(const std::string &childName); ///< Finds a child, looking recursively up the tree + void addChild(CScriptVar *child); + void addNamedChild(const std::string &childName, CScriptVar *child); ///< add the named child. if it is already owned, copy it. + CScriptVar *addChildNoDup(CScriptVar *child); ///< add a child overwriting any with the same name + void removeChild(CScriptVar *child); + void removeAllChildren(); + CScriptVar *getRoot(); ///< Get the absolute root of the tree + + const std::string &getName(); + int getInt(); + bool getBool() { return getInt() != 0; } + double getDouble(); + const std::string &getString(); + std::string getParsableString(); ///< get Data as a parsable javascript string + void setInt(int num); + void setDouble(double val); + void setString(const std::string &str); + void setVoid(); + + bool isInt() { return (flags&SCRIPTVAR_NUMERIC)!=0 && (flags&SCRIPTVAR_INTEGER)!=0; } + bool isDouble() { return (flags&SCRIPTVAR_NUMERIC)!=0 && (flags&SCRIPTVAR_INTEGER)==0; } + bool isNumeric() { return (flags&SCRIPTVAR_NUMERIC)!=0; } + bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; } + bool isParameter() { return (flags&SCRIPTVAR_PARAMETER)!=0; } + bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; } + + CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable + void copyValue(CScriptVar *val); ///< copy the value from the value given + CScriptVar *deepCopy(); ///< deep copy this node and return the result + + + void trace(std::string indentStr = ""); ///< Dump out the contents of this using trace + void getJSON(std::ostringstream &destination); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) + void setCallback(JSCallback callback); + + CScriptVar *firstChild; + CScriptVar *lastChild; + CScriptVar *nextSibling; + CScriptVar *prevSibling; + CScriptVar *parent; +protected: + std::string name; + std::string data; + int flags; + JSCallback jsCallback; + + void init(); // initilisation of data members + + friend class CTinyJS; +}; + +class CTinyJS { +public: + CTinyJS(); + ~CTinyJS(); + + void execute(const std::string &code); + std::string evaluate(const std::string &code); + + /// add a function to the root scope + /** example: + \code + void scRandInt(CScriptVar *c) { ... } + tinyJS->addNative("function randInt(min, max)", scRandInt); + \endcode + */ + void addNative(const std::string &funcDesc, JSCallback ptr); + + /// Get the value of the given variable, or return 0 + const std::string *getVariable(const std::string &path); + + /// Send all variables to stdout + void trace(); + + CScriptVar *root; /// root of symbol table +private: + CScriptLex *l; /// current lexer + CScriptVar *symbol_base; /// current symbol table base + + // parsing - in order of precedence + CScriptVar *factor(bool &execute); + CScriptVar *unary(bool &execute); + CScriptVar *term(bool &execute); + CScriptVar *expression(bool &execute); + CScriptVar *condition(bool &execute); + CScriptVar *logic(bool &execute); + CScriptVar *base(bool &execute); + void block(bool &execute); + void statement(bool &execute); +}; + +#endif diff --git a/TinyJS_Functions.cpp b/TinyJS_Functions.cpp index 36e188f..d5ce6b4 100755 --- a/TinyJS_Functions.cpp +++ b/TinyJS_Functions.cpp @@ -1,86 +1,86 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include "TinyJS_Functions.h" -#include -#include - -using namespace std; -// ----------------------------------------------- Actual Functions - -void scTrace(CScriptVar *c) { - while (c->parent) c = c->parent; - c->trace(); -} - -void scRand(CScriptVar *c) { - c->getChild(TINYJS_RETURN_VAR)->setDouble((double)rand()/RAND_MAX); -} - -void scRandInt(CScriptVar *c) { - int min = c->getChild("min")->getInt(); - int max = c->getChild("max")->getInt(); - int val = min + (int)((long)rand()*(1+max-min)/RAND_MAX); - if (val>max) val=max; - c->getChild(TINYJS_RETURN_VAR)->setInt(val); -} - -void scCharToInt(CScriptVar *c) { - string str = c->getChild("ch")->getString();; - int val = 0; - if (str.length()>0) - val = (int)str.c_str()[0]; - c->getChild(TINYJS_RETURN_VAR)->setInt(val); -} - -void scStrLen(CScriptVar *c) { - string str = c->getChild("str")->getString();; - int val = str.length(); - c->getChild(TINYJS_RETURN_VAR)->setInt(val); -} - -void scStrPos(CScriptVar *c) { - string str = c->getChild("string")->getString(); - string search = c->getChild("search")->getString(); - size_t p = str.find(search); - int val = (p==string::npos) ? -1 : p; - c->getChild(TINYJS_RETURN_VAR)->setInt(val); -} - -void scAtoi(CScriptVar *c) { - int val = c->getChild("str")->getInt(); - c->getChild(TINYJS_RETURN_VAR)->setInt(val); -} - -// ----------------------------------------------- Register Functions -void registerFunctions(CTinyJS *tinyJS) { - tinyJS->addNative("function trace()", scTrace); - tinyJS->addNative("function rand()", scRand); - tinyJS->addNative("function randInt(min, max)", scRandInt); - tinyJS->addNative("function charToInt(ch)", scCharToInt); // convert a character to an int - get its value - tinyJS->addNative("function strlen(str)", scStrLen); // length of a string - tinyJS->addNative("function strpos(string,search)", scStrPos); // find the position of a string in a string, -1 if not - tinyJS->addNative("function atoi(str)", scAtoi); // string to int -} +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "TinyJS_Functions.h" +#include +#include + +using namespace std; +// ----------------------------------------------- Actual Functions + +void scTrace(CScriptVar *c) { + while (c->parent) c = c->parent; + c->trace(); +} + +void scRand(CScriptVar *c) { + c->findChildOrCreate(TINYJS_RETURN_VAR)->setDouble((double)rand()/RAND_MAX); +} + +void scRandInt(CScriptVar *c) { + int min = c->findChildOrCreate("min")->getInt(); + int max = c->findChildOrCreate("max")->getInt(); + int val = min + (int)((long)rand()*(1+max-min)/RAND_MAX); + if (val>max) val=max; + c->findChildOrCreate(TINYJS_RETURN_VAR)->setInt(val); +} + +void scCharToInt(CScriptVar *c) { + string str = c->findChildOrCreate("ch")->getString();; + int val = 0; + if (str.length()>0) + val = (int)str.c_str()[0]; + c->findChildOrCreate(TINYJS_RETURN_VAR)->setInt(val); +} + +void scStrLen(CScriptVar *c) { + string str = c->findChildOrCreate("str")->getString();; + int val = str.length(); + c->findChildOrCreate(TINYJS_RETURN_VAR)->setInt(val); +} + +void scStrPos(CScriptVar *c) { + string str = c->findChildOrCreate("string")->getString(); + string search = c->findChildOrCreate("search")->getString(); + size_t p = str.find(search); + int val = (p==string::npos) ? -1 : p; + c->findChildOrCreate(TINYJS_RETURN_VAR)->setInt(val); +} + +void scAtoi(CScriptVar *c) { + int val = c->findChildOrCreate("str")->getInt(); + c->findChildOrCreate(TINYJS_RETURN_VAR)->setInt(val); +} + +// ----------------------------------------------- Register Functions +void registerFunctions(CTinyJS *tinyJS) { + tinyJS->addNative("function trace()", scTrace); + tinyJS->addNative("function rand()", scRand); + tinyJS->addNative("function randInt(min, max)", scRandInt); + tinyJS->addNative("function charToInt(ch)", scCharToInt); // convert a character to an int - get its value + tinyJS->addNative("function strlen(str)", scStrLen); // length of a string + tinyJS->addNative("function strpos(string,search)", scStrPos); // find the position of a string in a string, -1 if not + tinyJS->addNative("function atoi(str)", scAtoi); // string to int +} diff --git a/TinyJS_Functions.h b/TinyJS_Functions.h index ab948db..bffff37 100755 --- a/TinyJS_Functions.h +++ b/TinyJS_Functions.h @@ -1,34 +1,34 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef TINYJS_FUNCTIONS_H -#define TINYJS_FUNCTIONS_H - -#include "TinyJS.h" - -/// Register useful functions with the TinyJS interpreter -extern void registerFunctions(CTinyJS *tinyJS); - -#endif +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef TINYJS_FUNCTIONS_H +#define TINYJS_FUNCTIONS_H + +#include "TinyJS.h" + +/// Register useful functions with the TinyJS interpreter +extern void registerFunctions(CTinyJS *tinyJS); + +#endif