From 0e77954ec0fb4a1a8fd7465b142e7497b15903bc Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 26 Oct 2014 22:42:19 +0100 Subject: [PATCH] formatting 90% done; encapsulated everything in the TinyJS namespace, and renamed most classes --- Makefile | 55 +- include/tinyjs.h | 929 ++++----- src/exception.cpp | 10 + src/functions.cpp | 133 +- src/lexer.cpp | 596 ++++++ src/main.cpp | 92 +- src/mathfuncs.cpp | 63 +- src/private.h | 79 +- run_tests.cpp => src/run_tests.cpp | 6 +- src/scriptvar.cpp | 902 +++++++++ src/tinyjs.cpp | 3641 ++++++++++++++---------------------- src/util.cpp | 162 ++ src/varlink.cpp | 70 + 13 files changed, 3937 insertions(+), 2801 deletions(-) rewrite Makefile (86%) rewrite include/tinyjs.h (88%) create mode 100644 src/exception.cpp mode change 100755 => 100644 src/functions.cpp create mode 100644 src/lexer.cpp mode change 100755 => 100644 src/main.cpp mode change 100755 => 100644 src/mathfuncs.cpp rename run_tests.cpp => src/run_tests.cpp (94%) create mode 100644 src/scriptvar.cpp rewrite src/tinyjs.cpp (94%) mode change 100755 => 100644 create mode 100644 src/util.cpp create mode 100644 src/varlink.cpp diff --git a/Makefile b/Makefile dissimilarity index 86% index 0563c08..6b0c78e 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,31 @@ -CC=g++ -CFLAGS=-c -g -Wall -rdynamic -D_DEBUG -LDFLAGS=-g -rdynamic - -SOURCES= \ -TinyJS.cpp \ -TinyJS_Functions.cpp \ -TinyJS_MathFunctions.cpp - -OBJECTS=$(SOURCES:.cpp=.o) - -all: run_tests Script - -run_tests: run_tests.o $(OBJECTS) - $(CC) $(LDFLAGS) run_tests.o $(OBJECTS) -o $@ - -Script: Script.o $(OBJECTS) - $(CC) $(LDFLAGS) Script.o $(OBJECTS) -o $@ - -.cpp.o: - $(CC) $(CFLAGS) $< -o $@ - -clean: - rm -f run_tests Script run_tests.o Script.o $(OBJECTS) + +CXX = clang++ -std=c++11 +CFLAGS = -Iinclude -c -g -rdynamic -D_DEBUG # -fno-color-diagnostics +#WFLAGS = -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused +LDFLAGS = -g -rdynamic + +SOURCES= \ + src/util.cpp \ + src/scriptvar.cpp \ + src/varlink.cpp \ + src/exception.cpp \ + src/lexer.cpp \ + src/tinyjs.cpp \ + src/functions.cpp \ + src/mathfuncs.cpp + +OBJECTS=$(SOURCES:.cpp=.o) + +all: main + + +main: src/main.o $(OBJECTS) + $(CXX) $(LDFLAGS) src/main.o $(OBJECTS) -o $@ + +.cpp.o: + $(CXX) $(CFLAGS) $(WFLAGS) $< -o $@ 2>&1 | sed 's/\o33\[30m/\o33[37m/g' + +rebuild: clean all + +clean: + rm -f run_tests main run_tests.o src/main.o $(OBJECTS) diff --git a/include/tinyjs.h b/include/tinyjs.h dissimilarity index 88% index 1f75ace..77f5b3b 100755 --- a/include/tinyjs.h +++ b/include/tinyjs.h @@ -1,452 +1,477 @@ -/* - * TinyJS - * - * A single-file Javascript-alike engine - * - * Authored By Gordon Williams - * - * Copyright (C) 2009 Pur3 Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is furnished to do - * so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#pragma once - -// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging -#define TINYJS_CALL_STACK - -#ifdef _WIN32 - #ifdef _DEBUG - #define _CRTDBG_MAP_ALLOC - #include - #include - #endif -#endif -#include -#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_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, - // reserved words -#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 /* always the last entry */ -}; - -enum SCRIPTVAR_FLAGS -{ - SCRIPTVAR_UNDEFINED = 0, - SCRIPTVAR_FUNCTION = 1, - SCRIPTVAR_OBJECT = 2, - SCRIPTVAR_ARRAY = 4, - SCRIPTVAR_DOUBLE = 8, // floating point double - SCRIPTVAR_INTEGER = 16, // integer number - SCRIPTVAR_STRING = 32, // string - SCRIPTVAR_NULL = 64, // it seems null is its own data type - SCRIPTVAR_NATIVE = 128, // to specify this is a native function - 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 "" - -/// 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: - char currCh; - char nextCh; - /// The type of the token that we have - int tk; - /// Position in the data at the beginning of the token we have here - int tokenStart; - /// Position in the data at the last character of the token we have here - int tokenEnd; - /// Position in the data at the last character of the last token - int tokenLastEnd; - /// Data contained in the token we have here - std::string tkStr; - - public: - CScriptLex(const std::string &input); - CScriptLex(CScriptLex *owner, int startChar, int endChar); - ~CScriptLex(void); - - - /// Lexical match wotsit - void match(int expected_tk); - - /// Get the string representation of the given token - static std::string getTokenStr(int token); - - /// Reset this lex so we can start again - void reset(); - - /// Return a sub-string from the given position up until right now - std::string getSubString(int pos); - - /// Return a sub-lexer from the given position up until right now - CScriptLex *getSubLex(int lastPosition); - - /// Return a string representing the position in lines and columns of the character pos given - std::string getPosition(int pos=-1); - - 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. - */ - /// Data string to get tokens from - char *data; - - /// Start and end position in data string - int dataStart; - int dataEnd; - - /// Do we own this data string? - bool dataOwned; - - /// Position in data (we CAN go past the end of the string here) - int dataPos; - - protected: - void getNextCh(); - /// Get the text token from our text string - 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; - - public: - CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME); - /// Copy constructor - CScriptVarLink(const CScriptVarLink &link); - ~CScriptVarLink(); - /// Replace the Variable pointed to - void replaceWith(CScriptVar *newVar); - /// Replace the Variable pointed to (just dereferences) - void replaceWith(CScriptVarLink *newVar); - /// Get the name as an integer (for arrays) - int getIntName(); - /// Set the name as an integer (for arrays) - void setIntName(int n); -}; - -/// Variable class (containing a doubly-linked list of children) -class CScriptVar -{ - public: - CScriptVarLink *firstChild; - CScriptVarLink *lastChild; - - public: - /// Create undefined - CScriptVar(); - - /// User defined - CScriptVar(const std::string &varData, int varFlags); - - /// Create a string - CScriptVar(const std::string &str); - CScriptVar(double varData); - CScriptVar(int val); - ~CScriptVar(void); - - /// If this is a function, get the result value (for use by native functions) - CScriptVar *getReturnVar(); - - /// Set the result value. Use this when setting complex return data as it avoids a deepCopy() - void setReturnVar(CScriptVar *var); - - /// If this is a function, get the parameter with the given name (for use by native functions) - CScriptVar *getParameter(const std::string &name); - - /// Tries to find a child with the given name, may return 0 - CScriptVarLink *findChild(const std::string &childName); - - /// Tries to find a child with the given name, or will create it with the given flags - CScriptVarLink *findChildOrCreate(const std::string &childName, int varFlags=SCRIPTVAR_UNDEFINED); - - ///< Tries to find a child with the given path (separated by dots) - CScriptVarLink *findChildOrCreateByPath(const std::string &path); - - ///< add a child overwriting any with the same name - CScriptVarLink *addChild(const std::string &childName, CScriptVar *child=NULL); - CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child=NULL); - void removeChild(CScriptVar *child); - - /// Remove a specific link (this is faster than finding via a child) - void removeLink(CScriptVarLink *link); - void removeAllChildren(); - - ///< The the value at an array index - CScriptVar *getArrayIndex(int idx); - - ///< Set the value at an array index - void setArrayIndex(int idx, CScriptVar *value); - - ///< If this is an array, return the number of items in it (else 0) - int getArrayLength(); - - /// Get the number of children - int getChildren(); - - /// get data from variable - int getInt(); - bool getBool() - { - return getInt() != 0; - } - double getDouble(); - const std::string& getString(); - - ///< get Data as a parsable javascript string - std::string getParsableString(); - - /// set data for variable - 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; } - - /// Is this *not* an array/object/etc - bool isBasic() { return firstChild==0; } - - /// do a maths op with another script variable - CScriptVar *mathsOp(CScriptVar *b, int op); - - ///< copy the value from the value given - void copyValue(CScriptVar *val); - - /// deep copy this node and return the result - CScriptVar *deepCopy(); - - /// Dump out the contents of this using trace - void trace(std::string indentStr = "", const std::string &name = ""); - - /// For debugging - just dump a string version of the flags - std::string getFlagsAsString(); - - ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) - void getJSON(std::ostringstream &destination, const std::string linePrefix=""); - - /// Set the callback for native functions - void setCallback(JSCallback callback, void *userdata); - - - /// For memory management/garbage collection - CScriptVar *ref(); ///< Add reference to this variable - void unref(); ///< Remove a reference, and delete this variable if required - int getRefs(); ///< Get the number of references to this script variable - protected: - int refs; ///< The number of references held to this - used for garbage collection - - std::string data; ///< The contents of this variable if it is a string - long intData; ///< The contents of this variable if it is an int - double doubleData; ///< The contents of this variable if it is a double - int flags; ///< the flags determine the type of the variable - int/double/string/etc - JSCallback jsCallback; ///< Callback for native functions - void *jsCallbackUserData; ///< user data passed as second argument to native functions - - void init(); ///< initialisation of data members - - /** Copy the basic data and flags from the variable given, with no - * children. Should be used internally only - by copyValue and deepCopy */ - void copySimpleData(CScriptVar *val); - - friend class CTinyJS; -}; - -class CTinyJS -{ - private: - CScriptLex *l; /// current lexer - std::vector scopes; /// stack of scopes when parsing - #ifdef TINYJS_CALL_STACK - std::vector call_stack; /// Names of places called so we can show when erroring - #endif - - CScriptVar* stringClass; /// Built in string class - CScriptVar* objectClass; /// Built in object class - CScriptVar* arrayClass; /// Built in array class - - private: - // parsing - in order of precedence - 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); - // parsing utility functions - CScriptVarLink *parseFunctionDefinition(); - void parseFunctionArguments(CScriptVar *funcVar); - - CScriptVarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes - /// Look up in any parent classes of the given object - CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name); - - public: - /// root of symbol table - CScriptVar *root; - - public: - CTinyJS(); - ~CTinyJS(); - - void execute(const std::string &code); - /** Evaluate the given code and return a link to a javascript object, - * useful for (dangerous) JSON parsing. If nothing to return, will return - * 'undefined' variable type. CScriptVarLink is returned as this will - * automatically unref the result as it goes out of scope. If you want to - * keep it, you must use ref() and unref() */ - CScriptVarLink evaluateComplex(const std::string &code); - /** Evaluate the given code and return a string. If nothing to return, will return - * 'undefined' */ - std::string evaluate(const std::string &code); - - /// add a native function to be called from TinyJS - /** example: - \code - void scRandInt(CScriptVar *c, void *userdata) { ... } - tinyJS->addNative("function randInt(min, max)", scRandInt, 0); - \endcode - - or - - \code - void scSubstring(CScriptVar *c, void *userdata) { ... } - tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); - \endcode - */ - void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); - - /// Get the given variable specified by a path (var1.var2.etc), or return 0 - CScriptVar *getScriptVariable(const std::string &path); - /// Get the value of the given variable, or return 0 - const std::string *getVariable(const std::string &path); - /// set the value of the given variable, return trur if it exists and gets set - bool setVariable(const std::string &path, const std::string &varData); - - /// Send all variables to stdout - void trace(); -}; +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging +#define TINYJS_CALL_STACK + +#ifdef _WIN32 + #ifdef _DEBUG + #define _CRTDBG_MAP_ALLOC + #include + #include + #endif +#endif +#include +#include + +#ifndef TRACE + #define TRACE printf +#endif // TRACE + + +namespace TinyJS +{ + + 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, + // reserved words + #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 /* always the last entry */ + }; + + enum SCRIPTVAR_FLAGS + { + SCRIPTVAR_UNDEFINED = 0, + SCRIPTVAR_FUNCTION = 1, + SCRIPTVAR_OBJECT = 2, + SCRIPTVAR_ARRAY = 4, + SCRIPTVAR_DOUBLE = 8, // floating point double + SCRIPTVAR_INTEGER = 16, // integer number + SCRIPTVAR_STRING = 32, // string + SCRIPTVAR_NULL = 64, // it seems null is its own data type + SCRIPTVAR_NATIVE = 128, // to specify this is a native function + 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 "" + + class RuntimeError + { + public: + std::string text; + RuntimeError(const std::string &exceptionText); + }; + + class Lexer + { + 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. + */ + /// Data string to get tokens from + char* m_data; + + /// Start and end position in data string + int m_dataStart; + int m_dataEnd; + + /// Do we own this data string? + bool m_dataOwned; + + /// Position in data (we CAN go past the end of the string here) + int m_dataPos; + + + public: + char m_currCh; + char m_nextCh; + /// The type of the token that we have + int m_tk; + /// Position in the data at the beginning of the token we have here + int m_tokenStart; + /// Position in the data at the last character of the token we have here + int m_tokenEnd; + /// Position in the data at the last character of the last token + int m_tokenLastEnd; + /// Data contained in the token we have here + std::string m_tkStr; + + protected: + void getNextCh(); + /// Get the text token from our text string + void getNextToken(); + + public: + Lexer(const std::string &input); + Lexer(Lexer *owner, int startChar, int endChar); + ~Lexer(void); + + + /// Lexical match wotsit + void match(int expected_tk); + + /// Get the string representation of the given token + static std::string getTokenStr(int token); + + /// Reset this lex so we can start again + void reset(); + + /// Return a sub-string from the given position up until right now + std::string getSubString(int pos); + + /// Return a sub-lexer from the given position up until right now + Lexer *getSubLex(int lastPosition); + + /// Return a string representing the position in lines and columns of the character pos given + std::string getPosition(int pos=-1); + }; + + class Variable; + + typedef void (*JSCallback)(Variable *var, void *userdata); + + class VarLink + { + public: + std::string name; + VarLink *nextSibling; + VarLink *prevSibling; + Variable *var; + bool owned; + + public: + VarLink(Variable *var, const std::string &name = TINYJS_TEMP_NAME); + /// Copy constructor + VarLink(const VarLink &link); + ~VarLink(); + /// Replace the Variable pointed to + void replaceWith(Variable *newVar); + /// Replace the Variable pointed to (just dereferences) + void replaceWith(VarLink *newVar); + /// Get the name as an integer (for arrays) + int getIntName(); + /// Set the name as an integer (for arrays) + void setIntName(int n); + }; + + /// Variable class (containing a doubly-linked list of children) + class Variable + { + public: + VarLink *firstChild; + VarLink *lastChild; + + public: + /// Create undefined + Variable(); + + /// User defined + Variable(const std::string &varData, int varFlags); + + /// Create a string + Variable(const std::string &str); + Variable(double varData); + Variable(int val); + ~Variable(void); + + /// If this is a function, get the result value (for use by native functions) + Variable *getReturnVar(); + + /// Set the result value. Use this when setting complex return data as it avoids a deepCopy() + void setReturnVar(Variable *var); + + /// If this is a function, get the parameter with the given name (for use by native functions) + Variable *getParameter(const std::string &name); + + /// Tries to find a child with the given name, may return 0 + VarLink *findChild(const std::string &childName); + + /// Tries to find a child with the given name, or will create it with the given flags + VarLink *findChildOrCreate(const std::string &childName, int varFlags=SCRIPTVAR_UNDEFINED); + + ///< Tries to find a child with the given path (separated by dots) + VarLink *findChildOrCreateByPath(const std::string &path); + + ///< add a child overwriting any with the same name + VarLink *addChild(const std::string &childName, Variable *child=NULL); + VarLink *addChildNoDup(const std::string &childName, Variable *child=NULL); + void removeChild(Variable *child); + + /// Remove a specific link (this is faster than finding via a child) + void removeLink(VarLink *link); + void removeAllChildren(); + + ///< The the value at an array index + Variable *getArrayIndex(int idx); + + ///< Set the value at an array index + void setArrayIndex(int idx, Variable *value); + + ///< If this is an array, return the number of items in it (else 0) + int getArrayLength(); + + /// Get the number of children + int getChildren(); + + /// get data from variable + int getInt(); + bool getBool() + { + return getInt() != 0; + } + double getDouble(); + const std::string& getString(); + + ///< get Data as a parsable javascript string + std::string getParsableString(); + + /// set data for variable + void setInt(int num); + void setDouble(double val); + void setString(const std::string &str); + void setUndefined(); + void setArray(); + + + bool equals(Variable *v); + + bool isInt(); + bool isDouble(); + bool isString(); + bool isNumeric(); + bool isFunction(); + bool isObject(); + bool isArray(); + bool isNative(); + bool isUndefined(); + bool isNull(); + + /// Is this *not* an array/object/etc + bool isBasic(); + + /// do a maths op with another script variable + Variable *mathsOp(Variable *b, int op); + + ///< copy the value from the value given + void copyValue(Variable *val); + + /// deep copy this node and return the result + Variable *deepCopy(); + + /// Dump out the contents of this using trace + void trace(std::string indentStr = "", const std::string &name = ""); + + /// For debugging - just dump a string version of the flags + std::string getFlagsAsString(); + + ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) + void getJSON(std::ostringstream &destination, const std::string linePrefix=""); + + /// Set the callback for native functions + void setCallback(JSCallback callback, void *userdata); + + + /// For memory management/garbage collection + Variable *ref(); ///< Add reference to this variable + void unref(); ///< Remove a reference, and delete this variable if required + int getRefs(); ///< Get the number of references to this script variable + protected: + int refs; ///< The number of references held to this - used for garbage collection + + std::string data; ///< The contents of this variable if it is a string + long intData; ///< The contents of this variable if it is an int + double doubleData; ///< The contents of this variable if it is a double + int flags; ///< the flags determine the type of the variable - int/double/string/etc + JSCallback jsCallback; ///< Callback for native functions + void *jsCallbackUserData; ///< user data passed as second argument to native functions + + void init(); ///< initialisation of data members + + /** Copy the basic data and flags from the variable given, with no + * children. Should be used internally only - by copyValue and deepCopy */ + void copySimpleData(Variable *val); + + friend class Interpreter; + }; + + class Interpreter + { + private: + /// current lexer + Lexer* m_lexer; + + /// stack of scopes when parsing + std::vector m_scopes; + + #ifdef TINYJS_CALL_STACK + /// Names of places called so we can show when erroring + std::vector m_callstack; + #endif + + /// Built in string class + Variable* m_stringclass; + + /// Built in object class + Variable* m_objectclass; + + /// Built in array class + Variable* m_arrayclass; + + /// root of symbol table + Variable* m_roottable; + + + private: + // parsing - in order of precedence + VarLink *functionCall( + bool &execute, VarLink *function, Variable *parent); + VarLink *factor(bool &execute); + VarLink *unary(bool &execute); + VarLink *term(bool &execute); + VarLink *expression(bool &execute); + VarLink *shift(bool &execute); + VarLink *condition(bool &execute); + VarLink *logic(bool &execute); + VarLink *ternary(bool &execute); + VarLink *base(bool &execute); + void block(bool &execute); + void statement(bool &execute); + // parsing utility functions + VarLink *parseFunctionDefinition(); + void parseFunctionArguments(Variable *funcVar); + + VarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes + /// Look up in any parent classes of the given object + VarLink *findInParentClasses(Variable *object, const std::string &name); + + public: + Interpreter(); + ~Interpreter(); + + void execute(const std::string &code); + /** + * Evaluate the given code and return a link to a javascript object, + * useful for (dangerous) JSON parsing. If nothing to return, will return + * 'undefined' variable type. VarLink is returned as this will + * automatically unref the result as it goes out of scope. If you want to + * keep it, you must use ref() and unref() */ + VarLink evaluateComplex(const std::string &code); + + /** + * Evaluate the given code and return a string. + * If nothing to return, will return + * 'undefined' + */ + std::string evaluate(const std::string &code); + + /// add a native function to be called from TinyJS + /** example: + \code + void scRandInt(Variable *c, void *userdata) { ... } + tinyJS->addNative("function randInt(min, max)", scRandInt, 0); + \endcode + + or + + \code + void scSubstring(Variable *c, void *userdata) { ... } + tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); + \endcode + */ + void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata); + + /// Get the given variable specified by a path (var1.var2.etc), or return 0 + Variable *getScriptVariable(const std::string &path); + /// Get the value of the given variable, or return 0 + const std::string *getVariable(const std::string &path); + /// set the value of the given variable, return trur if it exists and gets set + bool setVariable(const std::string &path, const std::string &varData); + + /// Send all variables to stdout + void trace(); + + /// get roottable + Variable* getRoot(); + }; + + /// Register useful functions with the TinyJS interpreter + extern void registerFunctions(Interpreter *tinyJS); + void registerMathFunctions(Interpreter *tinyJS); +} diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 0000000..0e83a8e --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,10 @@ + +#include "private.h" + +namespace TinyJS +{ + RuntimeError::RuntimeError(const std::string& exceptionText) + { + text = exceptionText; + } +} diff --git a/src/functions.cpp b/src/functions.cpp old mode 100755 new mode 100644 index 8a66994..9bcc475 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -28,39 +28,46 @@ * SOFTWARE. */ -#include "TinyJS_Functions.h" #include #include #include +#include "private.h" using namespace std; -// ----------------------------------------------- Actual Functions -void scTrace(CScriptVar *c, void *userdata) { - CTinyJS *js = (CTinyJS*)userdata; - js->root->trace(); +using namespace TinyJS; + +void scTrace(Variable *c, void *userdata) +{ + Interpreter *js = (Interpreter*)userdata; + js->getRoot()->trace(); } -void scObjectDump(CScriptVar *c, void *) { +void scObjectDump(Variable *c, void *) +{ c->getParameter("this")->trace("> "); } -void scObjectClone(CScriptVar *c, void *) { - CScriptVar *obj = c->getParameter("this"); +void scObjectClone(Variable *c, void *) +{ + Variable *obj = c->getParameter("this"); c->getReturnVar()->copyValue(obj); } -void scMathRand(CScriptVar *c, void *) { +void scMathRand(Variable *c, void *) +{ c->getReturnVar()->setDouble((double)rand()/RAND_MAX); } -void scMathRandInt(CScriptVar *c, void *) { +void scMathRandInt(Variable *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 *) { +void scCharToInt(Variable *c, void *) +{ string str = c->getParameter("ch")->getString();; int val = 0; if (str.length()>0) @@ -68,7 +75,7 @@ void scCharToInt(CScriptVar *c, void *) { c->getReturnVar()->setInt(val); } -void scStringIndexOf(CScriptVar *c, void *) { +void scStringIndexOf(Variable *c, void *) { string str = c->getParameter("this")->getString(); string search = c->getParameter("search")->getString(); size_t p = str.find(search); @@ -76,7 +83,7 @@ void scStringIndexOf(CScriptVar *c, void *) { c->getReturnVar()->setInt(val); } -void scStringSubstring(CScriptVar *c, void *) { +void scStringSubstring(Variable *c, void *) { string str = c->getParameter("this")->getString(); int lo = c->getParameter("lo")->getInt(); int hi = c->getParameter("hi")->getInt(); @@ -88,7 +95,7 @@ void scStringSubstring(CScriptVar *c, void *) { c->getReturnVar()->setString(""); } -void scStringCharAt(CScriptVar *c, void *) { +void scStringCharAt(Variable *c, void *) { string str = c->getParameter("this")->getString(); int p = c->getParameter("pos")->getInt(); if (p>=0 && p<(int)str.length()) @@ -97,7 +104,7 @@ void scStringCharAt(CScriptVar *c, void *) { c->getReturnVar()->setString(""); } -void scStringCharCodeAt(CScriptVar *c, void *) { +void scStringCharCodeAt(Variable *c, void *) { string str = c->getParameter("this")->getString(); int p = c->getParameter("pos")->getInt(); if (p>=0 && p<(int)str.length()) @@ -106,38 +113,38 @@ void scStringCharCodeAt(CScriptVar *c, void *) { c->getReturnVar()->setInt(0); } -void scStringSplit(CScriptVar *c, void *) { +void scStringSplit(Variable *c, void *) { string str = c->getParameter("this")->getString(); string sep = c->getParameter("separator")->getString(); - CScriptVar *result = c->getReturnVar(); + Variable *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))); + result->setArrayIndex(length++, new Variable(str.substr(0,pos))); str = str.substr(pos+1); pos = str.find(sep); } if (str.size()>0) - result->setArrayIndex(length++, new CScriptVar(str)); + result->setArrayIndex(length++, new Variable(str)); } -void scStringFromCharCode(CScriptVar *c, void *) { +void scStringFromCharCode(Variable *c, void *) { char str[2]; str[0] = c->getParameter("char")->getInt(); str[1] = 0; c->getReturnVar()->setString(str); } -void scIntegerParseInt(CScriptVar *c, void *) { +void scIntegerParseInt(Variable *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 *) { +void scIntegerValueOf(Variable *c, void *) { string str = c->getParameter("str")->getString(); int val = 0; @@ -146,27 +153,27 @@ void scIntegerValueOf(CScriptVar *c, void *) { c->getReturnVar()->setInt(val); } -void scJSONStringify(CScriptVar *c, void *) { +void scJSONStringify(Variable *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; +void scExec(Variable *c, void *data) { + Interpreter *tinyJS = (Interpreter *)data; std::string str = c->getParameter("jsCode")->getString(); tinyJS->execute(str); } -void scEval(CScriptVar *c, void *data) { - CTinyJS *tinyJS = (CTinyJS *)data; +void scEval(Variable *c, void *data) { + Interpreter *tinyJS = (Interpreter *)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; +void scArrayContains(Variable *c, void *data) { + Variable *obj = c->getParameter("obj"); + VarLink *v = c->getParameter("this")->firstChild; bool contains = false; while (v) { @@ -180,10 +187,10 @@ void scArrayContains(CScriptVar *c, void *data) { c->getReturnVar()->setInt(contains); } -void scArrayRemove(CScriptVar *c, void *data) { - CScriptVar *obj = c->getParameter("obj"); +void scArrayRemove(Variable *c, void *data) { + Variable *obj = c->getParameter("obj"); vector removedIndices; - CScriptVarLink *v; + VarLink *v; // remove v = c->getParameter("this")->firstChild; while (v) { @@ -206,9 +213,9 @@ void scArrayRemove(CScriptVar *c, void *data) { } } -void scArrayJoin(CScriptVar *c, void *data) { +void scArrayJoin(Variable *c, void *data) { string sep = c->getParameter("separator")->getString(); - CScriptVar *arr = c->getParameter("this"); + Variable *arr = c->getParameter("this"); ostringstream sstr; int l = arr->getArrayLength(); @@ -220,28 +227,38 @@ void scArrayJoin(CScriptVar *c, void *data) { c->getReturnVar()->setString(sstr.str()); } -// ----------------------------------------------- Register Functions -void registerFunctions(CTinyJS *tinyJS) { - tinyJS->addNative("function exec(jsCode)", scExec, tinyJS); // execute the given code - tinyJS->addNative("function eval(jsCode)", scEval, tinyJS); // execute the given string (an expression) and return the result - 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); // convert a character to an int - get its value - tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not - 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); // string to int - tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character - tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); // convert to JSON. replacer is ignored at the moment - // JSON.parse is left out as you can (unsafely!) use eval instead - tinyJS->addNative("function Array.contains(obj)", scArrayContains, 0); - tinyJS->addNative("function Array.remove(obj)", scArrayRemove, 0); - tinyJS->addNative("function Array.join(separator)", scArrayJoin, 0); +namespace TinyJS +{ + void registerFunctions(Interpreter *tinyJS) + { + // execute the given code + tinyJS->addNative("function exec(jsCode)", scExec, tinyJS); + // execute the given string (an expression) and return the result + 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); + // convert a character to an int - get its value + tinyJS->addNative("function charToInt(ch)", scCharToInt, 0); + // find the position of a string in a string, -1 if not + 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); + // string to int + tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); + // value of a single character + tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); + // convert to JSON. replacer is ignored at the moment + tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0); + // JSON.parse is left out as you can (unsafely!) use eval instead + tinyJS->addNative("function Array.contains(obj)", scArrayContains, 0); + tinyJS->addNative("function Array.remove(obj)", scArrayRemove, 0); + tinyJS->addNative("function Array.join(separator)", scArrayJoin, 0); + } } diff --git a/src/lexer.cpp b/src/lexer.cpp new file mode 100644 index 0000000..22a8ab5 --- /dev/null +++ b/src/lexer.cpp @@ -0,0 +1,596 @@ + +#include "private.h" + +namespace TinyJS +{ + Lexer::Lexer(const std::string& input) + { + m_data = strncopy(input.c_str(), input.size()); + m_dataOwned = true; + m_dataStart = 0; + m_dataEnd = int(input.size()); + reset(); + } + + Lexer::Lexer(Lexer* owner, int startChar, int endChar) + { + m_data = owner->m_data; + m_dataOwned = false; + m_dataStart = startChar; + m_dataEnd = endChar; + reset(); + } + + Lexer::~Lexer(void) + { + if(m_dataOwned) + { + free((void*)m_data); + } + } + + void Lexer::reset() + { + m_dataPos = m_dataStart; + m_tokenStart = 0; + m_tokenEnd = 0; + m_tokenLastEnd = 0; + m_tk = 0; + m_tkStr = ""; + getNextCh(); + getNextCh(); + getNextToken(); + } + + void Lexer::match(int expected_tk) + { + if(m_tk != expected_tk) + { + std::stringstream errorString; + errorString << "Got " << getTokenStr(m_tk) << " expected " << getTokenStr(expected_tk) + << " at " << getPosition(m_tokenStart); + throw new RuntimeError(errorString.str()); + } + getNextToken(); + } + + std::string Lexer::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 "^="; + // reserved words + 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"; + } + std::stringstream msg; + msg << "?[" << token << "]"; + return msg.str(); + } + + void Lexer::getNextCh() + { + m_currCh = m_nextCh; + if(m_dataPos < m_dataEnd) + { + m_nextCh = m_data[m_dataPos]; + } + else + { + m_nextCh = 0; + } + m_dataPos++; + } + + void Lexer::getNextToken() + { + m_tk = LEX_EOF; + m_tkStr.clear(); + while(m_currCh && isWhitespace(m_currCh)) + { + getNextCh(); + } + // newline comments + if((m_currCh == '/') && (m_nextCh == '/')) + { + while(m_currCh && (m_currCh != '\n')) + { + getNextCh(); + } + getNextCh(); + getNextToken(); + return; + } + // block comments + if((m_currCh == '/') && (m_nextCh == '*')) + { + while(m_currCh && ((m_currCh != '*') || (m_nextCh != '/'))) + { + getNextCh(); + } + getNextCh(); + getNextCh(); + getNextToken(); + return; + } + // record beginning of this token + m_tokenStart = (m_dataPos - 2); + // tokens + if(isAlpha(m_currCh)) // IDs + { + while(isAlpha(m_currCh) || isNumeric(m_currCh)) + { + m_tkStr += m_currCh; + getNextCh(); + } + m_tk = LEX_ID; + if(m_tkStr == "if") + { + m_tk = LEX_R_IF; + } + else if(m_tkStr == "else") + { + m_tk = LEX_R_ELSE; + } + else if(m_tkStr == "do") + { + m_tk = LEX_R_DO; + } + else if(m_tkStr == "while") + { + m_tk = LEX_R_WHILE; + } + else if(m_tkStr == "for") + { + m_tk = LEX_R_FOR; + } + else if(m_tkStr == "break") + { + m_tk = LEX_R_BREAK; + } + else if(m_tkStr == "continue") + { + m_tk = LEX_R_CONTINUE; + } + else if(m_tkStr == "function") + { + m_tk = LEX_R_FUNCTION; + } + else if(m_tkStr == "return") + { + m_tk = LEX_R_RETURN; + } + else if(m_tkStr == "var") + { + m_tk = LEX_R_VAR; + } + else if(m_tkStr == "true") + { + m_tk = LEX_R_TRUE; + } + else if(m_tkStr == "false") + { + m_tk = LEX_R_FALSE; + } + else if(m_tkStr == "null") + { + m_tk = LEX_R_NULL; + } + else if(m_tkStr == "undefined") + { + m_tk = LEX_R_UNDEFINED; + } + else if(m_tkStr == "new") + { + m_tk = LEX_R_NEW; + } + } + else if(isNumeric(m_currCh)) // Numbers + { + bool isHex = false; + if(m_currCh == '0') + { + m_tkStr += m_currCh; + getNextCh(); + } + if(m_currCh == 'x') + { + isHex = true; + m_tkStr += m_currCh; + getNextCh(); + } + m_tk = LEX_INT; + while(isNumeric(m_currCh) || (isHex && isHexadecimal(m_currCh))) + { + m_tkStr += m_currCh; + getNextCh(); + } + if(!isHex && (m_currCh == '.')) + { + m_tk = LEX_FLOAT; + m_tkStr += '.'; + getNextCh(); + while(isNumeric(m_currCh)) + { + m_tkStr += m_currCh; + getNextCh(); + } + } + // do fancy e-style floating point + if(!isHex && ((m_currCh == 'e') || (m_currCh == 'E'))) + { + m_tk = LEX_FLOAT; + m_tkStr += m_currCh; + getNextCh(); + if(m_currCh == '-') + { + m_tkStr += m_currCh; + getNextCh(); + } + while(isNumeric(m_currCh)) + { + m_tkStr += m_currCh; + getNextCh(); + } + } + } + else if(m_currCh == '"') + { + // strings... + getNextCh(); + while(m_currCh && (m_currCh != '"')) + { + if(m_currCh == '\\') + { + getNextCh(); + switch(m_currCh) + { + case 'n' : + m_tkStr += '\n'; + break; + case '"' : + m_tkStr += '"'; + break; + case '\\' : + m_tkStr += '\\'; + break; + default: + m_tkStr += m_currCh; + } + } + else + { + m_tkStr += m_currCh; + } + getNextCh(); + } + getNextCh(); + m_tk = LEX_STR; + } + else if(m_currCh == '\'') + { + // strings again... + getNextCh(); + while(m_currCh && (m_currCh != '\'')) + { + if(m_currCh == '\\') + { + getNextCh(); + switch(m_currCh) + { + case 'n' : + m_tkStr += '\n'; + break; + case 'a' : + m_tkStr += '\a'; + break; + case 'r' : + m_tkStr += '\r'; + break; + case 't' : + m_tkStr += '\t'; + break; + case '\'' : + m_tkStr += '\''; + break; + case '\\' : + m_tkStr += '\\'; + break; + case 'x' : // hex digits + { + char buf[3] = "??"; + getNextCh(); + buf[0] = m_currCh; + getNextCh(); + buf[1] = m_currCh; + m_tkStr += (char)strtol(buf,0,16); + } + break; + default: + if((m_currCh >= '0') && (m_currCh <= '7')) + { + // octal digits + char buf[4] = "???"; + buf[0] = m_currCh; + getNextCh(); + buf[1] = m_currCh; + getNextCh(); + buf[2] = m_currCh; + m_tkStr += (char)strtol(buf,0,8); + } + else + { + m_tkStr += m_currCh; + } + } + } + else + { + m_tkStr += m_currCh; + } + getNextCh(); + } + getNextCh(); + m_tk = LEX_STR; + } + else + { + // single chars + m_tk = m_currCh; + if(m_currCh) + { + getNextCh(); + } + if((m_tk == '=') && (m_currCh == '=')) // == + { + m_tk = LEX_EQUAL; + getNextCh(); + if(m_currCh == '=') // === + { + m_tk = LEX_TYPEEQUAL; + getNextCh(); + } + } + else if((m_tk == '!') && (m_currCh == '=')) // != + { + m_tk = LEX_NEQUAL; + getNextCh(); + if(m_currCh == '=') // !== + { + m_tk = LEX_NTYPEEQUAL; + getNextCh(); + } + } + else if((m_tk == '<') && (m_currCh == '=')) + { + m_tk = LEX_LEQUAL; + getNextCh(); + } + else if((m_tk == '<') && (m_currCh == '<')) + { + m_tk = LEX_LSHIFT; + getNextCh(); + if(m_currCh == '=') // <<= + { + m_tk = LEX_LSHIFTEQUAL; + getNextCh(); + } + } + else if((m_tk == '>') && (m_currCh == '=')) + { + m_tk = LEX_GEQUAL; + getNextCh(); + } + else if((m_tk == '>') && (m_currCh == '>')) + { + m_tk = LEX_RSHIFT; + getNextCh(); + if(m_currCh == '=') // >>= + { + m_tk = LEX_RSHIFTEQUAL; + getNextCh(); + } + else if(m_currCh == '>') // >>> + { + m_tk = LEX_RSHIFTUNSIGNED; + getNextCh(); + } + } + else if((m_tk == '+') && (m_currCh == '=')) + { + m_tk = LEX_PLUSEQUAL; + getNextCh(); + } + else if((m_tk == '-') && (m_currCh == '=')) + { + m_tk = LEX_MINUSEQUAL; + getNextCh(); + } + else if((m_tk == '+') && (m_currCh == '+')) + { + m_tk = LEX_PLUSPLUS; + getNextCh(); + } + else if((m_tk == '-') && (m_currCh == '-')) + { + m_tk = LEX_MINUSMINUS; + getNextCh(); + } + else if((m_tk == '&') && (m_currCh == '=')) + { + m_tk = LEX_ANDEQUAL; + getNextCh(); + } + else if((m_tk == '&') && (m_currCh == '&')) + { + m_tk = LEX_ANDAND; + getNextCh(); + } + else if((m_tk == '|') && (m_currCh == '=')) + { + m_tk = LEX_OREQUAL; + getNextCh(); + } + else if((m_tk == '|') && (m_currCh == '|')) + { + m_tk = LEX_OROR; + getNextCh(); + } + else if((m_tk == '^') && (m_currCh == '=')) + { + m_tk = LEX_XOREQUAL; + getNextCh(); + } + } + /* This isn't quite right yet */ + m_tokenLastEnd = m_tokenEnd; + m_tokenEnd = (m_dataPos - 3); + } + + std::string Lexer::getSubString(int lastPosition) + { + int lastCharIdx = (m_tokenLastEnd + 1); + if(lastCharIdx < m_dataEnd) + { + /* save a memory alloc by using our data array to create the string */ + char old = m_data[lastCharIdx]; + m_data[lastCharIdx] = 0; + std::string value = &m_data[lastPosition]; + m_data[lastCharIdx] = old; + return value; + } + else + { + return std::string(&m_data[lastPosition]); + } + } + + Lexer* Lexer::getSubLex(int lastPosition) + { + int lastCharIdx = (m_tokenLastEnd + 1); + if(lastCharIdx < m_dataEnd) + { + return new Lexer(this, lastPosition, lastCharIdx); + } + else + { + return new Lexer(this, lastPosition, m_dataEnd); + } + } + + std::string Lexer::getPosition(int pos) + { + int line; + int col; + if(pos < 0) + { + pos = m_tokenLastEnd; + } + line = 1; + col = 1; + for(int i=0; i #include +#include "tinyjs.h" +const char *code = + "function myfunc(x, y)" + "{" + "return x + y;" + "}" + "var a = myfunc(1,2);" + "print(a);" +; -//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, void *userdata) { +void js_print(TinyJS::Variable *v, void *userdata) +{ + (void)userdata; printf("> %s\n", v->getParameter("text")->getString().c_str()); } -void js_dump(CScriptVar *v, void *userdata) { - CTinyJS *js = (CTinyJS*)userdata; - js->root->trace("> "); +void js_dump(TinyJS::Variable *v, void *userdata) +{ + (void)v; + auto js = (TinyJS::Interpreter*)userdata; + js->getRoot()->trace("> "); +} + +void js_lexer(TinyJS::Variable* v, void* userdata) +{ + } int main(int argc, char **argv) { - CTinyJS *js = new CTinyJS(); - /* add the functions from TinyJS_Functions.cpp */ - registerFunctions(js); - /* Add a native function */ - js->addNative("function print(text)", &js_print, 0); - js->addNative("function dump()", &js_dump, js); - /* Execute out bit of code - we could call 'evaluate' here if - we wanted something returned */ - try { + (void)argc; + (void)argv; + auto js = new TinyJS::Interpreter(); + /* add the functions from TinyJS_Functions.cpp */ + registerFunctions(js); + /* Add a native function */ + js->addNative("function print(text)", &js_print, 0); + js->addNative("function dump()", &js_dump, js); + /* + * Execute out bit of code - we could call 'evaluate' here if + * we wanted something returned + */ js->execute("var lets_quit = 0; function quit() { lets_quit = 1; }"); - js->execute("print(\"Interactive mode... Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!\");"); - } catch (CScriptException *e) { - printf("ERROR: %s\n", e->text.c_str()); - } - - while (js->evaluate("lets_quit") == "0") { - char buffer[2048]; - fgets ( buffer, sizeof(buffer), stdin ); - try { - js->execute(buffer); - } catch (CScriptException *e) { - printf("ERROR: %s\n", e->text.c_str()); + printf( + "Interactive mode...\n" + "Type quit(); to exit, or print(...); to print something, or dump() to dump the symbol table!" + ); + while (js->evaluate("lets_quit") == "0") + { + char buffer[2048]; + fgets(buffer, sizeof(buffer), stdin); + try + { + js->execute(buffer); + } + catch(TinyJS::RuntimeError *e) + { + printf("ERROR: %s\n", e->text.c_str()); + } } - } - delete js; -#ifdef _WIN32 -#ifdef _DEBUG - _CrtDumpMemoryLeaks(); -#endif -#endif - return 0; + delete js; + return 0; } diff --git a/src/mathfuncs.cpp b/src/mathfuncs.cpp old mode 100755 new mode 100644 index 1d79882..6372c7c --- a/src/mathfuncs.cpp +++ b/src/mathfuncs.cpp @@ -31,9 +31,10 @@ #include #include #include -#include "TinyJS_MathFunctions.h" +#include "private.h" using namespace std; +using namespace TinyJS; #define k_E exp(1.0) #define k_PI 3.1415926535897932384626433832795 @@ -45,7 +46,7 @@ using namespace std; #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) ) -//CScriptVar shortcut macro +//Variable shortcut macro #define scIsInt(a) ( c->getParameter(a)->isInt() ) #define scIsDouble(a) ( c->getParameter(a)->isDouble() ) #define scGetInt(a) ( c->getParameter(a)->getInt() ) @@ -83,7 +84,7 @@ namespace #endif //Math.abs(x) - returns absolute of given value -void scMathAbs(CScriptVar *c, void *userdata) { +void scMathAbs(Variable *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_ABS( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { @@ -92,7 +93,7 @@ void scMathAbs(CScriptVar *c, void *userdata) { } //Math.round(a) - returns nearest round of given value -void scMathRound(CScriptVar *c, void *userdata) { +void scMathRound(Variable *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_ROUND( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { @@ -101,7 +102,7 @@ void scMathRound(CScriptVar *c, void *userdata) { } //Math.min(a,b) - returns minimum of two given values -void scMathMin(CScriptVar *c, void *userdata) { +void scMathMin(Variable *c, void *userdata) { if ( (scIsInt("a")) && (scIsInt("b")) ) { scReturnInt( F_MIN( scGetInt("a"), scGetInt("b") ) ); } else { @@ -110,7 +111,7 @@ void scMathMin(CScriptVar *c, void *userdata) { } //Math.max(a,b) - returns maximum of two given values -void scMathMax(CScriptVar *c, void *userdata) { +void scMathMax(Variable *c, void *userdata) { if ( (scIsInt("a")) && (scIsInt("b")) ) { scReturnInt( F_MAX( scGetInt("a"), scGetInt("b") ) ); } else { @@ -119,7 +120,7 @@ void scMathMax(CScriptVar *c, void *userdata) { } //Math.range(x,a,b) - returns value limited between two given values -void scMathRange(CScriptVar *c, void *userdata) { +void scMathRange(Variable *c, void *userdata) { if ( (scIsInt("x")) ) { scReturnInt( F_RNG( scGetInt("x"), scGetInt("a"), scGetInt("b") ) ); } else { @@ -128,7 +129,7 @@ void scMathRange(CScriptVar *c, void *userdata) { } //Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive) -void scMathSign(CScriptVar *c, void *userdata) { +void scMathSign(Variable *c, void *userdata) { if ( scIsInt("a") ) { scReturnInt( F_SGN( scGetInt("a") ) ); } else if ( scIsDouble("a") ) { @@ -137,117 +138,117 @@ void scMathSign(CScriptVar *c, void *userdata) { } //Math.PI() - returns PI value -void scMathPI(CScriptVar *c, void *userdata) { +void scMathPI(Variable *c, void *userdata) { scReturnDouble(k_PI); } //Math.toDegrees(a) - returns degree value of a given angle in radians -void scMathToDegrees(CScriptVar *c, void *userdata) { +void scMathToDegrees(Variable *c, void *userdata) { scReturnDouble( (180.0/k_PI)*( scGetDouble("a") ) ); } //Math.toRadians(a) - returns radians value of a given angle in degrees -void scMathToRadians(CScriptVar *c, void *userdata) { +void scMathToRadians(Variable *c, void *userdata) { scReturnDouble( (k_PI/180.0)*( scGetDouble("a") ) ); } //Math.sin(a) - returns trig. sine of given angle in radians -void scMathSin(CScriptVar *c, void *userdata) { +void scMathSin(Variable *c, void *userdata) { scReturnDouble( sin( scGetDouble("a") ) ); } //Math.asin(a) - returns trig. arcsine of given angle in radians -void scMathASin(CScriptVar *c, void *userdata) { +void scMathASin(Variable *c, void *userdata) { scReturnDouble( asin( scGetDouble("a") ) ); } //Math.cos(a) - returns trig. cosine of given angle in radians -void scMathCos(CScriptVar *c, void *userdata) { +void scMathCos(Variable *c, void *userdata) { scReturnDouble( cos( scGetDouble("a") ) ); } //Math.acos(a) - returns trig. arccosine of given angle in radians -void scMathACos(CScriptVar *c, void *userdata) { +void scMathACos(Variable *c, void *userdata) { scReturnDouble( acos( scGetDouble("a") ) ); } //Math.tan(a) - returns trig. tangent of given angle in radians -void scMathTan(CScriptVar *c, void *userdata) { +void scMathTan(Variable *c, void *userdata) { scReturnDouble( tan( scGetDouble("a") ) ); } //Math.atan(a) - returns trig. arctangent of given angle in radians -void scMathATan(CScriptVar *c, void *userdata) { +void scMathATan(Variable *c, void *userdata) { scReturnDouble( atan( scGetDouble("a") ) ); } //Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians -void scMathSinh(CScriptVar *c, void *userdata) { +void scMathSinh(Variable *c, void *userdata) { scReturnDouble( sinh( scGetDouble("a") ) ); } //Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians -void scMathASinh(CScriptVar *c, void *userdata) { +void scMathASinh(Variable *c, void *userdata) { scReturnDouble( asinh( scGetDouble("a") ) ); } //Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians -void scMathCosh(CScriptVar *c, void *userdata) { +void scMathCosh(Variable *c, void *userdata) { scReturnDouble( cosh( scGetDouble("a") ) ); } //Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians -void scMathACosh(CScriptVar *c, void *userdata) { +void scMathACosh(Variable *c, void *userdata) { scReturnDouble( acosh( scGetDouble("a") ) ); } //Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians -void scMathTanh(CScriptVar *c, void *userdata) { +void scMathTanh(Variable *c, void *userdata) { scReturnDouble( tanh( scGetDouble("a") ) ); } //Math.atan(a) - returns trig. hyperbolic arctangent of given angle in radians -void scMathATanh(CScriptVar *c, void *userdata) { +void scMathATanh(Variable *c, void *userdata) { scReturnDouble( atan( scGetDouble("a") ) ); } //Math.E() - returns E Neplero value -void scMathE(CScriptVar *c, void *userdata) { +void scMathE(Variable *c, void *userdata) { scReturnDouble(k_E); } //Math.log(a) - returns natural logaritm (base E) of given value -void scMathLog(CScriptVar *c, void *userdata) { +void scMathLog(Variable *c, void *userdata) { scReturnDouble( log( scGetDouble("a") ) ); } //Math.log10(a) - returns logaritm(base 10) of given value -void scMathLog10(CScriptVar *c, void *userdata) { +void scMathLog10(Variable *c, void *userdata) { scReturnDouble( log10( scGetDouble("a") ) ); } //Math.exp(a) - returns e raised to the power of a given number -void scMathExp(CScriptVar *c, void *userdata) { +void scMathExp(Variable *c, void *userdata) { scReturnDouble( exp( scGetDouble("a") ) ); } //Math.pow(a,b) - returns the result of a number raised to a power (a)^(b) -void scMathPow(CScriptVar *c, void *userdata) { +void scMathPow(Variable *c, void *userdata) { scReturnDouble( pow( scGetDouble("a"), scGetDouble("b") ) ); } //Math.sqr(a) - returns square of given value -void scMathSqr(CScriptVar *c, void *userdata) { +void scMathSqr(Variable *c, void *userdata) { scReturnDouble( ( scGetDouble("a") * scGetDouble("a") ) ); } //Math.sqrt(a) - returns square root of given value -void scMathSqrt(CScriptVar *c, void *userdata) { +void scMathSqrt(Variable *c, void *userdata) { scReturnDouble( sqrtf( scGetDouble("a") ) ); } // ----------------------------------------------- Register Functions -void registerMathFunctions(CTinyJS *tinyJS) { +void registerMathFunctions(Interpreter *tinyJS) { // --- Math and Trigonometry functions --- tinyJS->addNative("function Math.abs(a)", scMathAbs, 0); diff --git a/src/private.h b/src/private.h index d9daca9..d11b502 100644 --- a/src/private.h +++ b/src/private.h @@ -28,8 +28,81 @@ #pragma once +#include +#include +#include +#include +#include +#include #include "tinyjs.h" -/// Register useful functions with the TinyJS interpreter -extern void registerFunctions(CTinyJS *tinyJS); -void registerMathFunctions(CTinyJS *tinyJS); +#define DEBUG_MEMORY 0 + +#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 ASSERT(X) \ + assert(X) + + + +/* Frees the given link IF it isn't owned by anything else */ +#define CLEAN(x) \ + { \ + VarLink* __v = x; \ + if(__v && !__v->owned) \ + { \ + delete __v; \ + } \ + } + + +/* +* Create a LINK to point to VAR and free the old link. +* BUT this is more clever - it tries to keep the old link if it's +* not owned to save allocations +*/ + +#define CREATE_LINK(LINK, VAR) \ + { \ + if(!LINK || LINK->owned) \ + { \ + LINK = new VarLink(VAR); \ + } \ + else \ + { \ + LINK->replaceWith(VAR); \ + } \ + } + +#define __this_is_not_used_by_anyone 0 + +// ----------------------------------------------------------------------------------- Utils + +bool isWhitespace(char ch); +bool isNumeric(char ch); +bool isNumber(const std::string& str); +bool isHexadecimal(char ch); +bool isAlpha(char ch); +bool isIDString(const char* s); +void replace(std::string& str, char textFrom, const char* textTo); +/// convert the given std::string into a quoted std::string suitable for javascript +std::string getJSString(const std::string& str); +/** Is the std::string alphanumeric */ +bool isAlphaNum(const std::string& str); +char* strncopy(const char* instr, size_t n); + + diff --git a/run_tests.cpp b/src/run_tests.cpp similarity index 94% rename from run_tests.cpp rename to src/run_tests.cpp index d1c7f05..aed7e57 100644 --- a/run_tests.cpp +++ b/src/run_tests.cpp @@ -211,13 +211,13 @@ bool run_test(const char *filename) { buffer[size]=0; fclose(file); - CTinyJS s; + Interpreter s; registerFunctions(&s); registerMathFunctions(&s); - s.root->addChild("result", new CScriptVar("0",SCRIPTVAR_INTEGER)); + s.getRoot()->addChild("result", new Variable("0",SCRIPTVAR_INTEGER)); try { s.execute(buffer); - } catch (CScriptException *e) { + } catch (RuntimeError *e) { printf("ERROR: %s\n", e->text.c_str()); } bool pass = s.root->getParameter("result")->getBool(); diff --git a/src/scriptvar.cpp b/src/scriptvar.cpp new file mode 100644 index 0000000..2b96549 --- /dev/null +++ b/src/scriptvar.cpp @@ -0,0 +1,902 @@ + +#include "private.h" + +namespace TinyJS +{ + Variable::Variable() + { + refs = 0; + #if DEBUG_MEMORY + mark_allocated(this); + #endif + init(); + flags = SCRIPTVAR_UNDEFINED; + } + + Variable::Variable(const std::string& str) + { + refs = 0; + #if DEBUG_MEMORY + mark_allocated(this); + #endif + init(); + flags = SCRIPTVAR_STRING; + data = str; + } + + Variable::Variable(const std::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; + } + } + + Variable::Variable(double val) + { + refs = 0; + #if DEBUG_MEMORY + mark_allocated(this); + #endif + init(); + setDouble(val); + } + + Variable::Variable(int val) + { + refs = 0; + #if DEBUG_MEMORY + mark_allocated(this); + #endif + init(); + setInt(val); + } + + Variable::~Variable(void) + { + #if DEBUG_MEMORY + mark_deallocated(this); + #endif + removeAllChildren(); + } + + void Variable::init() + { + firstChild = 0; + lastChild = 0; + flags = 0; + jsCallback = 0; + jsCallbackUserData = 0; + data = TINYJS_BLANK_DATA; + intData = 0; + doubleData = 0; + } + + Variable* Variable::getReturnVar() + { + return getParameter(TINYJS_RETURN_VAR); + } + + void Variable::setReturnVar(Variable* var) + { + findChildOrCreate(TINYJS_RETURN_VAR)->replaceWith(var); + } + + Variable* Variable::getParameter(const std::string& name) + { + return findChildOrCreate(name)->var; + } + + VarLink* Variable::findChild(const std::string& childName) + { + VarLink* v = firstChild; + while(v) + { + if(v->name.compare(childName)==0) + { + return v; + } + v = v->nextSibling; + } + return 0; + } + + VarLink* Variable::findChildOrCreate(const std::string& childName, int varFlags) + { + VarLink* l = findChild(childName); + if(l) + { + return l; + } + return addChild(childName, new Variable(TINYJS_BLANK_DATA, varFlags)); + } + + VarLink* Variable::findChildOrCreateByPath(const std::string& path) + { + size_t p = path.find('.'); + if(p == std::string::npos) + { + return findChildOrCreate(path); + } + return findChildOrCreate(path.substr(0,p), SCRIPTVAR_OBJECT)->var-> + findChildOrCreateByPath(path.substr(p+1)); + } + + VarLink* Variable::addChild(const std::string& childName, Variable* child) + { + if(isUndefined()) + { + flags = SCRIPTVAR_OBJECT; + } + // if no child supplied, create one + if(!child) + { + child = new Variable(); + } + VarLink* link = new VarLink(child, childName); + link->owned = true; + if(lastChild) + { + lastChild->nextSibling = link; + link->prevSibling = lastChild; + lastChild = link; + } + else + { + firstChild = link; + lastChild = link; + } + return link; + } + + VarLink* Variable::addChildNoDup(const std::string& childName, Variable* child) + { + // if no child supplied, create one + if(!child) + { + child = new Variable(); + } + VarLink* v = findChild(childName); + if(v) + { + v->replaceWith(child); + } + else + { + v = addChild(childName, child); + } + return v; + } + + void Variable::removeChild(Variable* child) + { + VarLink* link = firstChild; + while(link) + { + if(link->var == child) + { + break; + } + link = link->nextSibling; + } + ASSERT(link); + removeLink(link); + } + + void Variable::removeLink(VarLink* 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 Variable::removeAllChildren() + { + VarLink* c = firstChild; + while(c) + { + VarLink* t = c->nextSibling; + delete c; + c = t; + } + firstChild = 0; + lastChild = 0; + } + + Variable* Variable::getArrayIndex(int idx) + { + char sIdx[64]; + sprintf_s(sIdx, sizeof(sIdx), "%d", idx); + VarLink* link = findChild(sIdx); + if(link) + { + return link->var; + } + else + { + return new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_NULL); // undefined + } + } + + void Variable::setArrayIndex(int idx, Variable* value) + { + char sIdx[64]; + sprintf_s(sIdx, sizeof(sIdx), "%d", idx); + VarLink* link = findChild(sIdx); + if(link) + { + if(value->isUndefined()) + { + removeLink(link); + } + else + { + link->replaceWith(value); + } + } + else + { + if(!value->isUndefined()) + { + addChild(sIdx, value); + } + } + } + + int Variable::getArrayLength() + { + int highest = -1; + if(!isArray()) + { + return 0; + } + VarLink* 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 Variable::getChildren() + { + int n = 0; + VarLink* link = firstChild; + while(link) + { + n++; + link = link->nextSibling; + } + return n; + } + + int Variable::getInt() + { + /* strtol understands about hex and octal */ + if(isInt()) + { + return intData; + } + if(isNull()) + { + return 0; + } + if(isUndefined()) + { + return 0; + } + if(isDouble()) + { + return (int)doubleData; + } + return 0; + } + + double Variable::getDouble() + { + if(isDouble()) + { + return doubleData; + } + if(isInt()) + { + return intData; + } + if(isNull()) + { + return 0; + } + if(isUndefined()) + { + return 0; + } + return 0; /* or NaN? */ + } + + const std::string& Variable::getString() + { + /* Because we can't return a std::string that is generated on demand. + + * I should really just use char* :) */ + static std::string s_null = "null"; + static std::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; + } + // are we just a std::string here? + return data; + } + + void Variable::setInt(int val) + { + flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_INTEGER; + intData = val; + doubleData = 0; + data = TINYJS_BLANK_DATA; + } + + void Variable::setDouble(double val) + { + flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_DOUBLE; + doubleData = val; + intData = 0; + data = TINYJS_BLANK_DATA; + } + + void Variable::setString(const std::string& str) + { + // name sure it's not still a number or integer + flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING; + data = str; + intData = 0; + doubleData = 0; + } + + void Variable::setUndefined() + { + // name sure it's not still a number or integer + flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED; + data = TINYJS_BLANK_DATA; + intData = 0; + doubleData = 0; + removeAllChildren(); + } + + void Variable::setArray() + { + // name sure it's not still a number or integer + flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_ARRAY; + data = TINYJS_BLANK_DATA; + intData = 0; + doubleData = 0; + removeAllChildren(); + } + + bool Variable::equals(Variable* v) + { + Variable* resV = mathsOp(v, LEX_EQUAL); + bool res = resV->getBool(); + delete resV; + return res; + } + + Variable* Variable::mathsOp(Variable* b, int op) + { + Variable* a = this; + // Type equality check + if(op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) + { + // check type first, then call again to check data + bool eql = ((a->flags & SCRIPTVAR_VARTYPEMASK) == + (b->flags & SCRIPTVAR_VARTYPEMASK)); + if(eql) + { + Variable* contents = a->mathsOp(b, LEX_EQUAL); + if(!contents->getBool()) + { + eql = false; + } + if(!contents->refs) + { + delete contents; + } + } + ; + if(op == LEX_TYPEEQUAL) + { + return new Variable(eql); + } + else + { + return new Variable(!eql); + } + } + // do maths... + if(a->isUndefined() && b->isUndefined()) + { + if(op == LEX_EQUAL) + { + return new Variable(true); + } + else if(op == LEX_NEQUAL) + { + return new Variable(false); + } + else + { + return new Variable(); // undefined + } + } + else if((a->isNumeric() || a->isUndefined()) && + (b->isNumeric() || b->isUndefined())) + { + if(!a->isDouble() && !b->isDouble()) + { + // use ints + int da = a->getInt(); + int db = b->getInt(); + switch(op) + { + case '+': + return new Variable(da+db); + case '-': + return new Variable(da-db); + case '*': + return new Variable(da*db); + case '/': + return new Variable(da/db); + case '&': + return new Variable(da&db); + case '|': + return new Variable(da|db); + case '^': + return new Variable(da^db); + case '%': + return new Variable(da%db); + case LEX_EQUAL: + return new Variable(da==db); + case LEX_NEQUAL: + return new Variable(da!=db); + case '<': + return new Variable(da': + return new Variable(da>db); + case LEX_GEQUAL: + return new Variable(da>=db); + default: + throw new RuntimeError("Operation "+Lexer::getTokenStr(op)+" not supported on the Int datatype"); + } + } + else + { + // use doubles + double da = a->getDouble(); + double db = b->getDouble(); + switch(op) + { + case '+': + return new Variable(da+db); + case '-': + return new Variable(da-db); + case '*': + return new Variable(da*db); + case '/': + return new Variable(da/db); + case LEX_EQUAL: + return new Variable(da==db); + case LEX_NEQUAL: + return new Variable(da!=db); + case '<': + return new Variable(da': + return new Variable(da>db); + case LEX_GEQUAL: + return new Variable(da>=db); + default: + throw new RuntimeError("Operation "+Lexer::getTokenStr(op)+" not supported on the Double datatype"); + } + } + } + else if(a->isArray()) + { + /* Just check pointers */ + switch(op) + { + case LEX_EQUAL: + return new Variable(a==b); + case LEX_NEQUAL: + return new Variable(a!=b); + default: + throw new RuntimeError("Operation "+Lexer::getTokenStr(op)+" not supported on the Array datatype"); + } + } + else if(a->isObject()) + { + /* Just check pointers */ + switch(op) + { + case LEX_EQUAL: + return new Variable(a==b); + case LEX_NEQUAL: + return new Variable(a!=b); + default: + throw new RuntimeError("Operation "+Lexer::getTokenStr(op)+" not supported on the Object datatype"); + } + } + else + { + std::string da = a->getString(); + std::string db = b->getString(); + // use std::strings + switch(op) + { + case '+': + return new Variable(da+db, SCRIPTVAR_STRING); + case LEX_EQUAL: + return new Variable(da==db); + case LEX_NEQUAL: + return new Variable(da!=db); + case '<': + return new Variable(da': + return new Variable(da>db); + case LEX_GEQUAL: + return new Variable(da>=db); + default: + throw new RuntimeError("Operation "+Lexer::getTokenStr(op)+" not supported on the std::string datatype"); + } + } + ASSERT(0); + return 0; + } + + void Variable::copySimpleData(Variable* val) + { + data = val->data; + intData = val->intData; + doubleData = val->doubleData; + flags = (flags & ~SCRIPTVAR_VARTYPEMASK) | (val->flags & SCRIPTVAR_VARTYPEMASK); + } + + void Variable::copyValue(Variable* val) + { + if(val) + { + copySimpleData(val); + // remove all current children + removeAllChildren(); + // copy children of 'val' + VarLink* child = val->firstChild; + while(child) + { + Variable* copied; + // don't copy the 'parent' object... + if(child->name != TINYJS_PROTOTYPE_CLASS) + { + copied = child->var->deepCopy(); + } + else + { + copied = child->var; + } + addChild(child->name, copied); + child = child->nextSibling; + } + } + else + { + setUndefined(); + } + } + + Variable* Variable::deepCopy() + { + Variable* newVar = new Variable(); + newVar->copySimpleData(this); + // copy children + VarLink* child = firstChild; + while(child) + { + Variable* copied; + // don't copy the 'parent' object... + 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 Variable::trace(std::string indentStr, const std::string& name) + { + TRACE("%s'%s' = '%s' %s\n", + indentStr.c_str(), + name.c_str(), + getString().c_str(), + getFlagsAsString().c_str()); + std::string indent = indentStr+" "; + VarLink* link = firstChild; + while(link) + { + link->var->trace(indent, link->name); + link = link->nextSibling; + } + } + + std::string Variable::getFlagsAsString() + { + std::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; + } + + std::string Variable::getParsableString() + { + // Numbers can just be put in directly + if(isNumeric()) + { + return getString(); + } + if(isFunction()) + { + std::stringstream funcStr; + funcStr << "function ("; + // get list of parameters + VarLink* link = firstChild; + while(link) + { + funcStr << link->name; + if(link->nextSibling) + { + funcStr << ","; + } + link = link->nextSibling; + } + // add function body + funcStr << ") " << getString(); + return funcStr.str(); + } + // if it is a std::string then we quote it + if(isString()) + { + return getJSString(getString()); + } + if(isNull()) + { + return "null"; + } + return "undefined"; + } + + void Variable::getJSON(std::ostringstream& destination, const std::string linePrefix) + + { + if(isObject()) + { + std::string indentedLinePrefix = linePrefix+" "; + // children - handle with bracketed list + destination << "{ \n"; + VarLink* 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()) + { + std::string indentedLinePrefix = linePrefix+" "; + destination << "[\n"; + int len = getArrayLength(); + if(len>10000) + { + len=10000; // we don't want to get stuck here! + } + for(int i=0; igetJSON(destination, indentedLinePrefix); + if(i - * - * Copyright (C) 2009 Pur3 Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is furnished to do - * so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* 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 - Version 0.13 : Added tokenEnd/tokenLastEnd to lexer to avoid parsing whitespace - Ability to define functions without names - Can now do "var mine = function(a,b) { ... };" - Slightly better 'trace' function - Added findChildOrCreateByPath function - Added simple test suite - Added skipping of blocks when not executing - Version 0.14 : Added parsing of more number types - Added parsing of string defined with ' - Changed nil to null as per spec, added 'undefined' - Now set variables with the correct scope, and treat unknown - as 'undefined' rather than failing - Added proper (I hope) handling of null and undefined - Added === check - Version 0.15 : Fix for possible memory leaks - Version 0.16 : Removal of un-needed findRecursive calls - symbol_base removed and replaced with 'scopes' stack - Added reference counting a proper tree structure - (Allowing pass by reference) - Allowed JSON output to output IDs, not strings - Added get/set for array indices - Changed Callbacks to include user data pointer - Added some support for objects - Added more Java-esque builtin functions - Version 0.17 : Now we don't deepCopy the parent object of the class - Added JSON.stringify and eval() - Nicer JSON indenting - Fixed function output in JSON - Added evaluateComplex - Fixed some reentrancy issues with evaluate/execute - Version 0.18 : Fixed some issues with code being executed when it shouldn't - Version 0.19 : Added array.length - Changed '__parent' to 'prototype' to bring it more in line with javascript - Version 0.20 : Added '%' operator - Version 0.21 : Added array type - String.length() no more - now String.length - Added extra constructors to reduce confusion - Fixed checks against undefined - Version 0.22 : First part of ardi's changes: - sprintf -> sprintf_s - extra tokens parsed - array memory leak fixed - Fixed memory leak in evaluateComplex - Fixed memory leak in FOR loops - Fixed memory leak for unary minus - Version 0.23 : Allowed evaluate[Complex] to take in semi-colon separated - statements and then only return the value from the last one. - Also checks to make sure *everything* was parsed. - Ints + doubles are now stored in binary form (faster + more precise) - Version 0.24 : More useful error for maths ops - Don't dump everything on a match error. - Version 0.25 : Better string escaping - Version 0.26 : Add CScriptVar::equals - Add built-in array functions - Version 0.27 : Added OZLB's TinyJS.setVariable (with some tweaks) - Added OZLB's Maths Functions - Version 0.28 : Ternary operator - Rudimentary call stack on error - Added String Character functions - Added shift operators - Version 0.29 : Added new object via functions - Fixed getString() for double on some platforms - Version 0.30 : Rlyeh Mario's patch for Math Functions on VC++ - Version 0.31 : Add exec() to TinyJS functions - Now print quoted JSON that can be read by PHP/Python parsers - Fixed postfix increment operator - Version 0.32 : Fixed Math.randInt on 32 bit PCs, where it was broken - Version 0.33 : Fixed Memory leak + brokenness on === comparison - - NOTE: - Constructing an array with an initial length 'Array(5)' doesn't work - Recursive loops of data such as a.foo = a; fail to be garbage collected - length variable cannot be set - The postfix increment operator returns the current value, not the previous as it should. - There is no prefix increment operator - Arrays are implemented as a linked list - hence a lookup time is O(n) - - TODO: - Utility va-args style function in TinyJS for executing a function directly - Merge the parsing of expressions/statements so eval("statement") works like we'd expect. - Move 'shift' implementation into mathsOp - - */ - -#include "TinyJS.h" -#include - -#define ASSERT(X) assert(X) -/* Frees the given link IF it isn't owned by anything else */ -#define CLEAN(x) { CScriptVarLink *__v = x; if (__v && !__v->owned) { delete __v; } } -/* Create a LINK to point to VAR and free the old link. - * BUT this is more clever - it tries to keep the old link if it's not owned to save allocations */ -#define CREATE_LINK(LINK, VAR) { if (!LINK || LINK->owned) LINK = new CScriptVarLink(VAR); else LINK->replaceWith(VAR); } - -#include -#include -#include -#include -#include - -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 - -// ----------------------------------------------------------------------------------- Memory Debug - -#define DEBUG_MEMORY 0 - -#if DEBUG_MEMORY - -vector allocatedVars; -vector allocatedLinks; - -void mark_allocated(CScriptVar *v) { - allocatedVars.push_back(v); -} - -void mark_deallocated(CScriptVar *v) { - for (size_t i=0;igetRefs()); - allocatedVars[i]->trace(" "); - } - for (size_t i=0;iname.c_str(), allocatedLinks[i]->var->getRefs()); - allocatedLinks[i]->var->trace(" "); - } - allocatedVars.clear(); - allocatedLinks.clear(); -} -#endif - -// ----------------------------------------------------------------------------------- Utils -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='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); - } -} - -/// 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;i127) { - 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 + "\""; -} - -/** Is the string alphanumeric */ -bool isAlphaNum(const std::string &str) { - if (str.size()==0) return true; - if (!isAlpha(str[0])) return false; - 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; - 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 "^="; - // reserved words - 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(); - // 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 - tokenStart = 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=="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)) { // Numbers - 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(); - } - } - // do fancy e-style floating point - 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=='"') { - // strings... - 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=='\'') { - // strings again... - 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' : { // hex digits - char buf[3] = "??"; - getNextCh(); buf[0] = currCh; - getNextCh(); buf[1] = currCh; - tkStr += (char)strtol(buf,0,16); - } break; - default: if (currCh>='0' && currCh<='7') { - // octal digits - 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 { - // single chars - tk = currCh; - if (currCh) getNextCh(); - if (tk=='=' && currCh=='=') { // == - tk = LEX_EQUAL; - getNextCh(); - if (currCh=='=') { // === - tk = LEX_TYPEEQUAL; - getNextCh(); - } - } else if (tk=='!' && currCh=='=') { // != - tk = LEX_NEQUAL; - getNextCh(); - if (currCh=='=') { // !== - tk = LEX_NTYPEEQUAL; - getNextCh(); - } - } else if (tk=='<' && 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(); - } - } - /* This isn't quite right yet */ - tokenLastEnd = tokenEnd; - tokenEnd = dataPos-3; -} - -string CScriptLex::getSubString(int lastPosition) { - int lastCharIdx = tokenLastEnd+1; - if (lastCharIdx < dataEnd) { - /* save a memory alloc by using our data array to create the - substring */ - 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;iname = name; - this->nextSibling = 0; - this->prevSibling = 0; - this->var = var->ref(); - this->owned = false; -} - -CScriptVarLink::CScriptVarLink(const CScriptVarLink &link) { - // Copy constructor -#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::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 no child supplied, create one - 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 no child supplied, create one - 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); // undefined -} - -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() { - /* strtol understands about hex and octal */ - 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; /* or NaN? */ -} - -const string &CScriptVar::getString() { - /* Because we can't return a string that is generated on demand. - * I should really just use char* :) */ - 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; - // are we just a string here? - 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) { - // name sure it's not still a number or integer - flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_STRING; - data = str; - intData = 0; - doubleData = 0; -} - -void CScriptVar::setUndefined() { - // name sure it's not still a number or integer - flags = (flags&~SCRIPTVAR_VARTYPEMASK) | SCRIPTVAR_UNDEFINED; - data = TINYJS_BLANK_DATA; - intData = 0; - doubleData = 0; - removeAllChildren(); -} - -void CScriptVar::setArray() { - // name sure it's not still a number or integer - 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; - // Type equality check - if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) { - // check type first, then call again to check data - 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); - } - // do maths... - 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(); // undefined - } else if ((a->isNumeric() || a->isUndefined()) && - (b->isNumeric() || b->isUndefined())) { - if (!a->isDouble() && !b->isDouble()) { - // 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("Operation "+CScriptLex::getTokenStr(op)+" 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("Operation "+CScriptLex::getTokenStr(op)+" not supported on the Double datatype"); - } - } - } else if (a->isArray()) { - /* Just check pointers */ - 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()) { - /* Just check pointers */ - 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(); - // use strings - 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': 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); - // remove all current children - removeAllChildren(); - // copy children of 'val' - CScriptVarLink *child = val->firstChild; - while (child) { - CScriptVar *copied; - // don't copy the 'parent' object... - 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); - // copy children - CScriptVarLink *child = firstChild; - while (child) { - CScriptVar *copied; - // don't copy the 'parent' object... - 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() { - // Numbers can just be put in directly - if (isNumeric()) - return getString(); - if (isFunction()) { - ostringstream funcStr; - funcStr << "function ("; - // get list of parameters - CScriptVarLink *link = firstChild; - while (link) { - funcStr << link->name; - if (link->nextSibling) funcStr << ","; - link = link->nextSibling; - } - // add function body - funcStr << ") " << getString(); - return funcStr.str(); - } - // if it is a string then we quote it - if (isString()) - return getJSString(getString()); - if (isNull()) - return "null"; - return "undefined"; -} - -void CScriptVar::getJSON(ostringstream &destination, const string linePrefix) { - if (isObject()) { - string indentedLinePrefix = linePrefix+" "; - // children - handle with bracketed list - 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; // we don't want to get stuck here! - - for (int i=0;igetJSON(destination, indentedLinePrefix); - if (iref(); - // Add built-in classes - 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 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 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 undefined... - 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); - /* Check for dots, we might want to do something like function String.substring ... */ - while (l->tk == '.') { - l->match('.'); - CScriptVarLink *link = base->findChild(funcName); - // if it doesn't exist, make an object class - 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() { - // actually parse a function... - l->match(LEX_R_FUNCTION); - string funcName = TINYJS_TEMP_NAME; - /* we can have functions without names */ - 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; -} - -/** Handle a function call (assumes we've parsed the function name and we're - * on the start bracket). 'parent' is the object that contains this method, - * if there was one (otherwise it's just a normnal function). - */ -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('('); - // create a new symbol table entry for execution of this function - CScriptVar *functionRoot = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); - if (parent) - functionRoot->addChildNoDup("this", parent); - // grab in all parameters - CScriptVarLink *v = function->var->firstChild; - while (v) { - CScriptVarLink *value = base(execute); - if (execute) { - if (value->var->isBasic()) { - // pass by value - functionRoot->addChild(v->name, value->var->deepCopy()); - } else { - // pass by reference - functionRoot->addChild(v->name, value->var); - } - } - CLEAN(value); - if (l->tk!=')') l->match(','); - v = v->nextSibling; - } - l->match(')'); - // setup a return variable - CScriptVarLink *returnVar = NULL; - // execute function! - // add the function's execute space to the symbol table so we can recurse - 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 { - /* we just want to execute the block, but something could - * have messed up and left us with the wrong ScriptLex, so - * we want to be careful here... */ - CScriptException *exception = 0; - CScriptLex *oldLex = l; - CScriptLex *newLex = new CScriptLex(function->var->getString()); - l = newLex; - try { - block(execute); - // because return will probably have called this, and set execute to false - 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(); - /* get the real return var before we remove it from our function */ - returnVar = new CScriptVarLink(returnVarLink->var); - functionRoot->removeLink(returnVarLink); - delete functionRoot; - if (returnVar) - return returnVar; - else - return new CScriptVarLink(new CScriptVar()); - } else { - // function, but not executing - just parse args and be done - l->match('('); - while (l->tk != ')') { - CScriptVarLink *value = base(execute); - CLEAN(value); - if (l->tk!=')') l->match(','); - } - l->match(')'); - if (l->tk == '{') { // TODO: why is this here? - block(execute); - } - /* function will be a blank scriptvarlink if we're not executing, - * so just return it rather than an alloc/free */ - 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()); - //printf("0x%08X for %s at %s\n", (unsigned int)a, l->tkStr.c_str(), l->getPosition().c_str()); - /* The parent if we're executing a method call */ - CScriptVar *parent = 0; - - if (execute && !a) { - /* Variable doesn't exist! JavaScript says we should create it - * (we won't add it here. This is done in the assignment operator)*/ - a = new CScriptVarLink(new CScriptVar(), l->tkStr); - } - l->match(LEX_ID); - while (l->tk=='(' || l->tk=='.' || l->tk=='[') { - if (l->tk=='(') { // ------------------------------------- Function Call - a = functionCall(execute, a, parent); - } else if (l->tk == '.') { // ------------------------------------- Record Access - 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 we haven't found this defined yet, use the built-in - 'length' properly */ - 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 == '[') { // ------------------------------------- Array Access - 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); - /* JSON-style object definition */ - l->match('{'); - while (l->tk != '}') { - string id = l->tkStr; - // we only allow strings or IDs on the left hand side of an initialisation - 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); - } - // no need to clean here, as it will definitely be used - if (l->tk != '}') l->match(','); - } - - l->match('}'); - return new CScriptVarLink(contents); - } - if (l->tk=='[') { - CScriptVar *contents = new CScriptVar(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY); - /* JSON-style array */ - l->match('['); - int idx = 0; - while (l->tk != ']') { - if (execute) { - char idx_str[16]; // big enough for 2^32 - sprintf_s(idx_str, sizeof(idx_str), "%d",idx); - - CScriptVarLink *a = base(execute); - contents->addChild(idx_str, a->var); - CLEAN(a); - } - // no need to clean here, as it will definitely be used - 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) { - // new -> create a new object - 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(')'); - } - } - } - // Nothing we can do here... just hope it's the end... - l->match(LEX_EOF); - return 0; -} - -CScriptVarLink *CTinyJS::unary(bool &execute) { - CScriptVarLink *a; - if (l->tk=='!') { - l->match('!'); // binary not - 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); - // in-place add/subtract - a->replaceWith(res); - CLEAN(a); - a = oldValue; - } - } else { - CScriptVarLink *b = term(execute); - if (execute) { - // not in-place, so just replace - 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 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->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 we're assigning to this and we don't have a parent, - * add it to the symbol table root as per JavaScript. */ - 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 { - // fast skip of blocks - 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=='-') { - /* Execute a simple statement that only contains basic arithmetic... */ - CLEAN(base(execute)); - l->match(';'); - } else if (l->tk=='{') { - /* A block of code */ - block(execute); - } else if (l->tk==';') { - /* Empty statement - to allow things like ;;; */ - l->match(';'); - } else if (l->tk==LEX_R_VAR) { - /* variable creation. TODO - we need a better way of parsing the left - * hand side. Maybe just have a flag called can_create_var that we - * set and then we parse as if we're doing a normal equals.*/ - l->match(LEX_R_VAR); - while (l->tk != ';') { - CScriptVarLink *a = 0; - if (execute) - a = scopes.back()->findChildOrCreate(l->tkStr); - l->match(LEX_ID); - // now do stuff defined with dots - while (l->tk == '.') { - l->match('.'); - if (execute) { - CScriptVarLink *lastA = a; - a = lastA->var->findChildOrCreate(l->tkStr); - } - l->match(LEX_ID); - } - // sort out initialiser - 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; // 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->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); // initialisation - //l->match(';'); - int forCondStart = l->tokenStart; - bool noexecute = false; - CScriptVarLink *cond = base(execute); // condition - bool loopCond = execute && cond->var->getBool(); - CLEAN(cond); - CScriptLex *forCond = l->getSubLex(forCondStart); - l->match(';'); - int forIterStart = l->tokenStart; - CLEAN(base(noexecute)); // iterator - 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); -} - -/// Get the given variable specified by a path (var1.var2.etc), or return 0 -CScriptVar *CTinyJS::getScriptVariable(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); - var = varl?varl->var:0; - prevIdx = thisIdx+1; - thisIdx = path.find('.', prevIdx); - if (thisIdx == string::npos) thisIdx = path.length(); - } - return var; -} - -/// Get the value of the given variable, or return 0 -const string *CTinyJS::getVariable(const string &path) { - CScriptVar *var = getScriptVariable(path); - // return result - if (var) - return &var->getString(); - else - return 0; -} - -/// set the value of the given variable, return trur if it exists and gets set -bool CTinyJS::setVariable(const std::string &path, const std::string &varData) { - CScriptVar *var = getScriptVariable(path); - // return result - 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; -} - -/// Finds a child, looking recursively up the scopes -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; - -} - -/// Look up in any parent classes of the given object -CScriptVarLink *CTinyJS::findInParentClasses(CScriptVar *object, const std::string &name) { - // Look for links to actual parent classes - 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); - } - // else fake it for strings and finally objects - 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; -} +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + + +/* + Version 0.1: + (gw) First published on Google Code + + Version 0.11: + Making sure the 'm_roottable' variable never changes + 'symbol_base' added for the current base of the sybmbol table + + Version 0.12: + Added findChildOrCreate, changed std::string passing to use references + Fixed broken std::string encoding in getJSString() + Removed getInitCode and added getJSON instead + Added nil + Added rough JSON parsing + Improved example app + + Version 0.13: + Added tokenEnd/tokenLastEnd to lexer to avoid parsing whitespace + Ability to define functions without names + Can now do "var mine = function(a,b) { ... };" + Slightly better 'trace' function + Added findChildOrCreateByPath function + Added simple test suite + Added skipping of blocks when not executing + + Version 0.14: + Added parsing of more number types + Added parsing of std::string defined with ' + Changed nil to null as per spec, added 'undefined' + Now set variables with the correct scope, and treat unknown as 'undefined' rather than failing + Added proper (I hope) handling of null and undefined + Added check for '===' + + Version 0.15: + Fix for possible memory leaks + + Version 0.16: + Removal of un-needed findRecursive calls + symbol_base removed and replaced with 'm_scopes' stack + Added reference counting a proper tree structure + (Allowing pass by reference) + Allowed JSON output to output IDs, not std::strings + Added get/set for array indices + Changed Callbacks to include user data pointer + Added some support for objects + Added more Java-esque builtin functions + + Version 0.17: + Now we don't deepCopy the parent object of the class + Added JSON.std::stringify and eval() + Nicer JSON indenting + Fixed function output in JSON + Added evaluateComplex + Fixed some reentrancy issues with evaluate/execute + + Version 0.18: + Fixed some issues with code being executed when it shouldn't + + Version 0.19: + Added array.length + Changed '__parent' to 'prototype' to bring it more in line with javascript + + Version 0.20: + Added '%' operator + + Version 0.21: + Added array type + String.length() no more - now String.length + Added extra constructors to reduce confusion + Fixed checks against undefined + + Version 0.22: + First part of ardi's changes: + sprintf -> sprintf_s + extra tokens parsed + array memory leak fixed + Fixed memory leak in evaluateComplex + Fixed memory leak in FOR loops + Fixed memory leak for unary minus + + Version 0.23: + Allowed evaluate[Complex] to take in semi-colon separated + statements and then only return the value from the last one. + Also checks to make sure *everything* was parsed. + Ints + doubles are now stored in binary form (faster + more precise) + + Version 0.24: + More useful error for maths ops + Don't dump everything on a match error. + + Version 0.25: + Better std::string escaping + + Version 0.26: + Add Variable::equals + Add built-in array functions + + Version 0.27: + Added OZLB's TinyJS.setVariable (with some tweaks) + Added OZLB's Maths Functions + + Version 0.28: + Ternary operator + Rudimentary call stack on error + Added String Character functions + Added shift operators + + Version 0.29: + Added new object via functions + Fixed getString() for double on some platforms + + Version 0.30: + Rlyeh Mario's patch for Math Functions on VC++ + + Version 0.31: + Add exec() to TinyJS functions + Now print quoted JSON that can be read by PHP/Python parsers + Fixed postfix increment operator + + Version 0.32: + Fixed Math.randInt on 32 bit PCs, where it was broken + + Version 0.33: + Fixed Memory leak + brokenness on === comparison + + + NOTE: + Constructing an array with an initial length 'Array(5)' doesn't work + Recursive loops of data such as a.foo = a; fail to be garbage collected + length variable cannot be set + The postfix increment operator returns the current value, not the previous as it should. + There is no prefix increment operator + Arrays are implemented as a linked list - hence a lookup time is O(n) + + TODO: + Utility va-args style function in TinyJS for executing a function directly + Merge the parsing of expressions/statements so eval("statement") works like we'd expect. + Move 'shift' implementation into mathsOp +*/ + + +#include "private.h" + +namespace TinyJS +{ + #if DEBUG_MEMORY + std::vector allocatedVars; + std::vector allocatedLinks; + + void mark_allocated(Variable* v) + { + allocatedVars.push_back(v); + } + + void mark_deallocated(Variable* v) + { + for(size_t i=0; igetRefs()); + allocatedVars[i]->trace(" "); + } + for(size_t i=0; iname.c_str(), allocatedLinks[i]->var->getRefs()); + allocatedLinks[i]->var->trace(" "); + } + allocatedVars.clear(); + allocatedLinks.clear(); + } + #endif + + Interpreter::Interpreter() + { + m_lexer = 0; + m_roottable = (new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); + // Add built-in classes + m_stringclass = (new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); + m_arrayclass = (new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); + m_objectclass = (new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT))->ref(); + m_roottable->addChild("String", m_stringclass); + m_roottable->addChild("Array", m_arrayclass); + m_roottable->addChild("Object", m_objectclass); + } + + Interpreter::~Interpreter() + { + ASSERT(!m_lexer); + m_scopes.clear(); + m_stringclass->unref(); + m_arrayclass->unref(); + m_objectclass->unref(); + m_roottable->unref(); + #if DEBUG_MEMORY + show_allocated(); + #endif + } + + void Interpreter::trace() + { + m_roottable->trace(); + } + + void Interpreter::execute(const std::string& code) + { + Lexer* oldLex = m_lexer; + std::vector oldScopes = m_scopes; + m_lexer = new Lexer(code); + #ifdef TINYJS_CALL_STACK + m_callstack.clear(); + #endif + m_scopes.clear(); + m_scopes.push_back(m_roottable); + try + { + bool execute = true; + while(m_lexer->m_tk) + { + statement(execute); + } + } + catch(RuntimeError* e) + { + std::stringstream msg; + msg << "Error " << e->text; + #ifdef TINYJS_CALL_STACK + for(int i=int(m_callstack.size())-1; i>=0; i--) + { + msg << "\n" << i << ": " << m_callstack.at(size_t(i)); + } + #endif + msg << " at " << m_lexer->getPosition(); + delete m_lexer; + m_lexer = oldLex; + throw new RuntimeError(msg.str()); + } + delete m_lexer; + m_lexer = oldLex; + m_scopes = oldScopes; + } + + VarLink Interpreter::evaluateComplex(const std::string& code) + { + Lexer* oldLex = m_lexer; + std::vector oldScopes = m_scopes; + m_lexer = new Lexer(code); + #ifdef TINYJS_CALL_STACK + m_callstack.clear(); + #endif + m_scopes.clear(); + m_scopes.push_back(m_roottable); + VarLink* v = 0; + try + { + bool execute = true; + do + { + CLEAN(v); + v = base(execute); + if(m_lexer->m_tk!=LEX_EOF) + { + m_lexer->match(';'); + } + } + while(m_lexer->m_tk!=LEX_EOF); + } + catch(RuntimeError* e) + { + std::stringstream msg; + msg << "Error " << e->text; + #ifdef TINYJS_CALL_STACK + for(int i=int(m_callstack.size())-1; i>=0; i--) + { + msg << "\n" << i << ": " << m_callstack.at(size_t(i)); + } + #endif + msg << " at " << m_lexer->getPosition(); + delete m_lexer; + m_lexer = oldLex; + throw new RuntimeError(msg.str()); + } + delete m_lexer; + m_lexer = oldLex; + m_scopes = oldScopes; + if(v) + { + VarLink r = *v; + CLEAN(v); + return r; + } + // return undefined... + return VarLink(new Variable()); + } + + std::string Interpreter::evaluate(const std::string& code) + { + return evaluateComplex(code).var->getString(); + } + + void Interpreter::parseFunctionArguments(Variable* funcVar) + { + m_lexer->match('('); + while(m_lexer->m_tk != ')') + { + funcVar->addChildNoDup(m_lexer->m_tkStr); + m_lexer->match(LEX_ID); + if(m_lexer->m_tk != ')') + { + m_lexer->match(','); + } + } + m_lexer->match(')'); + } + + void Interpreter::addNative(const std::string& funcDesc, JSCallback ptr, void* userdata) + { + Lexer* oldLex = m_lexer; + m_lexer = new Lexer(funcDesc); + Variable* base = m_roottable; + m_lexer->match(LEX_R_FUNCTION); + std::string funcName = m_lexer->m_tkStr; + m_lexer->match(LEX_ID); + /* Check for dots, we might want to do something like function String.substd::string ... */ + while(m_lexer->m_tk == '.') + { + m_lexer->match('.'); + VarLink* link = base->findChild(funcName); + // if it doesn't exist, make an object class + if(!link) + { + link = base->addChild(funcName, new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT)); + } + base = link->var; + funcName = m_lexer->m_tkStr; + m_lexer->match(LEX_ID); + } + Variable* funcVar = new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION | SCRIPTVAR_NATIVE); + funcVar->setCallback(ptr, userdata); + parseFunctionArguments(funcVar); + delete m_lexer; + m_lexer = oldLex; + base->addChild(funcName, funcVar); + } + + VarLink* Interpreter::parseFunctionDefinition() + { + // actually parse a function... + m_lexer->match(LEX_R_FUNCTION); + std::string funcName = TINYJS_TEMP_NAME; + /* we can have functions without names */ + if(m_lexer->m_tk==LEX_ID) + { + funcName = m_lexer->m_tkStr; + m_lexer->match(LEX_ID); + } + VarLink* funcVar = new VarLink(new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION), funcName); + parseFunctionArguments(funcVar->var); + int funcBegin = m_lexer->m_tokenStart; + bool noexecute = false; + block(noexecute); + funcVar->var->data = m_lexer->getSubString(funcBegin); + return funcVar; + } + + /* + * Handle a function call (assumes we've parsed the function name and we're + * on the start bracket). 'parent' is the object that contains this method, + * if there was one (otherwise it's just a normnal function). + */ + VarLink* Interpreter::functionCall(bool& execute, VarLink* function, Variable* parent) + { + if(execute) + { + if(!function->var->isFunction()) + { + std::string errorMsg = "Expecting '"; + errorMsg = errorMsg + function->name + "' to be a function"; + throw new RuntimeError(errorMsg.c_str()); + } + m_lexer->match('('); + // create a new symbol table entry for execution of this function + Variable* functionRoot = new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_FUNCTION); + if(parent) + { + functionRoot->addChildNoDup("this", parent); + } + // grab in all parameters + VarLink* v = function->var->firstChild; + while(v) + { + VarLink* value = base(execute); + if(execute) + { + if(value->var->isBasic()) + { + // pass by value + functionRoot->addChild(v->name, value->var->deepCopy()); + } + else + { + // pass by reference + functionRoot->addChild(v->name, value->var); + } + } + CLEAN(value); + if(m_lexer->m_tk!=')') + { + m_lexer->match(','); + } + v = v->nextSibling; + } + m_lexer->match(')'); + // setup a return variable + VarLink* returnVar = NULL; + // execute function! + // add the function's execute space to the symbol table so we can recurse + VarLink* returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); + m_scopes.push_back(functionRoot); + #ifdef TINYJS_CALL_STACK + m_callstack.push_back(function->name + " from " + m_lexer->getPosition()); + #endif + if(function->var->isNative()) + { + ASSERT(function->var->jsCallback); + function->var->jsCallback(functionRoot, function->var->jsCallbackUserData); + } + else + { + /* + * we just want to execute the block, but something could + * have messed up and left us with the wrong ScriptLex, so + * we want to be careful here... + */ + RuntimeError* exception = 0; + Lexer* oldLex = m_lexer; + Lexer* newLex = new Lexer(function->var->getString()); + m_lexer = newLex; + try + { + block(execute); + // because return will probably have called this, and set execute to false + execute = true; + } + catch(RuntimeError* e) + { + exception = e; + } + delete newLex; + m_lexer = oldLex; + if(exception) + { + throw exception; + } + } + #ifdef TINYJS_CALL_STACK + if(!m_callstack.empty()) + { + m_callstack.pop_back(); + } + #endif + m_scopes.pop_back(); + /* get the real return var before we remove it from our function */ + returnVar = new VarLink(returnVarLink->var); + functionRoot->removeLink(returnVarLink); + delete functionRoot; + if(returnVar) + { + return returnVar; + } + else + { + return new VarLink(new Variable()); + } + } + else + { + // function, but not executing - just parse args and be done + m_lexer->match('('); + while(m_lexer->m_tk != ')') + { + VarLink* value = base(execute); + CLEAN(value); + if(m_lexer->m_tk!=')') + { + m_lexer->match(','); + } + } + m_lexer->match(')'); + if(m_lexer->m_tk == '{') // TODO: why is this here? + { + block(execute); + } + /* + * function will be a blank scriptvarlink if we're not executing, + * so just return it rather than an alloc/free + */ + return function; + } + } + + VarLink* Interpreter::factor(bool& execute) + { + if(m_lexer->m_tk=='(') + { + m_lexer->match('('); + VarLink* a = base(execute); + m_lexer->match(')'); + return a; + } + if(m_lexer->m_tk==LEX_R_TRUE) + { + m_lexer->match(LEX_R_TRUE); + return new VarLink(new Variable(1)); + } + if(m_lexer->m_tk==LEX_R_FALSE) + { + m_lexer->match(LEX_R_FALSE); + return new VarLink(new Variable(0)); + } + if(m_lexer->m_tk==LEX_R_NULL) + { + m_lexer->match(LEX_R_NULL); + return new VarLink(new Variable(TINYJS_BLANK_DATA,SCRIPTVAR_NULL)); + } + if(m_lexer->m_tk==LEX_R_UNDEFINED) + { + m_lexer->match(LEX_R_UNDEFINED); + return new VarLink(new Variable(TINYJS_BLANK_DATA,SCRIPTVAR_UNDEFINED)); + } + if(m_lexer->m_tk==LEX_ID) + { + VarLink* a = execute ? findInScopes(m_lexer->m_tkStr) : new VarLink(new Variable()); + //printf("0x%08X for %s at %s\n", (unsigned int)a, m_lexer->m_tkStr.c_str(), m_lexer->m_getPosition().c_str()); + /* The parent if we're executing a method call */ + Variable* parent = 0; + if(execute && !a) + { + /* Variable doesn't exist! JavaScript says we should create it + + * (we won't add it here. This is done in the assignment operator)*/ + a = new VarLink(new Variable(), m_lexer->m_tkStr); + } + m_lexer->match(LEX_ID); + while(m_lexer->m_tk=='(' || m_lexer->m_tk=='.' || m_lexer->m_tk=='[') + { + if(m_lexer->m_tk=='(') // ------------------------------------- Function Call + { + a = functionCall(execute, a, parent); + } + else if(m_lexer->m_tk == '.') // ------------------------------------- Record Access + { + m_lexer->match('.'); + if(execute) + { + const std::string& name = m_lexer->m_tkStr; + VarLink* child = a->var->findChild(name); + if(!child) + { + child = findInParentClasses(a->var, name); + } + if(!child) + { + /* if we haven't found this defined yet, use the built-in + + 'length' properly */ + if(a->var->isArray() && name == "length") + { + int l = a->var->getArrayLength(); + child = new VarLink(new Variable(l)); + } + else if(a->var->isString() && name == "length") + { + int l = a->var->getString().size(); + child = new VarLink(new Variable(l)); + } + else + { + child = a->var->addChild(name); + } + } + parent = a->var; + a = child; + } + m_lexer->match(LEX_ID); + } + else if(m_lexer->m_tk == '[') // ------------------------------------- Array Access + { + m_lexer->match('['); + VarLink* index = base(execute); + m_lexer->match(']'); + if(execute) + { + VarLink* child = a->var->findChildOrCreate(index->var->getString()); + parent = a->var; + a = child; + } + CLEAN(index); + } + else + { + ASSERT(0); + } + } + return a; + } + if(m_lexer->m_tk==LEX_INT || m_lexer->m_tk==LEX_FLOAT) + { + Variable* a = new Variable(m_lexer->m_tkStr, + ((m_lexer->m_tk==LEX_INT)?SCRIPTVAR_INTEGER:SCRIPTVAR_DOUBLE)); + m_lexer->match(m_lexer->m_tk); + return new VarLink(a); + } + if(m_lexer->m_tk==LEX_STR) + { + Variable* a = new Variable(m_lexer->m_tkStr, SCRIPTVAR_STRING); + m_lexer->match(LEX_STR); + return new VarLink(a); + } + if(m_lexer->m_tk=='{') + { + Variable* contents = new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); + /* JSON-style object definition */ + m_lexer->match('{'); + while(m_lexer->m_tk != '}') + { + std::string id = m_lexer->m_tkStr; + // we only allow std::strings or IDs on the left hand side of an initialisation + if(m_lexer->m_tk==LEX_STR) + { + m_lexer->match(LEX_STR); + } + else + { + m_lexer->match(LEX_ID); + } + m_lexer->match(':'); + if(execute) + { + VarLink* a = base(execute); + contents->addChild(id, a->var); + CLEAN(a); + } + // no need to clean here, as it will definitely be used + if(m_lexer->m_tk != '}') + { + m_lexer->match(','); + } + } + m_lexer->match('}'); + return new VarLink(contents); + } + if(m_lexer->m_tk=='[') + { + Variable* contents = new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_ARRAY); + /* JSON-style array */ + m_lexer->match('['); + int idx = 0; + while(m_lexer->m_tk != ']') + { + if(execute) + { + char idx_str[16]; // big enough for 2^32 + sprintf_s(idx_str, sizeof(idx_str), "%d",idx); + VarLink* a = base(execute); + contents->addChild(idx_str, a->var); + CLEAN(a); + } + // no need to clean here, as it will definitely be used + if(m_lexer->m_tk != ']') + { + m_lexer->match(','); + } + idx++; + } + m_lexer->match(']'); + return new VarLink(contents); + } + if(m_lexer->m_tk==LEX_R_FUNCTION) + { + VarLink* 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(m_lexer->m_tk==LEX_R_NEW) + { + // new -> create a new object + m_lexer->match(LEX_R_NEW); + const std::string& className = m_lexer->m_tkStr; + if(execute) + { + VarLink* objClassOrFunc = findInScopes(className); + if(!objClassOrFunc) + { + TRACE("%s is not a valid class name", className.c_str()); + return new VarLink(new Variable()); + } + m_lexer->match(LEX_ID); + Variable* obj = new Variable(TINYJS_BLANK_DATA, SCRIPTVAR_OBJECT); + VarLink* objLink = new VarLink(obj); + if(objClassOrFunc->var->isFunction()) + { + CLEAN(functionCall(execute, objClassOrFunc, obj)); + } + else + { + obj->addChild(TINYJS_PROTOTYPE_CLASS, objClassOrFunc->var); + if(m_lexer->m_tk == '(') + { + m_lexer->match('('); + m_lexer->match(')'); + } + } + return objLink; + } + else + { + m_lexer->match(LEX_ID); + if(m_lexer->m_tk == '(') + { + m_lexer->match('('); + m_lexer->match(')'); + } + } + } + // Nothing we can do here... just hope it's the end... + m_lexer->match(LEX_EOF); + return 0; + } + + VarLink* Interpreter::unary(bool& execute) + { + VarLink* a; + if(m_lexer->m_tk=='!') + { + m_lexer->match('!'); // binary not + a = factor(execute); + if(execute) + { + Variable zero(0); + Variable* res = a->var->mathsOp(&zero, LEX_EQUAL); + CREATE_LINK(a, res); + } + } + else + { + a = factor(execute); + } + return a; + } + + VarLink* Interpreter::term(bool& execute) + { + VarLink* a = unary(execute); + while(m_lexer->m_tk=='*' || m_lexer->m_tk=='/' || m_lexer->m_tk=='%') + { + int op = m_lexer->m_tk; + m_lexer->match(m_lexer->m_tk); + VarLink* b = unary(execute); + if(execute) + { + Variable* res = a->var->mathsOp(b->var, op); + CREATE_LINK(a, res); + } + CLEAN(b); + } + return a; + } + + VarLink* Interpreter::expression(bool& execute) + { + bool negate = false; + if(m_lexer->m_tk=='-') + { + m_lexer->match('-'); + negate = true; + } + VarLink* a = term(execute); + if(negate) + { + Variable zero(0); + Variable* res = zero.mathsOp(a->var, '-'); + CREATE_LINK(a, res); + } + while(m_lexer->m_tk=='+' || m_lexer->m_tk=='-' || + m_lexer->m_tk==LEX_PLUSPLUS || m_lexer->m_tk==LEX_MINUSMINUS) + { + int op = m_lexer->m_tk; + m_lexer->match(m_lexer->m_tk); + if(op==LEX_PLUSPLUS || op==LEX_MINUSMINUS) + { + if(execute) + { + Variable one(1); + Variable* res = a->var->mathsOp(&one, op==LEX_PLUSPLUS ? '+' : '-'); + VarLink* oldValue = new VarLink(a->var); + // in-place add/subtract + a->replaceWith(res); + CLEAN(a); + a = oldValue; + } + } + else + { + VarLink* b = term(execute); + if(execute) + { + // not in-place, so just replace + Variable* res = a->var->mathsOp(b->var, op); + CREATE_LINK(a, res); + } + CLEAN(b); + } + } + return a; + } + + VarLink* Interpreter::shift(bool& execute) + { + VarLink* a = expression(execute); + if(m_lexer->m_tk==LEX_LSHIFT || m_lexer->m_tk==LEX_RSHIFT || m_lexer->m_tk==LEX_RSHIFTUNSIGNED) + { + int op = m_lexer->m_tk; + m_lexer->match(op); + VarLink* 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; + } + + VarLink* Interpreter::condition(bool& execute) + { + VarLink* a = shift(execute); + VarLink* b; + while(m_lexer->m_tk==LEX_EQUAL || m_lexer->m_tk==LEX_NEQUAL || + m_lexer->m_tk==LEX_TYPEEQUAL || m_lexer->m_tk==LEX_NTYPEEQUAL || + m_lexer->m_tk==LEX_LEQUAL || m_lexer->m_tk==LEX_GEQUAL || + m_lexer->m_tk=='<' || m_lexer->m_tk=='>') + { + int op = m_lexer->m_tk; + m_lexer->match(m_lexer->m_tk); + b = shift(execute); + if(execute) + { + Variable* res = a->var->mathsOp(b->var, op); + CREATE_LINK(a,res); + } + CLEAN(b); + } + return a; + } + + VarLink* Interpreter::logic(bool& execute) + { + VarLink* a = condition(execute); + VarLink* b; + while(m_lexer->m_tk=='&' || m_lexer->m_tk=='|' || m_lexer->m_tk=='^' || m_lexer->m_tk==LEX_ANDAND || m_lexer->m_tk==LEX_OROR) + { + bool noexecute = false; + int op = m_lexer->m_tk; + m_lexer->match(m_lexer->m_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->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) + { + Variable* newa = new Variable(a->var->getBool()); + Variable* newb = new Variable(b->var->getBool()); + CREATE_LINK(a, newa); + CREATE_LINK(b, newb); + } + Variable* res = a->var->mathsOp(b->var, op); + CREATE_LINK(a, res); + } + CLEAN(b); + } + return a; + } + + VarLink* Interpreter::ternary(bool& execute) + { + VarLink* lhs = logic(execute); + bool noexec = false; + if(m_lexer->m_tk=='?') + { + m_lexer->match('?'); + if(!execute) + { + CLEAN(lhs); + CLEAN(base(noexec)); + m_lexer->match(':'); + CLEAN(base(noexec)); + } + else + { + bool first = lhs->var->getBool(); + CLEAN(lhs); + if(first) + { + lhs = base(execute); + m_lexer->match(':'); + CLEAN(base(noexec)); + } + else + { + CLEAN(base(noexec)); + m_lexer->match(':'); + lhs = base(execute); + } + } + } + return lhs; + } + + VarLink* Interpreter::base(bool& execute) + { + VarLink* lhs = ternary(execute); + if(m_lexer->m_tk=='=' || m_lexer->m_tk==LEX_PLUSEQUAL || m_lexer->m_tk==LEX_MINUSEQUAL) + { + /* If we're assigning to this and we don't have a parent, + + * add it to the symbol table m_roottable as per JavaScript. */ + if(execute && !lhs->owned) + { + if(lhs->name.length()>0) + { + VarLink* realLhs = m_roottable->addChildNoDup(lhs->name, lhs->var); + CLEAN(lhs); + lhs = realLhs; + } + else + { + TRACE("Trying to assign to an un-named type\n"); + } + } + int op = m_lexer->m_tk; + m_lexer->match(m_lexer->m_tk); + VarLink* rhs = base(execute); + if(execute) + { + if(op=='=') + { + lhs->replaceWith(rhs); + } + else if(op==LEX_PLUSEQUAL) + { + Variable* res = lhs->var->mathsOp(rhs->var, '+'); + lhs->replaceWith(res); + } + else if(op==LEX_MINUSEQUAL) + { + Variable* res = lhs->var->mathsOp(rhs->var, '-'); + lhs->replaceWith(res); + } + else + { + ASSERT(0); + } + } + CLEAN(rhs); + } + return lhs; + } + + void Interpreter::block(bool& execute) + { + m_lexer->match('{'); + if(execute) + { + while(m_lexer->m_tk && m_lexer->m_tk!='}') + { + statement(execute); + } + m_lexer->match('}'); + } + else + { + // fast skip of blocks + int brackets = 1; + while(m_lexer->m_tk && brackets) + { + if(m_lexer->m_tk == '{') + { + brackets++; + } + if(m_lexer->m_tk == '}') + { + brackets--; + } + m_lexer->match(m_lexer->m_tk); + } + } + } + + void Interpreter::statement(bool& execute) + { + if(m_lexer->m_tk==LEX_ID || + m_lexer->m_tk==LEX_INT || + m_lexer->m_tk==LEX_FLOAT || + m_lexer->m_tk==LEX_STR || + m_lexer->m_tk=='-') + { + /* Execute a simple statement that only contains basic arithmetic... */ + CLEAN(base(execute)); + m_lexer->match(';'); + } + else if(m_lexer->m_tk=='{') + { + /* A block of code */ + block(execute); + } + else if(m_lexer->m_tk==';') + { + /* Empty statement - to allow things like ;;; */ + m_lexer->match(';'); + } + else if(m_lexer->m_tk==LEX_R_VAR) + { + /* variable creation. TODO - we need a better way of parsing the left + + * hand side. Maybe just have a flag called can_create_var that we + + * set and then we parse as if we're doing a normal equals.*/ + m_lexer->match(LEX_R_VAR); + while(m_lexer->m_tk != ';') + { + VarLink* a = 0; + if(execute) + { + a = m_scopes.back()->findChildOrCreate(m_lexer->m_tkStr); + } + m_lexer->match(LEX_ID); + // now do stuff defined with dots + while(m_lexer->m_tk == '.') + { + m_lexer->match('.'); + if(execute) + { + VarLink* lastA = a; + a = lastA->var->findChildOrCreate(m_lexer->m_tkStr); + } + m_lexer->match(LEX_ID); + } + // sort out initialiser + if(m_lexer->m_tk == '=') + { + m_lexer->match('='); + VarLink* var = base(execute); + if(execute) + { + a->replaceWith(var); + } + CLEAN(var); + } + if(m_lexer->m_tk != ';') + { + m_lexer->match(','); + } + } + m_lexer->match(';'); + } + else if(m_lexer->m_tk==LEX_R_IF) + { + m_lexer->match(LEX_R_IF); + m_lexer->match('('); + VarLink* var = base(execute); + m_lexer->match(')'); + bool cond = execute && var->var->getBool(); + CLEAN(var); + bool noexecute = false; // because we need to be abl;e to write to it + statement(cond ? execute : noexecute); + if(m_lexer->m_tk==LEX_R_ELSE) + { + m_lexer->match(LEX_R_ELSE); + statement(cond ? noexecute : execute); + } + } + else if(m_lexer->m_tk==LEX_R_WHILE) + { + // We do repetition by pulling out the std::string representing our statement + // there's definitely some opportunity for optimisation here + m_lexer->match(LEX_R_WHILE); + m_lexer->match('('); + int whileCondStart = m_lexer->m_tokenStart; + bool noexecute = false; + VarLink* cond = base(execute); + bool loopCond = execute && cond->var->getBool(); + CLEAN(cond); + Lexer* whileCond = m_lexer->getSubLex(whileCondStart); + m_lexer->match(')'); + int whileBodyStart = m_lexer->m_tokenStart; + statement(loopCond ? execute : noexecute); + Lexer* whileBody = m_lexer->getSubLex(whileBodyStart); + Lexer* oldLex = m_lexer; + int loopCount = TINYJS_LOOP_MAX_ITERATIONS; + while(loopCond && loopCount-->0) + { + whileCond->reset(); + m_lexer = whileCond; + cond = base(execute); + loopCond = execute && cond->var->getBool(); + CLEAN(cond); + if(loopCond) + { + whileBody->reset(); + m_lexer = whileBody; + statement(execute); + } + } + m_lexer = oldLex; + delete whileCond; + delete whileBody; + if(loopCount<=0) + { + m_roottable->trace(); + TRACE("WHILE Loop exceeded %d iterations at %s\n", + TINYJS_LOOP_MAX_ITERATIONS, m_lexer->getPosition().c_str()); + throw new RuntimeError("LOOP_ERROR"); + } + } + else if(m_lexer->m_tk == LEX_R_FOR) + { + int loopCount; + int forCondStart; + int forIterStart; + int forBodyStart; + bool noexecute; + bool loopCond; + noexecute = false; + m_lexer->match(LEX_R_FOR); + m_lexer->match('('); + statement(execute); // initialisation + //m_lexer->match(';'); + forCondStart = m_lexer->m_tokenStart; + VarLink* cond = base(execute); // condition + loopCond = execute && cond->var->getBool(); + CLEAN(cond); + Lexer* forCond = m_lexer->getSubLex(forCondStart); + m_lexer->match(';'); + forIterStart = m_lexer->m_tokenStart; + CLEAN(base(noexecute)); // iterator + Lexer* forIter = m_lexer->getSubLex(forIterStart); + m_lexer->match(')'); + forBodyStart = m_lexer->m_tokenStart; + statement(loopCond ? execute : noexecute); + Lexer* forBody = m_lexer->getSubLex(forBodyStart); + Lexer* oldLex = m_lexer; + if(loopCond) + { + forIter->reset(); + m_lexer = forIter; + CLEAN(base(execute)); + } + loopCount = TINYJS_LOOP_MAX_ITERATIONS; + while(execute && loopCond && loopCount-->0) + { + forCond->reset(); + m_lexer = forCond; + cond = base(execute); + loopCond = cond->var->getBool(); + CLEAN(cond); + if(execute && loopCond) + { + forBody->reset(); + m_lexer = forBody; + statement(execute); + } + if(execute && loopCond) + { + forIter->reset(); + m_lexer = forIter; + CLEAN(base(execute)); + } + } + m_lexer = oldLex; + delete forCond; + delete forIter; + delete forBody; + if(loopCount<=0) + { + m_roottable->trace(); + TRACE("FOR Loop exceeded %d iterations at %s\n", + TINYJS_LOOP_MAX_ITERATIONS, m_lexer->getPosition().c_str()); + throw new RuntimeError("LOOP_ERROR"); + } + } + else if(m_lexer->m_tk == LEX_R_RETURN) + { + m_lexer->match(LEX_R_RETURN); + VarLink* result = 0; + if(m_lexer->m_tk != ';') + { + result = base(execute); + } + if(execute) + { + VarLink* resultVar = m_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); + m_lexer->match(';'); + } + else if(m_lexer->m_tk==LEX_R_FUNCTION) + { + VarLink* funcVar = parseFunctionDefinition(); + if(execute) + { + if(funcVar->name == TINYJS_TEMP_NAME) + { + TRACE("Functions defined at statement-level are meant to have a name\n"); + } + else + { + m_scopes.back()->addChildNoDup(funcVar->name, funcVar->var); + } + } + CLEAN(funcVar); + } + else + { + m_lexer->match(LEX_EOF); + } + } + + /// Get the given variable specified by a path (var1.var2.etc), or return 0 + Variable* Interpreter::getScriptVariable(const std::string& path) + { + // traverse path + size_t prevIdx = 0; + size_t thisIdx = path.find('.'); + if(thisIdx == std::string::npos) + { + thisIdx = path.length(); + } + Variable* var = m_roottable; + while(var && (prevIdx < path.length())) + { + std::string el = path.substr(prevIdx, thisIdx-prevIdx); + VarLink* varl = var->findChild(el); + var = varl ? varl->var : 0; + prevIdx = (thisIdx + 1); + thisIdx = path.find('.', prevIdx); + if(thisIdx == std::string::npos) + { + thisIdx = path.length(); + } + } + return var; + } + + /// Get the value of the given variable, or return 0 + const std::string* Interpreter::getVariable(const std::string& path) + { + Variable* var = getScriptVariable(path); + // return result + if(var) + { + return &var->getString(); + } + else + { + return 0; + } + } + + /// set the value of the given variable, return trur if it exists and gets set + bool Interpreter::setVariable(const std::string& path, const std::string& varData) + { + Variable* var = getScriptVariable(path); + // return result + 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; + } + } + + /// Finds a child, looking recursively up the m_scopes + VarLink* Interpreter::findInScopes(const std::string& childName) + { + for(int s=m_scopes.size()-1; s>=0; s--) + { + VarLink* v = m_scopes[s]->findChild(childName); + if(v) + { + return v; + } + } + return NULL; + } + + /// Look up in any parent classes of the given object + VarLink* Interpreter::findInParentClasses(Variable* object, const std::string& name) + { + // Look for links to actual parent classes + VarLink* parentClass = object->findChild(TINYJS_PROTOTYPE_CLASS); + while(parentClass) + { + VarLink* implementation = parentClass->var->findChild(name); + if(implementation) + { + return implementation; + } + parentClass = parentClass->var->findChild(TINYJS_PROTOTYPE_CLASS); + } + // else fake it for std::strings and finally objects + if(object->isString()) + { + VarLink* implementation = m_stringclass->findChild(name); + if(implementation) + { + return implementation; + } + } + if(object->isArray()) + { + VarLink* implementation = m_arrayclass->findChild(name); + if(implementation) + { + return implementation; + } + } + VarLink* implementation = m_objectclass->findChild(name); + if(implementation) + { + return implementation; + } + return 0; + } + + + Variable* Interpreter::getRoot() + { + return m_roottable; + } +} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..a495206 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,162 @@ + +#include "private.h" + +// ----------------------------------------------------------------------------------- Utils + +bool isWhitespace(char ch) +{ + return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); +} + +bool isNumeric(char ch) +{ + return (ch>='0') && (ch<='9'); +} + +bool isNumber(const std::string& str) +{ + for(size_t i=0; i= '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(std::string& str, char textFrom, const char* textTo) +{ + size_t sLen = strlen(textTo); + size_t p = str.find(textFrom); + while(p != std::string::npos) + { + str = str.substr(0, p) + textTo + str.substr(p+1); + p = str.find(textFrom, p+sLen); + } +} + +/// convert the given std::string into a quoted std::string suitable for javascript +std::string getJSString(const std::string& str) +{ + std::string nStr = str; + for(size_t i=0; i127) + { + 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 + "\""; +} + +/** Is the std::string alphanumeric */ +bool isAlphaNum(const std::string& str) +{ + if(str.size()==0) + { + return true; + } + if(!isAlpha(str[0])) + { + return false; + } + for(size_t i=0; iname = nname; + this->nextSibling = 0; + this->prevSibling = 0; + this->var = nvar->ref(); + this->owned = false; + } + + VarLink::VarLink(const VarLink& link) + { + // Copy constructor + #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; + } + + VarLink::~VarLink() + { + #if DEBUG_MEMORY + mark_deallocated(this); + #endif + var->unref(); + } + + void VarLink::replaceWith(Variable* newVar) + { + Variable* oldVar = var; + var = newVar->ref(); + oldVar->unref(); + } + + void VarLink::replaceWith(VarLink* newVar) + { + if(newVar) + { + replaceWith(newVar->var); + } + else + { + replaceWith(new Variable()); + } + } + + int VarLink::getIntName() + { + return atoi(name.c_str()); + } + + void VarLink::setIntName(int n) + { + char sIdx[64]; + sprintf_s(sIdx, sizeof(sIdx), "%d", n); + name = sIdx; + } +} + -- 2.11.4.GIT