diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8df0e29 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# +# ************************************************************** +# * Simple C++ Makefile Template * +# * * +# * Author: Arash Partow (2003) * +# * URL: http://www.partow.net/programming/makefile/index.html * +# * * +# * Copyright notice: * +# * Free use of this C++ Makefile template is permitted under * +# * the guidelines and in accordance with the the MIT License * +# * http://www.opensource.org/licenses/MIT * +# * * +# ************************************************************** +# + +CXX := -c++ +CXXFLAGS := -pedantic-errors -Wall -Wextra -Werror -O3 -Wno-unused-parameter +LDFLAGS := -L/usr/lib -lstdc++ -lm -lpthread +BUILD := build +OBJ_DIR := $(BUILD)/objects +APP_DIR := $(BUILD)/apps +TARGET := program +INCLUDE := -Iinclude/ +SRC := \ + $(wildcard src/TinyJS/*.cpp) \ + $(wildcard src/*.cpp) \ + +OBJECTS := $(SRC:%.cpp=$(OBJ_DIR)/%.o) + +all: build $(TARGET) + +$(OBJ_DIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(INCLUDE) -o $@ -c $< + +$(TARGET): $(OBJECTS) + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(INCLUDE) $(LDFLAGS) -o $(APP_DIR)/$(TARGET) $(OBJECTS) + +.PHONY: all build clean + +build: + @mkdir -p $(APP_DIR) + @mkdir -p $(OBJ_DIR) + +clean: + rm -rf build/* diff --git a/include/TinyJS/TinyJS.h b/include/TinyJS/TinyJS.h new file mode 100755 index 0000000..545d076 --- /dev/null +++ b/include/TinyJS/TinyJS.h @@ -0,0 +1,2302 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TINYJS_H +#define TINYJS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TinyJS/config.h" + +#ifdef NO_POOL_ALLOCATOR + template + class fixed_size_object {}; +#else +# include "TinyJS/pool_allocator.h" +#endif + +#ifdef _MSC_VER +# if defined(_DEBUG) && defined(_DEBUG_NEW) +# define _AFXDLL +# include // MFC-Kern- und -Standardkomponenten +# define new DEBUG_NEW +# endif +# define DEPRECATED(_Text) __declspec(deprecated(_Text)) +#elif defined(__GNUC__) +# define DEPRECATED(_Text) __attribute__ ((deprecated)) +#else +# define DEPRECATED(_Text) +#endif + +#ifndef ASSERT +# define ASSERT(X) assert(X) +#endif + +#undef TRACE +#ifndef TRACE +#define TRACE printf +#endif // TRACE + +enum LEX_TYPES { + LEX_EOF = 0, +#define LEX_RELATIONS_1_BEGIN LEX_EQUAL + LEX_EQUAL = 256, + LEX_TYPEEQUAL, + LEX_NEQUAL, + LEX_NTYPEEQUAL, +#define LEX_RELATIONS_1_END LEX_NTYPEEQUAL + LEX_LEQUAL, + LEX_GEQUAL, +#define LEX_SHIFTS_BEGIN LEX_LSHIFT + LEX_LSHIFT, + LEX_RSHIFT, + LEX_RSHIFTU, // unsigned +#define LEX_SHIFTS_END LEX_RSHIFTU + LEX_PLUSPLUS, + LEX_MINUSMINUS, + LEX_ANDAND, + LEX_OROR, + LEX_INT, + +#define LEX_ASSIGNMENTS_BEGIN LEX_PLUSEQUAL + LEX_PLUSEQUAL, + LEX_MINUSEQUAL, + LEX_ASTERISKEQUAL, + LEX_SLASHEQUAL, + LEX_PERCENTEQUAL, + LEX_LSHIFTEQUAL, + LEX_RSHIFTEQUAL, + LEX_RSHIFTUEQUAL, // unsigned + LEX_ANDEQUAL, + LEX_OREQUAL, + LEX_XOREQUAL, +#define LEX_ASSIGNMENTS_END LEX_XOREQUAL + +#define LEX_TOKEN_NONSIMPLE_1_BEGIN LEX_TOKEN_STRING_BEGIN +#define LEX_TOKEN_STRING_BEGIN LEX_ID + LEX_ID, + LEX_STR, + LEX_REGEXP, + LEX_T_LABEL, + LEX_T_DUMMY_LABEL, +#define LEX_TOKEN_STRING_END LEX_T_DUMMY_LABEL + + LEX_FLOAT, +#define LEX_TOKEN_NONSIMPLE_1_END LEX_FLOAT + + // reserved words + LEX_R_IF, + LEX_R_ELSE, + LEX_R_DO, + LEX_R_WHILE, + LEX_R_FOR, + LEX_R_IN, + LEX_T_OF, + LEX_R_BREAK, + LEX_R_CONTINUE, + LEX_R_RETURN, + LEX_R_VAR, + LEX_R_LET, + LEX_R_CONST, + LEX_R_WITH, + LEX_R_TRUE, + LEX_R_FALSE, + LEX_R_NULL, + LEX_R_NEW, + LEX_R_TRY, + LEX_R_CATCH, + LEX_R_FINALLY, + LEX_R_THROW, + LEX_R_TYPEOF, + LEX_R_VOID, + LEX_R_DELETE, + LEX_R_INSTANCEOF, + LEX_R_SWITCH, + LEX_R_CASE, + LEX_R_DEFAULT, + + // special token +// LEX_T_FILE, +#define LEX_TOKEN_NONSIMPLE_2_BEGIN LEX_TOKEN_FOR_BEGIN +#define LEX_TOKEN_FOR_BEGIN LEX_T_LOOP + LEX_T_LOOP, + LEX_T_FOR_IN, +#define LEX_TOKEN_FOR_END LEX_T_FOR_IN +#define LEX_TOKEN_FUNCTION_BEGIN LEX_R_FUNCTION + LEX_R_FUNCTION, + LEX_T_FUNCTION_PLACEHOLDER, + LEX_T_FUNCTION_OPERATOR, + LEX_T_GET, + LEX_T_SET, +#define LEX_TOKEN_FUNCTION_END LEX_T_SET + LEX_T_TRY, + LEX_T_OBJECT_LITERAL, + LEX_T_DESTRUCTURING_VAR, + LEX_T_FORWARD, +#define LEX_TOKEN_NONSIMPLE_2_END LEX_T_FORWARD + + LEX_T_EXCEPTION_VAR, + LEX_T_SKIP, + + LEX_R_YIELD, + +}; +#define LEX_TOKEN_DATA_STRING(tk) ((LEX_TOKEN_STRING_BEGIN<= tk && tk <= LEX_TOKEN_STRING_END)) +#define LEX_TOKEN_DATA_FLOAT(tk) (tk==LEX_FLOAT) +#define LEX_TOKEN_DATA_LOOP(tk) (LEX_TOKEN_FOR_BEGIN <= tk && tk <= LEX_TOKEN_FOR_END) +#define LEX_TOKEN_DATA_FUNCTION(tk) (LEX_TOKEN_FUNCTION_BEGIN <= tk && tk <= LEX_TOKEN_FUNCTION_END) +#define LEX_TOKEN_DATA_TRY(tk) (tk == LEX_T_TRY) +#define LEX_TOKEN_DATA_OBJECT_LITERAL(tk) (tk==LEX_T_OBJECT_LITERAL) +#define LEX_TOKEN_DATA_DESTRUCTURING_VAR(tk) (tk==LEX_T_DESTRUCTURING_VAR) +#define LEX_TOKEN_DATA_FORWARDER(tk) (tk==LEX_T_FORWARD) + +#define LEX_TOKEN_DATA_SIMPLE(tk) (!((LEX_TOKEN_NONSIMPLE_1_BEGIN <= tk && tk <= LEX_TOKEN_NONSIMPLE_1_END) || (LEX_TOKEN_NONSIMPLE_2_BEGIN <= tk && tk <= LEX_TOKEN_NONSIMPLE_2_END))) + +enum SCRIPTVARLINK_FLAGS { + SCRIPTVARLINK_WRITABLE = 1<<0, + SCRIPTVARLINK_CONFIGURABLE = 1<<1, + SCRIPTVARLINK_ENUMERABLE = 1<<2, + SCRIPTVARLINK_DEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_CONFIGURABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_VARDEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_CONSTDEFAULT = SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_BUILDINDEFAULT = SCRIPTVARLINK_WRITABLE | SCRIPTVARLINK_CONFIGURABLE, + SCRIPTVARLINK_READONLY = SCRIPTVARLINK_CONFIGURABLE, + SCRIPTVARLINK_READONLY_ENUM = SCRIPTVARLINK_CONFIGURABLE | SCRIPTVARLINK_ENUMERABLE, + SCRIPTVARLINK_CONSTANT = 0, +}; + +enum ERROR_TYPES { + Error = 0, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError +}; +#define ERROR_MAX TypeError +#define ERROR_COUNT (ERROR_MAX+1) +extern const char *ERROR_NAME[]; + +#define TEMPORARY_MARK_SLOTS 5 + +#define TINYJS_RETURN_VAR "return" +#define TINYJS_LOKALE_VAR "__locale__" +#define TINYJS_ANONYMOUS_VAR "__anonymous__" +#define TINYJS_ARGUMENTS_VAR "arguments" +#define TINYJS___PROTO___VAR "__proto__" +#define TINYJS_PROTOTYPE_CLASS "prototype" +#define TINYJS_FUNCTION_CLOSURE_VAR "__function_closure__" +#define TINYJS_SCOPE_PARENT_VAR "__scope_parent__" +#define TINYJS_SCOPE_WITH_VAR "__scope_with__" +#define TINYJS_ACCESSOR_GET_VAR "__accessor_get__" +#define TINYJS_ACCESSOR_SET_VAR "__accessor_set__" +#define TINYJS_CONSTRUCTOR_VAR "constructor" +#define TINYJS_TEMP_NAME "" +#define TINYJS_BLANK_DATA "" + +typedef std::vector STRING_VECTOR_t; +typedef STRING_VECTOR_t::iterator STRING_VECTOR_it; + +typedef std::set STRING_SET_t; +typedef STRING_SET_t::iterator STRING_SET_it; + +/// convert the given string into a quoted string suitable for javascript +std::string getJSString(const std::string &str); +/// convert the given int into a string +std::string int2string(int32_t intData); +std::string int2string(uint32_t intData); +/// convert the given double into a string +std::string float2string(const double &floatData); + + +////////////////////////////////////////////////////////////////////////// +/// CScriptException +////////////////////////////////////////////////////////////////////////// + +class CScriptException { +public: + ERROR_TYPES errorType; + std::string message; + std::string fileName; + int lineNumber; + int column; + CScriptException(const std::string &Message, const std::string &File, int Line=-1, int Column=-1) : + errorType(Error), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(ERROR_TYPES ErrorType, const std::string &Message, const std::string &File, int Line=-1, int Column=-1) : + errorType(ErrorType), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(const std::string &Message, const char *File="", int Line=-1, int Column=-1) : + errorType(Error), message(Message), fileName(File), lineNumber(Line), column(Column){} + CScriptException(ERROR_TYPES ErrorType, const std::string &Message, const char *File="", int Line=-1, int Column=-1) : + errorType(ErrorType), message(Message), fileName(File), lineNumber(Line), column(Column){} + std::string toString(); +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptLex +////////////////////////////////////////////////////////////////////////// + +class CScriptLex +{ +public: + CScriptLex(const char *Code, const std::string &File="", int Line=0, int Column=0); + struct POS; + int tk; ///< The type of the token that we have + int last_tk; ///< The type of the last token that we have + std::string tkStr; ///< Data contained in the token we have here + + void check(int expected_tk, int alternate_tk=-1); ///< Lexical check wotsit + void match(int expected_tk, int alternate_tk=-1); ///< Lexical match wotsit + void reset(const POS &toPos); ///< Reset this lex so we can start again + + std::string currentFile; + struct POS { + const char *tokenStart; + int currentLine; + const char *currentLineStart; + int currentColumn() { return tokenStart-currentLineStart; } + } pos; + int currentLine() { return pos.currentLine; } + int currentColumn() { return pos.currentColumn(); } + bool lineBreakBeforeToken; +private: + const char *data; + const char *dataPos; + char currCh, nextCh; + + void getNextCh(); + void getNextToken(); ///< Get the text token from our text string +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptTokenData +////////////////////////////////////////////////////////////////////////// + +class CScriptToken; +typedef std::vector TOKEN_VECT; +typedef std::vector::iterator TOKEN_VECT_it; +typedef std::vector::const_iterator TOKEN_VECT_cit; +class CScriptTokenData +{ +protected: + CScriptTokenData() : refs(0){} + virtual ~CScriptTokenData() {} +private: +// CScriptTokenData(const CScriptTokenData &noCopy); +// CScriptTokenData &operator=(const CScriptTokenData &noCopy); +public: + void ref() { refs++; } + void unref() { if(--refs == 0) delete this; } +private: + int refs; +}; +template +class CScriptTokenDataPtr { +public: + CScriptTokenDataPtr() : ptr(0) {} + CScriptTokenDataPtr(const CScriptTokenDataPtr &Copy) : ptr(0) { *this=Copy; } + CScriptTokenDataPtr &operator=(const CScriptTokenDataPtr &Copy) { + if(ptr != Copy.ptr) { + if(ptr) ptr->unref(); + if((ptr = Copy.ptr)) ptr->ref(); + } + return *this; + } + CScriptTokenDataPtr(C &Init) { (ptr=&Init)->ref(); } + ~CScriptTokenDataPtr() { if(ptr) ptr->unref(); } + C *operator->() { return ptr; } + C &operator*() { return *ptr; } + operator bool() { return ptr!=0; } + bool operator==(const CScriptTokenDataPtr& rhs) { return ptr==rhs.ptr; } +private: + C *ptr; +}; + +class CScriptTokenDataString : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataString(const std::string &String) : tokenStr(String) {} + std::string tokenStr; +private: +}; + +class CScriptTokenDataFnc : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataFnc() : line(0),isGenerator(false) {} + std::string file; + int line; + std::string name; + TOKEN_VECT arguments; + TOKEN_VECT body; + std::string getArgumentsString(); + bool isGenerator; +}; + +class CScriptTokenDataForwards : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataForwards() {} + enum { + LETS = 0, + VARS, + CONSTS, + END + }; + STRING_SET_t varNames[END]; + STRING_SET_t vars_in_letscope; + class compare_fnc_token_by_name { + public: + bool operator()(const CScriptToken& lhs, const CScriptToken& rhs) const; + }; + typedef std::set FNC_SET_t; + typedef FNC_SET_t::iterator FNC_SET_it; + FNC_SET_t functions; + bool checkRedefinition(const std::string &Str, bool checkVars); + void addVars( STRING_VECTOR_t &Vars ); + void addConsts( STRING_VECTOR_t &Vars ); + std::string addVarsInLetscope(STRING_VECTOR_t &Vars); + std::string addLets(STRING_VECTOR_t &Lets); + bool empty() { return varNames[LETS].empty() && varNames[VARS].empty() && varNames[CONSTS].empty() && functions.empty(); } +private: +}; +class CScriptTokenDataForwardsPtr { +public: + CScriptTokenDataForwardsPtr() : ptr(0) {} + CScriptTokenDataForwardsPtr(const CScriptTokenDataForwardsPtr &Copy) : ptr(0) { *this=Copy; } + CScriptTokenDataForwardsPtr &operator=(const CScriptTokenDataForwardsPtr &Copy) { + if(ptr != Copy.ptr) { + if(ptr) ptr->unref(); + if((ptr = Copy.ptr)) ptr->ref(); + } + return *this; + } + CScriptTokenDataForwardsPtr(CScriptTokenDataForwards &Init) { (ptr=&Init)->ref(); } + ~CScriptTokenDataForwardsPtr() { if(ptr) ptr->unref(); } + CScriptTokenDataForwards *operator->() { return ptr; } + operator bool() { return ptr!=0; } + bool operator==(const CScriptTokenDataForwardsPtr& rhs) { return ptr==rhs.ptr; } +private: + CScriptTokenDataForwards *ptr; +}; +typedef std::vector FORWARDER_VECTOR_t; + +class CScriptTokenDataLoop : public fixed_size_object, public CScriptTokenData { +public: + CScriptTokenDataLoop() { type=FOR; } + enum {FOR_EACH=0, FOR_IN, FOR_OF, FOR, WHILE, DO} type; // do not change the order + STRING_VECTOR_t labels; + TOKEN_VECT init; + TOKEN_VECT condition; + TOKEN_VECT iter; + TOKEN_VECT body; + std::string getParsableString(const std::string &IndentString="", const std::string &Indent=""); +}; + +typedef std::pair DESTRUCTURING_VAR_t; +typedef std::vector DESTRUCTURING_VARS_t; +typedef DESTRUCTURING_VARS_t::iterator DESTRUCTURING_VARS_it; +typedef DESTRUCTURING_VARS_t::const_iterator DESTRUCTURING_VARS_cit; +class CScriptTokenDataDestructuringVar : public fixed_size_object, public CScriptTokenData { +public: + DESTRUCTURING_VARS_t vars; + void getVarNames(STRING_VECTOR_t Name); + std::string getParsableString(); +private: +}; + +class CScriptTokenDataObjectLiteral : public fixed_size_object, public CScriptTokenData { +public: + enum {ARRAY, OBJECT} type; + int flags; + struct ELEMENT { + std::string id; + TOKEN_VECT value; + }; + bool destructuring; + bool structuring; + std::vector elements; + void setMode(bool Destructuring); + std::string getParsableString(); +private: +}; + +class CScriptTokenDataTry : public fixed_size_object, public CScriptTokenData { +public: + TOKEN_VECT tryBlock; + struct CatchBlock { + CScriptTokenDataPtr indentifiers; + TOKEN_VECT condition; + TOKEN_VECT block; + }; + std::vector catchBlocks; + typedef std::vector::iterator CatchBlock_it; + TOKEN_VECT finallyBlock; + std::string getParsableString(const std::string &IndentString="", const std::string &Indent=""); +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptToken +////////////////////////////////////////////////////////////////////////// + +class CScriptTokenizer; +/* + a Token needs 8 Byte + 2 Bytes for the Row-Position of the Token + 2 Bytes for the Token self + and + 4 Bytes for special Datas in an union + e.g. an int for interger-literals + or pointer for double-literals, + for string-literals or for functions +*/ +class CScriptToken : public fixed_size_object +{ +public: + CScriptToken() : line(0), column(0), token(0), intData(0) {} + CScriptToken(CScriptLex *l, int Match=-1, int Alternate=-1); + CScriptToken(uint16_t Tk, int IntData=0); + CScriptToken(uint16_t Tk, const std::string &TkStr); + CScriptToken(const CScriptToken &Copy) : token(0) { *this = Copy; } + CScriptToken &operator =(const CScriptToken &Copy); + ~CScriptToken() { clear(); } + + int &Int() { ASSERT(LEX_TOKEN_DATA_SIMPLE(token)); return intData; } + std::string &String() { ASSERT(LEX_TOKEN_DATA_STRING(token)); return dynamic_cast(tokenData)->tokenStr; } + double &Float() { ASSERT(LEX_TOKEN_DATA_FLOAT(token)); return *floatData; } + CScriptTokenDataFnc &Fnc() { ASSERT(LEX_TOKEN_DATA_FUNCTION(token)); return *dynamic_cast(tokenData); } + const CScriptTokenDataFnc &Fnc() const { ASSERT(LEX_TOKEN_DATA_FUNCTION(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataObjectLiteral &Object() { ASSERT(LEX_TOKEN_DATA_OBJECT_LITERAL(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataDestructuringVar &DestructuringVar() { ASSERT(LEX_TOKEN_DATA_DESTRUCTURING_VAR(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataLoop &Loop() { ASSERT(LEX_TOKEN_DATA_LOOP(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataTry &Try() { ASSERT(LEX_TOKEN_DATA_TRY(token)); return *dynamic_cast(tokenData); } + CScriptTokenDataForwards &Forwarder() { ASSERT(LEX_TOKEN_DATA_FORWARDER(token)); return *dynamic_cast(tokenData); } +#ifdef _DEBUG + std::string token_str; +#endif + uint16_t line; + uint16_t column; + uint16_t token; + + static std::string getParsableString(TOKEN_VECT &Tokens, const std::string &IndentString="", const std::string &Indent=""); + static std::string getParsableString(TOKEN_VECT_it Begin, TOKEN_VECT_it End, const std::string &IndentString="", const std::string &Indent=""); + static std::string getTokenStr( int token, bool *need_space=0 ); + static const char *isReservedWord(int Token); + static int isReservedWord(const std::string &Str); +private: + + void clear(); + union { + int intData; + double *floatData; + CScriptTokenData *tokenData; + }; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptTokenizer - converts the code in a vector with tokens +////////////////////////////////////////////////////////////////////////// + +class CScriptTokenizer +{ +public: + struct ScriptTokenPosition { + ScriptTokenPosition(TOKEN_VECT *Tokens) : tokens(Tokens), pos(tokens->begin())/*, currentLine(0)*//*, currentColumn(0)*/ {} + bool operator ==(const ScriptTokenPosition &eq) { return pos == eq.pos; } + ScriptTokenPosition &operator =(const ScriptTokenPosition ©) { + tokens=copy.tokens; pos=copy.pos; + return *this; + } + TOKEN_VECT *tokens; + TOKEN_VECT_it pos; + int currentLine() { return pos->line; } + int currentColumn() { return pos->column; } + }; + struct ScriptTokenState { + TOKEN_VECT Tokens; + FORWARDER_VECTOR_t Forwarders; + std::vector Marks; + STRING_VECTOR_t Labels; + STRING_VECTOR_t LoopLabels; + bool LeftHand; + void pushLeftHandState() { States.push_back(LeftHand); } + void popLeftHandeState() { LeftHand = States.back(); States.pop_back(); } + std::vector States; + bool FunctionIsGenerator; + bool HaveReturnValue; + }; + CScriptTokenizer(); + CScriptTokenizer(CScriptLex &Lexer); + CScriptTokenizer(const char *Code, const std::string &File="", int Line=0, int Column=0); + void tokenizeCode(CScriptLex &Lexer); + + CScriptToken &getToken() { return *(tokenScopeStack.back().pos); } + void getNextToken(); + bool check(int ExpectedToken, int AlternateToken=-1); + void match(int ExpectedToken, int AlternateToken=-1); + void pushTokenScope(TOKEN_VECT &Tokens); + ScriptTokenPosition &getPos() { return tokenScopeStack.back(); } + void setPos(ScriptTokenPosition &TokenPos); + ScriptTokenPosition &getPrevPos() { return prevPos; } + void skip(int Tokens); + int tk; // current Token + std::string currentFile; + int currentLine() { return getPos().currentLine();} + int currentColumn() { return getPos().currentColumn();} + const std::string &tkStr() { static std::string empty; return LEX_TOKEN_DATA_STRING(getToken().token)?getToken().String():empty; } +private: + void tokenizeTry(ScriptTokenState &State, int Flags); + void tokenizeSwitch(ScriptTokenState &State, int Flags); + void tokenizeWith(ScriptTokenState &State, int Flags); + void tokenizeWhileAndDo(ScriptTokenState &State, int Flags); + void tokenizeIf(ScriptTokenState &State, int Flags); + void tokenizeFor(ScriptTokenState &State, int Flags); + CScriptToken tokenizeVarIdentifier(STRING_VECTOR_t *VarNames=0, bool *NeedAssignment=0); + void tokenizeFunction(ScriptTokenState &State, int Flags, bool noLetDef=false); + void tokenizeLet(ScriptTokenState &State, int Flags, bool noLetDef=false); + void tokenizeVarNoConst(ScriptTokenState &State, int Flags); + void tokenizeVarAndConst(ScriptTokenState &State, int Flags); + void _tokenizeLiteralObject(ScriptTokenState &State, int Flags); + void _tokenizeLiteralArray(ScriptTokenState &State, int Flags); + + void tokenizeLiteral(ScriptTokenState &State, int Flags); + void tokenizeMember(ScriptTokenState &State, int Flags); + void tokenizeFunctionCall(ScriptTokenState &State, int Flags); + void tokenizeSubExpression(ScriptTokenState &State, int Flags); + void tokenizeLogic(ScriptTokenState &State, int Flags, int op= LEX_OROR, int op_n=LEX_ANDAND); + void tokenizeCondition(ScriptTokenState &State, int Flags); + void tokenizeAssignment(ScriptTokenState &State, int Flags); + void tokenizeExpression(ScriptTokenState &State, int Flags); + void tokenizeBlock(ScriptTokenState &State, int Flags); + void tokenizeStatementNoLet(ScriptTokenState &State, int Flags); + void tokenizeStatement(ScriptTokenState &State, int Flags); + + int pushToken(TOKEN_VECT &Tokens, int Match=-1, int Alternate=-1); + int pushToken(TOKEN_VECT &Tokens, const CScriptToken &Token); + void pushForwarder(ScriptTokenState &State, bool noMarks=false); + void removeEmptyForwarder(ScriptTokenState &State); + void pushForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, std::vector &Marks); + void removeEmptyForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, std::vector &Marks); + void throwTokenNotExpected(); + CScriptLex *l; + TOKEN_VECT tokens; + ScriptTokenPosition prevPos; + std::vector tokenScopeStack; +}; + + +////////////////////////////////////////////////////////////////////////// +/// forward-declaration +////////////////////////////////////////////////////////////////////////// + +class CNumber; +class CScriptVar; +class CScriptVarPtr; +template class CScriptVarPointer; +class CScriptVarLink; +class CScriptVarLinkPtr; +class CScriptVarLinkWorkPtr; + +class CScriptVarPrimitive; +typedef CScriptVarPointer CScriptVarPrimitivePtr; + +class CScriptVarScopeFnc; +typedef CScriptVarPointer CFunctionsScopePtr; +typedef void (*JSCallback)(const CFunctionsScopePtr &var, void *userdata); + +class CTinyJS; +class CScriptResult; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVar +////////////////////////////////////////////////////////////////////////// + +typedef std::vector SCRIPTVAR_CHILDS_t; +typedef SCRIPTVAR_CHILDS_t::iterator SCRIPTVAR_CHILDS_it; +typedef SCRIPTVAR_CHILDS_t::const_iterator SCRIPTVAR_CHILDS_cit; + +class CScriptVar : public fixed_size_object { +protected: + CScriptVar(CTinyJS *Context, const CScriptVarPtr &Prototype); ///< Create + CScriptVar(const CScriptVar &Copy); ///< Copy protected -> use clone for public +private: + CScriptVar & operator=(const CScriptVar &Copy) MEMBER_DELETE; ///< private -> no assignment-Copy +public: + virtual ~CScriptVar(); + virtual CScriptVarPtr clone()=0; + + /// Type + virtual bool isObject(); ///< is an Object + virtual bool isArray(); ///< is an Array + virtual bool isError(); ///< is an ErrorObject + virtual bool isRegExp(); ///< is a RegExpObject + virtual bool isAccessor(); ///< is an Accessor + virtual bool isNull(); ///< is Null + virtual bool isUndefined();///< is Undefined + virtual bool isNaN(); ///< is NaN + virtual bool isString(); ///< is String + virtual bool isInt(); ///< is Integer + virtual bool isBool(); ///< is Bool + virtual int isInfinity(); ///< is Infinity ///< +1==POSITIVE_INFINITY, -1==NEGATIVE_INFINITY, 0==is not an InfinityVar + virtual bool isDouble(); ///< is Double + + virtual bool isRealNumber(); ///< is isInt | isDouble + virtual bool isNumber(); ///< is isNaN | isInt | isDouble | isInfinity + virtual bool isPrimitive();///< isNull | isUndefined | isNaN | isString | isInt | isDouble | isInfinity + + virtual bool isFunction(); ///< is CScriptVarFunction / CScriptVarFunctionNativeCallback / CScriptVarFunctionNativeClass + virtual bool isNative(); ///< is CScriptVarFunctionNativeCallback / CScriptVarFunctionNativeClass + virtual bool isBounded(); ///< is CScriptVarFunctionBounded + + virtual bool isIterator(); + virtual bool isGenerator(); + + bool isBasic() { return Childs.empty(); } ///< Is this *not* an array/object/etc + + + ////////////////////////////////////////////////////////////////////////// + /// Value + ////////////////////////////////////////////////////////////////////////// + + virtual CScriptVarPrimitivePtr getRawPrimitive()=0; ///< is Var==Primitive -> return this isObject return Value + CScriptVarPrimitivePtr toPrimitive(); ///< by default call getDefaultValue_hintNumber by a Date-object calls getDefaultValue_hintString + virtual CScriptVarPrimitivePtr toPrimitive(CScriptResult &execute); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintString(int32_t radix=0); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintString(CScriptResult &execute, int32_t radix=0); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintNumber(); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + CScriptVarPrimitivePtr toPrimitive_hintNumber(CScriptResult &execute); ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + + CScriptVarPtr callJS_toString(CScriptResult &execute, int radix=0); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + CScriptVarPtr callJS_valueOf(CScriptResult &execute); + virtual CScriptVarPtr valueOf_CallBack(); + + CNumber toNumber(); + CNumber toNumber(CScriptResult &execute); + virtual bool toBoolean(); + std::string toString(int32_t radix=0); ///< shortcut for this->toPrimitive_hintString()->toCString(); + std::string toString(CScriptResult &execute, int32_t radix=0); ///< shortcut for this->toPrimitive_hintString(execute)->toCString(); +#define WARN_DEPRECATED +#ifdef WARN_DEPRECATED + int DEPRECATED("getInt() is deprecated use toNumber().toInt32 instead") getInt(); + bool DEPRECATED("getBool() is deprecated use toBoolean() instead") getBool(); + double DEPRECATED("getDouble() is deprecated use toNumber().toDouble() instead") getDouble(); + std::string DEPRECATED("getString() is deprecated use toString() instead") getString(); +#else + int getInt(); + bool getBool(); + double getDouble(); + std::string getString(); +#endif + virtual CScriptTokenDataFnc *getFunctionData(); ///< { return 0; } + + virtual CScriptVarPtr toObject()=0; + + CScriptVarPtr toIterator(int Mode=3); + CScriptVarPtr toIterator(CScriptResult &execute, int Mode=3); + + +// virtual std::string getParsableString(const std::string &indentString, const std::string &indent, bool &hasRecursion); ///< get Data as a parsable javascript string +#define getParsableStringRecursionsCheck() do{ \ + if(uniqueID) { \ + if(uniqueID==getTemporaryMark()) { hasRecursion=true; return "recursion"; } \ + setTemporaryMark(uniqueID); \ + } \ + } while(0) + + std::string getParsableString(); ///< get Data as a parsable javascript string + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); ///< get Data as a parsable javascript string + virtual std::string getVarType()=0; + +#ifdef WARN_DEPRECATED + CScriptVarPtr DEPRECATED("getNumericVar() is deprecated use toNumber() instead") getNumericVar(); ///< returns an Integer, a Double, an Infinity or a NaN +#else + CScriptVarPtr getNumericVar(); ///< returns an Integer, a Double, an Infinity or a NaN +#endif + + ////////////////////////////////////////////////////////////////////////// + /// Childs + ////////////////////////////////////////////////////////////////////////// + + + CScriptVarPtr getOwnPropertyDescriptor(const std::string &Name); + const char *defineProperty(const std::string &Name, CScriptVarPtr Attributes); + + /// flags + void setExtensible(bool On=true) { extensible=On; } + void preventExtensions() { extensible=false; } + bool isExtensible() const { return extensible; } + void seal(); + bool isSealed() const; + void freeze(); + bool isFrozen() const; + + /// find + CScriptVarLinkPtr findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0 + CScriptVarLinkWorkPtr findChildWithStringChars(const std::string &childName); + CScriptVarLinkPtr findChildInPrototypeChain(const std::string &childName); + CScriptVarLinkWorkPtr findChildWithPrototypeChain(const std::string &childName); + CScriptVarLinkPtr findChildByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) + CScriptVarLinkPtr findChildOrCreate(const std::string &childName/*, int varFlags=SCRIPTVAR_UNDEFINED*/); ///< Tries to find a child with the given name, or will create it with the given flags + CScriptVarLinkPtr findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots) + void keys(STRING_SET_t &Keys, bool OnlyEnumerable=true, uint32_t ID=0); + /// add & remove + CScriptVarLinkPtr addChild(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr DEPRECATED("addChildNoDup is deprecated use addChildOrReplace instead!") addChildNoDup(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr addChildOrReplace(const std::string &childName, const CScriptVarPtr &child, int linkFlags = SCRIPTVARLINK_DEFAULT); ///< add a child overwriting any with the same name + bool removeLink(CScriptVarLinkPtr &link); ///< Remove a specific link (this is faster than finding via a child) + virtual void removeAllChildren(); + + /// ARRAY + CScriptVarPtr getArrayIndex(uint32_t idx); ///< The the value at an array index + void setArrayIndex(uint32_t idx, const CScriptVarPtr &value); ///< Set the value at an array index + uint32_t getArrayLength(); ///< If this is an array, return the number of items in it (else 0) + + ////////////////////////////////////////////////////////////////////////// + int getChildren() { return Childs.size(); } ///< Get the number of children + CTinyJS *getContext() { return context; } + CScriptVarPtr mathsOp(const CScriptVarPtr &b, int op); ///< do a maths op with another script variable + + void trace(const std::string &name = ""); ///< Dump out the contents of this using trace + void trace(std::string &indentStr, uint32_t uniqueID, const std::string &name = ""); ///< Dump out the contents of this using trace + std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags +// void getJSON(std::ostringstream &destination, const std::string linePrefix=""); ///< Write out all the JS code needed to recreate this script variable to the stream (as JSON) + + SCRIPTVAR_CHILDS_t Childs; + + /// For memory management/garbage collection +private: + CScriptVar *ref(); ///< Add reference to this variable + void unref(); ///< Remove a reference, and delete this variable if required +public: + int getRefs(); ///< Get the number of references to this script variable + template + operator T *(){ T *ret = dynamic_cast(this); ASSERT(ret!=0); return ret; } + template + T *get(){ T *ret = dynamic_cast(this); ASSERT(ret!=0); return ret; } + + //CScriptVarPtr newScriptVar(const CNumber &t); // { return ::newScriptVar(context, t); } + template CScriptVarPtr newScriptVar(T t); // { return ::newScriptVar(context, t); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2); // { return ::newScriptVar(context, t); } + template const CScriptVarPtr &constScriptVar(T t); // { return ::newScriptVar(context, t); } + void setTemporaryMark(uint32_t ID); // defined as inline at end of this file { temporaryMark[context->getCurrentMarkSlot()] = ID; } + virtual void setTemporaryMark_recursive(uint32_t ID); + uint32_t getTemporaryMark(); // defined as inline at end of this file { return temporaryMark[context->getCurrentMarkSlot()]; } +protected: + bool extensible; + CTinyJS *context; + int refs; ///< The number of references held to this - used for garbage collection + CScriptVar *prev; +public: + CScriptVar *next; + uint32_t temporaryMark[TEMPORARY_MARK_SLOTS]; + + friend class CScriptVarPtr; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarPtr { +public: + // construct + CScriptVarPtr() : var(0) {} ///< 0-Pointer + CScriptVarPtr(CScriptVar *Var) : var(Var) { if(var) var->ref(); } // creates a new CScriptVar (from new); + + // copy + CScriptVarPtr(const CScriptVarPtr &Copy) : var(Copy.var) { if(var) var->ref(); } + CScriptVarPtr& operator=(const CScriptVarPtr &Copy) { + if(var != Copy.var) { + if(var) var->unref(); + var = Copy.var; if(var) var->ref(); + } + return *this; + } + // deconstruct + ~CScriptVarPtr() { if(var) var->unref(); } + + // if + operator bool() const { return var!=0; } + + bool operator ==(const CScriptVarPtr &Other) const { return var == Other.var; } + bool operator !=(const CScriptVarPtr &Other) const { return var != Other.var; } + + // access + CScriptVar * operator ->() const { return var; } + CScriptVar *getVar() const { return var; } + + void clear() { if(var) var->unref(); var=0; } +protected: + CScriptVar *var; +}; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPointer - template +////////////////////////////////////////////////////////////////////////// + +template +class CScriptVarPointer : public CScriptVarPtr { +public: + CScriptVarPointer() {} + CScriptVarPointer(CScriptVar *Var) : CScriptVarPtr(dynamic_cast(Var)) {} + CScriptVarPointer(const CScriptVarPtr &Copy) : CScriptVarPtr(dynamic_cast(Copy.getVar())) {} + CScriptVarPointer &operator=(const CScriptVarPtr &Copy) { CScriptVarPtr::operator=(dynamic_cast(Copy.getVar())); return *this; } + C * operator ->() const { C *Var = dynamic_cast(var); ASSERT(var && Var); return Var; } +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLink +////////////////////////////////////////////////////////////////////////// +class CScriptVarLink : public fixed_size_object +{ +private: // prevent gloabal creating + CScriptVarLink(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT); +private: // prevent Copy + CScriptVarLink(const CScriptVarLink &link) MEMBER_DELETE; ///< Copy constructor +public: + ~CScriptVarLink(); + + const std::string &getName() const { return name; } + + int getFlags() { return flags; } + const CScriptVarPtr &getVarPtr() const { return var; } + const CScriptVarPtr &setVarPtr(const CScriptVarPtr &Var) { return var = Var; } ///< simple Replace the Variable pointed to + + + bool isOwned() const { return owner!=0; } + + bool isWritable() const { return (flags & SCRIPTVARLINK_WRITABLE) != 0; } + void setWritable(bool On) { On ? (flags |= SCRIPTVARLINK_WRITABLE) : (flags &= ~SCRIPTVARLINK_WRITABLE); } + bool isConfigurable() const { return (flags & SCRIPTVARLINK_CONFIGURABLE) != 0; } + void setConfigurable(bool On) { On ? (flags |= SCRIPTVARLINK_CONFIGURABLE) : (flags &= ~SCRIPTVARLINK_CONFIGURABLE); } + bool isEnumerable() const { return (flags & SCRIPTVARLINK_ENUMERABLE) != 0; } + void setEnumerable(bool On) { On ? (flags |= SCRIPTVARLINK_ENUMERABLE) : (flags &= ~SCRIPTVARLINK_ENUMERABLE); } + + CScriptVar *getOwner() { return owner; }; + void setOwner(CScriptVar *Owner) { owner = Owner; } + + /// forward to ScriptVar + + CScriptVarPrimitivePtr toPrimitive() { ///< by default call getDefaultValue_hintNumber by a Date-object calls getDefaultValue_hintString + return var->toPrimitive(); } + CScriptVarPrimitivePtr toPrimitive(CScriptResult &execute) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive(execute); } + CScriptVarPrimitivePtr toPrimitive_hintString(int32_t radix=0) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintString(radix); } + CScriptVarPrimitivePtr toPrimitive_hintString(CScriptResult &execute, int32_t radix=0) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintString(execute, radix); } + CScriptVarPrimitivePtr toPrimitive_hintNumber() { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintNumber(); } + CScriptVarPrimitivePtr toPrimitive_hintNumber(CScriptResult &execute) { ///< if the var an ObjectType gets the valueOf; if valueOf of an ObjectType gets toString / otherwise gets the Var itself + return var->toPrimitive_hintNumber(execute); } + + CNumber toNumber(); // { return var->toNumber(); } + CNumber toNumber(CScriptResult &execute); // { return var->toNumber(execute); } + bool toBoolean() { return var->toBoolean(); } + std::string toString(int32_t radix=0) { ///< shortcut for this->toPrimitive_hintString()->toCString(); + return var->toString(radix); } + std::string toString(CScriptResult &execute, int32_t radix=0) { ///< shortcut for this->toPrimitive_hintString(execute)->toCString(); + return var->toString(execute, radix); } + CScriptVarPtr toObject() { return var->toObject(); }; + +private: + std::string name; + CScriptVar *owner; // pointer to the owner CScriptVar + uint32_t flags; + CScriptVarPtr var; +#ifdef _DEBUG + char dummy[24]; +#endif + CScriptVarLink *ref(); + void unref(); +private: + int refs; + friend class CScriptVarLinkPtr; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarLinkPtr { +public: + // construct + CScriptVarLinkPtr() : link(0) {} ///< 0-Pointer + CScriptVarLinkPtr(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) { link=(new CScriptVarLink(var, name, flags))->ref(); } + CScriptVarLinkPtr(CScriptVarLink *Link) : link(Link) { if(link) link->ref(); } // creates a new CScriptVarLink (from new); + + // reconstruct + CScriptVarLinkPtr &operator()(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT); + CScriptVarLinkPtr &operator=(const CScriptVarPtr &var) { return operator()(var); } + // deconstruct + ~CScriptVarLinkPtr() { if(link) link->unref(); } + + // copy + CScriptVarLinkPtr(const CScriptVarLinkPtr &Copy) : link(Copy.link) { if(link) link->ref(); } + CScriptVarLinkPtr &operator=(const CScriptVarLinkPtr &Copy) { + if(link != Copy.link) { + if(link) link->unref(); + link = Copy.link; if(link) link->ref(); + } + return *this; + } + + // getter & setter + CScriptVarLinkWorkPtr getter(); + CScriptVarLinkWorkPtr getter(CScriptResult &execute); + CScriptVarLinkWorkPtr setter(const CScriptVarPtr &Var); + CScriptVarLinkWorkPtr setter(CScriptResult &execute, const CScriptVarPtr &Var); + + // if + operator bool() const { return link!=0; } + + // for sorting in child-list + bool operator <(const std::string &rhs) const; + bool operator ==(const CScriptVarLinkPtr &rhs) const { return link==rhs.link; } + // access to CScriptVarLink + CScriptVarLink *operator ->() const { return link; } + + operator const CScriptVarPtr &() const { static CScriptVarPtr NullPtr; return link?link->getVarPtr():NullPtr; } + + void clear() { if(link) link->unref(); link=0; } +protected: + CScriptVarLink *link; +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkWorkPtr +////////////////////////////////////////////////////////////////////////// + +class CScriptVarLinkWorkPtr : public CScriptVarLinkPtr { +public: + // construct + CScriptVarLinkWorkPtr() {} + CScriptVarLinkWorkPtr(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) : CScriptVarLinkPtr(var, name, flags) {} + CScriptVarLinkWorkPtr(CScriptVarLink *Link) : CScriptVarLinkPtr(Link) { if(link) referencedOwner = link->getOwner(); } // creates a new CScriptVarLink (from new); + CScriptVarLinkWorkPtr(const CScriptVarLinkPtr &Copy) : CScriptVarLinkPtr(Copy) { if(link) referencedOwner = link->getOwner(); } + + // reconstruct + CScriptVarLinkWorkPtr &operator()(const CScriptVarPtr &var, const std::string &name = TINYJS_TEMP_NAME, int flags = SCRIPTVARLINK_DEFAULT) {CScriptVarLinkPtr::operator()(var, name, flags); referencedOwner.clear(); return *this; } + + // copy + CScriptVarLinkWorkPtr(const CScriptVarLinkWorkPtr &Copy) : CScriptVarLinkPtr(Copy), referencedOwner(Copy.referencedOwner) {} + CScriptVarLinkWorkPtr &operator=(const CScriptVarLinkWorkPtr &Copy) { CScriptVarLinkPtr::operator=(Copy); referencedOwner = Copy.referencedOwner; return *this; } + + // getter & setter + CScriptVarLinkWorkPtr getter(); + CScriptVarLinkWorkPtr getter(CScriptResult &execute); + CScriptVarLinkWorkPtr setter(const CScriptVarPtr &Var); + CScriptVarLinkWorkPtr setter(CScriptResult &execute, const CScriptVarPtr &Var); + + + void swap(CScriptVarLinkWorkPtr &Link) { + CScriptVarPtr _referencedOwner = referencedOwner; referencedOwner = Link.referencedOwner; Link.referencedOwner = _referencedOwner; + CScriptVarLink *_link=link; link=Link.link; Link.link=_link; + } + + void clear() { CScriptVarLinkPtr::clear(); referencedOwner.clear(); } + void setReferencedOwner(const CScriptVarPtr &Owner) { referencedOwner = Owner; } + const CScriptVarPtr &getReferencedOwner() const { return referencedOwner; } + bool hasReferencedOwner() const { return referencedOwner; } +private: + CScriptVarPtr referencedOwner; +}; + + +////////////////////////////////////////////////////////////////////////// +#define define_dummy_t(t1) struct t1##_t{}; extern t1##_t t1 +#define declare_dummy_t(t1) t1##_t t1 +#define define_newScriptVar_Fnc(t1, ...) CScriptVarPtr newScriptVar(__VA_ARGS__) +#define define_newScriptVar_NamedFnc(t1, ...) CScriptVarPtr newScriptVar##t1(__VA_ARGS__) +#define define_ScriptVarPtr_Type(t1) class CScriptVar##t1; typedef CScriptVarPointer CScriptVar##t1##Ptr + +#define define_DEPRECATED_newScriptVar_Fnc(t1, ...) CScriptVarPtr DEPRECATED("newScriptVar("#__VA_ARGS__") is deprecated use constScriptVar("#__VA_ARGS__") instead") newScriptVar(__VA_ARGS__) + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPrimitive +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Primitive); +class CScriptVarPrimitive : public CScriptVar { +protected: + CScriptVarPrimitive(CTinyJS *Context, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype) { setExtensible(false); } + CScriptVarPrimitive(const CScriptVarPrimitive &Copy) : CScriptVar(Copy) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarPrimitive(); + + virtual bool isPrimitive(); ///< return true; + + virtual CScriptVarPrimitivePtr getRawPrimitive(); + virtual bool toBoolean(); /// false by default + virtual CNumber toNumber_Callback()=0; + virtual std::string toCString(int radix=0)=0; + + virtual CScriptVarPtr toObject(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); +protected: +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarUndefined +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Undefined); +define_ScriptVarPtr_Type(Undefined); +class CScriptVarUndefined : public CScriptVarPrimitive { +protected: + CScriptVarUndefined(CTinyJS *Context); + CScriptVarUndefined(const CScriptVarUndefined &Copy) : CScriptVarPrimitive(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarUndefined(); + virtual CScriptVarPtr clone(); + + virtual bool isUndefined(); // { return true; } + + virtual CNumber toNumber_Callback(); // { return NaN; } + virtual std::string toCString(int radix=0);// { return "undefined"; } + + virtual std::string getVarType(); // { return "undefined"; } + friend define_DEPRECATED_newScriptVar_Fnc(Undefined, CTinyJS *, Undefined_t); + friend define_newScriptVar_NamedFnc(Undefined, CTinyJS *Context); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Undefined, CTinyJS *Context, Undefined_t) { return new CScriptVarUndefined(Context); } +inline define_newScriptVar_NamedFnc(Undefined, CTinyJS *Context) { return new CScriptVarUndefined(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarNull +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Null); +define_ScriptVarPtr_Type(Null); +class CScriptVarNull : public CScriptVarPrimitive { +protected: + CScriptVarNull(CTinyJS *Context); + CScriptVarNull(const CScriptVarNull &Copy) : CScriptVarPrimitive(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarNull(); + virtual CScriptVarPtr clone(); + + virtual bool isNull(); // { return true; } + + virtual CNumber toNumber_Callback(); // { return 0; } + virtual std::string toCString(int radix=0);// { return "null"; } + + virtual std::string getVarType(); // { return "null"; } + + friend define_DEPRECATED_newScriptVar_Fnc(Null, CTinyJS *Context, Null_t); + friend define_newScriptVar_NamedFnc(Null, CTinyJS *Context); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Null, CTinyJS *Context, Null_t) { return new CScriptVarNull(Context); } +inline define_newScriptVar_NamedFnc(Null, CTinyJS *Context) { return new CScriptVarNull(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarString +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(String); +class CScriptVarString : public CScriptVarPrimitive { +protected: + CScriptVarString(CTinyJS *Context, const std::string &Data); + CScriptVarString(const CScriptVarString &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarString(); + virtual CScriptVarPtr clone(); + virtual bool isString(); // { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); // { return getJSString(data); } + virtual std::string getVarType(); // { return "string"; } + + virtual CScriptVarPtr toObject(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + uint32_t stringLength() { return data.size(); } + int getChar(uint32_t Idx); +protected: + std::string data; +private: + friend define_newScriptVar_Fnc(String, CTinyJS *Context, const std::string &); + friend define_newScriptVar_Fnc(String, CTinyJS *Context, const char *); + friend define_newScriptVar_Fnc(String, CTinyJS *Context, char *); +}; +inline define_newScriptVar_Fnc(String, CTinyJS *Context, const std::string &Obj) { return new CScriptVarString(Context, Obj); } +inline define_newScriptVar_Fnc(String, CTinyJS *Context, const char *Obj) { return new CScriptVarString(Context, Obj); } +inline define_newScriptVar_Fnc(String, CTinyJS *Context, char *Obj) { return new CScriptVarString(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CNumber +////////////////////////////////////////////////////////////////////////// +define_dummy_t(NegativeZero); +define_dummy_t(NaN); +class Infinity{public:Infinity(int Sig=1):sig(Sig){} int Sig(){return sig;} private:int sig; } ; +extern Infinity InfinityPositive; +extern Infinity InfinityNegative; + +class CNumber { +private: + enum NType { + tnNULL, tInt32, tDouble, tNaN, tInfinity + }; + CNumber(NType Type, int32_t InfinitySign=0) : type(Type) { Int32 = InfinitySign; } +public: + + CNumber(const CNumber &Copy) { *this=Copy; } + + CNumber(int32_t Value=0) : type(tInt32) { Int32=Value; } +#if 1 + templateCNumber(T Value) { *this = Value; } +#else + CNumber(negativeZero_t Value) { *this = Value; } + CNumber(NaN_t Value) { *this = Value; } + CNumber(Infinity Value) { *this = Value; } + CNumber(uint32_t Value) { *this = Value; } + CNumber(double Value) { *this = Value; } + CNumber(unsigned char Value) { *this = Value; } + CNumber(const char *Value) { *this = Value; } + CNumber(const std::string &Value) { *this = Value; } +#endif + CNumber &operator=(NegativeZero_t) { type=tnNULL; Int32=0; return *this; } + CNumber &operator=(NaN_t) { type=tNaN; Int32=0; return *this; } + CNumber &operator=(Infinity v) { type=tInfinity; Int32=v.Sig(); return *this; } + CNumber &operator=(int32_t Value) { type=tInt32; Int32=Value; return *this; } + CNumber &operator=(uint32_t Value) { + if(Value<=(uint32_t)std::numeric_limits::max()) + type=tInt32, Int32=int32_t(Value); + else + type=tDouble, Double=Value; + return *this; + } + CNumber &operator=(double Value); + CNumber &operator=(unsigned char Value) { type=tInt32; Int32=Value; return *this; } + CNumber &operator=(const char *Value); + CNumber &operator=(const std::string &Value) { return operator=(Value.c_str());} + + int32_t parseInt(const char *str, int32_t radix=0, const char **endptr=0); + void parseInt(const std::string &str, int32_t radix=0) { parseInt(str.c_str(), radix); } + void parseFloat(const char *str, const char **endptr=0); + void parseFloat(const std::string &str) { parseFloat(str.c_str()); } + + CNumber add(const CNumber &Value) const; + CNumber operator-() const; + CNumber operator~() const { if(type==tNaN) return *this; else return ~toInt32(); } + bool operator!() const { return isZero(); } + CNumber multi(const CNumber &Value) const; + CNumber div(const CNumber &Value) const; + CNumber modulo(const CNumber &Value) const; + + CNumber round() const; + CNumber floor() const; + CNumber ceil() const; + CNumber abs() const; + + CNumber shift(const CNumber &Value, bool right) const; + CNumber ushift(const CNumber &Value, bool right=true) const; + + CNumber binary(const CNumber &Value, char Mode) const; + + + int less(const CNumber &Value) const; + bool equal(const CNumber &Value) const; + + + bool isInt32() const { return type == tInt32; } + bool isDouble() const { return type == tDouble; } + + bool isNaN() const { return type == tNaN; } + int isInfinity() const { return type == tInfinity ? Int32 : 0; } + bool isFinite() const { return type == tInt32 || type == tDouble || type == tnNULL; } + bool isNegativeZero() const { return type==tnNULL; } + bool isZero() const; ///< is 0, -0 + bool isInteger() const; + int sign() const; + + int32_t toInt32() const { return cast(); } + uint32_t toUInt32() const { return cast(); } + double toDouble() const; + bool toBoolean() const { return !isZero() && type!=tNaN; } + std::string toString(uint32_t Radix=10) const; +private: + template T cast() const { + switch(type) { + case tInt32: + return T(Int32); + case tDouble: + return T(Double); + default: + return T(0); + } + } + NType type; + union { + int32_t Int32; + double Double; + }; +}; +inline CNumber operator+(const CNumber &lhs, const CNumber &rhs) { return lhs.add(rhs); } +inline CNumber &operator+=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.add(rhs); } +inline CNumber operator-(const CNumber &lhs, const CNumber &rhs) { return lhs.add(-rhs); } +inline CNumber &operator-=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.add(-rhs); } +inline CNumber operator*(const CNumber &lhs, const CNumber &rhs) { return lhs.multi(rhs); } +inline CNumber &operator*=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.multi(rhs); } +inline CNumber operator/(const CNumber &lhs, const CNumber &rhs) { return lhs.div(rhs); } +inline CNumber &operator/=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.div(rhs); } +inline CNumber operator%(const CNumber &lhs, const CNumber &rhs) { return lhs.modulo(rhs); } +inline CNumber &operator%=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.modulo(rhs); } +inline CNumber operator>>(const CNumber &lhs, const CNumber &rhs) { return lhs.shift(rhs, true); } +inline CNumber &operator>>=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.shift(rhs, true); } +inline CNumber operator<<(const CNumber &lhs, const CNumber &rhs) { return lhs.shift(rhs, false); } +inline CNumber &operator<<=(CNumber &lhs, const CNumber &rhs) { return lhs=lhs.shift(rhs, false); } + +inline bool operator==(const CNumber &lhs, const CNumber &rhs) { return lhs.equal(rhs); } +inline bool operator!=(const CNumber &lhs, const CNumber &rhs) { return !lhs.equal(rhs); } +inline bool operator<(const CNumber &lhs, const CNumber &rhs) { return lhs.less(rhs)>0; } +inline bool operator<=(const CNumber &lhs, const CNumber &rhs) { return rhs.less(lhs)<0; } +inline bool operator>(const CNumber &lhs, const CNumber &rhs) { return rhs.less(lhs)>0; } +inline bool operator>=(const CNumber &lhs, const CNumber &rhs) { return lhs.less(rhs)<0; } + +inline CNumber round(const CNumber &lhs) { return lhs.round(); } +inline CNumber floor(const CNumber &lhs) { return lhs.floor(); } +inline CNumber ceil(const CNumber &lhs) { return lhs.ceil(); } +inline CNumber abs(const CNumber &lhs) { return lhs.abs(); } + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarNumber +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Number); +class CScriptVarNumber : public CScriptVarPrimitive { +protected: + CScriptVarNumber(CTinyJS *Context, const CNumber &Data); + CScriptVarNumber(const CScriptVarNumber &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarNumber(); + virtual CScriptVarPtr clone(); + virtual bool isNumber(); // { return true; } + virtual bool isInt(); // { return true; } + virtual bool isDouble(); // { return true; } + virtual bool isRealNumber(); // { return true; } + virtual int isInfinity(); // { return data; } + virtual bool isNaN();// { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getVarType(); // { return "number"; } + + virtual CScriptVarPtr toObject(); +private: + CNumber data; + friend define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &); + friend define_newScriptVar_NamedFnc(Number, CTinyJS *Context, const CNumber &); +}; +define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &Obj); +inline define_newScriptVar_NamedFnc(Number, CTinyJS *Context, const CNumber &Obj) { return new CScriptVarNumber(Context, Obj); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, unsigned int Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, unsigned long Obj) { return newScriptVarNumber(Context, CNumber((uint32_t)Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, int Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, long Obj) { return newScriptVarNumber(Context, CNumber((int32_t)Obj)); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, double Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } +inline define_DEPRECATED_newScriptVar_Fnc(NaN, CTinyJS *Context, NaN_t) { return newScriptVarNumber(Context, CNumber(NaN)); } +inline define_DEPRECATED_newScriptVar_Fnc(Infinity, CTinyJS *Context, Infinity Obj) { return newScriptVarNumber(Context, CNumber(Obj)); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarBool +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Bool); +class CScriptVarBool : public CScriptVarPrimitive { +protected: + CScriptVarBool(CTinyJS *Context, bool Data); + CScriptVarBool(const CScriptVarBool &Copy) : CScriptVarPrimitive(Copy), data(Copy.data) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarBool(); + virtual CScriptVarPtr clone(); + virtual bool isBool(); // { return true; } + + virtual bool toBoolean(); + virtual CNumber toNumber_Callback(); + virtual std::string toCString(int radix=0); + + virtual std::string getVarType(); // { return "boolean"; } + + virtual CScriptVarPtr toObject(); +protected: + bool data; + + friend define_DEPRECATED_newScriptVar_Fnc(Bool, CTinyJS *, bool); + friend define_newScriptVar_NamedFnc(Bool, CTinyJS *Context, bool); +}; +inline define_DEPRECATED_newScriptVar_Fnc(Bool, CTinyJS *Context, bool Obj) { return new CScriptVarBool(Context, Obj); } +inline define_newScriptVar_NamedFnc(Bool, CTinyJS *Context, bool Obj) { return new CScriptVarBool(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObject +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Object); +define_ScriptVarPtr_Type(Object); + +class CScriptVarObject : public CScriptVar { +protected: + CScriptVarObject(CTinyJS *Context); + CScriptVarObject(CTinyJS *Context, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype) {} + CScriptVarObject(CTinyJS *Context, const CScriptVarPrimitivePtr &Value, const CScriptVarPtr &Prototype) : CScriptVar(Context, Prototype), value(Value) {} + CScriptVarObject(const CScriptVarObject &Copy) : CScriptVar(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarObject(); + virtual CScriptVarPtr clone(); + + virtual void removeAllChildren(); + + virtual CScriptVarPrimitivePtr getRawPrimitive(); + virtual bool isObject(); // { return true; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual std::string getVarType(); ///< always "object" + virtual std::string getVarTypeTagName(); ///< always "Object" + virtual CScriptVarPtr toObject(); + + virtual CScriptVarPtr valueOf_CallBack(); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + virtual void setTemporaryMark_recursive(uint32_t ID); +protected: +private: + CScriptVarPrimitivePtr value; + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPrimitivePtr &, const CScriptVarPtr &); +}; +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t) { return new CScriptVarObject(Context); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Prototype); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Prototype); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPrimitivePtr &Value, const CScriptVarPtr &Prototype) { return new CScriptVarObject(Context, Value, Prototype); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObjectTyped (simple Object with TypeTagName) +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(StopIteration); + +class CScriptVarObjectTypeTagged : public CScriptVarObject { +protected: +// CScriptVarObjectTyped(CTinyJS *Context); + CScriptVarObjectTypeTagged(CTinyJS *Context, const CScriptVarPtr &Prototype, const std::string &TypeTagName) : CScriptVarObject(Context, Prototype), typeTagName(TypeTagName) {} + CScriptVarObjectTypeTagged(const CScriptVarObjectTypeTagged &Copy) : CScriptVarObject(Copy), typeTagName(Copy.typeTagName) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarObjectTypeTagged(); + virtual CScriptVarPtr clone(); + + virtual std::string getVarTypeTagName(); +protected: +private: + std::string typeTagName; + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &, const std::string &); + friend define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &, const std::string &); +}; +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, Object_t, const CScriptVarPtr &Prototype, const std::string &TypeTagName) { return new CScriptVarObjectTypeTagged(Context, Prototype, TypeTagName); } +inline define_newScriptVar_Fnc(Object, CTinyJS *Context, const CScriptVarPtr &Prototype, const std::string &TypeTagName) { return new CScriptVarObjectTypeTagged(Context, Prototype, TypeTagName); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarError +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Error); + +class CScriptVarError : public CScriptVarObject { +protected: + CScriptVarError(CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column);// : CScriptVarObject(Context), value(Value) {} + CScriptVarError(const CScriptVarError &Copy) : CScriptVarObject(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarError(); + virtual CScriptVarPtr clone(); + virtual bool isError(); // { return true; } + +// virtual std::string getParsableString(const std::string &indentString, const std::string &indent); ///< get Data as a parsable javascript string + + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + CScriptException *toCScriptException(); +private: + friend define_newScriptVar_NamedFnc(Error, CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column); + friend define_newScriptVar_NamedFnc(Error, CTinyJS *Context, const CScriptException &Exception); +}; +inline define_newScriptVar_NamedFnc(Error, CTinyJS *Context, ERROR_TYPES type, const char *message=0, const char *file=0, int line=-1, int column=-1) { return new CScriptVarError(Context, type, message, file, line, column); } +inline define_newScriptVar_NamedFnc(Error, CTinyJS *Context, const CScriptException &Exception) { return new CScriptVarError(Context, Exception.errorType, Exception.message.c_str(), Exception.fileName.c_str(), Exception.lineNumber, Exception.column); } + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarArray +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Array); +define_ScriptVarPtr_Type(Array); +class CScriptVarArray : public CScriptVarObject { +protected: + CScriptVarArray(CTinyJS *Context); + CScriptVarArray(const CScriptVarArray &Copy) : CScriptVarObject(Copy), toStringRecursion(Copy.toStringRecursion) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarArray(); + virtual CScriptVarPtr clone(); + virtual bool isArray(); // { return true; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + friend define_newScriptVar_Fnc(Array, CTinyJS *Context, Array_t); +private: + void native_Length(const CFunctionsScopePtr &c, void *data); + bool toStringRecursion; +}; +inline define_newScriptVar_Fnc(Array, CTinyJS *Context, Array_t) { return new CScriptVarArray(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarRegExp +////////////////////////////////////////////////////////////////////////// +#ifndef NO_REGEXP + +define_ScriptVarPtr_Type(RegExp); +class CScriptVarRegExp : public CScriptVarObject { +protected: + CScriptVarRegExp(CTinyJS *Context, const std::string &Source, const std::string &Flags); + CScriptVarRegExp(const CScriptVarRegExp &Copy) : CScriptVarObject(Copy), regexp(Copy.regexp), flags(Copy.flags) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarRegExp(); + virtual CScriptVarPtr clone(); + virtual bool isRegExp(); // { return true; } + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + + CScriptVarPtr exec(const std::string &Input, bool Test=false); + + bool Global() { return flags.find('g')!=std::string::npos; } + bool IgnoreCase() { return flags.find('i')!=std::string::npos; } + bool Multiline() { return true; /* currently always true -- flags.find('m')!=std::string::npos;*/ } + bool Sticky() { return flags.find('y')!=std::string::npos; } + const std::string &Regexp() { return regexp; } + unsigned int LastIndex(); + void LastIndex(unsigned int Idx); + + static const char *ErrorStr(int Error); +protected: + std::string regexp; + std::string flags; +private: + void native_Global(const CFunctionsScopePtr &c, void *data); + void native_IgnoreCase(const CFunctionsScopePtr &c, void *data); + void native_Multiline(const CFunctionsScopePtr &c, void *data); + void native_Sticky(const CFunctionsScopePtr &c, void *data); + void native_Source(const CFunctionsScopePtr &c, void *data); + + friend define_newScriptVar_Fnc(RegExp, CTinyJS *Context, const std::string &, const std::string &); + +}; +inline define_newScriptVar_Fnc(RegExp, CTinyJS *Context, const std::string &Obj, const std::string &Flags) { return new CScriptVarRegExp(Context, Obj, Flags); } + +#endif /* NO_REGEXP */ + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunction +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(Function); +class CScriptVarFunction : public CScriptVarObject { +protected: + CScriptVarFunction(CTinyJS *Context, CScriptTokenDataFnc *Data); + CScriptVarFunction(const CScriptVarFunction &Copy) : CScriptVarObject(Copy), data(Copy.data) { data->ref(); } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunction(); + virtual CScriptVarPtr clone(); + virtual bool isObject(); // { return true; } + virtual bool isFunction(); // { return true; } + virtual bool isPrimitive(); // { return false; } + + virtual std::string getVarType(); // { return "function"; } + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual CScriptVarPtr toString_CallBack(CScriptResult &execute, int radix=0); + virtual CScriptTokenDataFnc *getFunctionData(); + void setFunctionData(CScriptTokenDataFnc *Data); +private: + CScriptTokenDataFnc *data; + + friend define_newScriptVar_Fnc(Function, CTinyJS *Context, CScriptTokenDataFnc *); +}; +inline define_newScriptVar_Fnc(Function, CTinyJS *Context, CScriptTokenDataFnc *Obj) { return new CScriptVarFunction(Context, Obj); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionBounded +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionBounded); +class CScriptVarFunctionBounded : public CScriptVarFunction { +protected: + CScriptVarFunctionBounded(CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments); + CScriptVarFunctionBounded(const CScriptVarFunctionBounded &Copy) : CScriptVarFunction(Copy), boundedThis(Copy.boundedThis), boundedArguments(Copy.boundedArguments) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionBounded(); + virtual CScriptVarPtr clone(); + virtual bool isBounded(); ///< is CScriptVarFunctionBounded + virtual void setTemporaryMark_recursive(uint32_t ID); + CScriptVarPtr callFunction(CScriptResult &execute, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); +protected: +private: + CScriptVarFunctionPtr boundedFunction; + CScriptVarPtr boundedThis; + std::vector boundedArguments; + + friend define_newScriptVar_NamedFnc(FunctionBounded, CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments); +}; +inline define_newScriptVar_NamedFnc(FunctionBounded, CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments) { return new CScriptVarFunctionBounded(BoundedFunction, BoundedThis, BoundedArguments); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNative +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionNative); +class CScriptVarFunctionNative : public CScriptVarFunction { +protected: + CScriptVarFunctionNative(CTinyJS *Context, void *Userdata, const char *Name) : CScriptVarFunction(Context, new CScriptTokenDataFnc), jsUserData(Userdata) { + if(Name) getFunctionData()->name = Name; + } + CScriptVarFunctionNative(const CScriptVarFunctionNative &Copy) : CScriptVarFunction(Copy), jsUserData(Copy.jsUserData) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionNative(); + virtual CScriptVarPtr clone()=0; + virtual bool isNative(); // { return true; } + + virtual void callFunction(const CFunctionsScopePtr &c)=0;// { jsCallback(c, jsCallbackUserData); } +protected: + void *jsUserData; ///< user data passed as second argument to native functions +}; + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNativeCallback +////////////////////////////////////////////////////////////////////////// + +define_ScriptVarPtr_Type(FunctionNativeCallback); +class CScriptVarFunctionNativeCallback : public CScriptVarFunctionNative { +protected: + CScriptVarFunctionNativeCallback(CTinyJS *Context, JSCallback Callback, void *Userdata, const char *Name) : CScriptVarFunctionNative(Context, Userdata, Name), jsCallback(Callback) { } + CScriptVarFunctionNativeCallback(const CScriptVarFunctionNativeCallback &Copy) : CScriptVarFunctionNative(Copy), jsCallback(Copy.jsCallback) { } ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarFunctionNativeCallback(); + virtual CScriptVarPtr clone(); + virtual void callFunction(const CFunctionsScopePtr &c); +private: + JSCallback jsCallback; ///< Callback for native functions + friend define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, JSCallback Callback, void*, const char*); +}; +inline define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, JSCallback Callback, void *Userdata, const char *Name=0) { return new CScriptVarFunctionNativeCallback(Context, Callback, Userdata, Name); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNativeClass +////////////////////////////////////////////////////////////////////////// + +template +class CScriptVarFunctionNativeClass : public CScriptVarFunctionNative { +protected: + CScriptVarFunctionNativeClass(CTinyJS *Context, native *ClassPtr, void (native::*ClassFnc)(const CFunctionsScopePtr &, void *), void *Userdata, const char *Name) : CScriptVarFunctionNative(Context, Userdata, Name), classPtr(ClassPtr), classFnc(ClassFnc) { } + CScriptVarFunctionNativeClass(const CScriptVarFunctionNativeClass &Copy) : CScriptVarFunctionNative(Copy), classPtr(Copy.classPtr), classFnc(Copy.classFnc) { } ///< Copy protected -> use clone for public +public: + virtual CScriptVarPtr clone() { return new CScriptVarFunctionNativeClass(*this); } + + virtual void callFunction(const CFunctionsScopePtr &c) { (classPtr->*classFnc)(c, jsUserData); } +private: + native *classPtr; + void (native::*classFnc)(const CFunctionsScopePtr &c, void *userdata); + template + friend define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS*, native2 *, void (native2::*)(const CFunctionsScopePtr &, void *), void *, const char *); +}; +template +define_newScriptVar_Fnc(FunctionNativeCallback, CTinyJS *Context, native *ClassPtr, void (native::*ClassFnc)(const CFunctionsScopePtr &, void *), void *Userdata, const char *Name=0) { return new CScriptVarFunctionNativeClass(Context, ClassPtr, ClassFnc, Userdata, Name); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarAccessor +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Accessor); +define_ScriptVarPtr_Type(Accessor); + +class CScriptVarAccessor : public CScriptVarObject { +protected: + CScriptVarAccessor(CTinyJS *Context); + CScriptVarAccessor(CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata); + template CScriptVarAccessor(CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData) : CScriptVarObject(Context) { + if(getterFnc) + addChild(TINYJS_ACCESSOR_GET_VAR, ::newScriptVar(Context, class_ptr, getterFnc, getterData), 0); + if(setterFnc) + addChild(TINYJS_ACCESSOR_SET_VAR, ::newScriptVar(Context, class_ptr, setterFnc, setterData), 0); + } + CScriptVarAccessor(CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter); + + CScriptVarAccessor(const CScriptVarAccessor &Copy) : CScriptVarObject(Copy) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarAccessor(); + virtual CScriptVarPtr clone(); + virtual bool isAccessor(); // { return true; } + virtual bool isPrimitive(); // { return false; } + + virtual std::string getParsableString(const std::string &indentString, const std::string &indent, uint32_t uniqueID, bool &hasRecursion); + virtual std::string getVarType(); // { return "object"; } + + CScriptVarPtr getValue(); + + friend define_newScriptVar_Fnc(Accessor, CTinyJS *Context, Accessor_t); + friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata); + template friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData); + friend define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, const CScriptVarFunctionPtr &, const CScriptVarFunctionPtr &); +}; +inline define_newScriptVar_Fnc(Accessor, CTinyJS *Context, Accessor_t) { return new CScriptVarAccessor(Context); } +inline define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, JSCallback getter, void *getterdata, JSCallback setter, void *setterdata) { return new CScriptVarAccessor(Context, getter, getterdata, setter, setterdata); } +template define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, C *class_ptr, void(C::*getterFnc)(const CFunctionsScopePtr &, void *), void *getterData, void(C::*setterFnc)(const CFunctionsScopePtr &, void *), void *setterData) { return new CScriptVarAccessor(Context, class_ptr, getterFnc, getterData, setterFnc, setterData); } +inline define_newScriptVar_NamedFnc(Accessor, CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter) { return new CScriptVarAccessor(Context, getter, setter); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarDestructuring +////////////////////////////////////////////////////////////////////////// + +class CScriptVarDestructuring : public CScriptVarObject { +protected: // only derived classes or friends can be created + CScriptVarDestructuring(CTinyJS *Context) // constructor for rootScope + : CScriptVarObject(Context) {} + virtual CScriptVarPtr clone(); +public: + virtual ~CScriptVarDestructuring(); +}; + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScope +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(Scope); +define_ScriptVarPtr_Type(Scope); +class CScriptVarScope : public CScriptVarObject { +protected: // only derived classes or friends can be created + CScriptVarScope(CTinyJS *Context) // constructor for rootScope + : CScriptVarObject(Context) {} + virtual CScriptVarPtr clone(); + virtual bool isObject(); // { return false; } +public: + virtual ~CScriptVarScope(); + virtual CScriptVarPtr scopeVar(); ///< to create var like: var a = ... + virtual CScriptVarPtr scopeLet(); ///< to create var like: let a = ... + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + virtual CScriptVarScopePtr getParent(); + friend define_newScriptVar_Fnc(Scope, CTinyJS *Context, Scope_t); +}; +inline define_newScriptVar_Fnc(Scope, CTinyJS *Context, Scope_t) { return new CScriptVarScope(Context); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeFnc +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeFnc); +define_ScriptVarPtr_Type(ScopeFnc); +class CScriptVarScopeFnc : public CScriptVarScope { +protected: // only derived classes or friends can be created + CScriptVarScopeFnc(CTinyJS *Context, const CScriptVarScopePtr &Closure) // constructor for FncScope + : CScriptVarScope(Context), closure(Closure ? addChild(TINYJS_FUNCTION_CLOSURE_VAR, Closure, 0) : CScriptVarLinkPtr()) {} +public: + virtual ~CScriptVarScopeFnc(); + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + + void setReturnVar(const CScriptVarPtr &var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy() + + #define DEPRECATED_getParameter DEPRECATED("getParameter is deprecated use getArgument instead") + DEPRECATED_getParameter CScriptVarPtr getParameter(const std::string &name); + DEPRECATED_getParameter CScriptVarPtr getParameter(int Idx); + CScriptVarPtr getArgument(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions) + CScriptVarPtr getArgument(int Idx); ///< If this is a function, get the parameter with the given index (for use by native functions) + DEPRECATED("getParameterLength is deprecated use getArgumentsLength instead") int getParameterLength(); ///< If this is a function, get the count of parameters + int getArgumentsLength(); ///< If this is a function, get the count of parameters + + void throwError(ERROR_TYPES ErrorType, const std::string &message); + +protected: + CScriptVarLinkPtr closure; + friend define_newScriptVar_Fnc(ScopeFnc, CTinyJS *Context, ScopeFnc_t, const CScriptVarScopePtr &Closure); +}; +inline define_newScriptVar_Fnc(ScopeFnc, CTinyJS *Context, ScopeFnc_t, const CScriptVarScopePtr &Closure) { return new CScriptVarScopeFnc(Context, Closure); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeLet +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeLet); +define_ScriptVarPtr_Type(ScopeLet); +class CScriptVarScopeLet : public CScriptVarScope { +protected: // only derived classes or friends can be created + CScriptVarScopeLet(const CScriptVarScopePtr &Parent); // constructor for LetScope +// : CScriptVarScope(Parent->getContext()), parent( context->getRoot() != Parent ? addChild(TINYJS_SCOPE_PARENT_VAR, Parent, 0) : 0) {} +public: + virtual ~CScriptVarScopeLet(); + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); + virtual CScriptVarPtr scopeVar(); ///< to create var like: var a = ... + virtual CScriptVarScopePtr getParent(); + void setletExpressionInitMode(bool Mode) { letExpressionInitMode = Mode; } +protected: + CScriptVarLinkPtr parent; + bool letExpressionInitMode; + friend define_newScriptVar_Fnc(ScopeLet, CTinyJS *Context, ScopeLet_t, const CScriptVarScopePtr &Parent); +}; +inline define_newScriptVar_Fnc(ScopeLet, CTinyJS *, ScopeLet_t, const CScriptVarScopePtr &Parent) { return new CScriptVarScopeLet(Parent); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeWith +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(ScopeWith); +define_ScriptVarPtr_Type(ScopeWith); +class CScriptVarScopeWith : public CScriptVarScopeLet { +protected: + CScriptVarScopeWith(const CScriptVarScopePtr &Parent, const CScriptVarPtr &With) + : CScriptVarScopeLet(Parent), with(addChild(TINYJS_SCOPE_WITH_VAR, With, 0)) {} + +public: + virtual ~CScriptVarScopeWith(); + virtual CScriptVarPtr scopeLet(); ///< to create var like: let a = ... + virtual CScriptVarLinkWorkPtr findInScopes(const std::string &childName); +private: + CScriptVarLinkPtr with; + friend define_newScriptVar_Fnc(ScopeWith, CTinyJS *Context, ScopeWith_t, const CScriptVarScopePtr &Parent, const CScriptVarPtr &With); +}; +inline define_newScriptVar_Fnc(ScopeWith, CTinyJS *, ScopeWith_t, const CScriptVarScopePtr &Parent, const CScriptVarPtr &With) { return new CScriptVarScopeWith(Parent, With); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarDefaultIterator +////////////////////////////////////////////////////////////////////////// + +define_dummy_t(DefaultIterator); +define_ScriptVarPtr_Type(DefaultIterator); + +class CScriptVarDefaultIterator : public CScriptVarObject { +protected: + CScriptVarDefaultIterator(CTinyJS *Context, const CScriptVarPtr &Object, int Mode); + CScriptVarDefaultIterator(const CScriptVarDefaultIterator &Copy) + : + CScriptVarObject(Copy), mode(Copy.mode), object(Copy.object), + keys(Copy.keys), pos(keys.begin()){} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarDefaultIterator(); + virtual CScriptVarPtr clone(); + virtual bool isIterator(); + + void native_next(const CFunctionsScopePtr &c, void *data); +private: + int mode; + CScriptVarPtr object; + STRING_SET_t keys; + STRING_SET_it pos; + friend define_newScriptVar_NamedFnc(DefaultIterator, CTinyJS *, const CScriptVarPtr &, int); + +}; +inline define_newScriptVar_NamedFnc(DefaultIterator, CTinyJS *Context, const CScriptVarPtr &Object, int Mode) { return new CScriptVarDefaultIterator(Context, Object, Mode); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarGenerator +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_GENERATORS + +define_dummy_t(Generator); +define_ScriptVarPtr_Type(Generator); + +class CScriptVarGenerator : public CScriptVarObject { +protected: + CScriptVarGenerator(CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function); + CScriptVarGenerator(const CScriptVarGenerator &Copy) + : + CScriptVarObject(Copy), functionRoot(Copy.functionRoot), function(Copy.function), coroutine(this) {} ///< Copy protected -> use clone for public +public: + virtual ~CScriptVarGenerator(); + virtual CScriptVarPtr clone(); + virtual bool isIterator(); + virtual bool isGenerator(); + virtual std::string getVarType(); // { return "generator"; } + virtual std::string getVarTypeTagName(); // { return "Generator"; } + + CScriptVarPtr getFunctionRoot() { return functionRoot; } + CScriptVarFunctionPtr getFunction() { return function; } + + virtual void setTemporaryMark_recursive(uint32_t ID); + + void native_send(const CFunctionsScopePtr &c, void *data); + void native_throw(const CFunctionsScopePtr &c, void *data); + int Coroutine(); + void setException(const CScriptVarPtr &YieldVar) { + yieldVar = YieldVar; + yieldVarIsException = true; + } + bool isClosed() { return closed; } + CScriptVarPtr yield(CScriptResult &execute, CScriptVar *YieldIn); +private: + CScriptVarPtr functionRoot; + CScriptVarFunctionPtr function; + bool closed; + CScriptVarPtr yieldVar; + bool yieldVarIsException; + class CCooroutine : public CScriptCoroutine { + CCooroutine(CScriptVarGenerator* Parent) : parent(Parent) {} + int Coroutine() { return parent->Coroutine(); } + void yield() { CScriptCoroutine::yield(); } + CScriptVarGenerator* parent; + friend class CScriptVarGenerator; + } coroutine; + friend class CCooroutine; + +public: + void *callersStackBase; + int callersScopeSize; + CScriptTokenizer *callersTokenizer; + bool callersHaveTry; + std::vector generatorScopes; + + friend define_newScriptVar_NamedFnc(CScriptVarGenerator, CTinyJS *, const CScriptVarPtr &, const CScriptVarFunctionPtr &); + +}; +inline define_newScriptVar_NamedFnc(CScriptVarGenerator, CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function) { return new CScriptVarGenerator(Context, FunctionRoot, Function); } + +#endif + +////////////////////////////////////////////////////////////////////////// +template +inline CScriptVarPtr CScriptVar::newScriptVar(T t) { return ::newScriptVar(context, t); } +template +inline CScriptVarPtr CScriptVar::newScriptVar(T1 t1, T2 t2) { return ::newScriptVar(context, t1, t2); } +//inline CScriptVarPtr newScriptVar(const CNumber &t) { return ::newScriptVar(context, t); } +////////////////////////////////////////////////////////////////////////// + + +class CScriptResult { +public: + enum TYPE { + Normal, + Break, + Continue, + Return, + Throw, + noncatchableThrow, + noExecute + }; + CScriptResult() : type(Normal), throw_at_line(-1), throw_at_column(-1) {} +// CScriptResult(TYPE Type) : type(Type), throw_at_line(-1), throw_at_column(-1) {} +// ~RESULT() { if(type==Throw) throw value; } + bool isNormal() { return type==Normal; } + bool isBreak() { return type==Break; } + bool isContinue() { return type==Continue; } + bool isBreakContinue() { return type==Break || type==Continue; } + bool isReturn() { return type==Return; } + bool isReturnNormal() { return type==Return || type==Normal; } + bool isThrow() { return type==Throw; } + + + operator bool() const { return type==Normal; } + void set(TYPE Type, bool Clear=true) { type=Type; if(Clear) value.clear(), target.clear(); } + void set(TYPE Type, const CScriptVarPtr &Value) { type=Type; value=Value; } + void set(TYPE Type, const std::string &Target) { type=Type; target=Target; } + void setThrow(const CScriptVarPtr &Value, const std::string &File, int Line=-1, int Column=-1) { type=Throw; value=Value; throw_at_file=File, throw_at_line=Line; throw_at_column=Column; } + + void cThrow() { if(type==Throw) throw value; } + + CScriptResult &operator()(const CScriptResult &rhs) { if(rhs.type!=Normal) *this=rhs; return *this; } + + enum TYPE type; + CScriptVarPtr value; + std::string target; + std::string throw_at_file; + int throw_at_line; + int throw_at_column; + +}; + + + +////////////////////////////////////////////////////////////////////////// +/// CTinyJS +////////////////////////////////////////////////////////////////////////// + +typedef int (*native_require_read_fnc)(const std::string &Fname, std::string &Data); + +class CTinyJS { +public: + CTinyJS(); + ~CTinyJS(); + + void execute(CScriptTokenizer &Tokenizer); + void execute(const char *Code, const std::string &File="", int Line=0, int Column=0); + void execute(const std::string &Code, const std::string &File="", int Line=0, int Column=0); + /** 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() */ + CScriptVarLinkPtr evaluateComplex(CScriptTokenizer &Tokenizer); + /** 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() */ + CScriptVarLinkPtr evaluateComplex(const char *code, const std::string &File="", int Line=0, int Column=0); + /** 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() */ + CScriptVarLinkPtr evaluateComplex(const std::string &code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(CScriptTokenizer &Tokenizer); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(const char *code, const std::string &File="", int Line=0, int Column=0); + /** Evaluate the given code and return a string. If nothing to return, will return + * 'undefined' */ + std::string evaluate(const std::string &code, const std::string &File="", int Line=0, int Column=0); + + native_require_read_fnc setRequireReadFnc(native_require_read_fnc fnc) { + native_require_read_fnc old = native_require_read; + native_require_read = fnc; + return old; + } + + /// add a native function to be called from TinyJS + /** example: + \code + void scRandInt(const CFunctionsScopePtr &c, void *userdata) { ... } + tinyJS->addNative("function randInt(min, max)", scRandInt, 0); + \endcode + + or + + \code + void scSubstring(const CFunctionsScopePtr &c, void *userdata) { ... } + tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0); + \endcode + or + + \code + class Class + { + public: + void scSubstring(const CFunctionsScopePtr &c, void *userdata) { ... } + }; + Class Instanz; + tinyJS->addNative("function String.substring(lo, hi)", &Instanz, &Class::*scSubstring, 0); + \endcode + */ + + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, JSCallback ptr, void *userdata=0, int LinkFlags=SCRIPTVARLINK_BUILDINDEFAULT); + template + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, C *class_ptr, void(C::*class_fnc)(const CFunctionsScopePtr &, void *), void *userdata=0, int LinkFlags=SCRIPTVARLINK_BUILDINDEFAULT) + { + return addNative(funcDesc, ::newScriptVar(this, class_ptr, class_fnc, userdata), LinkFlags); + } + + /// Send all variables to stdout + void trace(); + + const CScriptVarScopePtr &getRoot() { return root; }; /// gets the root of symbol table + // CScriptVar *root; /// root of symbol table + + /// newVars & constVars + //CScriptVarPtr newScriptVar(const CNumber &t) { return ::newScriptVar(this, t); } + template CScriptVarPtr newScriptVar(T t) { return ::newScriptVar(this, t); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2) { return ::newScriptVar(this, t1, t2); } + template CScriptVarPtr newScriptVar(T1 t1, T2 t2, T3 t3) { return ::newScriptVar(this, t1, t2, t3); } + const CScriptVarPtr &constScriptVar(Undefined_t) { return constUndefined; } + const CScriptVarPtr &constScriptVar(Null_t) { return constNull; } + const CScriptVarPtr &constScriptVar(NaN_t) { return constNaN; } + const CScriptVarPtr &constScriptVar(Infinity t) { return t.Sig()<0 ? constInfinityNegative : constInfinityPositive; } + const CScriptVarPtr &constScriptVar(bool Val) { return Val?constTrue:constFalse; } + const CScriptVarPtr &constScriptVar(NegativeZero_t) { return constNegativZero; } + const CScriptVarPtr &constScriptVar(StopIteration_t) { return constStopIteration; } + +private: + CScriptTokenizer *t; /// current tokenizer + bool haveTry; + std::vectorscopes; + CScriptVarScopePtr root; + const CScriptVarScopePtr &scope() { return scopes.back(); } + + class CScopeControl { // helper-class to manage scopes + private: + CScopeControl(const CScopeControl& Copy) MEMBER_DELETE; // no copy + CScopeControl& operator =(const CScopeControl& Copy) MEMBER_DELETE; + public: + CScopeControl(CTinyJS *Context) : context(Context), count(0) {} + ~CScopeControl() { while(count--) {CScriptVarScopePtr parent = context->scopes.back()->getParent(); if(parent) context->scopes.back() = parent; else context->scopes.pop_back() ;} } + void addFncScope(const CScriptVarScopePtr &Scope) { context->scopes.push_back(Scope); count++; } + CScriptVarScopeLetPtr addLetScope() { count++; return context->scopes.back() = ::newScriptVar(context, ScopeLet, context->scopes.back()); } + void addWithScope(const CScriptVarPtr &With) { context->scopes.back() = ::newScriptVar(context, ScopeWith, context->scopes.back(), With); count++; } + private: + CTinyJS *context; + int count; + }; + friend class CScopeControl; +public: + CScriptVarPtr objectPrototype; /// Built in object class + CScriptVarPtr objectPrototype_valueOf; /// Built in object class + CScriptVarPtr objectPrototype_toString; /// Built in object class + CScriptVarPtr arrayPrototype; /// Built in array class + CScriptVarPtr stringPrototype; /// Built in string class + CScriptVarPtr regexpPrototype; /// Built in string class + CScriptVarPtr numberPrototype; /// Built in number class + CScriptVarPtr booleanPrototype; /// Built in boolean class + CScriptVarPtr iteratorPrototype; /// Built in iterator class +#ifndef NO_GENERATORS + CScriptVarPtr generatorPrototype; /// Built in generator class +#endif /*NO_GENERATORS*/ + CScriptVarPtr functionPrototype; /// Built in function class + const CScriptVarPtr &getErrorPrototype(ERROR_TYPES Type) { return errorPrototypes[Type]; } +private: + CScriptVarPtr errorPrototypes[ERROR_COUNT]; /// Built in error class + CScriptVarPtr constUndefined; + CScriptVarPtr constNull; + CScriptVarPtr constNaN; + CScriptVarPtr constInfinityPositive; + CScriptVarPtr constInfinityNegative; + CScriptVarPtr constNegativZero; + CScriptVarPtr constTrue; + CScriptVarPtr constFalse; + CScriptVarPtr constStopIteration; + + std::vector pseudo_refered; + + void CheckRightHandVar(CScriptResult &execute, CScriptVarLinkWorkPtr &link) + { + if(execute && link && !link->isOwned() && !link.hasReferencedOwner() && !link->getName().empty()) + throwError(execute, ReferenceError, link->getName() + " is not defined", t->getPrevPos()); + } + + void CheckRightHandVar(CScriptResult &execute, CScriptVarLinkWorkPtr &link, CScriptTokenizer::ScriptTokenPosition &Pos) + { + if(execute && link && !link->isOwned() && !link.hasReferencedOwner() && !link->getName().empty()) + throwError(execute, ReferenceError, link->getName() + " is not defined", Pos); + } + +public: + // function call + CScriptVarPtr callFunction(const CScriptVarFunctionPtr &Function, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); + CScriptVarPtr callFunction(CScriptResult &execute, const CScriptVarFunctionPtr &Function, std::vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis=0); + ////////////////////////////////////////////////////////////////////////// +#ifndef NO_GENERATORS + std::vector generatorStack; + void generator_start(CScriptVarGenerator *Generator); + CScriptVarPtr generator_yield(CScriptResult &execute, CScriptVar *YieldIn); +#endif + ////////////////////////////////////////////////////////////////////////// + + // parsing - in order of precedence + CScriptVarPtr mathsOp(CScriptResult &execute, const CScriptVarPtr &a, const CScriptVarPtr &b, int op); +private: + void assign_destructuring_var(const CScriptVarPtr &Scope, const CScriptTokenDataDestructuringVar &Objc, const CScriptVarPtr &Val, CScriptResult &execute); + void execute_var_init(bool hideLetScope, CScriptResult &execute); + void execute_destructuring(CScriptTokenDataObjectLiteral &Objc, const CScriptVarPtr &Val, CScriptResult &execute); + CScriptVarLinkWorkPtr execute_literals(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_member(CScriptVarLinkWorkPtr &parent, CScriptResult &execute); + CScriptVarLinkWorkPtr execute_function_call(CScriptResult &execute); + bool execute_unary_rhs(CScriptResult &execute, CScriptVarLinkWorkPtr& a); + CScriptVarLinkWorkPtr execute_unary(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_term(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_expression(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_binary_shift(CScriptResult &execute); + CScriptVarLinkWorkPtr execute_relation(CScriptResult &execute, int set=LEX_EQUAL, int set_n='<'); + CScriptVarLinkWorkPtr execute_binary_logic(CScriptResult &execute, int op='|', int op_n1='^', int op_n2='&'); + CScriptVarLinkWorkPtr execute_logic(CScriptResult &execute, int op=LEX_OROR, int op_n=LEX_ANDAND); + CScriptVarLinkWorkPtr execute_condition(CScriptResult &execute); + CScriptVarLinkPtr execute_assignment(CScriptVarLinkWorkPtr Lhs, CScriptResult &execute); + CScriptVarLinkPtr execute_assignment(CScriptResult &execute); + CScriptVarLinkPtr execute_base(CScriptResult &execute); + void execute_block(CScriptResult &execute); + void execute_statement(CScriptResult &execute); + // parsing utility functions + CScriptVarLinkWorkPtr parseFunctionDefinition(const CScriptToken &FncToken); + CScriptVarLinkWorkPtr parseFunctionsBodyFromString(const std::string &ArgumentList, const std::string &FncBody); +public: + CScriptVarLinkPtr findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes +private: + ////////////////////////////////////////////////////////////////////////// + /// addNative-helper + CScriptVarFunctionNativePtr addNative(const std::string &funcDesc, CScriptVarFunctionNativePtr Var, int LinkFlags); + + ////////////////////////////////////////////////////////////////////////// + /// throws an Error & Exception +public: + void throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const std::string &message); + void throwException(ERROR_TYPES ErrorType, const std::string &message); + void throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const std::string &message, CScriptTokenizer::ScriptTokenPosition &Pos); + void throwException(ERROR_TYPES ErrorType, const std::string &message, CScriptTokenizer::ScriptTokenPosition &Pos); +private: + ////////////////////////////////////////////////////////////////////////// + /// native Object-Constructors & prototype-functions + + void native_Object(const CFunctionsScopePtr &c, void *data); + void native_Object_getPrototypeOf(const CFunctionsScopePtr &c, void *data); + /* Userdate for set-/isObjectState + * 0 - preventExtensions / isExtensible + * 1 - seal / isSealed + * 2 - freeze / isFrozen + */ + void native_Object_setObjectSecure(const CFunctionsScopePtr &c, void *data); + void native_Object_isSecureObject(const CFunctionsScopePtr &c, void *data); + void native_Object_keys(const CFunctionsScopePtr &c, void *data); + void native_Object_getOwnPropertyDescriptor(const CFunctionsScopePtr &c, void *data); + void native_Object_defineProperty(const CFunctionsScopePtr &c, void *data); + void native_Object_defineProperties(const CFunctionsScopePtr &c, void *data); + + void native_Object_prototype_hasOwnProperty(const CFunctionsScopePtr &c, void *data); + void native_Object_prototype_valueOf(const CFunctionsScopePtr &c, void *data); + void native_Object_prototype_toString(const CFunctionsScopePtr &c, void *data); + + void native_Array(const CFunctionsScopePtr &c, void *data); + + void native_String(const CFunctionsScopePtr &c, void *data); + + void native_RegExp(const CFunctionsScopePtr &c, void *data); + + void native_Number(const CFunctionsScopePtr &c, void *data); + + void native_Boolean(const CFunctionsScopePtr &c, void *data); + + void native_Iterator(const CFunctionsScopePtr &c, void *data); + +// void native_Generator(const CFunctionsScopePtr &c, void *data); + void native_Generator_prototype_next(const CFunctionsScopePtr &c, void *data); + + void native_Function(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_call(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_apply(const CFunctionsScopePtr &c, void *data); + void native_Function_prototype_bind(const CFunctionsScopePtr &c, void *data); + + void native_Error(const CFunctionsScopePtr &c, void *data); + void native_EvalError(const CFunctionsScopePtr &c, void *data); + void native_RangeError(const CFunctionsScopePtr &c, void *data); + void native_ReferenceError(const CFunctionsScopePtr &c, void *data); + void native_SyntaxError(const CFunctionsScopePtr &c, void *data); + void native_TypeError(const CFunctionsScopePtr &c, void *data); + + + ////////////////////////////////////////////////////////////////////////// + /// global function + + void native_eval(const CFunctionsScopePtr &c, void *data); + void native_require(const CFunctionsScopePtr &c, void *data); + native_require_read_fnc native_require_read; + void native_isNAN(const CFunctionsScopePtr &c, void *data); + void native_isFinite(const CFunctionsScopePtr &c, void *data); + void native_parseInt(const CFunctionsScopePtr &c, void *data); + void native_parseFloat(const CFunctionsScopePtr &c, void *data); + + void native_JSON_parse(const CFunctionsScopePtr &c, void *data); + + + uint32_t uniqueID; + int32_t currentMarkSlot; + void *stackBase; +public: + int32_t getCurrentMarkSlot() { + ASSERT(currentMarkSlot >= 0); // UniqueID not allocated + return currentMarkSlot; + } + uint32_t allocUniqueID() { + ++currentMarkSlot; + ASSERT(currentMarkSlot= 0); // freeUniqueID without allocUniqueID + --currentMarkSlot; + } + CScriptVar *first; + void setTemporaryID_recursive(uint32_t ID); + void ClearUnreferedVars(const CScriptVarPtr &extra=CScriptVarPtr()); + void setStackBase(void * StackBase) { stackBase = StackBase; } + void setStackBase(uint32_t StackSize) { char dummy; stackBase = StackSize ? &dummy-StackSize : 0; } +}; + + +////////////////////////////////////////////////////////////////////////// +template +inline const CScriptVarPtr &CScriptVar::constScriptVar(T t) { return context->constScriptVar(t); } +////////////////////////////////////////////////////////////////////////// +inline CNumber CScriptVarLink::toNumber() { return var->toNumber(); } +inline CNumber CScriptVarLink::toNumber(CScriptResult &execute) { return var->toNumber(execute); } + +inline void CScriptVar::setTemporaryMark(uint32_t ID) { temporaryMark[context->getCurrentMarkSlot()] = ID; } +inline uint32_t CScriptVar::getTemporaryMark() { return temporaryMark[context->getCurrentMarkSlot()]; } + + +#endif + + diff --git a/include/TinyJS/TinyJS_Functions.h b/include/TinyJS/TinyJS_Functions.h new file mode 100755 index 0000000..998866e --- /dev/null +++ b/include/TinyJS/TinyJS_Functions.h @@ -0,0 +1,49 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma message("The include "__FILE__" is deprecated - Functions now registered by default") + +#ifndef TINYJS_FUNCTIONS_H +#define TINYJS_FUNCTIONS_H + +#include "TinyJS/TinyJS.h" + +/// Register useful functions with the TinyJS interpreter +extern void DEPRECATED("is deprecated - Functions now registered by default") registerFunctions(CTinyJS *tinyJS); + +#endif diff --git a/include/TinyJS/TinyJS_MathFunctions.h b/include/TinyJS/TinyJS_MathFunctions.h new file mode 100644 index 0000000..d68992d --- /dev/null +++ b/include/TinyJS/TinyJS_MathFunctions.h @@ -0,0 +1,51 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * - Math and Trigonometry functions + * + * Authored By O.Z.L.B. + * + * Copyright (C) 2011 O.Z.L.B. + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma message("The include "__FILE__" is deprecated - MathFunctions now registered by default") + +#ifndef TINYJS_MATHFUNCTIONS_H +#define TINYJS_MATHFUNCTIONS_H + +#include "TinyJS/TinyJS.h" + +/// Register useful math. functions with the TinyJS interpreter +extern void DEPRECATED("is deprecated - MathFunctions now registered by default") registerMathFunctions(CTinyJS *tinyJS); + +#endif diff --git a/include/TinyJS/TinyJS_StringFunctions.h b/include/TinyJS/TinyJS_StringFunctions.h new file mode 100644 index 0000000..6d65cf3 --- /dev/null +++ b/include/TinyJS/TinyJS_StringFunctions.h @@ -0,0 +1,40 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma message("The include "__FILE__" is deprecated - StringFunctions now registered by default") + +#ifndef TINYJS_STRINGFUNCTIONS_H +#define TINYJS_STRINGFUNCTIONS_H + +#include "TinyJS/TinyJS.h" + +/// Register useful functions with the TinyJS interpreter +extern void DEPRECATED("is deprecated - StringFunctions now registered by default") registerStringFunctions(CTinyJS *tinyJS); + +#endif diff --git a/include/TinyJS/TinyJS_Threading.h b/include/TinyJS/TinyJS_Threading.h new file mode 100644 index 0000000..3c8d5af --- /dev/null +++ b/include/TinyJS/TinyJS_Threading.h @@ -0,0 +1,160 @@ +#ifndef TinyJS_Threading_h__ +#define TinyJS_Threading_h__ +#include "TinyJS/config.h" +#ifndef NO_THREADING +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef HAVE_CXX_THREADS + +# include +# include +typedef std::mutex CScriptMutex; +typedef std::unique_lock CScriptUniqueLock; +typedef std::condition_variable CScriptCondVar; + +#else + +class CScriptMutex { +public: + CScriptMutex(); + ~CScriptMutex(); + void lock() { mutex->lock(); } + void unlock() { mutex->unlock(); } + void *getRealMutex() { return mutex->getRealMutex(); } + class CScriptMutex_t{ + public: +// virtual ~CScriptMutex_t()=0; + virtual void lock()=0; + virtual void unlock()=0; + virtual void *getRealMutex()=0; + }; +private: + CScriptMutex_t *mutex; + +}; + +class CScriptUniqueLock { +public: + CScriptUniqueLock(CScriptMutex &Mutex) : mutex(&Mutex) { mutex->lock(); } + ~CScriptUniqueLock() { mutex->unlock(); } + CScriptMutex *mutex; +}; + +class CScriptCondVar { +public: + CScriptCondVar(); + virtual ~CScriptCondVar(); + void notify_one() { condVar->notify_one(); } + void wait(CScriptUniqueLock &Lock) { condVar->wait(Lock); } + class CScriptCondVar_t{ + public: + virtual void notify_one()=0; + virtual void wait(CScriptUniqueLock &Lock)=0; + }; +private: + CScriptCondVar_t *condVar; +}; + +#endif /*HAVE_CXX_THREADS*/ + + +class CScriptSemaphore +{ +private: + CScriptCondVar cond; + CScriptMutex mutex; + unsigned int count; + unsigned int max_count; + +public: + CScriptSemaphore(unsigned int currentCount=1, unsigned int maxCount=1) : count(currentCount), max_count(maxCount) {} + void post() { + CScriptUniqueLock lock(mutex); + + ++count; + cond.notify_one(); + } + void wait() { + CScriptUniqueLock lock(mutex); + while(!count) cond.wait(lock); + --count; + } + unsigned int currentWaits() { + CScriptUniqueLock lock(mutex); + return count; + } +}; + +class CScriptThread { +public: + CScriptThread(); + virtual ~CScriptThread(); + void Run() { thread->Run(); } + int Stop(bool Wait=true) { return thread->Stop(Wait); } + int retValue() { return thread->retValue(); } + virtual int ThreadFnc()=0; + virtual void ThreadFncFinished(); + bool isActiv() { return thread->isActiv(); } + bool isRunning() { return thread->isRunning(); } + bool isStarted() { return thread->isStarted(); } + class CScriptThread_t{ + public: + virtual void Run()=0; + virtual int Stop(bool Wait)=0; + virtual bool isActiv()=0; + virtual bool isRunning()=0; + virtual bool isStarted()=0; + virtual int retValue()=0; + }; +private: + CScriptThread_t *thread; +}; + +class CScriptCoroutine : protected CScriptThread { +public: + CScriptCoroutine() : wake_thread(0), wake_main(0) {} + typedef struct{} StopIteration_t; + static StopIteration_t StopIteration; + bool next(); // returns true if coroutine is running +protected: + virtual int Coroutine()=0; + void yield(); + bool yield_no_throw(); +private: + virtual int ThreadFnc(); + virtual void ThreadFncFinished(); + CScriptSemaphore wake_thread; + CScriptSemaphore wake_main; +}; + + + +#endif // NO_THREADING +#endif // TinyJS_Threading_h__ diff --git a/include/TinyJS/config.h b/include/TinyJS/config.h new file mode 100644 index 0000000..0642030 --- /dev/null +++ b/include/TinyJS/config.h @@ -0,0 +1,177 @@ +#ifndef _42TinyJS_config_h__ +#define _42TinyJS_config_h__ + +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +////////////////////////////////////////////////////////////////////////// + +/* POOL-ALLOCATOR + * ============== + * To speed-up new & delete 42TinyJS adds an object-pool + * The pool is activated by default. + * To deactivate this stuff define NO_POOL_ALLOCATOR + */ +//#define NO_POOL_ALLOCATOR + +/* + * for debugging-stuff you can define DEBUG_POOL_ALLOCATOR + * if a memory-leak detected the allocator usage is printed to stderr + */ +//#define DEBUG_POOL_ALLOCATOR +/* + * with define LOG_POOL_ALLOCATOR_MEMORY_USAGE + * the allocator usage is always printed to stderr + */ +//#define LOG_POOL_ALLOCATOR_MEMORY_USAGE + +// NOTE: _DEBUG or LOG_POOL_ALLOCATOR_MEMORY_USAGE implies DEBUG_POOL_ALLOCATOR + +////////////////////////////////////////////////////////////////////////// + +/* REGEXP-SUPPORT + * ============== + * The RegExp-support needs boost-regex or TR1-regex + * To deactivate this stuff define NO_REGEXP + */ +//#define NO_REGEXP + +/* if NO_REGEXP not defined is included and std::regex is used + * you can define HAVE_BOOST_REGEX and is included and boost::regex is used + */ +//#define HAVE_BOOST_REGEX + +/* or you can define HAVE_TR1_REGEX and is included and std::tr1::regex is used + */ +//#define HAVE_TR1_REGEX + + +////////////////////////////////////////////////////////////////////////// + +/* LET-STUFF + * ========= + * Redeclaration of LET-vars is not allowed in block-scopes. + * But in the root- and functions-scopes it is currently allowed + * In future ECMAScript versions this will be also in root-and functions-scopes forbidden + * To enable the future behavior define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + */ +//#define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + +////////////////////////////////////////////////////////////////////////// + +/* GENERATOR's + * =========== + * functions with "yield" in it is detected as Generator. + * Generator-support needs threading-stuff + * To disable Generators define NO_GENERATORS + */ +//#define NO_GENERATORS + + +////////////////////////////////////////////////////////////////////////// + +/* MULTI-THREADING + * =============== + * 42TinyJS is basically thread-save. + * You can run different or the same JS-code simultaneously in different instances of class TinyJS. + * >>> NOTE: You can NOT run more threads on the SAME instance of class TinyJS <<< + * The threading-stuff is needed by the pool-allocator (locking) and the generator-/yield-stuff + * to deactivate threading define NO_THREADING + * NOTE: if NO_POOL_ALLOCATOR not defined you can not run JS-code simultaneously + */ +//#define NO_THREADING + +/* with C++2011 (or MS VisualC++ 2012 and above) the C++ 2011 STL-Threading-API is used. + * You can define NO_CXX_THREADS to use alternate API's + */ +//#define NO_CXX_THREADS + +/* if C++ 2011 STL-Threading-API not available + * on Windows the windows-threading-API is used by default. + * on non-Windows (WIN32 is not defined) it is tried to use the POSIX pthread-API + * to force the pthread-API define HAVE_PTHREAD (windows needs in this case + * a pthread-lib e.g http://http://sourceware.org/pthreads-win32/) + */ +//#define HAVE_PTHREAD + +/* you can implement your own custom thread-implementation. + * to prevent the using of the win- or pthread-API define HAVE_CUSTOM_THREADING_IMPL + */ +//#define HAVE_CUSTOM_THREADING_IMPL + +//////////////////////////////////////////////// +// DO NOT MAKE CHANGES OF THE FOLLOWING STUFF // +//////////////////////////////////////////////// + +#if defined(NO_THREADING) && !defined(NO_GENERATORS) +# define NO_GENERATORS +#pragma message("\n***********************************************************************\n\ +* You have defined NO_THREADING and not defined NO_GENERATORS\n\ +* NOTE: GENERATORS needs THREADING. Generators/Yield are deactivated\n\ +***********************************************************************\n") +#endif + +#if defined(NO_POOL_ALLOCATOR) && defined(NO_GENERATORS) && !defined(NO_THREADING) +# define NO_THREADING +#endif + +#if !defined(NO_POOL_ALLOCATOR) && defined(NO_THREADING) +#pragma message("\n***********************************************************************\n\ +* You have defined NO_THREADING and not defined NO_POOL_ALLOCATOR\n\ +* NOTE: you can not run JS-code simultaneously in different threads\n\ +***********************************************************************\n") +#endif + +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L + +# define HAVE_CXX11_RVALUE_REFERENCE 1 +# define MEMBER_DELETE =delete + +# if !defined(NO_CXX_THREADS) && !defined(NO_THREADING) +# define HAVE_CXX_THREADS 1 +# endif +#else +# if _MSC_VER >= 1600 +# define HAVE_CXX11_RVALUE_REFERENCE 1 +# endif +# if _MSC_VER >= 1700 +# if !defined(NO_CXX_THREADS) && !defined(NO_THREADING) +# define HAVE_CXX_THREADS 1 +# endif +# endif +# if _MSC_VER >= 1800 +# define define MEMBER_DELETE =delete +# endif +#endif + +#ifndef MEMBER_DELETE +# define MEMBER_DELETE +#endif + +#endif // _42TinyJS_config_h__ diff --git a/include/TinyJS/pool_allocator.h b/include/TinyJS/pool_allocator.h new file mode 100644 index 0000000..827e73b --- /dev/null +++ b/include/TinyJS/pool_allocator.h @@ -0,0 +1,162 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef pool_allocator_h__ +#define pool_allocator_h__ + +#include +#include +#include +#include "TinyJS/config.h" +#include "TinyJS/TinyJS_Threading.h" + +/************************************************************************ + * TinyJS must many many times allocates and frees objects + * To prevent memory fragmentation and speed ups allocates & frees, i have + * added to 42TinyJS a pool_allocator. This allocator allocates every 64 objects + * as a pool of objects. Is an object needed it can faster allocated from this pool as + * from the heap. + ************************************************************************/ + +#if !defined(DEBUG_POOL_ALLOCATOR) && (defined(_DEBUG) || defined(LOG_POOL_ALLOCATOR_MEMORY_USAGE)) +# define DEBUG_POOL_ALLOCATOR +#endif + +struct block_head; +class fixed_size_allocator { +public: + ~fixed_size_allocator(); + static void *alloc(size_t,const char* for_class=0); + static void free(void *, size_t); + size_t objectSize() { return object_size; } +#ifndef NO_THREADING + static CScriptMutex locker; +#endif +private: + fixed_size_allocator(size_t num_objects, size_t object_size, const char* for_class); + fixed_size_allocator(const fixed_size_allocator&); + fixed_size_allocator& operator=(const fixed_size_allocator&); + void *_alloc(size_t); + bool _free(void* p, size_t); + size_t num_objects; + size_t object_size; + void *head_of_free_list; + block_head *head; + int refs; +#ifdef DEBUG_POOL_ALLOCATOR + // Debug + std::string name; + int allocs; + int frees; + int current; + int max; + int blocks; +#endif +}; +//************************************************************************************** +template +class fixed_size_object { +public: + static void* operator new(size_t size) { +#ifdef DEBUG_POOL_ALLOCATOR + return fixed_size_allocator::alloc(size, typeid(T).name()); +#else + return fixed_size_allocator::alloc(size); +#endif + } + static void* operator new(size_t size, void* p) { + return p; + } + static void operator delete(void* p, size_t size) { + fixed_size_allocator::free(p, size); + } +private: +}; +#if 0 // under construction +template +class block_allocator_stl { +public : + // typedefs + + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; +public : + // convert an allocator to allocator + + template + struct rebind { + typedef block_allocator_stl other; + }; + + inline explicit block_allocator_stl() {} + inline ~block_allocator_stl() {} + inline block_allocator_stl(block_allocator_stl const&) {} + template + inline block_allocator_stl(block_allocator_stl const&) {} + inline block_allocator_stl &operator=(block_allocator_stl const&) {} + template + inline block_allocator_stl &operator=(block_allocator_stl const&) {} + + // address + + inline pointer address(reference r) { return &r; } + inline const_pointer address(const_reference r) { return &r; } + + // memory allocation + inline pointer allocate(size_type cnt, const void*) { + return reinterpret_cast(fixed_size_allocator::get(cnt * sizeof (T), true, typeid(T).name())->alloc(cnt * sizeof (T))); + // return reinterpret_cast(::operator new(cnt * sizeof (T))); + } + inline void deallocate(pointer p, size_type cnt) { + fixed_size_allocator::get(cnt * sizeof (T), false)->free(p, cnt * sizeof (T)); + // ::operator delete(p); + } + + // size + + inline size_type max_size() const { + return SIZE_MAX / sizeof(T); + } + + inline void construct(pointer _Ptr, value_type& _Val) { + ::new ((void*)_Ptr) value_type(_Val); + } + inline void destroy(pointer _Ptr) { + _Ptr->~value_type(); + } +}; +#endif + +#endif // pool_allocator_h__ diff --git a/include/TinyJS/time_logger.h b/include/TinyJS/time_logger.h new file mode 100644 index 0000000..592515c --- /dev/null +++ b/include/TinyJS/time_logger.h @@ -0,0 +1,128 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef time_logger_h__ +#define time_logger_h__ +#if defined(WITH_TIME_LOGGER) + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +class TimeLogger { +public: + TimeLogger(const char *Name, bool Started=false, const char *extName=0) + : + name(Name), start_time(gettime()), sum_time(0), calls(0), started(Started) { + if(extName) { + name+="["; + name+=extName; + name+="]"; + } + } + TimeLogger(const char *Name, const char *extName, bool Started=false) + : + name(Name), start_time(gettime()), sum_time(0), calls(0), started(Started) { + if(extName) { + name+="["; + name+=extName; + name+="]"; + } + } + ~TimeLogger() { + printLog(); +// getchar(); + } + void startTimer() { + start_time = gettime(); + started = true; + } + void stopTimer() { + if(!started) return; + sum_time += gettime()-start_time; + calls++; + started = false; + } + void printLog() { + if(started) stopTimer(); + if(calls == 1) + printf("Timer( %s ) = %d,%06d sec \n", + name.c_str(), (int)(sum_time / 1000000LL), (int)(sum_time % 1000000LL)); + else if(calls>1) + printf("Timer( %s ) = %d,%06d sec (called %d times) -> %.d microsec per call\n", + name.c_str(), (int)(sum_time / 1000000LL), (int)(sum_time % 1000000LL), + calls, (int)(sum_time/calls)); + calls = 0; sum_time = 0; + } +private: +// static int64_t frequenzy = 0; + std::string name; + int64_t start_time, sum_time; + uint32_t calls; + bool started; + int64_t gettime() { // set out to time in millisec +#ifdef _WIN32 + static LARGE_INTEGER fr = {0}; + LARGE_INTEGER li; + if(fr.QuadPart == 0) QueryPerformanceFrequency(&fr); + QueryPerformanceCounter(&li); + return (li.QuadPart * 1000000LL) / fr.QuadPart; +#else + return (clock() * 1000000LL) / CLOCKS_PER_SEC; +#endif + }; +}; +class _TimeLoggerHelper { +public: + _TimeLoggerHelper(TimeLogger &Tl) : tl(Tl) { tl.startTimer(); } + ~_TimeLoggerHelper() { tl.stopTimer(); } +private: + TimeLogger &tl; +}; +# define TimeLoggerCreate(a, ...) TimeLogger a##_TimeLogger(#a,##__VA_ARGS__) +# define TimeLoggerStart(a) a##_TimeLogger.startTimer() +# define TimeLoggerStop(a) a##_TimeLogger.stopTimer() +# define TimeLoggerLogprint(a) a##_TimeLogger.printLog() +# define TimeLoggerHelper(a) _TimeLoggerHelper a##_helper(a##_TimeLogger) +#else /* _DEBUG */ +# define TimeLoggerCreate(...) +# define TimeLoggerStart(...) do{}while(0) +# define TimeLoggerStop(...) do{}while(0) +# define TimeLoggerLogprint(a) do{}while(0) +# define TimeLoggerHelper(a) do{}while(0) +#endif /* _DEBUG */ + + + +#endif // time_logger_h__ diff --git a/include/catch.hpp b/include/catch.hpp new file mode 100644 index 0000000..f7681f4 --- /dev/null +++ b/include/catch.hpp @@ -0,0 +1,11545 @@ +/* + * Catch v1.9.6 + * Generated: 2017-06-27 12:19:54.557875 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wparentheses" + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) +// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? +// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE + +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(CATCH_CPP11_OR_GREATER) + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) +# define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) +# define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#endif +// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for +// analytics) because, at time of writing, __COUNTER__ is not properly handled by it. +// This does not affect compilation +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_SHUFFLE +#endif +# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TYPE_TRAITS +# endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include + +namespace Catch { + + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo(SourceLineInfo const& other) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( CATCH_NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = CATCH_NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + +struct AutoReg { + + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + registerTestCase + ( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestCaseName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct DecomposedExpression + { + virtual ~DecomposedExpression() {} + virtual bool isBinaryExpression() const { + return false; + } + virtual void reconstructExpression( std::string& dest ) const = 0; + + // Only simple binary comparisons can be decomposed. + // If more complex check is required then wrap sub-expressions in parentheses. + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); + + private: + DecomposedExpression& operator = (DecomposedExpression const&); + }; + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( char const * _macroName, + SourceLineInfo const& _lineInfo, + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg = ""); + + char const * macroName; + SourceLineInfo lineInfo; + char const * capturedExpression; + ResultDisposition::Flags resultDisposition; + char const * secondArg; + }; + + struct AssertionResultData + { + AssertionResultData() : decomposedExpression( CATCH_NULL ) + , resultType( ResultWas::Unknown ) + , negated( false ) + , parenthesized( false ) {} + + void negate( bool parenthesize ) { + negated = !negated; + parenthesized = parenthesize; + if( resultType == ResultWas::Ok ) + resultType = ResultWas::ExpressionFailed; + else if( resultType == ResultWas::ExpressionFailed ) + resultType = ResultWas::Ok; + } + + std::string const& reconstructExpression() const { + if( decomposedExpression != CATCH_NULL ) { + decomposedExpression->reconstructExpression( reconstructedExpression ); + if( parenthesized ) { + reconstructedExpression.insert( 0, 1, '(' ); + reconstructedExpression.append( 1, ')' ); + } + if( negated ) { + reconstructedExpression.insert( 0, 1, '!' ); + } + decomposedExpression = CATCH_NULL; + } + return reconstructedExpression; + } + + mutable DecomposedExpression const* decomposedExpression; + mutable std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + bool negated; + bool parenthesized; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + void discardDecomposedExpression() const; + void expandDecomposedExpression() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + std::string toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + private: + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); + }; + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (!m_matchers[i]->match(arg)) + return false; + } + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " and "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (m_matchers[i]->match(arg)) + return true; + } + return false; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " or "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + return !m_underlyingMatcher.match( arg ); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + // - deprecated: prefer ||, && and ! + template + inline Impl::MatchNotOf Not( Impl::MatcherBase const& underlyingMatcher ) { + return Impl::MatchNotOf( underlyingMatcher ); + } + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAllOf() && m1 && m2; + } + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAllOf() && m1 && m2 && m3; + } + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAnyOf() || m1 || m2; + } + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAnyOf() || m1 || m2 || m3; + } + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(std::string()); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder : public DecomposedExpression { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); + ~ResultBuilder(); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream().oss << value; + return *this; + } + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + + void endExpression( DecomposedExpression const& expr ); + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; + + AssertionResult build() const; + AssertionResult build( DecomposedExpression const& expr ) const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::MatcherBase const& matcher ); + void handleResult( AssertionResult const& result ); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + template + void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); + + void setExceptionGuard(); + void unsetExceptionGuard(); + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + + static CopyableStream &m_stream() + { + static CopyableStream s; + return s; + } + + bool m_shouldDebugBreak; + bool m_shouldThrow; + bool m_guardException; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return bool( opCast( lhs ) == opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) != opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) < opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) > opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) >= opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) <= opCast( rhs ) ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( nullptr, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, nullptr ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern const std::string unprintableString; + + #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; +#else + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype( std::declval() << std::declval(), std::true_type() ); + + template + static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; +#endif + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +template +class BinaryExpression; + +template +class MatchExpression; + +// Wraps the LHS of an expression and overloads comparison operators +// for also capturing those and RHS (if any) +template +class ExpressionLhs : public DecomposedExpression { +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} + + ExpressionLhs& operator = ( const ExpressionLhs& ); + + template + BinaryExpression + operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + BinaryExpression operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + BinaryExpression operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + m_truthy = m_lhs ? true : false; + m_rb + .setResultType( m_truthy ) + .endExpression( *this ); + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + dest = Catch::toString( m_lhs ); + } + +private: + template + BinaryExpression captureExpression( RhsT& rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); + } + + template + BinaryExpression captureExpression( bool rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; + bool m_truthy; +}; + +template +class BinaryExpression : public DecomposedExpression { +public: + BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) + : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} + + BinaryExpression& operator = ( BinaryExpression& ); + + void endExpression() const { + m_rb + .setResultType( Internal::compare( m_lhs, m_rhs ) ) + .endExpression( *this ); + } + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string lhs = Catch::toString( m_lhs ); + std::string rhs = Catch::toString( m_rhs ); + char delim = lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ? ' ' : '\n'; + dest.reserve( 7 + lhs.size() + rhs.size() ); + // 2 for spaces around operator + // 2 for operator + // 2 for parentheses (conditionally added later) + // 1 for negation (conditionally added later) + dest = lhs; + dest += delim; + dest += Internal::OperatorTraits::getName(); + dest += delim; + dest += rhs; + } + +private: + ResultBuilder& m_rb; + LhsT m_lhs; + RhsT m_rhs; +}; + +template +class MatchExpression : public DecomposedExpression { +public: + MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) + : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string matcherAsString = m_matcher.toString(); + dest = Catch::toString( m_arg ); + dest += ' '; + if( matcherAsString == Detail::unprintableString ) + dest += m_matcherString; + else + dest += matcherAsString; + } + +private: + ArgT m_arg; + MatcherT m_matcher; + char const* m_matcherString; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + + template + inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, + char const* matcherString ) { + MatchExpression expr( arg, matcher, matcherString ); + setResultType( matcher.match( arg ) ); + endExpression( expr ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void exceptionEarlyReported() = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_IPHONE +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +# define CATCH_PLATFORM_WINDOWS +# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINES_NOMINMAX +# endif +# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# endif +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_TRAP() \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ) + #else + #define CATCH_TRAP() __asm__("int $3\n" : : ) + #endif + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +#if defined(CATCH_CONFIG_FAST_COMPILE) +/////////////////////////////////////////////////////////////////////////////// +// We can speedup compilation significantly by breaking into debugger lower in +// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER +// macro in each assertion +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +// This can potentially cause false negative, if the test code catches +// the exception before it propagates back up to the runner. +#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look +// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +#else +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureExpectedException( matcher ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + try { \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef _MSC_VER + +namespace Catch { + typedef unsigned long long UInt64; +} +#else +#include +namespace Catch { + typedef uint64_t UInt64; +} +#endif + +namespace Catch { + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + UInt64 m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +#include + +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator; + typedef std::vector ExceptionTranslators; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { + try { + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) +#include +#endif + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_margin( other.m_margin ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) + + template ::value>::type> + Approx operator()( T value ) { + Approx approx( static_cast(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template ::value>::type> + explicit Approx( T value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + auto lhs_v = double(lhs); + bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); + if (relativeOK) { + return true; + } + return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin; + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T lhs, Approx const& rhs ) { + return double(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T rhs ) { + return lhs.m_value < double(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T lhs, Approx const& rhs ) { + return double(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T rhs ) { + return lhs.m_value > double(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T newEpsilon ) { + m_epsilon = double(newEpsilon); + return *this; + } + + template ::value>::type> + Approx& margin( T newMargin ) { + m_margin = double(newMargin); + return *this; + } + + template ::value>::type> + Approx& scale( T newScale ) { + m_scale = double(newScale); + return *this; + } + +#else + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); + if (relativeOK) { + return true; + } + return std::fabs(lhs - rhs.m_value) < rhs.m_margin; + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + friend bool operator <= ( double lhs, Approx const& rhs ) { + return lhs < rhs.m_value || lhs == rhs; + } + + friend bool operator <= ( Approx const& lhs, double rhs ) { + return lhs.m_value < rhs || lhs == rhs; + } + + friend bool operator >= ( double lhs, Approx const& rhs ) { + return lhs > rhs.m_value || lhs == rhs; + } + + friend bool operator >= ( Approx const& lhs, double rhs ) { + return lhs.m_value > rhs || lhs == rhs; + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& margin( double newMargin ) { + m_margin = newMargin; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } +#endif + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers_string.h +#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + virtual std::string describe() const CATCH_OVERRIDE; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_matchers_vector.h +#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace Vector { + + template + struct ContainsElementMatcher : MatcherBase, T> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + return std::find(v.begin(), v.end(), m_comparator) != v.end(); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase, std::vector > { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (size_t i = 0; i < m_comparator.size(); ++i) + if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase, std::vector > { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Equals: " + Catch::toString( m_comparator ); + } + std::vector const& m_comparator; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( CATCH_NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = CATCH_NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } + + bool operator !() const { return nullableValue == CATCH_NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T *nullableValue; + union { + char storage[sizeof(T)]; + + // These are here to force alignment for the storage + long double dummy1; + void (*dummy2)(); + long double dummy3; +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + long long dummy4; +#endif + }; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( CATCH_NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + virtual bool match( NSString* arg ) const CATCH_OVERRIDE { + return false; + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const CATCH_OVERRIDE { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL + +// !TBD: Move the leak detector code into a separate header +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include +class LeakDetector { +public: + LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +}; +#else +class LeakDetector {}; +#endif + +LeakDetector leakDetector; + +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_session.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { + if( !(*it)->matches( testCase ) ) + return false; + } + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + for( size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; +} + +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + listExtraInfo( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + filenamesAsTags( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + bool listExtraInfo; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool filenamesAsTags; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; + + std::string outputFilename; + std::string name; + std::string processName; + + std::vector reporterNames; + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() {} + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + bool listExtraInfo() const { return m_data.listExtraInfo; } + + std::string getProcessName() const { return m_data.processName; } + + std::vector const& getReporterNames() const { return m_data.reporterNames; } + std::vector const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } + + virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + + // IConfig interface + virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } + virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } + virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } + virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } + virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } + virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } + virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } + virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } + + private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } + ConfigData m_data; + + CATCH_AUTO_PTR( IStream const ) m_stream; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Version 0.0.2.4 + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + +#include +#include +#include + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +#endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( CLARA_NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != CLARA_NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + inline std::vector argsToVector( int argc, char const* const* const argv ) { + std::vector args( static_cast( argc ) ); + for( std::size_t i = 0; i < static_cast( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector const& args, std::vector& tokens ) { + const std::string doubleDash = "--"; + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); + } + + void parseIntoTokens( std::string const& arg, std::vector& tokens ) { + for( std::size_t i = 0; i < arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); + } + mode = handleMode( arg.size(), '\0', arg, tokens ); + } + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); + } + } + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( std::vector const& args ) const { + ConfigT config; + parseInto( args, config ); + return config; + } + + std::vector parseInto( std::vector const& args, ConfigT& config ) const { + std::string processName = args.empty() ? std::string() : args[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( args, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.set( config, "true" ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + addTestOrTags( config, line + ',' ); + } + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &addReporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes|no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + + cli["-c"]["--section"] + .describe( "specify section to run" ) + .bind( &addSectionToRun, "section name" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-extra-info"] + .describe( "list all/matching test cases with more info" ) + .bind( &ConfigData::listExtraInfo ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + const std::string wrappableBeforeChars = "[({<\t"; + const std::string wrappableAfterChars = "])}>-,./|\\"; + const std::string wrappableInsteadOfChars = " \n\r"; + std::string indent = _attr.initialIndent != std::string::npos + ? std::string( _attr.initialIndent, ' ' ) + : std::string( _attr.indent, ' ' ); + + typedef std::string::const_iterator iterator; + iterator it = _str.begin(); + const iterator strEnd = _str.end(); + + while( it != strEnd ) { + + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + + std::string suffix; + std::size_t width = (std::min)( static_cast( strEnd-it ), _attr.width-static_cast( indent.size() ) ); + iterator itEnd = it+width; + iterator itNext = _str.end(); + + iterator itNewLine = std::find( it, itEnd, '\n' ); + if( itNewLine != itEnd ) + itEnd = itNewLine; + + if( itEnd != strEnd ) { + bool foundWrapPoint = false; + iterator findIt = itEnd; + do { + if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { + itEnd = findIt+1; + itNext = findIt+1; + foundWrapPoint = true; + } + else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { + itEnd = findIt; + itNext = findIt; + foundWrapPoint = true; + } + else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { + itNext = findIt+1; + itEnd = findIt; + foundWrapPoint = true; + } + if( findIt == it ) + break; + else + --findIt; + } + while( !foundWrapPoint ); + + if( !foundWrapPoint ) { + // No good wrap char, so we'll break mid word and add a hyphen + --itEnd; + itNext = itEnd; + suffix = "-"; + } + else { + while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) + --itEnd; + } + } + lines.push_back( indent + std::string( it, itEnd ) + suffix ); + + if( indent.size() != _attr.indent ) + indent = std::string( _attr.indent, ' ' ); + it = itNext; + } + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + class MultipleReporters; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } + }; + + struct IReporterFactory : IShared { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map > FactoryMap; + typedef std::vector > Listeners; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, descAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + descAttr.setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( config.listExtraInfo() ) { + Catch::cout() << " " << testCaseInfo.lineInfo << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Text( description, descAttr ) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.listExtraInfo() ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ':' + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << '\n'; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_run_context.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include +#include +#include + +CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + }; + + struct ITracker : SharedImpl<> { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr const& child ) = 0; + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + Ptr m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) + {} + + ITracker& startRun(); + + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; + } + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& currentTracker() { + return *m_currentTracker; + } + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool operator ()( Ptr const& tracker ) { + return + tracker->nameAndLocation().name == m_nameAndLocation.name && + tracker->nameAndLocation().location == m_nameAndLocation.location; + } + }; + typedef std::vector > Children; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { + return m_nameAndLocation; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { + return !m_children.empty(); + } + + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } + + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); + } + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } + private: + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentTracker( this ); + } + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + virtual ~SectionTracker(); + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); + } + else { + section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); + + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast( childTracker ); + } + else { + tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition + inline void reportFatal( std::string const& message ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +// #included from: catch_windows_h_proxy.h + +#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED + +#ifdef CATCH_DEFINES_NOMINMAX +# define NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINES_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + + +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + extern SignalDefs signalDefs[]; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = CATCH_NULL; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if (isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = CATCH_NULL; + isSet = false; + } + } + + ~FatalConditionHandler() { + reset(); + } + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + +} // namespace Catch + +# endif // CATCH_CONFIG_WINDOWS_SEH + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_POSIX_SIGNALS is defined + +#include + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[SIGSTKSZ]; + + static void handleSignal( int sig ) { + std::string name = ""; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + SignalDefs &def = signalDefs[i]; + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { 0 }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { + reset(); + } + static void reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); + } + // Return the old stack + sigaltstack(&oldSigStack, CATCH_NULL); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + +} // namespace Catch + +# endif // CATCH_CONFIG_POSIX_SIGNALS + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), + m_reporter( reporter ), + m_shouldReportUnexpected ( true ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + + do { + ITracker& rootTracker = m_trackerContext.startRun(); + assert( rootTracker.isSectionTracker() ); + static_cast( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); + } + // !TBD: deprecated - this will be replaced by indexed trackers + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); + if( !sectionTracker.isOpen() ) + return false; + m_activeSections.push_back( §ionTracker ); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult; + tempResult.resultType = ResultWas::FatalErrorCondition; + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + getResultCapture().assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + std::string(), + std::string(), + false ) ); + m_totals.testCases.failed++; + testGroupEnded( std::string(), m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + + seedRng( *m_config ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + makeUnexpectedResultBuilder().useActiveException(); + } + } + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName, + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression, + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( *it ); + m_unfinishedSections.clear(); + } + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_shouldReportUnexpected; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + inline Version libraryVersion(); +} + +#include +#include +#include + +namespace Catch { + + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } + + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); + + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr const& config ) { + + Ptr iconfig = config.get(); + + Ptr reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); + } + + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } + + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; + + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); + + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); + + tags.insert( "#" + filename ); + setTags( test, tags ); + } + } + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char const* const* const argv ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + #if defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t const* const* const argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + if( returnCode == 0 ) + returnCode = run(); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } + #endif + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runTests( m_config ).assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + struct RandomNumberGenerator { + typedef std::ptrdiff_t result_type; + + result_type operator()( result_type n ) const { return std::rand() % n; } + +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return 1000000; } + result_type operator()() const { return std::rand() % max(); } +#endif + template + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } + }; + + inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + RandomNumberGenerator::shuffle( sorted ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); + } + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} + virtual ~TestRegistry(); + + virtual void registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + std::ostringstream oss; + oss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( oss.str() ) ); + } + m_functions.push_back( testCase ); + } + + virtual std::vector const& getAllTests() const { + return m_functions; + } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; + size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); + } + + AutoReg::~AutoReg() {} + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() CATCH_OVERRIDE {} + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return CATCH_NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, Ptr const& factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } + + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { + return m_factories; + } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } + + private: + std::vector m_translators; + }; +} + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { + return m_exceptionTranslatorRegistry; + } + virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { + return m_tagAliasRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = CATCH_NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = CATCH_NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << '\''; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + std::ostream& DebugOutStream::stream() const { + return m_os; + } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: + virtual ~Context() { + deleteAllValues( m_generatorsByTestName ); + } + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : CATCH_NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = CATCH_NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + void cleanUpContext() { + delete currentContext; + currentContext = CATCH_NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +// #included from: catch_errno_guard.hpp +#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED + +#include + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard():m_oldErrno(errno){} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + +} + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( char const * _macroName, + SourceLineInfo const& _lineInfo, + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ), + secondArg( _secondArg ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) { + return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"') + ? capturedExpression + : std::string(capturedExpression) + ", " + secondArg; + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + else + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName[0] == 0 ) + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + else + return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructExpression(); + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + + void AssertionResult::discardDecomposedExpression() const { + m_resultData.decomposedExpression = CATCH_NULL; + } + + void AssertionResult::expandDecomposedExpression() const { + m_resultData.reconstructExpression(); + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +#include + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + std::ostringstream ss; + ss << Colour(Colour::Red) + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << Colour(Colour::FileName) + << _lineInfo << '\n'; + throw std::runtime_error(ss.str()); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << '[' << *it << ']'; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + inline Version libraryVersion() { + static Version version( 1, 9, 6, "", 0 ); + return version; + } + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + if ( !std::uncaught_exception() ){ + getResultCapture().popScopedMessage(m_info); + } + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS + +#else + +#include + +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + UInt64 getCurrentTicks() { + static UInt64 hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + UInt64 t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + UInt64 getCurrentTicks() { + timeval t; + gettimeofday(&t,CATCH_NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + + SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + bool SourceLineInfo::empty() const { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << '\''; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 +#endif + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return '"' + s + '"'; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + 'f'; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + if ( value == '\r' ) + return "'\\r'"; + if ( value == '\f' ) + return "'\\f'"; + if ( value == '\n' ) + return "'\\n'"; + if ( value == '\t' ) + return "'\\t'"; + if ( '\0' <= value && value < ' ' ) + return toString( static_cast( value ) ); + char chstr[] = "' '"; + chstr[1] = value; + return chstr; +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ), + m_guardException( false ) + { + m_stream().oss.str(""); + } + + ResultBuilder::~ResultBuilder() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if ( m_guardException ) { + m_stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + captureResult( ResultWas::ThrewException ); + getCurrentContext().getResultCapture()->exceptionEarlyReported(); + } +#endif + } + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + + void ResultBuilder::endExpression( DecomposedExpression const& expr ) { + AssertionResult result = build( expr ); + handleResult( result ); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream().oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::MatchAllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase const& matcher ) { + + assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + handleResult( result ); + } + + void ResultBuilder::handleResult( AssertionResult const& result ) + { + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + + void ResultBuilder::react() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if (m_shouldDebugBreak) { + /////////////////////////////////////////////////////////////////// + // To inspect the state during test, you need to go one level up the callstack + // To go back to the test and change execution, jump over the throw statement + /////////////////////////////////////////////////////////////////// + CATCH_BREAK_INTO_DEBUGGER(); + } +#endif + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + return build( *this ); + } + + // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, + // a temporary DecomposedExpression, which in turn holds references to + // operands, possibly temporary as well. + // It should immediately be passed to handleResult; if the expression + // needs to be reported, its string expansion must be composed before + // the temporaries are destroyed. + AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const + { + assert( m_data.resultType != ResultWas::Unknown ); + AssertionResultData data = m_data; + + // Flip bool results if FalseTest flag is set + if( isFalseTest( m_assertionInfo.resultDisposition ) ) { + data.negate( expr.isBinaryExpression() ); + } + + data.message = m_stream().oss.str(); + data.decomposedExpression = &expr; // for lazy reconstruction + return AssertionResult( m_assertionInfo, data ); + } + + void ResultBuilder::reconstructExpression( std::string& dest ) const { + dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + } + + void ResultBuilder::setExceptionGuard() { + m_guardException = true; + } + void ResultBuilder::unsetExceptionGuard() { + m_guardException = false; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" + << Colour( Colour::FileName ) + << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " + << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' + << Colour( Colour::Red ) << "\tRedefined at " + << Colour( Colour::FileName) << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); + } + +} // end namespace Catch + +// #included from: catch_matchers_string.hpp + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = existingReporter->tryAsMulti(); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + namespace { + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + } + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual ~StreamingReporterBase() CATCH_OVERRIDE; + + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + ~CumulativeReporterBase(); + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} + + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void prepareExpandedExpression( AssertionResult& result ) const { + if( result.isOk() ) + result.discardDecomposedExpression(); + else + result.expandDecomposedExpression(); + } + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public SharedImpl { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// Deprecated - use the form without INTERNAL_ +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 + if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast( c ); + } + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = CATCH_NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( Catch::cout() ) + { + writeDeclaration(); + } + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( os ) + { + writeDeclaration(); + } + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + void writeDeclaration() { + m_os << "\n"; + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()), + m_sectionDepth( 0 ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~XmlReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + virtual std::string getStylesheetRef() const { + return std::string(); + } + + void writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + public: // StreamingReporterBase + + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults ) { + // Print any info messages in tags. + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + } + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ), + m_okToFail( false ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~JunitReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { + m_okToFail = testCaseInfo.okToFail(); + } + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() CATCH_OVERRIDE { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << '\n'; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << '\n'; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + bool m_okToFail; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter() CATCH_OVERRIDE; + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, includeResults ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_config->showDurations() == ShowDurations::Always ) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if( m_headerPrinted ) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << '\n'; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ':' << '\n'; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << '\n'; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << '\n'; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = ' ' + *it; + while( it->size() > row.size() ) + row = ' ' + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ')' + << '\n'; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << ' ' << it->label; + } + } + stream << '\n'; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << '\n'; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ':'; + } + + void printResultType( Colour::Code colour, std::string const& passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue( std::string const& issue ) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ';'; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ':'; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << '\''; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? std::string() : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : std::string(); + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} + + void Config::dummy() {} + + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + int result = Catch::Session().run( argc, argv ); + return ( result < 0xff ? result : 0xff ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return ( result < 0xff ? result : 0xff ); +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#else +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) + +#else +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else +#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/src/TinyJS/TinyJS.cpp b/src/TinyJS/TinyJS.cpp new file mode 100755 index 0000000..cc2af89 --- /dev/null +++ b/src/TinyJS/TinyJS.cpp @@ -0,0 +1,6556 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef _DEBUG +# ifndef _MSC_VER +# define DEBUG_MEMORY 1 +# endif +#endif +#include +#include +#include + +#include "TinyJS/TinyJS.h" + +#ifndef ASSERT +# define ASSERT(X) assert(X) +#endif + +#ifndef NO_REGEXP +# if defined HAVE_TR1_REGEX +# include + using namespace std::tr1; +# elif defined HAVE_BOOST_REGEX +# include + using namespace boost; +# else +# include +# endif +#else +# include +# include +# include +#endif + +using namespace std; + +// ----------------------------------------------------------------------------------- +////////////////////////////////////////////////////////////////////////// +/// Memory Debug +////////////////////////////////////////////////////////////////////////// + +//#define DEBUG_MEMORY 1 + +#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;igetName().c_str(), allocatedLinks[i]->getVarPtr()->getRefs()); + allocatedLinks[i]->getVarPtr()->trace(" "); + } + allocatedVars.clear(); + allocatedLinks.clear(); +} +#endif + + +////////////////////////////////////////////////////////////////////////// +/// Utils +////////////////////////////////////////////////////////////////////////// + +inline bool isWhitespace(char ch) { + return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'); +} + +inline bool isNumeric(char ch) { + return (ch>='0') && (ch<='9'); +} +uint32_t isArrayIndex(const string &str) { + if(str.size()==0 || !isNumeric(str[0]) || (str.size()>1 && str[0]=='0') ) return -1; // empty or (more as 1 digit and beginning with '0') + CNumber idx; + const char *endptr; + idx.parseInt(str.c_str(), 10, &endptr); + if(*endptr || idx>uint32_t(0xFFFFFFFFUL)) return -1; + return idx.toUInt32(); +} +inline bool isHexadecimal(char ch) { + return ((ch>='0') && (ch<='9')) || ((ch>='a') && (ch<='f')) || ((ch>='A') && (ch<='F')); +} +inline bool isOctal(char ch) { + return ((ch>='0') && (ch<='7')); +} +inline bool isAlpha(char ch) { + return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_' || ch=='$'; +} + +bool isIDString(const char *s) { + if (!isAlpha(*s)) + return false; + while (*s) { + if (!(isAlpha(*s) || isNumeric(*s))) + return false; + s++; + } + return true; +} + +void replace(string &str, char textFrom, const char *textTo) { + int sLen = strlen(textTo); + size_t p = str.find(textFrom); + while (p != string::npos) { + str = str.substr(0, p) + textTo + str.substr(p+1); + p = str.find(textFrom, p+sLen); + } +} +string int2string(int32_t intData) { + ostringstream str; + str << intData; + return str.str(); +} +string int2string(uint32_t intData) { + ostringstream str; + str << intData; + return str.str(); +} +string float2string(const double &floatData) { + ostringstream str; + str.unsetf(ios::floatfield); +#if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L + str.precision(numeric_limits::max_digits10); +#else + str.precision(numeric_limits::digits10+2); +#endif + str << floatData; + return str.str(); +} +/// convert the given string into a quoted string suitable for javascript +string getJSString(const string &str) { + char buffer[5] = "\\x00"; + string nStr; nStr.reserve(str.length()); + nStr.push_back('\"'); + for (string::const_iterator i=str.begin();i!=str.end();i++) { + const char *replaceWith = 0; + switch (*i) { + case '\\': replaceWith = "\\\\"; break; + case '\n': replaceWith = "\\n"; break; + case '\r': replaceWith = "\\r"; break; + case '\a': replaceWith = "\\a"; break; + case '\b': replaceWith = "\\b"; break; + case '\f': replaceWith = "\\f"; break; + case '\t': replaceWith = "\\t"; break; + case '\v': replaceWith = "\\v"; break; + case '"': replaceWith = "\\\""; break; + default: { + int nCh = ((unsigned char)*i) & 0xff; + if(nCh<32 || nCh>127) { + static char hex[] = "0123456789ABCDEF"; + buffer[2] = hex[(nCh>>4)&0x0f]; + buffer[3] = hex[nCh&0x0f]; + replaceWith = buffer; + }; + } + } + if (replaceWith) + nStr.append(replaceWith); + else + nStr.push_back(*i); + } + nStr.push_back('\"'); + return nStr; +} + +static inline string getIDString(const string& str) { + if(isIDString(str.c_str()) && CScriptToken::isReservedWord(str)==LEX_ID) + return str; + return getJSString(str); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptException +////////////////////////////////////////////////////////////////////////// + +string CScriptException::toString() { + ostringstream msg; + msg << ERROR_NAME[errorType] << ": " << message; + if(lineNumber >= 0) msg << " at Line:" << lineNumber+1; + if(column >=0) msg << " Column:" << column+1; + if(fileName.length()) msg << " in " << fileName; + return msg.str(); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptLex +////////////////////////////////////////////////////////////////////////// + +CScriptLex::CScriptLex(const char *Code, const string &File, int Line, int Column) : data(Code) { + currentFile = File; + pos.currentLineStart = pos.tokenStart = data; + pos.currentLine = Line; + reset(pos); +} + +void CScriptLex::reset(const POS &toPos) { ///< Reset this lex so we can start again + dataPos = toPos.tokenStart; + tk = last_tk = 0; + tkStr = ""; + pos = toPos; + lineBreakBeforeToken = false; + currCh = nextCh = 0; + getNextCh(); // currCh + getNextCh(); // nextCh + getNextToken(); +} + +void CScriptLex::check(int expected_tk, int alternate_tk/*=-1*/) { + if (expected_tk==';' && tk==LEX_EOF) return; // ignore last missing ';' + if (tk!=expected_tk && tk!=alternate_tk) { + ostringstream errorString; + if(expected_tk == LEX_EOF) + errorString << "Got unexpected " << CScriptToken::getTokenStr(tk); + else + errorString << "Got '" << CScriptToken::getTokenStr(tk) << "' expected '" << CScriptToken::getTokenStr(expected_tk) << "'"; + if(alternate_tk!=-1) errorString << " or '" << CScriptToken::getTokenStr(alternate_tk) << "'"; + throw new CScriptException(SyntaxError, errorString.str(), currentFile, pos.currentLine, currentColumn()); + } +} +void CScriptLex::match(int expected_tk1, int alternate_tk/*=-1*/) { + check(expected_tk1, alternate_tk); + int line = pos.currentLine; + getNextToken(); + lineBreakBeforeToken = line != pos.currentLine; +} + +void CScriptLex::getNextCh() { + if(currCh == '\n') { // Windows or Linux + pos.currentLine++; + pos.tokenStart = pos.currentLineStart = dataPos - (nextCh == LEX_EOF ? 0 : 1); + } + currCh = nextCh; + if ( (nextCh = *dataPos) != LEX_EOF ) dataPos++; // stay on EOF +// if(nextCh == -124) nextCh = '\n'; // + if(currCh == '\r') { // Windows or Mac + if(nextCh == '\n') + getNextCh(); // Windows '\r\n\' --> skip '\r' + else + currCh = '\n'; // Mac (only '\r') --> convert '\r' to '\n' + } +} + +static uint16_t not_allowed_tokens_befor_regexp[] = {LEX_ID, LEX_INT, LEX_FLOAT, LEX_STR, LEX_R_TRUE, LEX_R_FALSE, LEX_R_NULL, ']', ')', '.', LEX_PLUSPLUS, LEX_MINUSMINUS, LEX_EOF}; +void CScriptLex::getNextToken() { + while (currCh && isWhitespace(currCh)) getNextCh(); + // newline comments + if (currCh=='/' && nextCh=='/') { + while (currCh && currCh!='\n') getNextCh(); + getNextCh(); + getNextToken(); + return; + } + // block comments + if (currCh=='/' && nextCh=='*') { + while (currCh && (currCh!='*' || nextCh!='/')) getNextCh(); + getNextCh(); + getNextCh(); + getNextToken(); + return; + } + last_tk = tk; + tk = LEX_EOF; + tkStr.clear(); + // record beginning of this token + pos.tokenStart = dataPos - (nextCh == LEX_EOF ? (currCh == LEX_EOF ? 0 : 1) : 2); + // tokens + if (isAlpha(currCh)) { // IDs + while (isAlpha(currCh) || isNumeric(currCh)) { + tkStr += currCh; + getNextCh(); + } + tk = CScriptToken::isReservedWord(tkStr); +#ifdef NO_GENERATORS + if(tk == LEX_R_YIELD) + throw new CScriptException(Error, "42TinyJS was built without support of generators (yield expression)", currentFile, pos.currentLine, currentColumn()); +#endif + + } else if (isNumeric(currCh) || (currCh=='.' && isNumeric(nextCh))) { // Numbers + if(currCh=='.') tkStr+='0'; + bool isHex = false, isOct=false; + if (currCh=='0') { + tkStr += currCh; getNextCh(); + if(isOctal(currCh)) isOct = true; + } + if (currCh=='x' || currCh=='X') { + isHex = true; + tkStr += currCh; getNextCh(); + } + tk = LEX_INT; + while (isOctal(currCh) || (!isOct && isNumeric(currCh)) || (isHex && isHexadecimal(currCh))) { + tkStr += currCh; + getNextCh(); + } + if (!isHex && !isOct && currCh=='.') { + tk = LEX_FLOAT; + tkStr += '.'; + getNextCh(); + while (isNumeric(currCh)) { + tkStr += currCh; + getNextCh(); + } + } + // do fancy e-style floating point + if (!isHex && !isOct && (currCh=='e' || currCh=='E')) { + tk = LEX_FLOAT; + tkStr += currCh; getNextCh(); + if (currCh=='-') { tkStr += currCh; getNextCh(); } + while (isNumeric(currCh)) { + tkStr += currCh; getNextCh(); + } + } + } else if (currCh=='"' || currCh=='\'') { // strings... + char endCh = currCh; + getNextCh(); + while (currCh && currCh!=endCh && currCh!='\n') { + if (currCh == '\\') { + getNextCh(); + switch (currCh) { + case '\n' : break; // ignore newline after '\' + case 'n': tkStr += '\n'; break; + case 'r': tkStr += '\r'; break; + case 'a': tkStr += '\a'; break; + case 'b': tkStr += '\b'; break; + case 'f': tkStr += '\f'; break; + case 't': tkStr += '\t'; break; + case 'v': tkStr += '\v'; break; + case 'x': { // hex digits + getNextCh(); + if(isHexadecimal(currCh)) { + char buf[3]="\0\0"; + buf[0] = currCh; + for(int i=0; i<2 && isHexadecimal(nextCh); i++) { + getNextCh(); buf[i] = currCh; + } + tkStr += (char)strtol(buf, 0, 16); + } else + throw new CScriptException(SyntaxError, "malformed hexadezimal character escape sequence", currentFile, pos.currentLine, currentColumn()); + } + default: { + if(isOctal(currCh)) { + char buf[4]="\0\0\0"; + buf[0] = currCh; + for(int i=1; i<3 && isOctal(nextCh); i++) { + getNextCh(); buf[i] = currCh; + } + tkStr += (char)strtol(buf, 0, 8); + } + else tkStr += currCh; + } + } + } else { + tkStr += currCh; + } + getNextCh(); + } + if(currCh != endCh) + throw new CScriptException(SyntaxError, "unterminated string literal", currentFile, pos.currentLine, currentColumn()); + getNextCh(); + tk = LEX_STR; + } else { + // single chars + tk = currCh; + if (currCh) getNextCh(); + if (tk=='=' && currCh=='=') { // == + tk = LEX_EQUAL; + getNextCh(); + if (currCh=='=') { // === + tk = LEX_TYPEEQUAL; + getNextCh(); + } + } else if (tk=='!' && currCh=='=') { // != + tk = LEX_NEQUAL; + getNextCh(); + if (currCh=='=') { // !== + tk = LEX_NTYPEEQUAL; + getNextCh(); + } + } else if (tk=='<') { + if (currCh=='=') { // <= + tk = LEX_LEQUAL; + getNextCh(); + } else if (currCh=='<') { // << + tk = LEX_LSHIFT; + getNextCh(); + if (currCh=='=') { // <<= + tk = LEX_LSHIFTEQUAL; + getNextCh(); + } + } + } else if (tk=='>') { + if (currCh=='=') { // >= + tk = LEX_GEQUAL; + getNextCh(); + } else if (currCh=='>') { // >> + tk = LEX_RSHIFT; + getNextCh(); + if (currCh=='=') { // >>= + tk = LEX_RSHIFTEQUAL; + getNextCh(); + } else if (currCh=='>') { // >>> + tk = LEX_RSHIFTU; + getNextCh(); + if (currCh=='=') { // >>>= + tk = LEX_RSHIFTUEQUAL; + getNextCh(); + } + } + } + } else if (tk=='+') { + if (currCh=='=') { // += + tk = LEX_PLUSEQUAL; + getNextCh(); + } else if (currCh=='+') { // ++ + tk = LEX_PLUSPLUS; + getNextCh(); + } + } else if (tk=='-') { + if (currCh=='=') { // -= + tk = LEX_MINUSEQUAL; + getNextCh(); + } else if (currCh=='-') { // -- + tk = LEX_MINUSMINUS; + getNextCh(); + } + } else if (tk=='&') { + if (currCh=='=') { // &= + tk = LEX_ANDEQUAL; + getNextCh(); + } else if (currCh=='&') { // && + tk = LEX_ANDAND; + getNextCh(); + } + } else if (tk=='|') { + if (currCh=='=') { // |= + tk = LEX_OREQUAL; + getNextCh(); + } else if (currCh=='|') { // || + tk = LEX_OROR; + getNextCh(); + } + } else if (tk=='^' && currCh=='=') { + tk = LEX_XOREQUAL; + getNextCh(); + } else if (tk=='*' && currCh=='=') { + tk = LEX_ASTERISKEQUAL; + getNextCh(); + } else if (tk=='/') { + // check if it's a RegExp-Literal + tk = LEX_REGEXP; + for(uint16_t *p = not_allowed_tokens_befor_regexp; *p; p++) { + if(*p==last_tk) { tk = '/'; break; } + } + if(tk == LEX_REGEXP) { +#ifdef NO_REGEXP + throw new CScriptException(Error, "42TinyJS was built without support of regular expressions", currentFile, pos.currentLine, currentColumn()); +#endif + tkStr = "/"; + while (currCh && currCh!='/' && currCh!='\n') { + if (currCh == '\\' && nextCh == '/') { + tkStr.append(1, currCh); + getNextCh(); + } + tkStr.append(1, currCh); + getNextCh(); + } + if(currCh == '/') { +#ifndef NO_REGEXP + try { regex(tkStr.substr(1), regex_constants::ECMAScript); } catch(regex_error e) { + throw new CScriptException(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code()), currentFile, pos.currentLine, currentColumn()); + } +#endif /* NO_REGEXP */ + do { + tkStr.append(1, currCh); + getNextCh(); + } while (currCh=='g' || currCh=='i' || currCh=='m' || currCh=='y'); + } else + throw new CScriptException(SyntaxError, "unterminated regular expression literal", currentFile, pos.currentLine, currentColumn()); + } else if(currCh=='=') { + tk = LEX_SLASHEQUAL; + getNextCh(); + } + } else if (tk=='%' && currCh=='=') { + tk = LEX_PERCENTEQUAL; + getNextCh(); + } + } + /* This isn't quite right yet */ +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataForwards +////////////////////////////////////////////////////////////////////////// + +bool CScriptTokenDataForwards::compare_fnc_token_by_name::operator()(const CScriptToken& lhs, const CScriptToken& rhs) const { + return lhs.Fnc().name < rhs.Fnc().name; +} +bool CScriptTokenDataForwards::checkRedefinition(const string &Str, bool checkVarsInLetScope) { + STRING_SET_it it = varNames[LETS].find(Str); + if(it!=varNames[LETS].end()) return false; + else if(checkVarsInLetScope) { + STRING_SET_it it = vars_in_letscope.find(Str); + if(it!=vars_in_letscope.end()) return false; + } + return true; +} + +void CScriptTokenDataForwards::addVars( STRING_VECTOR_t &Vars ) { + varNames[VARS].insert(Vars.begin(), Vars.end()); +} +void CScriptTokenDataForwards::addConsts( STRING_VECTOR_t &Vars ) { + varNames[CONSTS].insert(Vars.begin(), Vars.end()); +} +std::string CScriptTokenDataForwards::addVarsInLetscope( STRING_VECTOR_t &Vars ) +{ + for(STRING_VECTOR_it it=Vars.begin(); it!=Vars.end(); ++it) { + if(!checkRedefinition(*it, false)) return *it; + vars_in_letscope.insert(*it); + } + return ""; +} + +std::string CScriptTokenDataForwards::addLets( STRING_VECTOR_t &Lets ) +{ + for(STRING_VECTOR_it it=Lets.begin(); it!=Lets.end(); ++it) { + if(!checkRedefinition(*it, true)) return *it; + varNames[LETS].insert(*it); + } + return ""; +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataLoop +////////////////////////////////////////////////////////////////////////// + +std::string CScriptTokenDataLoop::getParsableString(const string &IndentString/*=""*/, const string &Indent/*=""*/ ) { + static const char *heads[] = {"for each(", "for(", "for(", "for(", "while(", "do "}; + static const char *ops[] = {" in ", " in ", " of ", "; "}; + string out = heads[type]; + if(init.size() && type==FOR)out.append(CScriptToken::getParsableString(init)); + if(type<=WHILE) out.append(CScriptToken::getParsableString(condition.begin(), condition.end()-(type>=FOR ? 0 : 7))); + if(type<=FOR) out.append(ops[type]); + if(iter.size()) out.append(CScriptToken::getParsableString(iter)); + out.append(")"); + out.append(CScriptToken::getParsableString(body)); + if(type==DO)out.append(" while(").append(CScriptToken::getParsableString(condition)).append(");"); + return out; +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataTry +////////////////////////////////////////////////////////////////////////// + +std::string CScriptTokenDataTry::getParsableString( const string &IndentString/*=""*/, const string &Indent/*=""*/ ) { + string out = "try "; + string nl = Indent.size() ? "\n"+IndentString : " "; + + out.append(CScriptToken::getParsableString(tryBlock, IndentString, Indent)); + for(CScriptTokenDataTry::CatchBlock_it catchBlock = catchBlocks.begin(); catchBlock!=catchBlocks.end(); catchBlock++) { + out.append(nl).append("catch(").append(catchBlock->indentifiers->getParsableString()); + if(catchBlock->condition.size()>1) { + out.append(" if ").append(CScriptToken::getParsableString(catchBlock->condition.begin()+1, catchBlock->condition.end())); + } + out.append(") ").append(CScriptToken::getParsableString(catchBlock->block, IndentString, Indent)); + } + if(finallyBlock.size()) + out.append(nl).append("finally ").append(CScriptToken::getParsableString(finallyBlock, IndentString, Indent)); + return out; +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataFnc +////////////////////////////////////////////////////////////////////////// + +string CScriptTokenDataFnc::getArgumentsString() +{ + ostringstream destination; + destination << "("; + if(arguments.size()) { + const char *comma = ""; + for(TOKEN_VECT_it argument=arguments.begin(); argument!=arguments.end(); ++argument, comma=", ") { + if(argument->token == LEX_ID) + destination << comma << argument->String(); + else { + vector isObject(1, false); + for(DESTRUCTURING_VARS_it it=argument->DestructuringVar().vars.begin(); it!=argument->DestructuringVar().vars.end(); ++it) { + if(it->second == "}" || it->second == "]") { + destination << it->second; + isObject.pop_back(); + } else { + destination << comma; + if(it->second == "[" || it->second == "{") { + comma = ""; + if(isObject.back() && it->first.length()) + destination << getIDString(it->first) << ":"; + destination << it->second; + isObject.push_back(it->second == "{"); + } else { + comma = ", "; + if(it->second.empty()) + continue; // skip empty entries + if(isObject.back() && it->first!=it->second) + destination << getIDString(it->first) << ":"; + destination << it->second; + } + } + } + } + } + } + destination << ") "; + return destination.str(); +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataDestructuringVar +////////////////////////////////////////////////////////////////////////// + +void CScriptTokenDataDestructuringVar::getVarNames(STRING_VECTOR_t Names) { + for(DESTRUCTURING_VARS_it it = vars.begin(); it != vars.end(); ++it) { + if(it->second.size() && it->second.find_first_of("{[]}") == string::npos) + Names.push_back(it->second); + } +} + +std::string CScriptTokenDataDestructuringVar::getParsableString() +{ + string out; + const char *comma = ""; + vector isObject(1, false); + for(DESTRUCTURING_VARS_it it=vars.begin(); it!=vars.end(); ++it) { + if(it->second == "}" || it->second == "]") { + out.append(it->second); + isObject.pop_back(); + } else { + out.append(comma); + if(it->second == "[" || it->second == "{") { + comma = ""; + if(isObject.back() && it->first.length()) + out.append(getIDString(it->first)).append(":"); + out.append(it->second); + isObject.push_back(it->second == "{"); + } else { + comma = ", "; + if(it->second.empty()) + continue; // skip empty entries + if(isObject.back() && it->first!=it->second) + out.append(getIDString(it->first)).append(":"); + out.append(it->second); + } + } + } + return out; +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptTokenDataObjectLiteral +////////////////////////////////////////////////////////////////////////// + +void CScriptTokenDataObjectLiteral::setMode(bool Destructuring) { + structuring = !(destructuring = Destructuring); + for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) { + if(it->value.size() && it->value.front().token == LEX_T_OBJECT_LITERAL) { + CScriptTokenDataObjectLiteral& e = it->value.front().Object(); + if(e.destructuring && e.structuring) + e.setMode(Destructuring); + } + } +} + +string CScriptTokenDataObjectLiteral::getParsableString() +{ + string out = type == OBJECT ? "{ " : "[ "; + const char *comma = ""; + for(vector::iterator it=elements.begin(); it!=elements.end(); ++it) { + out.append(comma); comma=", "; + if(it->value.empty()) continue; + if(type == OBJECT) + out.append(getIDString(it->id)).append(" : "); + out.append(CScriptToken::getParsableString(it->value)); + } + out.append(type == OBJECT ? " }" : " ]"); + return out; +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptToken +////////////////////////////////////////////////////////////////////////// + +typedef struct { int id; const char *str; bool need_space; } token2str_t; +static token2str_t reserved_words_begin[] ={ + // reserved words + { LEX_R_IF, "if", true }, + { LEX_R_ELSE, "else", true }, + { LEX_R_DO, "do", true }, + { LEX_R_WHILE, "while", true }, + { LEX_R_FOR, "for", true }, + { LEX_R_IN, "in", true }, + { LEX_R_BREAK, "break", true }, + { LEX_R_CONTINUE, "continue", true }, + { LEX_R_FUNCTION, "function", true }, + { LEX_R_RETURN, "return", true }, + { LEX_R_VAR, "var", true }, + { LEX_R_LET, "let", true }, + { LEX_R_CONST, "const", true }, + { LEX_R_WITH, "with", true }, + { LEX_R_TRUE, "true", true }, + { LEX_R_FALSE, "false", true }, + { LEX_R_NULL, "null", true }, + { LEX_R_NEW, "new", true }, + { LEX_R_TRY, "try", true }, + { LEX_R_CATCH, "catch", true }, + { LEX_R_FINALLY, "finally", true }, + { LEX_R_THROW, "throw", true }, + { LEX_R_TYPEOF, "typeof", true }, + { LEX_R_VOID, "void", true }, + { LEX_R_DELETE, "delete", true }, + { LEX_R_INSTANCEOF, "instanceof", true }, + { LEX_R_SWITCH, "switch", true }, + { LEX_R_CASE, "case", true }, + { LEX_R_DEFAULT, "default", true }, + { LEX_R_YIELD, "yield", true }, +}; +#define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0])) +#define ARRAY_END(array) (&array[ARRAY_LENGTH(array)]) +static token2str_t *reserved_words_end = ARRAY_END(reserved_words_begin);//&reserved_words_begin[ARRAY_LENGTH(reserved_words_begin)]; +static token2str_t *str2reserved_begin[sizeof(reserved_words_begin)/sizeof(reserved_words_begin[0])]; +static token2str_t **str2reserved_end = &str2reserved_begin[sizeof(str2reserved_begin)/sizeof(str2reserved_begin[0])]; +static token2str_t tokens2str_begin[] = { + { LEX_EOF, "EOF", false }, + { LEX_ID, "ID", true }, + { LEX_INT, "INT", true }, + { LEX_FLOAT, "FLOAT", true }, + { LEX_STR, "STRING", true }, + { LEX_REGEXP, "REGEXP", true }, + { LEX_EQUAL, "==", false }, + { LEX_TYPEEQUAL, "===", false }, + { LEX_NEQUAL, "!=", false }, + { LEX_NTYPEEQUAL, "!==", false }, + { LEX_LEQUAL, "<=", false }, + { LEX_LSHIFT, "<<", false }, + { LEX_LSHIFTEQUAL, "<<=", false }, + { LEX_GEQUAL, ">=", false }, + { LEX_RSHIFT, ">>", false }, + { LEX_RSHIFTEQUAL, ">>=", false }, + { LEX_RSHIFTU, ">>>", false }, + { LEX_RSHIFTUEQUAL, ">>>=", false }, + { LEX_PLUSEQUAL, "+=", false }, + { LEX_MINUSEQUAL, "-=", false }, + { LEX_PLUSPLUS, "++", false }, + { LEX_MINUSMINUS, "--", false }, + { LEX_ANDEQUAL, "&=", false }, + { LEX_ANDAND, "&&", false }, + { LEX_OREQUAL, "|=", false }, + { LEX_OROR, "||", false }, + { LEX_XOREQUAL, "^=", false }, + { LEX_ASTERISKEQUAL, "*=", false }, + { LEX_SLASHEQUAL, "/=", false }, + { LEX_PERCENTEQUAL, "%=", false }, + // special tokens + { LEX_T_OF, "of", true }, + { LEX_T_FUNCTION_OPERATOR, "function", true }, + { LEX_T_GET, "get", true }, + { LEX_T_SET, "set", true }, + { LEX_T_EXCEPTION_VAR, "LEX_T_EXCEPTION_VAR", false }, + { LEX_T_SKIP, "LEX_SKIP", false }, + { LEX_T_DUMMY_LABEL, "LABEL", true }, + { LEX_T_LABEL, "LABEL", true }, + { LEX_T_LOOP, "LEX_LOOP", true }, + { LEX_T_FOR_IN, "LEX_FOR_IN", true }, + { LEX_T_FORWARD, "LEX_T_FORWARD", false }, + { LEX_T_OBJECT_LITERAL, "LEX_OBJECT_LITERAL", false }, + { LEX_T_DESTRUCTURING_VAR, "Destructuring Var", false }, +}; +static token2str_t *tokens2str_end = &tokens2str_begin[sizeof(tokens2str_begin)/sizeof(tokens2str_begin[0])]; +struct token2str_cmp_t { + bool operator()(const token2str_t &lhs, const token2str_t &rhs) { + return lhs.id < rhs.id; + } + bool operator()(const token2str_t &lhs, int rhs) { + return lhs.id < rhs; + } + bool operator()(const token2str_t *lhs, const token2str_t *rhs) { + return strcmp(lhs->str, rhs->str)<0; + } + bool operator()(const token2str_t *lhs, const char *rhs) { + return strcmp(lhs->str, rhs)<0; + } +}; +static bool tokens2str_sort() { +// printf("tokens2str_sort called\n"); + sort(tokens2str_begin, tokens2str_end, token2str_cmp_t()); + sort(reserved_words_begin, reserved_words_end, token2str_cmp_t()); + for(unsigned int i=0; icurrentLine()), column(l->currentColumn()), token(l->tk), intData(0) +{ + if(token == LEX_INT || LEX_TOKEN_DATA_FLOAT(token)) { + CNumber number(l->tkStr); + if(number.isInfinity()) + token=LEX_ID, (tokenData=new CScriptTokenDataString("Infinity"))->ref(); + else if(number.isInt32()) + token=LEX_INT, intData=number.toInt32(); + else + token=LEX_FLOAT, floatData=new double(number.toDouble()); + } else if(LEX_TOKEN_DATA_STRING(token)) + (tokenData = new CScriptTokenDataString(l->tkStr))->ref(); + else if(LEX_TOKEN_DATA_FUNCTION(token)) + (tokenData = new CScriptTokenDataFnc)->ref(); + else if (LEX_TOKEN_DATA_LOOP(token)) + (tokenData = new CScriptTokenDataLoop)->ref(); + else if (LEX_TOKEN_DATA_TRY(token)) + (tokenData = new CScriptTokenDataTry)->ref(); + if(Match>=0) + l->match(Match, Alternate); + else + l->match(l->tk); +#ifdef _DEBUG + token_str = getTokenStr(token); +#endif +} +CScriptToken::CScriptToken(uint16_t Tk, int IntData) : line(0), column(0), token(Tk), intData(0) { + if (LEX_TOKEN_DATA_SIMPLE(token)) + intData = IntData; + else if (LEX_TOKEN_DATA_FUNCTION(token)) + (tokenData = new CScriptTokenDataFnc)->ref(); + else if (LEX_TOKEN_DATA_DESTRUCTURING_VAR(token)) + (tokenData = new CScriptTokenDataDestructuringVar)->ref(); + else if (LEX_TOKEN_DATA_OBJECT_LITERAL(token)) + (tokenData = new CScriptTokenDataObjectLiteral)->ref(); + else if (LEX_TOKEN_DATA_LOOP(token)) + (tokenData = new CScriptTokenDataLoop)->ref(); + else if (LEX_TOKEN_DATA_TRY(token)) + (tokenData = new CScriptTokenDataTry)->ref(); + else if (LEX_TOKEN_DATA_FORWARDER(token)) + (tokenData = new CScriptTokenDataForwards)->ref(); + else + ASSERT(0); +#ifdef _DEBUG + token_str = getTokenStr(token); +#endif +} + +CScriptToken::CScriptToken(uint16_t Tk, const string &TkStr) : line(0), column(0), token(Tk), intData(0) { + ASSERT(LEX_TOKEN_DATA_STRING(token)); + (tokenData = new CScriptTokenDataString(TkStr))->ref(); +#ifdef _DEBUG + token_str = getTokenStr(token); +#endif +} + +CScriptToken &CScriptToken::operator =(const CScriptToken &Copy) +{ + if(this == &Copy) return *this; + clear(); +#ifdef _DEBUG + token_str = Copy.token_str; +#endif + line = Copy.line; + column = Copy.column; + token = Copy.token; + if(LEX_TOKEN_DATA_FLOAT(token)) + floatData = new double(*Copy.floatData); + else if(!LEX_TOKEN_DATA_SIMPLE(token)) + (tokenData = Copy.tokenData)->ref(); + else + intData = Copy.intData; + return *this; +} +string CScriptToken::getParsableString(TOKEN_VECT &Tokens, const string &IndentString, const string &Indent) { + return getParsableString(Tokens.begin(), Tokens.end(), IndentString, Indent); +} +string CScriptToken::getParsableString(TOKEN_VECT_it Begin, TOKEN_VECT_it End, const string &IndentString, const string &Indent) { + ostringstream destination; + string nl = Indent.size() ? "\n" : " "; + string my_indentString = IndentString; + bool add_nl=false, block_start=false, need_space=false; + int skip_collon = 0; + + for(TOKEN_VECT_it it=Begin; it != End; ++it) { + string OutString; + if(add_nl) OutString.append(nl).append(my_indentString); + bool old_block_start = block_start; + bool old_need_space = need_space; + add_nl = block_start = need_space =false; + if(it->token == LEX_STR) + OutString.append(getJSString(it->String())), need_space=true; + else if(LEX_TOKEN_DATA_STRING(it->token)) + OutString.append(it->String()), need_space=true; + else if(LEX_TOKEN_DATA_FLOAT(it->token)) + OutString.append(CNumber(it->Float()).toString()), need_space=true; + else if(it->token == LEX_INT) + OutString.append(CNumber(it->Int()).toString()), need_space=true; + else if(LEX_TOKEN_DATA_FUNCTION(it->token)) { + OutString.append("function "); + if(it->Fnc().name.size() ) + OutString.append(it->Fnc().name); + OutString.append(it->Fnc().getArgumentsString()); + OutString.append(getParsableString(it->Fnc().body, my_indentString, Indent)); + if(it->Fnc().body.front().token != '{') { + OutString.append(";"); + } + } else if(LEX_TOKEN_DATA_LOOP(it->token)) { + OutString.append(it->Loop().getParsableString(my_indentString, Indent)); + } else if(LEX_TOKEN_DATA_TRY(it->token)) { + OutString.append(it->Try().getParsableString(my_indentString, Indent)); + } else if(LEX_TOKEN_DATA_DESTRUCTURING_VAR(it->token)) { + OutString.append(it->DestructuringVar().getParsableString()); + } else if(LEX_TOKEN_DATA_OBJECT_LITERAL(it->token)) { + OutString.append(it->Object().getParsableString()); + } else if(it->token == '{') { + OutString.append("{"); + my_indentString.append(Indent); + add_nl = block_start = true; + } else if(it->token == '}') { + my_indentString.resize(my_indentString.size() - min(my_indentString.size(),Indent.size())); + if(old_block_start) + OutString = "}"; + else + OutString = nl + my_indentString + "}"; + add_nl = true; + } else if(it->token == LEX_T_SKIP) { + // ignore SKIP-Token + } else if(it->token == LEX_T_FORWARD) { + // ignore Forwarder-Token + } else if(it->token == LEX_R_FOR) { + OutString.append(CScriptToken::getTokenStr(it->token)); + skip_collon=2; + } else { + OutString.append(CScriptToken::getTokenStr(it->token,&need_space)); + if(it->token==';') { + if(skip_collon) { --skip_collon; } + else add_nl=true; + } + } + if(need_space && old_need_space) destination << " "; + destination << OutString; + } + return destination.str(); + +} + +void CScriptToken::clear() +{ + if(LEX_TOKEN_DATA_FLOAT(token)) + delete floatData; + else if(!LEX_TOKEN_DATA_SIMPLE(token)) + tokenData->unref(); + token = 0; +} +string CScriptToken::getTokenStr( int token, bool *need_space/*=0*/ ) +{ + if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); + token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, token, token2str_cmp_t()); + if(found != reserved_words_end && found->id==token) { + if(need_space) *need_space=found->need_space; + return found->str; + } + found = lower_bound(tokens2str_begin, tokens2str_end, token, token2str_cmp_t()); + if(found != tokens2str_end && found->id==token) { + if(need_space) *need_space=found->need_space; + return found->str; + } + if(need_space) *need_space=false; + + if (token>32 && token<128) { + char buf[2] = " "; + buf[0] = (char)token; + return buf; + } + + ostringstream msg; + msg << "?[" << token << "]"; + return msg.str(); +} +const char *CScriptToken::isReservedWord(int Token) { + if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); + token2str_t *found = lower_bound(reserved_words_begin, reserved_words_end, Token, token2str_cmp_t()); + if(found != reserved_words_end && found->id==Token) { + return found->str; + } + return 0; +} +int CScriptToken::isReservedWord(const string &Str) { + const char *str = Str.c_str(); + if(!tokens2str_sorted) tokens2str_sorted=tokens2str_sort(); + token2str_t **found = lower_bound(str2reserved_begin, str2reserved_end, str, token2str_cmp_t()); + if(found != str2reserved_end && strcmp((*found)->str, str)==0) { + return (*found)->id; + } + return LEX_ID; +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptTokenizer +////////////////////////////////////////////////////////////////////////// + +CScriptTokenizer::CScriptTokenizer() : l(0), prevPos(&tokens) { +} +CScriptTokenizer::CScriptTokenizer(CScriptLex &Lexer) : l(0), prevPos(&tokens) { + tokenizeCode(Lexer); +} +CScriptTokenizer::CScriptTokenizer(const char *Code, const string &File, int Line, int Column) : l(0), prevPos(&tokens) { + CScriptLex lexer(Code, File, Line, Column); + tokenizeCode(lexer); +} +void CScriptTokenizer::tokenizeCode(CScriptLex &Lexer) { + try { + l=&Lexer; + tokens.clear(); + tokenScopeStack.clear(); + ScriptTokenState state; + pushForwarder(state); + if(l->tk == '§') { // special-Token at Start means the code begins not at Statement-Level + l->match('§'); + tokenizeLiteral(state, 0); + } else do { + tokenizeStatement(state, 0); + } while (l->tk!=LEX_EOF); + pushToken(state.Tokens, LEX_EOF); // add LEX_EOF-Token + removeEmptyForwarder(state); +// TOKEN_VECT(tokens).swap(tokens);// tokens.shrink_to_fit(); + tokens.swap(state.Tokens); + pushTokenScope(tokens); + currentFile = l->currentFile; + tk = getToken().token; + } catch (...) { + l=0; + throw; + } +} + +void CScriptTokenizer::getNextToken() { + prevPos = tokenScopeStack.back(); + if(getToken().token == LEX_EOF) + return; + ScriptTokenPosition &_TokenPos = tokenScopeStack.back(); + _TokenPos.pos++; + if(_TokenPos.pos == _TokenPos.tokens->end()) + tokenScopeStack.pop_back(); +// ScriptTokenPosition &TokenPos = tokenScopeStack.back(); + tk = getToken().token; +} + + + +void CScriptTokenizer::match(int ExpectedToken, int AlternateToken/*=-1*/) { + if(check(ExpectedToken, AlternateToken)) + getNextToken(); +} +bool CScriptTokenizer::check(int ExpectedToken, int AlternateToken/*=-1*/) { + int currentToken = getToken().token; + if (ExpectedToken==';' && (currentToken==LEX_EOF || currentToken=='}')) return false; // ignore last missing ';' + if (currentToken!=ExpectedToken && currentToken!=AlternateToken) { + ostringstream errorString; + if(ExpectedToken == LEX_EOF) + errorString << "Got unexpected " << CScriptToken::getTokenStr(currentToken); + else { + errorString << "Got '" << CScriptToken::getTokenStr(currentToken) << "' expected '" << CScriptToken::getTokenStr(ExpectedToken) << "'"; + if(AlternateToken!=-1) errorString << " or '" << CScriptToken::getTokenStr(AlternateToken) << "'"; + } + throw new CScriptException(SyntaxError, errorString.str(), currentFile, currentLine(), currentColumn()); + } + return true; +} +void CScriptTokenizer::pushTokenScope(TOKEN_VECT &Tokens) { + tokenScopeStack.push_back(ScriptTokenPosition(&Tokens)); + tk = getToken().token; +} + +void CScriptTokenizer::setPos(ScriptTokenPosition &TokenPos) { + ASSERT( TokenPos.tokens == tokenScopeStack.back().tokens); + tokenScopeStack.back().pos = TokenPos.pos; + tk = getToken().token; +} +void CScriptTokenizer::skip(int Tokens) { + ASSERT(tokenScopeStack.back().tokens->end()-tokenScopeStack.back().pos-Tokens>=0); + tokenScopeStack.back().pos+=Tokens-1; + getNextToken(); +} + +static inline void setTokenSkip(CScriptTokenizer::ScriptTokenState &State) { + int tokenBeginIdx = State.Marks.back(); + State.Marks.pop_back(); + State.Tokens[tokenBeginIdx].Int() = State.Tokens.size()-tokenBeginIdx; +} + +enum { + TOKENIZE_FLAGS_canLabel = 1<<0, + TOKENIZE_FLAGS_canBreak = 1<<1, + TOKENIZE_FLAGS_canContinue = 1<<2, + TOKENIZE_FLAGS_canReturn = 1<<3, + TOKENIZE_FLAGS_canYield = 1<<4, + TOKENIZE_FLAGS_asStatement = 1<<5, + TOKENIZE_FLAGS_noIn = 1<<6, + TOKENIZE_FLAGS_isAccessor = 1<<7, + TOKENIZE_FLAGS_callForNew = 1<<8, + TOKENIZE_FLAGS_noBlockStart = 1<<9, + TOKENIZE_FLAGS_nestedObject = 1<<10, +}; +void CScriptTokenizer::tokenizeTry(ScriptTokenState &State, int Flags) { + l->match(LEX_R_TRY); + CScriptToken TryToken(LEX_T_TRY); + CScriptTokenDataTry &TryData = TryToken.Try(); + pushToken(State.Tokens, TryToken); + + TOKEN_VECT mainTokens; + State.Tokens.swap(mainTokens); + + // try-block + tokenizeBlock(State, Flags); + State.Tokens.swap(TryData.tryBlock); + + // catch-blocks + l->check(LEX_R_CATCH, LEX_R_FINALLY); + bool unconditionalCatch = false; + while(l->tk == LEX_R_CATCH) { + if(unconditionalCatch) throw new CScriptException(SyntaxError, "catch after unconditional catch", l->currentFile, l->currentLine(), l->currentColumn()); + + // vars & condition + l->match(LEX_R_CATCH); + l->match('('); + TryData.catchBlocks.resize(TryData.catchBlocks.size()+1); + CScriptTokenDataTry::CatchBlock &catchBlock = TryData.catchBlocks.back(); + pushForwarder(State, true); + STRING_VECTOR_t vars; + catchBlock.indentifiers = tokenizeVarIdentifier(&vars).DestructuringVar(); + State.Forwarders.back()->addLets(vars); + if(l->tk == LEX_R_IF) { + l->match(LEX_R_IF); + tokenizeExpression(State, Flags); + } else + unconditionalCatch = true; + State.Tokens.swap(catchBlock.condition); + l->match(')'); + + // catch-block + tokenizeBlock(State, Flags | TOKENIZE_FLAGS_noBlockStart); + State.Tokens.swap(catchBlock.block); + State.Forwarders.pop_back(); + } + // finally-block + if(l->tk == LEX_R_FINALLY) { + l->match(LEX_R_FINALLY); + tokenizeBlock(State, Flags); + State.Tokens.swap(TryData.finallyBlock); + } + State.Tokens.swap(mainTokens); +} +void CScriptTokenizer::tokenizeSwitch(ScriptTokenState &State, int Flags) { + + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx + pushToken(State.Tokens, '('); + tokenizeExpression(State, Flags); + pushToken(State.Tokens, ')'); + + State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push blockBeginIdx + pushForwarder(State); + + + vector::size_type MarksSize = State.Marks.size(); + Flags |= TOKENIZE_FLAGS_canBreak; + for(bool hasDefault=false;;) { + if( l->tk == LEX_R_CASE || l->tk == LEX_R_DEFAULT) { + if(l->tk == LEX_R_CASE) { + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx + State.Marks.push_back(pushToken(State.Tokens,CScriptToken(LEX_T_SKIP))); // skipper to skip case-expression + tokenizeExpression(State, Flags); + setTokenSkip(State); + } else { // default + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push caseBeginIdx + if(hasDefault) throw new CScriptException(SyntaxError, "more than one switch default", l->currentFile, l->currentLine(), l->currentColumn()); + hasDefault = true; + } + + State.Marks.push_back(pushToken(State.Tokens, ':')); + while(l->tk != '}' && l->tk != LEX_R_CASE && l->tk != LEX_R_DEFAULT && l->tk != LEX_EOF ) + tokenizeStatement(State, Flags); + setTokenSkip(State); + } else if(l->tk == '}') + break; + else + throw new CScriptException(SyntaxError, "invalid switch statement", l->currentFile, l->currentLine(), l->currentColumn()); + } + while(MarksSize < State.Marks.size()) setTokenSkip(State); + removeEmptyForwarder(State); // remove Forwarder if empty + pushToken(State.Tokens, '}'); + setTokenSkip(State); // switch-block + setTokenSkip(State); // switch-statement +} +void CScriptTokenizer::tokenizeWith(ScriptTokenState &State, int Flags) { + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx + + pushToken(State.Tokens, '('); + tokenizeExpression(State, Flags); + pushToken(State.Tokens, ')'); + tokenizeStatementNoLet(State, Flags); + + setTokenSkip(State); +} + +static inline uint32_t GetLoopLabels(CScriptTokenizer::ScriptTokenState &State, CScriptTokenDataLoop &LoopData) { + uint32_t label_count = 0; + if(State.Tokens.size()>=2) { + for(TOKEN_VECT::reverse_iterator it = State.Tokens.rbegin(); it!=State.Tokens.rend(); ++it) { + if(it->token == ':' && (++it)->token == LEX_T_LABEL) { + ++label_count; + LoopData.labels.push_back(it->String()); + State.LoopLabels.push_back(it->String()); + it->token = LEX_T_DUMMY_LABEL; + } else + break; + } + } + return label_count; +} +static inline void PopLoopLabels(uint32_t label_count, STRING_VECTOR_t &LoopLabels) { + ASSERT(label_count <= LoopLabels.size()); + LoopLabels.resize(LoopLabels.size()-label_count); +} +void CScriptTokenizer::tokenizeWhileAndDo(ScriptTokenState &State, int Flags) { + + bool do_while = l->tk==LEX_R_DO; + + CScriptToken LoopToken(LEX_T_LOOP); + CScriptTokenDataLoop &LoopData = LoopToken.Loop(); + LoopData.type = do_while ? CScriptTokenDataLoop::DO : CScriptTokenDataLoop::WHILE; + + // get loop-labels + uint32_t label_count = GetLoopLabels(State, LoopData); + + l->match(l->tk); // match while or do + + pushToken(State.Tokens, LoopToken); + + TOKEN_VECT mainTokens; + State.Tokens.swap(mainTokens); + + if(!do_while) { + l->match('('); + tokenizeExpression(State, Flags); + State.Tokens.swap(LoopData.condition); + l->match(')'); + } + tokenizeStatementNoLet(State, Flags | TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue); + State.Tokens.swap(LoopData.body); + if(do_while) { + l->match(LEX_R_WHILE); + l->match('('); + tokenizeExpression(State, Flags); + State.Tokens.swap(LoopData.condition); + l->match(')'); + l->match(';'); + } + State.Tokens.swap(mainTokens); + PopLoopLabels(label_count, State.LoopLabels); +} + +void CScriptTokenizer::tokenizeIf(ScriptTokenState &State, int Flags) { + + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx + + pushToken(State.Tokens, '('); + tokenizeExpression(State, Flags); + pushToken(State.Tokens, ')'); + State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx + tokenizeStatementNoLet(State, Flags); + + setTokenSkip(State); + + if(l->tk == LEX_R_ELSE) { + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push tokenBeginIdx + tokenizeStatementNoLet(State, Flags); + setTokenSkip(State); + } + + setTokenSkip(State); +} + +void CScriptTokenizer::tokenizeFor(ScriptTokenState &State, int Flags) { + bool for_in=false, for_of=false, for_each_in=false; + CScriptToken LoopToken(LEX_T_LOOP); + CScriptTokenDataLoop &LoopData = LoopToken.Loop(); + + // get loop-labels + uint32_t label_count = GetLoopLabels(State, LoopData); + + l->match(LEX_R_FOR); + if((for_in = for_each_in = (l->tk == LEX_ID && l->tkStr == "each"))) + l->match(LEX_ID); // match "each" + + pushToken(State.Tokens, LoopToken); + + l->match('('); + TOKEN_VECT mainTokens; + State.Tokens.swap(mainTokens); + + bool haveLetScope = false; + + if(l->tk == LEX_R_VAR || l->tk == LEX_R_LET) { + if(l->tk == LEX_R_VAR) + tokenizeVarNoConst(State, Flags | TOKENIZE_FLAGS_noIn); + else { //if(l->tk == LEX_R_LET) + haveLetScope = true; + pushForwarder(State, true); // no clean up empty tokenizer + tokenizeLet(State, Flags | TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_asStatement); + } + } else if(l->tk!=';') { + tokenizeExpression(State, Flags | TOKENIZE_FLAGS_noIn); + } + if((for_in=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) { + if(!State.LeftHand) + throw new CScriptException(ReferenceError, "invalid for/in left-hand side", l->currentFile, l->currentLine(), l->currentColumn()); + if(l->tk==LEX_ID && l->tkStr=="of") l->tk = LEX_T_OF; // fake token + if((for_of = (!for_each_in && l->tk==LEX_T_OF))) { + l->match(LEX_T_OF); + LoopData.type = CScriptTokenDataLoop::FOR_OF; + } else { + l->match(LEX_R_IN); + LoopData.type = for_each_in ? CScriptTokenDataLoop::FOR_EACH : CScriptTokenDataLoop::FOR_IN; + } + State.Tokens.swap(LoopData.condition); + + if(LoopData.condition.front().token == LEX_T_FORWARD) { + LoopData.init.push_back(LoopData.condition.front()); + LoopData.condition.erase(LoopData.condition.begin()); + } + mainTokens.back().token = LEX_T_FOR_IN; + } else { + l->check(';'); // no automatic ;-injection + pushToken(State.Tokens, ';'); + State.Tokens.swap(LoopData.init); + if(l->tk != ';') tokenizeExpression(State, Flags); + l->check(';'); // no automatic ;-injection + l->match(';'); // no automatic ;-injection + State.Tokens.swap(LoopData.condition); + } + + if(for_in || l->tk != ')') tokenizeExpression(State, Flags); + l->match(')'); + State.Tokens.swap(LoopData.iter); + Flags = (Flags & (TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield)) | TOKENIZE_FLAGS_canBreak | TOKENIZE_FLAGS_canContinue; + if(haveLetScope) Flags |= TOKENIZE_FLAGS_noBlockStart; + tokenizeStatementNoLet(State, Flags); + if(haveLetScope) State.Forwarders.pop_back(); + + State.Tokens.swap(LoopData.body); + State.Tokens.swap(mainTokens); + if(for_in) { + LoopData.condition.push_back('='); + LoopData.condition.push_back(LEX_T_EXCEPTION_VAR); + LoopData.condition.push_back('.'); + LoopData.condition.push_back(CScriptToken(LEX_ID, "next")); + LoopData.condition.push_back('('); + LoopData.condition.push_back(')'); + LoopData.condition.push_back(';'); + } + PopLoopLabels(label_count, State.LoopLabels); +} + +static void tokenizeVarIdentifierDestructuring( CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames ); +static void tokenizeVarIdentifierDestructuringObject(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) { + Lexer->match('{'); + while(Lexer->tk != '}') { + CScriptLex::POS prev_pos = Lexer->pos; + string Path = Lexer->tkStr; + Lexer->match(LEX_ID, LEX_STR); + if(Lexer->tk == ':') { + Lexer->match(':'); + tokenizeVarIdentifierDestructuring(Lexer, Vars, Path, VarNames); + } else { + Lexer->reset(prev_pos); + if(VarNames) VarNames->push_back(Lexer->tkStr); + Vars.push_back(DESTRUCTURING_VAR_t(Lexer->tkStr, Lexer->tkStr)); + Lexer->match(LEX_ID); + } + if (Lexer->tk!='}') Lexer->match(',', '}'); + } + Lexer->match('}'); +} +static void tokenizeVarIdentifierDestructuringArray(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, STRING_VECTOR_t *VarNames) { + int idx = 0; + Lexer->match('['); + while(Lexer->tk != ']') { + if(Lexer->tk == ',') + Vars.push_back(DESTRUCTURING_VAR_t("", "")); // empty + else + tokenizeVarIdentifierDestructuring(Lexer, Vars, int2string(idx), VarNames); + ++idx; + if (Lexer->tk!=']') Lexer->match(',',']'); + } + Lexer->match(']'); +} +static void tokenizeVarIdentifierDestructuring(CScriptLex *Lexer, DESTRUCTURING_VARS_t &Vars, const std::string &Path, STRING_VECTOR_t *VarNames ) { + if(Lexer->tk == '[') { + Vars.push_back(DESTRUCTURING_VAR_t(Path, "[")); // marks array begin + tokenizeVarIdentifierDestructuringArray(Lexer, Vars, VarNames); + Vars.push_back(DESTRUCTURING_VAR_t("", "]")); // marks array end + } else if(Lexer->tk == '{') { + Vars.push_back(DESTRUCTURING_VAR_t(Path, "{")); // marks object begin + tokenizeVarIdentifierDestructuringObject(Lexer, Vars, VarNames); + Vars.push_back(DESTRUCTURING_VAR_t("", "}")); // marks object end + } else { + if(VarNames) VarNames->push_back(Lexer->tkStr); + Vars.push_back(DESTRUCTURING_VAR_t(Path, Lexer->tkStr)); + Lexer->match(LEX_ID); + } +} +CScriptToken CScriptTokenizer::tokenizeVarIdentifier( STRING_VECTOR_t *VarNames/*=0*/, bool *NeedAssignment/*=0*/ ) { + CScriptToken token(LEX_T_DESTRUCTURING_VAR); + if(NeedAssignment) *NeedAssignment=(l->tk == '[' || l->tk=='{'); + token.column = l->currentColumn(); + token.line = l->currentLine(); + tokenizeVarIdentifierDestructuring(l, token.DestructuringVar().vars, "", VarNames); + return token; +} + +void CScriptTokenizer::tokenizeFunction(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) { + bool forward = false; + bool Statement = (Flags & TOKENIZE_FLAGS_asStatement) != 0; + bool Accessor = (Flags & TOKENIZE_FLAGS_isAccessor) != 0; + CScriptLex::POS functionPos = l->pos; + + int tk = l->tk; + if(Accessor) { + tk = State.Tokens.back().String()=="get"?LEX_T_GET:LEX_T_SET; + State.Tokens.pop_back(); + } else { + l->match(LEX_R_FUNCTION); + if(!Statement) tk = LEX_T_FUNCTION_OPERATOR; + } + if(tk == LEX_R_FUNCTION) // only forward functions + forward = !noLetDef && State.Forwarders.front() == State.Forwarders.back(); + + CScriptToken FncToken(tk); + CScriptTokenDataFnc &FncData = FncToken.Fnc(); + + if(l->tk == LEX_ID || Accessor) { + FncData.name = l->tkStr; + l->match(LEX_ID, LEX_STR); + } else if(Statement) + throw new CScriptException(SyntaxError, "Function statement requires a name.", l->currentFile, l->currentLine(), l->currentColumn()); + l->match('('); + while(l->tk != ')') { + FncData.arguments.push_back(tokenizeVarIdentifier()); + if (l->tk!=')') l->match(',',')'); + } + // l->match(')'); + // to allow regexp at the beginning of a lambda-function fake last token + l->tk = '{'; + l->match('{'); + FncData.file = l->currentFile; + FncData.line = l->currentLine(); + + ScriptTokenState functionState; + functionState.HaveReturnValue = functionState.FunctionIsGenerator = false; + if(l->tk == '{' || tk==LEX_T_GET || tk==LEX_T_SET) + tokenizeBlock(functionState, TOKENIZE_FLAGS_canReturn | TOKENIZE_FLAGS_canYield); + else { + tokenizeExpression(functionState, TOKENIZE_FLAGS_canYield); + l->match(';'); + functionState.HaveReturnValue = true; + } + if(functionState.HaveReturnValue == true && functionState.FunctionIsGenerator == true) + throw new CScriptException(TypeError, "generator function returns a value.", l->currentFile, functionPos.currentLine, functionPos.currentColumn()); + FncData.isGenerator = functionState.FunctionIsGenerator; + + functionState.Tokens.swap(FncData.body); + if(forward) { + State.Forwarders.front()->functions.insert(FncToken); + FncToken.token = LEX_T_FUNCTION_PLACEHOLDER; + } + State.Tokens.push_back(FncToken); +} + +void CScriptTokenizer::tokenizeLet(ScriptTokenState &State, int Flags, bool noLetDef/*=false*/) { + bool Definition = (Flags & TOKENIZE_FLAGS_asStatement)!=0; + bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0; + bool Statement = Definition & !noIN; + bool Expression = !Definition; + Flags &= ~(TOKENIZE_FLAGS_asStatement); + if(!Definition) noIN=false, Flags &= ~TOKENIZE_FLAGS_noIn; + + bool foundIN = false; + bool leftHand = true; + int currLine = l->currentLine(), currColumn = l->currentColumn(); + + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + + if(l->tk == '(' || !Definition) { // no definition needs statement or expression + leftHand = false; + Expression = true; + pushToken(State.Tokens, '('); + pushForwarder(State); + } else if(noLetDef) + throw new CScriptException(SyntaxError, "let declaration not directly within block", l->currentFile, currLine, currColumn); + STRING_VECTOR_t vars; + for(;;) { + bool needAssignment = false; + State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment)); + if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) + break; + if(needAssignment || l->tk=='=') { + leftHand = false; + pushToken(State.Tokens, '='); + tokenizeAssignment(State, Flags); + if(noIN && (foundIN=(l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of")))) + break; + } + if(l->tk==',') { + leftHand = false; + pushToken(State.Tokens); + } + else + break; + } + if(Expression) { + string redeclared = State.Forwarders.back()->addLets(vars); + if(redeclared.size()) + throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); + if(!foundIN) { + pushToken(State.Tokens, ')'); + if(Statement) { + if(l->tk == '{') // no extra BlockStart by expression + tokenizeBlock(State, Flags|=TOKENIZE_FLAGS_noBlockStart); + else + tokenizeStatementNoLet(State, Flags); + } else + tokenizeAssignment(State, Flags); + } + // never remove Forwarder-token here -- popForwarder(Tokens, BlockStart, Marks); + State.Forwarders.back()->vars_in_letscope.clear(); // only clear vars_in_letscope + State.Marks.pop_back(); + } else { + if(!noIN) pushToken(State.Tokens, ';'); + + string redeclared; + if(State.Forwarders.size()<=1) { + // Currently it is allowed in javascript, to redeclare "let"-declared vars + // in root- or function-scopes. In this case, "let" handled like "var" + // To prevent redeclaration in root- or function-scopes define PREVENT_REDECLARATION_IN_FUNCTION_SCOPES +#ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + redeclared = State.Forwarders.front()->addLets(vars); +#else + State.Forwarders.front()->addVars(vars); +#endif + } else + redeclared = State.Forwarders.back()->addLets(vars); + if(redeclared.size()) + throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); + } + setTokenSkip(State); + if(leftHand) State.LeftHand = true; +} + +void CScriptTokenizer::tokenizeVarNoConst( ScriptTokenState &State, int Flags) { + l->check(LEX_R_VAR); + tokenizeVarAndConst(State, Flags); +} +void CScriptTokenizer::tokenizeVarAndConst( ScriptTokenState &State, int Flags) { + bool noIN = (Flags & TOKENIZE_FLAGS_noIn)!=0; + int currLine = l->currentLine(), currColumn = l->currentColumn(); + + bool leftHand = true; + int tk = l->tk; + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + + STRING_VECTOR_t vars; + for(;;) + { + bool needAssignment = false; + State.Tokens.push_back(tokenizeVarIdentifier(&vars, &needAssignment)); + if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))) + break; + if(needAssignment || l->tk=='=') { + leftHand = false; + pushToken(State.Tokens, '='); + tokenizeAssignment(State, Flags); + if(noIN && (l->tk==LEX_R_IN || (l->tk==LEX_ID && l->tkStr=="of"))) + break; + } + if(l->tk==',') { + leftHand = false; + pushToken(State.Tokens); + } + else + break; + } + if(!noIN) pushToken(State.Tokens, ';'); + + setTokenSkip(State); + + if(tk==LEX_R_VAR) + State.Forwarders.front()->addVars(vars); + else + State.Forwarders.front()->addConsts(vars); + string redeclared; + if(State.Forwarders.size()>1) // have let-scope + redeclared = State.Forwarders.back()->addVarsInLetscope(vars); +#ifdef PREVENT_REDECLARATION_IN_FUNCTION_SCOPES + else + redeclared = State.Forwarders.front()->addVarsInLetscope(vars); +#endif + if(redeclared.size()) + throw new CScriptException(TypeError, "redeclaration of variable '"+redeclared+"'", l->currentFile, currLine, currColumn); + if(leftHand) State.LeftHand = true; +} + +void CScriptTokenizer::_tokenizeLiteralObject(ScriptTokenState &State, int Flags) { + bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0; + bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0; + Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject); + CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL); + CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object(); + + Objc.type = CScriptTokenDataObjectLiteral::OBJECT; + Objc.destructuring = Objc.structuring = true; + + string msg, msgFile; + int msgLine=0, msgColumn=0; + + l->match('{'); + while (l->tk != '}') { + CScriptTokenDataObjectLiteral::ELEMENT element; + bool assign = false; + if(CScriptToken::isReservedWord(l->tk)) + l->tk = LEX_ID; // fake reserved-word as member.ID + if(l->tk == LEX_ID) { + element.id = l->tkStr; + CScriptToken Token(l, LEX_ID); + if((l->tk==LEX_ID || l->tk==LEX_STR ) && (element.id=="get" || element.id=="set")) { + element.id = l->tkStr; + element.value.push_back(Token); + State.Tokens.swap(element.value); + tokenizeFunction(State, Flags|TOKENIZE_FLAGS_isAccessor); + State.Tokens.swap(element.value); + Objc.destructuring = false; + } else { + if(Objc.destructuring && (l->tk == ',' || l->tk == '}')) { + if(!msg.size()) { + Objc.structuring = false; + msg.append("Got '").append(CScriptToken::getTokenStr(l->tk)).append("' expected ':'"); + msgFile = l->currentFile; + msgLine = l->currentLine(); + msgColumn = l->currentColumn(); + ; + } + element.value.push_back(Token); + } else + assign = true; + } + } else if(l->tk == LEX_INT) { + element.id = int2string((int32_t)strtol(l->tkStr.c_str(),0,0)); + l->match(LEX_INT); + assign = true; + } else if(l->tk == LEX_FLOAT) { + element.id = float2string(strtod(l->tkStr.c_str(),0)); + l->match(LEX_FLOAT); + assign = true; + } else if(LEX_TOKEN_DATA_STRING(l->tk) && l->tk != LEX_REGEXP) { + element.id = l->tkStr; + l->match(l->tk); + assign = true; + } else + l->match(LEX_ID, LEX_STR); + if(assign) { + l->match(':'); + int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0; + State.pushLeftHandState(); + State.Tokens.swap(element.value); + tokenizeAssignment(State, dFlags); + State.Tokens.swap(element.value); + if(Objc.destructuring) Objc.destructuring = State.LeftHand; + State.popLeftHandeState(); + } + + if(!Objc.destructuring && msg.size()) + throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn); + Objc.elements.push_back(element); + if (l->tk != '}') l->match(',', '}'); + } + l->match('}'); + if(Objc.destructuring && Objc.structuring) { + if(nestedObject) { + if(l->tk!=',' && l->tk!='}' && l->tk!='=') + Objc.destructuring = false; + } + else + Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of")))); + } else { + if(!Objc.destructuring && msg.size()) + throw new CScriptException(SyntaxError, msg, msgFile, msgLine, msgColumn); + if(!nestedObject) Objc.setMode(Objc.destructuring); + } + + if(Objc.destructuring) + State.LeftHand = true; + State.Tokens.push_back(ObjectToken); +} +void CScriptTokenizer::_tokenizeLiteralArray(ScriptTokenState &State, int Flags) { + bool forFor = (Flags & TOKENIZE_FLAGS_noIn)!=0; + bool nestedObject = (Flags & TOKENIZE_FLAGS_nestedObject) != 0; + Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_nestedObject); + CScriptToken ObjectToken(LEX_T_OBJECT_LITERAL); + CScriptTokenDataObjectLiteral &Objc = ObjectToken.Object(); + + Objc.type = CScriptTokenDataObjectLiteral::ARRAY; + Objc.destructuring = Objc.structuring = true; + int idx = 0; + + l->match('['); + while (l->tk != ']') { + CScriptTokenDataObjectLiteral::ELEMENT element; + element.id = int2string(idx++); + if(l->tk != ',') { + int dFlags = Flags | (l->tk == '{' || l->tk == '[') ? TOKENIZE_FLAGS_nestedObject : 0; + State.pushLeftHandState(); + State.Tokens.swap(element.value); + tokenizeAssignment(State, dFlags); + State.Tokens.swap(element.value); + if(Objc.destructuring) Objc.destructuring = State.LeftHand; + State.popLeftHandeState(); + } + Objc.elements.push_back(element); + if (l->tk != ']') l->match(',', ']'); + } + l->match(']'); + if(Objc.destructuring && Objc.structuring) { + if(nestedObject) { + if(l->tk!=',' && l->tk!=']' && l->tk!='=') + Objc.destructuring = false; + } + else + Objc.setMode(l->tk=='=' || (forFor && (l->tk==LEX_R_IN ||(l->tk==LEX_ID && l->tkStr=="of")))); + } else + if(!nestedObject) Objc.setMode(Objc.destructuring); + if(Objc.destructuring) + State.LeftHand = true; + State.Tokens.push_back(ObjectToken); +} + +void CScriptTokenizer::tokenizeLiteral(ScriptTokenState &State, int Flags) { + State.LeftHand = 0; + bool canLabel = Flags & TOKENIZE_FLAGS_canLabel; Flags &= ~TOKENIZE_FLAGS_canLabel; + int ObjectLiteralFlags = Flags; + Flags &= ~TOKENIZE_FLAGS_noIn; + switch(l->tk) { + case LEX_ID: + { + string label = l->tkStr; + pushToken(State.Tokens); + if(l->tk==':' && canLabel) { + if(find(State.Labels.begin(), State.Labels.end(), label) != State.Labels.end()) + throw new CScriptException(SyntaxError, "dublicate label '"+label+"'", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); + State.Tokens[State.Tokens.size()-1].token = LEX_T_LABEL; // change LEX_ID to LEX_T_LABEL + State.Labels.push_back(label); + } else if(label=="this") { + if( l->tk == '=' || (l->tk >= LEX_ASSIGNMENTS_BEGIN && l->tk <= LEX_ASSIGNMENTS_END) ) + throw new CScriptException(SyntaxError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); + if( l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS ) + throw new CScriptException(SyntaxError, l->tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn()-label.size()); + } else + State.LeftHand = true; + } + break; + case LEX_INT: + case LEX_FLOAT: + case LEX_STR: + case LEX_REGEXP: + case LEX_R_TRUE: + case LEX_R_FALSE: + case LEX_R_NULL: + pushToken(State.Tokens); + break; + case '{': + _tokenizeLiteralObject(State, ObjectLiteralFlags); + break; + case '[': + _tokenizeLiteralArray(State, ObjectLiteralFlags); + break; + case LEX_R_LET: // let as expression + tokenizeLet(State, Flags); + break; + case LEX_R_FUNCTION: + tokenizeFunction(State, Flags); + break; + case LEX_R_NEW: + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + { + tokenizeFunctionCall(State, (Flags | TOKENIZE_FLAGS_callForNew) & ~TOKENIZE_FLAGS_noIn); + State.LeftHand = 0; + } + setTokenSkip(State); + break; +#ifndef NO_GENERATORS + case LEX_R_YIELD: + if( (Flags & TOKENIZE_FLAGS_canYield)==0) + throw new CScriptException(SyntaxError, "'yield' expression, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn()); + pushToken(State.Tokens); + if(l->tk != ';' && l->tk != '}' && !l->lineBreakBeforeToken) { + tokenizeExpression(State, Flags); + } + State.FunctionIsGenerator = true; + break; +#endif + case '(': + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn); + State.LeftHand = 0; + pushToken(State.Tokens, ')'); + setTokenSkip(State); + break; + default: + l->check(LEX_EOF); + } +} +void CScriptTokenizer::tokenizeMember(ScriptTokenState &State, int Flags) { + while(l->tk == '.' || l->tk == '[') { + if(l->tk == '.') { + pushToken(State.Tokens); + if(CScriptToken::isReservedWord(l->tk)) + l->tk = LEX_ID; // fake reserved-word as member.ID + pushToken(State.Tokens , LEX_ID); + } else { + State.Marks.push_back(pushToken(State.Tokens)); + State.pushLeftHandState(); + tokenizeExpression(State, Flags & ~TOKENIZE_FLAGS_noIn); + State.popLeftHandeState(); + pushToken(State.Tokens, ']'); + setTokenSkip(State); + } + State.LeftHand = true; + } +} +void CScriptTokenizer::tokenizeFunctionCall(ScriptTokenState &State, int Flags) { + bool for_new = (Flags & TOKENIZE_FLAGS_callForNew)!=0; Flags &= ~TOKENIZE_FLAGS_callForNew; + tokenizeLiteral(State, Flags); + tokenizeMember(State, Flags); + while(l->tk == '(') { + State.LeftHand = false; + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + State.pushLeftHandState(); + while(l->tk!=')') { + tokenizeAssignment(State, Flags & ~TOKENIZE_FLAGS_noIn); + if (l->tk!=')') pushToken(State.Tokens, ',', ')'); + } + State.popLeftHandeState(); + pushToken(State.Tokens); + setTokenSkip(State); + if(for_new) break; + tokenizeMember(State, Flags); + } +} + +void CScriptTokenizer::tokenizeSubExpression(ScriptTokenState &State, int Flags) { + static int Left2Right_begin[] = { + /* Precedence 5 */ '*', '/', '%', + /* Precedence 6 */ '+', '-', + /* Precedence 7 */ LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU, + /* Precedence 8 */ LEX_EQUAL, LEX_NEQUAL, LEX_TYPEEQUAL, LEX_NTYPEEQUAL, + /* Precedence 9 */ '<', LEX_LEQUAL, '>', LEX_GEQUAL, LEX_R_IN, LEX_R_INSTANCEOF, + /* Precedence 10-12 */ '&', '^', '|', + }; + static int *Left2Right_end = &Left2Right_begin[sizeof(Left2Right_begin)/sizeof(Left2Right_begin[0])]; + static bool Left2Right_sorted = false; + if(!Left2Right_sorted) Left2Right_sorted = (sort(Left2Right_begin, Left2Right_end), true); + bool noLeftHand = false; + for(;;) { + bool right2left_end = false; + while(!right2left_end) { + switch(l->tk) { + case '-': + case '+': + case '!': + case '~': + case LEX_R_TYPEOF: + case LEX_R_VOID: + case LEX_R_DELETE: + Flags &= ~TOKENIZE_FLAGS_canLabel; + noLeftHand = true; + pushToken(State.Tokens); // Precedence 3 + break; + case LEX_PLUSPLUS: // pre-increment + case LEX_MINUSMINUS: // pre-decrement + { + int tk = l->tk; + Flags &= ~TOKENIZE_FLAGS_canLabel; + noLeftHand = true; + pushToken(State.Tokens); // Precedence 4 + if(l->tk == LEX_ID && l->tkStr == "this") + throw new CScriptException(SyntaxError, tk==LEX_PLUSPLUS?"invalid increment operand":"invalid decrement operand", l->currentFile, l->currentLine(), l->currentColumn()); + } + default: + right2left_end = true; + } + } + tokenizeFunctionCall(State, Flags); + + if (!l->lineBreakBeforeToken && (l->tk==LEX_PLUSPLUS || l->tk==LEX_MINUSMINUS)) { // post-in-/de-crement + noLeftHand = true;; + pushToken(State.Tokens); // Precedence 4 + } + if(Flags&TOKENIZE_FLAGS_noIn && l->tk==LEX_R_IN) + break; + int *found = lower_bound(Left2Right_begin, Left2Right_end, l->tk); + if(found != Left2Right_end && *found == l->tk) { + noLeftHand = true; + pushToken(State.Tokens); // Precedence 5-14 + } + else + break; + } + if(noLeftHand) State.LeftHand = false; +} + +void CScriptTokenizer::tokenizeLogic(ScriptTokenState &State, int Flags, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) { + op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags); + if(l->tk==op) { + unsigned int marks_count = State.Marks.size(); + while(l->tk==op) { + State.Marks.push_back(pushToken(State.Tokens)); + op_n ? tokenizeLogic(State, Flags, op_n, 0) : tokenizeSubExpression(State, Flags); + } + while(State.Marks.size()>marks_count) setTokenSkip(State); + State.LeftHand = false; + } +} + +void CScriptTokenizer::tokenizeCondition(ScriptTokenState &State, int Flags) { + tokenizeLogic(State, Flags); + if(l->tk == '?') { + Flags &= ~(TOKENIZE_FLAGS_noIn | TOKENIZE_FLAGS_canLabel); + State.Marks.push_back(pushToken(State.Tokens)); + tokenizeAssignment(State, Flags); + setTokenSkip(State); + State.Marks.push_back(pushToken(State.Tokens, ':')); + tokenizeAssignment(State, Flags); + setTokenSkip(State); + State.LeftHand = false; + } +} +void CScriptTokenizer::tokenizeAssignment(ScriptTokenState &State, int Flags) { + tokenizeCondition(State, Flags); + if (l->tk=='=' || (l->tk>=LEX_ASSIGNMENTS_BEGIN && l->tk<=LEX_ASSIGNMENTS_END) ) { + if(!State.LeftHand) + throw new CScriptException(ReferenceError, "invalid assignment left-hand side", l->currentFile, l->currentLine(), l->currentColumn()); + pushToken(State.Tokens); + tokenizeAssignment(State, Flags); + State.LeftHand = false; + } +} +void CScriptTokenizer::tokenizeExpression(ScriptTokenState &State, int Flags) { + tokenizeAssignment(State, Flags); + while(l->tk == ',') { + pushToken(State.Tokens); + tokenizeAssignment(State, Flags); + State.LeftHand = false; + } +} +void CScriptTokenizer::tokenizeBlock(ScriptTokenState &State, int Flags) { + bool addBlockStart = (Flags&TOKENIZE_FLAGS_noBlockStart)==0; + Flags&=~(TOKENIZE_FLAGS_noBlockStart); + State.Marks.push_back(pushToken(State.Tokens, '{')); // push Token & push BeginIdx + if(addBlockStart) pushForwarder(State); + + while(l->tk != '}' && l->tk != LEX_EOF) + tokenizeStatement(State, Flags); + pushToken(State.Tokens, '}'); + + if(addBlockStart) removeEmptyForwarder(State); // clean-up BlockStarts + + setTokenSkip(State); +} + +void CScriptTokenizer::tokenizeStatementNoLet(ScriptTokenState &State, int Flags) { + if(l->tk == LEX_R_LET) + tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement, true); + else if(l->tk==LEX_R_FUNCTION) + tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement, true); + else + tokenizeStatement(State, Flags); +} +void CScriptTokenizer::tokenizeStatement(ScriptTokenState &State, int Flags) { + int tk = l->tk; + switch(l->tk) + { + case '{': tokenizeBlock(State, Flags); break; + case ';': pushToken(State.Tokens); break; + case LEX_R_CONST: + case LEX_R_VAR: tokenizeVarAndConst(State, Flags); break; + case LEX_R_LET: tokenizeLet(State, Flags | TOKENIZE_FLAGS_asStatement); break; + case LEX_R_WITH: tokenizeWith(State, Flags); break; + case LEX_R_IF: tokenizeIf(State, Flags); break; + case LEX_R_SWITCH: tokenizeSwitch(State, Flags); break; + case LEX_R_DO: + case LEX_R_WHILE: tokenizeWhileAndDo(State, Flags); break; + case LEX_R_FOR: tokenizeFor(State, Flags); break; + case LEX_R_FUNCTION: tokenizeFunction(State, Flags | TOKENIZE_FLAGS_asStatement); break; + case LEX_R_TRY: tokenizeTry(State, Flags); break; + case LEX_R_RETURN: + if( (Flags & TOKENIZE_FLAGS_canReturn)==0) + throw new CScriptException(SyntaxError, "'return' statement, but not in a function.", l->currentFile, l->currentLine(), l->currentColumn()); + case LEX_R_THROW: + State.Marks.push_back(pushToken(State.Tokens)); // push Token & push BeginIdx + if(l->tk != ';' && l->tk != '}' && !l->lineBreakBeforeToken) { + if(tk==LEX_R_RETURN) State.HaveReturnValue = true; + tokenizeExpression(State, Flags); + } + pushToken(State.Tokens, ';'); // push ';' + setTokenSkip(State); + break; + case LEX_R_BREAK: + case LEX_R_CONTINUE: + { + bool isBreak = l->tk == LEX_R_BREAK; + State.Marks.push_back(pushToken(State.Tokens)); // push Token + + if(l->tk != ';' && !l->lineBreakBeforeToken) { + l->check(LEX_ID); + STRING_VECTOR_t &L = isBreak ? State.Labels : State.LoopLabels; + if(find(L.begin(), L.end(), l->tkStr) == L.end()) + throw new CScriptException(SyntaxError, "label '"+l->tkStr+"' not found", l->currentFile, l->currentLine(), l->currentColumn()); + pushToken(State.Tokens); // push 'Label' + } else if((Flags & (isBreak ? TOKENIZE_FLAGS_canBreak : TOKENIZE_FLAGS_canContinue) )==0) + throw new CScriptException(SyntaxError, + isBreak ? "'break' must be inside loop, switch or labeled statement" : "'continue' must be inside loop", + l->currentFile, l->currentLine(), l->currentColumn()); + pushToken(State.Tokens, ';'); // push ';' + setTokenSkip(State); + } + break; + case LEX_ID: + { + State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx + STRING_VECTOR_t::size_type label_count = State.Labels.size(); + tokenizeExpression(State, Flags | TOKENIZE_FLAGS_canLabel); + if(label_count < State.Labels.size() && l->tk == ':') { + State.Tokens.erase(State.Tokens.begin()+State.Marks.back()); // remove skip + State.Marks.pop_back(); + pushToken(State.Tokens); // push ':' + tokenizeStatement(State, Flags); + State.Labels.pop_back(); + } else { + pushToken(State.Tokens, ';'); + setTokenSkip(State); + } + } + break; + case 0: + break; + default: + State.Marks.push_back(pushToken(State.Tokens, CScriptToken(LEX_T_SKIP))); // push skip & skiperBeginIdx + tokenizeExpression(State, Flags); + pushToken(State.Tokens, ';'); + setTokenSkip(State); + break; + } + +} + +int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, int Match, int Alternate) { + if(Match == ';' && l->tk != ';' && (l->lineBreakBeforeToken || l->tk=='}' || l->tk==LEX_EOF)) + Tokens.push_back(CScriptToken(';')); // inject ';' + else + Tokens.push_back(CScriptToken(l, Match, Alternate)); + return Tokens.size()-1; +} +int CScriptTokenizer::pushToken(TOKEN_VECT &Tokens, const CScriptToken &Token) { + int ret = Tokens.size(); + Tokens.push_back(Token); + return ret; +} +void CScriptTokenizer::pushForwarder(TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks) { + Marks.push_back(Tokens.size()); + CScriptToken token(LEX_T_FORWARD); + Tokens.push_back(token); + Forwarders.push_back(token.Forwarder()); +} +void CScriptTokenizer::pushForwarder(ScriptTokenState &State, bool noMarks/*=false*/) { + if(!noMarks) State.Marks.push_back(State.Tokens.size()); + CScriptToken token(LEX_T_FORWARD); + State.Tokens.push_back(token); + State.Forwarders.push_back(token.Forwarder()); +} +void CScriptTokenizer::removeEmptyForwarder(ScriptTokenState &State) +{ + CScriptTokenDataForwardsPtr &forwarder = State.Forwarders.back(); + forwarder->vars_in_letscope.clear(); + if(forwarder->empty()) + State.Tokens.erase(State.Tokens.begin()+State.Marks.back()); + State.Forwarders.pop_back(); + State.Marks.pop_back(); +} + +void CScriptTokenizer::removeEmptyForwarder( TOKEN_VECT &Tokens, FORWARDER_VECTOR_t &Forwarders, vector &Marks ) +{ + CScriptTokenDataForwardsPtr &forwarder = Forwarders.back(); + forwarder->vars_in_letscope.clear(); + if(forwarder->empty()) + Tokens.erase(Tokens.begin()+Marks.back()); + Forwarders.pop_back(); + Marks.pop_back(); +} +void CScriptTokenizer::throwTokenNotExpected() { + throw new CScriptException(SyntaxError, "'"+CScriptToken::getTokenStr(l->tk)+"' was not expected", l->currentFile, l->currentLine(), l->currentColumn()); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVar +////////////////////////////////////////////////////////////////////////// + +CScriptVar::CScriptVar(CTinyJS *Context, const CScriptVarPtr &Prototype) { + extensible = true; + context = Context; + memset(temporaryMark, 0, sizeof(temporaryMark)); + if(context->first) { + next = context->first; + next->prev = this; + } else { + next = 0; + } + context->first = this; + prev = 0; + refs = 0; + if(Prototype) + addChild(TINYJS___PROTO___VAR, Prototype, SCRIPTVARLINK_WRITABLE); +#if DEBUG_MEMORY + mark_allocated(this); +#endif +} +CScriptVar::CScriptVar(const CScriptVar &Copy) { + extensible = Copy.extensible; + context = Copy.context; + memset(temporaryMark, 0, sizeof(temporaryMark)); + if(context->first) { + next = context->first; + next->prev = this; + } else { + next = 0; + } + context->first = this; + prev = 0; + refs = 0; + SCRIPTVAR_CHILDS_cit it; + for(it = Copy.Childs.begin(); it!= Copy.Childs.end(); ++it) { + addChild((*it)->getName(), (*it)->getVarPtr(), (*it)->getFlags()); + } + +#if DEBUG_MEMORY + mark_allocated(this); +#endif +} +CScriptVar::~CScriptVar(void) { +#if DEBUG_MEMORY + mark_deallocated(this); +#endif + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) + (*it)->setOwner(0); + removeAllChildren(); + if(prev) + prev->next = next; + else + context->first = next; + if(next) + next->prev = prev; +} + +/// Type + +bool CScriptVar::isObject() {return false;} +bool CScriptVar::isError() {return false;} +bool CScriptVar::isArray() {return false;} +bool CScriptVar::isRegExp() {return false;} +bool CScriptVar::isAccessor() {return false;} +bool CScriptVar::isNull() {return false;} +bool CScriptVar::isUndefined() {return false;} +bool CScriptVar::isNaN() {return false;} +bool CScriptVar::isString() {return false;} +bool CScriptVar::isInt() {return false;} +bool CScriptVar::isBool() {return false;} +int CScriptVar::isInfinity() { return 0; } ///< +1==POSITIVE_INFINITY, -1==NEGATIVE_INFINITY, 0==is not an InfinityVar +bool CScriptVar::isDouble() {return false;} +bool CScriptVar::isRealNumber() {return false;} +bool CScriptVar::isNumber() {return false;} +bool CScriptVar::isPrimitive() {return false;} +bool CScriptVar::isFunction() {return false;} +bool CScriptVar::isNative() {return false;} +bool CScriptVar::isBounded() {return false;} +bool CScriptVar::isIterator() {return false;} +bool CScriptVar::isGenerator() {return false;} + +////////////////////////////////////////////////////////////////////////// +/// Value +////////////////////////////////////////////////////////////////////////// + +CScriptVarPrimitivePtr CScriptVar::getRawPrimitive() { + return CScriptVarPrimitivePtr(); // default NULL-Ptr +} +CScriptVarPrimitivePtr CScriptVar::toPrimitive() { + return toPrimitive_hintNumber(); +} + +CScriptVarPrimitivePtr CScriptVar::toPrimitive(CScriptResult &execute) { + return toPrimitive_hintNumber(execute); +} + +CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(int32_t radix) { + CScriptResult execute; + CScriptVarPrimitivePtr var = toPrimitive_hintString(execute, radix); + execute.cThrow(); + return var; +} +CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintString(CScriptResult &execute, int32_t radix) { + if(execute) { + if(!isPrimitive()) { + CScriptVarPtr ret = callJS_toString(execute, radix); + if(execute && !ret->isPrimitive()) { + ret = callJS_valueOf(execute); + if(execute && !ret->isPrimitive()) + context->throwError(execute, TypeError, "can't convert to primitive type"); + } + return ret; + } + return this; + } + return constScriptVar(Undefined); +} +CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber() { + CScriptResult execute; + CScriptVarPrimitivePtr var = toPrimitive_hintNumber(execute); + execute.cThrow(); + return var; +} +CScriptVarPrimitivePtr CScriptVar::toPrimitive_hintNumber(CScriptResult &execute) { + if(execute) { + if(!isPrimitive()) { + CScriptVarPtr ret = callJS_valueOf(execute); + if(execute && !ret->isPrimitive()) { + ret = callJS_toString(execute); + if(execute && !ret->isPrimitive()) + context->throwError(execute, TypeError, "can't convert to primitive type"); + } + return ret; + } + return this; + } + return constScriptVar(Undefined); +} + +CScriptVarPtr CScriptVar::callJS_valueOf(CScriptResult &execute) { + if(execute) { + CScriptVarPtr FncValueOf = findChildWithPrototypeChain("valueOf").getter(execute); + if(FncValueOf && FncValueOf != context->objectPrototype_valueOf) { // custom valueOf in JavaScript + if(FncValueOf->isFunction()) { // no Error if toString not callable + vector Params; + return context->callFunction(execute, FncValueOf, Params, this); + } + } else + return valueOf_CallBack(); + } + return this; +} +CScriptVarPtr CScriptVar::valueOf_CallBack() { + return this; +} + +CScriptVarPtr CScriptVar::callJS_toString(CScriptResult &execute, int radix/*=0*/) { + if(execute) { + CScriptVarPtr FncToString = findChildWithPrototypeChain("toString").getter(execute); + if(FncToString && FncToString != context->objectPrototype_toString) { // custom valueOf in JavaScript + if(FncToString->isFunction()) { // no Error if toString not callable + vector Params; + Params.push_back(newScriptVar(radix)); + return context->callFunction(execute, FncToString, Params, this); + } + } else + return toString_CallBack(execute, radix); + } + return this; +} +CScriptVarPtr CScriptVar::toString_CallBack(CScriptResult &execute, int radix/*=0*/) { + return this; +} + +CNumber CScriptVar::toNumber() { return toPrimitive_hintNumber()->toNumber_Callback(); } +CNumber CScriptVar::toNumber(CScriptResult &execute) { return toPrimitive_hintNumber(execute)->toNumber_Callback(); } +bool CScriptVar::toBoolean() { return true; } +string CScriptVar::toString(int32_t radix) { return toPrimitive_hintString(radix)->toCString(radix); } +string CScriptVar::toString(CScriptResult &execute, int32_t radix/*=0*/) { return toPrimitive_hintString(execute, radix)->toCString(radix); } + +int CScriptVar::getInt() { return toNumber().toInt32(); } +double CScriptVar::getDouble() { return toNumber().toDouble(); } +bool CScriptVar::getBool() { return toBoolean(); } +string CScriptVar::getString() { return toPrimitive_hintString()->toCString(); } + +CScriptTokenDataFnc *CScriptVar::getFunctionData() { return 0; } + +CScriptVarPtr CScriptVar::toIterator(int Mode/*=3*/) { + CScriptResult execute; + CScriptVarPtr var = toIterator(execute, Mode); + execute.cThrow(); + return var; +} +CScriptVarPtr CScriptVar::toIterator(CScriptResult &execute, int Mode/*=3*/) { + if(!execute) return constScriptVar(Undefined); + if(isIterator()) return this; + CScriptVarFunctionPtr Generator(findChildWithPrototypeChain("__iterator__").getter(execute)); + vector args; + if(Generator) return context->callFunction(execute, Generator, args, this); + return newScriptVarDefaultIterator(context, this, Mode); +} + +string CScriptVar::getParsableString() { + uint32_t UniqueID = context->allocUniqueID(); + bool hasRecursion=false; + string ret = getParsableString("", " ", UniqueID, hasRecursion); + context->freeUniqueID(); + return ret; +} + +string CScriptVar::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { + getParsableStringRecursionsCheck(); + return indentString+toString(); +} + +CScriptVarPtr CScriptVar::getNumericVar() { return newScriptVar(toNumber()); } + +////// Flags + +void CScriptVar::seal() { + preventExtensions(); + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) + (*it)->setConfigurable(false); +} +bool CScriptVar::isSealed() const { + if(isExtensible()) return false; + for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it) + if((*it)->isConfigurable()) return false; + return true; +} +void CScriptVar::freeze() { + preventExtensions(); + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) + (*it)->setConfigurable(false), (*it)->setWritable(false); +} +bool CScriptVar::isFrozen() const { + if(isExtensible()) return false; + for(SCRIPTVAR_CHILDS_cit it = Childs.begin(); it != Childs.end(); ++it) + if((*it)->isConfigurable() || (*it)->isWritable()) return false; + return true; +} + +////// Childs + +CScriptVarPtr CScriptVar::getOwnPropertyDescriptor(const string &Name) { + CScriptVarLinkPtr child = findChild(Name); + if(!child) { + CScriptVarStringPtr strVar = getRawPrimitive(); + uint32_t Idx; + if (strVar && (Idx=isArrayIndex(Name))!=uint32_t(-1) && IdxstringLength()) { + int Char = strVar->getChar(Idx); + CScriptVarPtr ret = newScriptVar(Object); + ret->addChild("value", newScriptVar(string(1, (char)Char))); + ret->addChild("writable", constScriptVar(false)); + ret->addChild("enumerable", constScriptVar(true)); + ret->addChild("configurable", constScriptVar(false)); + return ret; + } + } + + if(!child || child->getVarPtr()->isUndefined()) return constScriptVar(Undefined); + CScriptVarPtr ret = newScriptVar(Object); + if(child->getVarPtr()->isAccessor()) { + CScriptVarLinkPtr value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR); + ret->addChild("get", value ? value->getVarPtr() : constScriptVar(Undefined)); + value = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR); + ret->addChild("set", value ? value->getVarPtr() : constScriptVar(Undefined)); + } else { + ret->addChild("value", child->getVarPtr()->valueOf_CallBack()); + ret->addChild("writable", constScriptVar(child->isWritable())); + } + ret->addChild("enumerable", constScriptVar(child->isEnumerable())); + ret->addChild("configurable", constScriptVar(child->isConfigurable())); + return ret; +} +const char *CScriptVar::defineProperty(const string &Name, CScriptVarPtr Attributes) { + CScriptVarPtr attr; + CScriptVarLinkPtr child = findChildWithStringChars(Name); + + CScriptVarPtr attr_value = Attributes->findChild("value"); + CScriptVarPtr attr_writable = Attributes->findChild("writable"); + CScriptVarPtr attr_get = Attributes->findChild("get"); + CScriptVarPtr attr_set = Attributes->findChild("set"); + CScriptVarPtr attr_enumerable = Attributes->findChild("enumerable"); + CScriptVarPtr attr_configurable = Attributes->findChild("configurable"); + bool attr_isDataDescriptor = !attr_get && !attr_set; + if(!attr_isDataDescriptor && (attr_value || attr_writable)) return "property descriptors must not specify a value or be writable when a getter or setter has been specified"; + if(attr_isDataDescriptor) { + if(attr_get && (!attr_get->isUndefined() || !attr_get->isFunction())) return "property descriptor's getter field is neither undefined nor a function"; + if(attr_set && (!attr_set->isUndefined() || !attr_set->isFunction())) return "property descriptor's setter field is neither undefined nor a function"; + } + if(!child) { + if(!isExtensible()) return "is not extensible"; + if(attr_isDataDescriptor) { + child = addChild(Name, attr_value?attr_value:constScriptVar(Undefined), 0); + if(attr_writable) child->setWritable(attr_writable->toBoolean()); + } else { + child = addChild(Name, newScriptVarAccessor(context, attr_get, attr_set), SCRIPTVARLINK_WRITABLE); + } + } else { + if(!child->isConfigurable()) { + if(attr_configurable && attr_configurable->toBoolean()) goto cant_redefine; + if(attr_enumerable && attr_enumerable->toBoolean() != child->isEnumerable()) goto cant_redefine; + if(child->getVarPtr()->isAccessor()) { + if(attr_isDataDescriptor) goto cant_redefine; + if(attr_get && attr_get != child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR)) goto cant_redefine; + if(attr_set && attr_set != child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR)) goto cant_redefine; + } else if(!attr_isDataDescriptor) goto cant_redefine; + else if(!child->isWritable()) { + if(attr_writable && attr_writable->toBoolean()) goto cant_redefine; + if(attr_value && !attr_value->mathsOp(child, LEX_EQUAL)->toBoolean()) goto cant_redefine; + } + } + if(attr_isDataDescriptor) { + if(child->getVarPtr()->isAccessor()) child->setWritable(false); + child->setVarPtr(attr_value?attr_value:constScriptVar(Undefined)); + if(attr_writable) child->setWritable(attr_writable->toBoolean()); + } else { + if(child->getVarPtr()->isAccessor()) { + if(!attr_get) attr_get = child->getVarPtr()->findChild(TINYJS_ACCESSOR_GET_VAR); + if(!attr_set) attr_set = child->getVarPtr()->findChild(TINYJS_ACCESSOR_SET_VAR); + } + child->setVarPtr(newScriptVarAccessor(context, attr_get, attr_set)); + child->setWritable(true); + } + } + if(attr_enumerable) child->setEnumerable(attr_enumerable->toBoolean()); + if(attr_configurable) child->setConfigurable(attr_configurable->toBoolean()); + return 0; +cant_redefine: + return "can't redefine non-configurable property"; +} + +CScriptVarLinkPtr CScriptVar::findChild(const string &childName) { + if(Childs.empty()) return 0; + SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); + if(it != Childs.end() && (*it)->getName() == childName) + return *it; + return 0; +} + +CScriptVarLinkWorkPtr CScriptVar::findChildWithStringChars(const string &childName) { + CScriptVarLinkWorkPtr child = findChild(childName); + if(child) return child; + CScriptVarStringPtr strVar = getRawPrimitive(); + uint32_t Idx; + if (strVar && (Idx=isArrayIndex(childName))!=uint32_t(-1)) { + if (IdxstringLength()) { + int Char = strVar->getChar(Idx); + child(newScriptVar(string(1, (char)Char)), childName, SCRIPTVARLINK_ENUMERABLE); + } else { + child(constScriptVar(Undefined), childName, SCRIPTVARLINK_ENUMERABLE); + } + child.setReferencedOwner(this); // fake referenced Owner + return child; + } + return 0; +} + +CScriptVarLinkPtr CScriptVar::findChildInPrototypeChain(const string &childName) { + unsigned int uniqueID = context->allocUniqueID(); + // Look for links to actual parent classes + CScriptVarPtr object = this; + CScriptVarLinkPtr __proto__; + while( object->getTemporaryMark() != uniqueID && (__proto__ = object->findChild(TINYJS___PROTO___VAR)) ) { + CScriptVarLinkPtr implementation = __proto__->getVarPtr()->findChild(childName); + if (implementation) { + context->freeUniqueID(); + return implementation; + } + object->setTemporaryMark(uniqueID); // prevents recursions + object = __proto__; + } + context->freeUniqueID(); + return 0; +} + +CScriptVarLinkWorkPtr CScriptVar::findChildWithPrototypeChain(const string &childName) { + CScriptVarLinkWorkPtr child = findChildWithStringChars(childName); + if(child) return child; + child = findChildInPrototypeChain(childName); + if(child) { + child(child->getVarPtr(), child->getName(), child->getFlags()); // recreate implementation + child.setReferencedOwner(this); // fake referenced Owner + } + return child; +} +CScriptVarLinkPtr CScriptVar::findChildByPath(const string &path) { + string::size_type p = path.find('.'); + CScriptVarLinkPtr child; + if (p == string::npos) + return findChild(path); + if( (child = findChild(path.substr(0,p))) ) + return child->getVarPtr()->findChildByPath(path.substr(p+1)); + return 0; +} + +CScriptVarLinkPtr CScriptVar::findChildOrCreate(const string &childName/*, int varFlags*/) { + CScriptVarLinkPtr l = findChild(childName); + if (l) return l; + return addChild(childName, constScriptVar(Undefined)); + // return addChild(childName, new CScriptVar(context, TINYJS_BLANK_DATA, varFlags)); +} + +CScriptVarLinkPtr CScriptVar::findChildOrCreateByPath(const string &path) { + string::size_type p = path.find('.'); + if (p == string::npos) + return findChildOrCreate(path); + string childName(path, 0, p); + CScriptVarLinkPtr l = findChild(childName); + if (!l) l = addChild(childName, newScriptVar(Object)); + return l->getVarPtr()->findChildOrCreateByPath(path.substr(p+1)); +} + +void CScriptVar::keys(set &Keys, bool OnlyEnumerable/*=true*/, uint32_t ID/*=0*/) +{ + if(ID) setTemporaryMark(ID); + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { + if(!OnlyEnumerable || (*it)->isEnumerable()) + Keys.insert((*it)->getName()); + } + CScriptVarStringPtr isStringObj = this->getRawPrimitive(); + if(isStringObj) { + uint32_t length = isStringObj->stringLength(); + for(uint32_t i=0; igetVarPtr()->getTemporaryMark() != ID ) + __proto__->getVarPtr()->keys(Keys, OnlyEnumerable, ID); +} + +/// add & remove +CScriptVarLinkPtr CScriptVar::addChild(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { + CScriptVarLinkPtr link; + SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); + if(it == Childs.end() || (*it)->getName() != childName) { + link = CScriptVarLinkPtr(child?child:constScriptVar(Undefined), childName, linkFlags); + link->setOwner(this); + + Childs.insert(it, 1, link); +#ifdef _DEBUG + } else { + ASSERT(0); // addChild - the child exists +#endif + } + return link; +} +CScriptVarLinkPtr CScriptVar::addChildNoDup(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { + return addChildOrReplace(childName, child, linkFlags); +} +CScriptVarLinkPtr CScriptVar::addChildOrReplace(const string &childName, const CScriptVarPtr &child, int linkFlags /*= SCRIPTVARLINK_DEFAULT*/) { + SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), childName); + if(it == Childs.end() || (*it)->getName() != childName) { + CScriptVarLinkPtr link(child, childName, linkFlags); + link->setOwner(this); + Childs.insert(it, 1, link); + return link; + } else { + (*it)->setVarPtr(child); + return (*it); + } +} + +bool CScriptVar::removeLink(CScriptVarLinkPtr &link) { + if (!link) return false; + SCRIPTVAR_CHILDS_it it = lower_bound(Childs.begin(), Childs.end(), link->getName()); + if(it != Childs.end() && (*it) == link) { + Childs.erase(it); +#ifdef _DEBUG + } else { + ASSERT(0); // removeLink - the link is not atached to this var +#endif + } + link.clear(); + return true; +} +void CScriptVar::removeAllChildren() { + Childs.clear(); +} + +CScriptVarPtr CScriptVar::getArrayIndex(uint32_t idx) { + CScriptVarLinkPtr link = findChild(int2string(idx)); + if (link) return link; + else return constScriptVar(Undefined); // undefined +} + +void CScriptVar::setArrayIndex(uint32_t idx, const CScriptVarPtr &value) { + string sIdx = int2string(idx); + CScriptVarLinkPtr link = findChild(sIdx); + + if (link) { + link->setVarPtr(value); + } else { + addChild(sIdx, value); + } +} + +uint32_t CScriptVar::getArrayLength() { + if (!isArray() || Childs.size()==0) return 0; + return isArrayIndex(Childs.back()->getName())+1; +} + +CScriptVarPtr CScriptVar::mathsOp(const CScriptVarPtr &b, int op) { + CScriptResult execute; + return context->mathsOp(execute, this, b, op); +} + +void CScriptVar::trace(const string &name) { + string indentStr; + uint32_t uniqueID = context->allocUniqueID(); + trace(indentStr, uniqueID, name); + context->freeUniqueID(); +} +void CScriptVar::trace(string &indentStr, uint32_t uniqueID, const string &name) { + string indent = " "; + const char *extra=""; + if(getTemporaryMark() == uniqueID) + extra = " recursion detected"; + TRACE("%s'%s' = '%s' %s%s\n", + indentStr.c_str(), + name.c_str(), + toString().c_str(), + getFlagsAsString().c_str(), + extra); + if(getTemporaryMark() != uniqueID) { + setTemporaryMark(uniqueID); + indentStr+=indent; + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { + if((*it)->isEnumerable()) + (*it)->getVarPtr()->trace(indentStr, uniqueID, (*it)->getName()); + } + indentStr = indentStr.substr(0, indentStr.length()-2); + } +} + +string CScriptVar::getFlagsAsString() { + string flagstr = ""; + if (isFunction()) flagstr = flagstr + "FUNCTION "; + if (isObject()) flagstr = flagstr + "OBJECT "; + if (isArray()) flagstr = flagstr + "ARRAY "; + if (isNative()) flagstr = flagstr + "NATIVE "; + if (isDouble()) flagstr = flagstr + "DOUBLE "; + if (isInt()) flagstr = flagstr + "INTEGER "; + if (isBool()) flagstr = flagstr + "BOOLEAN "; + if (isString()) flagstr = flagstr + "STRING "; + if (isRegExp()) flagstr = flagstr + "REGEXP "; + if (isNaN()) flagstr = flagstr + "NaN "; + if (isInfinity()) flagstr = flagstr + "INFINITY "; + return flagstr; +} + +CScriptVar *CScriptVar::ref() { + refs++; + return this; +} +void CScriptVar::unref() { + refs--; + ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n"); + if (refs==0) + delete this; +} + +int CScriptVar::getRefs() { + return refs; +} + +void CScriptVar::setTemporaryMark_recursive(uint32_t ID) +{ + if(getTemporaryMark() != ID) { + setTemporaryMark(ID); + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { + (*it)->getVarPtr()->setTemporaryMark_recursive(ID); + } + } +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLink +////////////////////////////////////////////////////////////////////////// + +CScriptVarLink::CScriptVarLink(const CScriptVarPtr &Var, const string &Name /*=TINYJS_TEMP_NAME*/, int Flags /*=SCRIPTVARLINK_DEFAULT*/) + : name(Name), owner(0), flags(Flags), refs(0) { +#if DEBUG_MEMORY + mark_allocated(this); +#endif + var = Var; +} + +CScriptVarLink::~CScriptVarLink() { +#if DEBUG_MEMORY + mark_deallocated(this); +#endif +} + +CScriptVarLink *CScriptVarLink::ref() { + refs++; + return this; +} +void CScriptVarLink::unref() { + refs--; + ASSERT(refs>=0); // printf("OMFG, we have unreffed too far!\n"); + if (refs==0) + delete this; +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkPtr +////////////////////////////////////////////////////////////////////////// + +CScriptVarLinkPtr & CScriptVarLinkPtr::operator()( const CScriptVarPtr &var, const std::string &name /*= TINYJS_TEMP_NAME*/, int flags /*= SCRIPTVARLINK_DEFAULT*/ ) { + if(link && link->refs == 1) { // the link is only refered by this + link->name = name; + link->owner = 0; + link->flags = flags; + link->var = var; + } else { + if(link) link->unref(); + link = (new CScriptVarLink(var, name, flags))->ref(); + } + return *this; +} + +CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter() { + return CScriptVarLinkWorkPtr(*this).getter(); +} + +CScriptVarLinkWorkPtr CScriptVarLinkPtr::getter( CScriptResult &execute ) { + return CScriptVarLinkWorkPtr(*this).getter(execute); +} + +CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( const CScriptVarPtr &Var ) { + return CScriptVarLinkWorkPtr(*this).setter(Var); +} + +CScriptVarLinkWorkPtr CScriptVarLinkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) { + return CScriptVarLinkWorkPtr(*this).setter(execute, Var); +} + +bool CScriptVarLinkPtr::operator <(const string &rhs) const { + uint32_t lhs_int = isArrayIndex(link->getName()); + uint32_t rhs_int = isArrayIndex(rhs); + if(lhs_int==uint32_t(-1)) { + if(rhs_int==uint32_t(-1)) + return link->getName() < rhs; + else + return true; + } else if(rhs_int==uint32_t(-1)) + return false; + return lhs_int < rhs_int; +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarLinkWorkPtr +////////////////////////////////////////////////////////////////////////// + +CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter() { + if(link && link->getVarPtr()) { + CScriptResult execute; + CScriptVarPtr ret = getter(execute); + execute.cThrow(); + return ret; + } + return *this; +} +CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::getter(CScriptResult &execute) { + if(execute && link && link->getVarPtr() && link->getVarPtr()->isAccessor()) { + const CScriptVarPtr &var = link->getVarPtr(); + CScriptVarLinkPtr getter = var->findChild(TINYJS_ACCESSOR_GET_VAR); + if(getter) { + vector Params; + ASSERT(getReferencedOwner()); + return getter->getVarPtr()->getContext()->callFunction(execute, getter->getVarPtr(), Params, getReferencedOwner()); + } else + return var->constScriptVar(Undefined); + } else + return *this; +} +CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( const CScriptVarPtr &Var ) { + if(link && link->getVarPtr()) { + CScriptResult execute; + CScriptVarPtr ret = setter(execute, Var); + execute.cThrow(); + return ret; + } + return *this; +} + +CScriptVarLinkWorkPtr CScriptVarLinkWorkPtr::setter( CScriptResult &execute, const CScriptVarPtr &Var ) { + if(execute) { + if(link) { + if(link->getVarPtr() && link->getVarPtr()->isAccessor()) { + const CScriptVarPtr &var = link->getVarPtr(); + CScriptVarLinkPtr setter = var->findChild(TINYJS_ACCESSOR_SET_VAR); + if(setter) { + vector Params; + Params.push_back(Var); + ASSERT(getReferencedOwner()); + setter->getVarPtr()->getContext()->callFunction(execute, setter->getVarPtr(), Params, getReferencedOwner()); + } + } else + link->setVarPtr(Var); + } + } + return *this; +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarPrimitive +////////////////////////////////////////////////////////////////////////// + +CScriptVarPrimitive::~CScriptVarPrimitive(){} + +bool CScriptVarPrimitive::isPrimitive() { return true; } +CScriptVarPrimitivePtr CScriptVarPrimitive::getRawPrimitive() { return this; } +bool CScriptVarPrimitive::toBoolean() { return false; } +CScriptVarPtr CScriptVarPrimitive::toObject() { return this; } +CScriptVarPtr CScriptVarPrimitive::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { + return newScriptVar(toCString(radix)); +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptVarUndefined +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Undefined); +CScriptVarUndefined::CScriptVarUndefined(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { } +CScriptVarUndefined::~CScriptVarUndefined() {} +CScriptVarPtr CScriptVarUndefined::clone() { return new CScriptVarUndefined(*this); } +bool CScriptVarUndefined::isUndefined() { return true; } + +CNumber CScriptVarUndefined::toNumber_Callback() { return NaN; } +string CScriptVarUndefined::toCString(int radix/*=0*/) { return "undefined"; } +string CScriptVarUndefined::getVarType() { return "undefined"; } + + +////////////////////////////////////////////////////////////////////////// +// CScriptVarNull +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Null); +CScriptVarNull::CScriptVarNull(CTinyJS *Context) : CScriptVarPrimitive(Context, Context->objectPrototype) { } +CScriptVarNull::~CScriptVarNull() {} +CScriptVarPtr CScriptVarNull::clone() { return new CScriptVarNull(*this); } +bool CScriptVarNull::isNull() { return true; } + +CNumber CScriptVarNull::toNumber_Callback() { return 0; } +string CScriptVarNull::toCString(int radix/*=0*/) { return "null"; } +string CScriptVarNull::getVarType() { return "null"; } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarString +////////////////////////////////////////////////////////////////////////// + +CScriptVarString::CScriptVarString(CTinyJS *Context, const string &Data) : CScriptVarPrimitive(Context, Context->stringPrototype), data(Data) { + addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT); +/* + CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0); + CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarString::native_Length, 0)); + getter->setFunctionData(new CScriptTokenDataFnc); + acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); +*/ +} +CScriptVarString::~CScriptVarString() {} +CScriptVarPtr CScriptVarString::clone() { return new CScriptVarString(*this); } +bool CScriptVarString::isString() { return true; } + +bool CScriptVarString::toBoolean() { return data.length()!=0; } +CNumber CScriptVarString::toNumber_Callback() { return data.c_str(); } +string CScriptVarString::toCString(int radix/*=0*/) { return data; } + +string CScriptVarString::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return indentString+getJSString(data); } +string CScriptVarString::getVarType() { return "string"; } + +CScriptVarPtr CScriptVarString::toObject() { + CScriptVarPtr ret = newScriptVar(CScriptVarPrimitivePtr(this), context->stringPrototype); + ret->addChild("length", newScriptVar(data.size()), SCRIPTVARLINK_CONSTANT); + return ret; +} + +CScriptVarPtr CScriptVarString::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { + return this; +} + +int CScriptVarString::getChar(uint32_t Idx) { + if((string::size_type)Idx >= data.length()) + return -1; + else + return (unsigned char)data[Idx]; +} + + +////////////////////////////////////////////////////////////////////////// +/// CNumber +////////////////////////////////////////////////////////////////////////// + +NegativeZero_t NegativeZero; +declare_dummy_t(NaN); +Infinity InfinityPositive(1); +Infinity InfinityNegative(-1); +#if 1 +static inline bool _isNaN(volatile double *Value1, volatile double *Value2) { + return !(*Value1==*Value2); +} + +static inline bool isNaN(double Value) { + return _isNaN(&Value, &Value); +} +inline bool isNegZero(double d) { + double x=-0.0; + return memcmp(&d, &x, sizeof(double))==0; +} + +CNumber &CNumber::operator=(double Value) { + double integral; + if(isNegZero(Value)) + type=tnNULL, Int32=0; + else if(numeric_limits::has_infinity && Value == numeric_limits::infinity()) + type=tInfinity, Int32=1; + else if(numeric_limits::has_infinity && Value == -numeric_limits::infinity()) + type=tInfinity, Int32=-1; + else if(::isNaN(Value) || Value == numeric_limits::quiet_NaN() || Value == std::numeric_limits::signaling_NaN()) + type=tNaN, Int32=0; + else if(modf(Value, &integral)==0.0 && numeric_limits::min()<=integral && integral<=numeric_limits::max()) + type=tInt32, Int32=int32_t(integral); + else + type=tDouble, Double=Value; + return *this; +} +CNumber &CNumber::operator=(const char *str) { + + while(isWhitespace(*str)) str++; + const char *start = str, *endptr; + if(*str == '-' || *str == '+') str++; + if(*str == '0' && ( str[1]=='x' || str[1]=='X')) + parseInt(start, 16, &endptr); + else if(*str == '0' && str[1]>='0' && str[1]<='7') + parseInt(start, 8, &endptr); + else + parseFloat(start, &endptr); + while(isWhitespace(*endptr)) endptr++; + if(*endptr != '\0') + type=tNaN, Int32=0; + return *this; +} +int32_t CNumber::parseInt(const char * str, int32_t radix/*=0*/, const char **endptr/*=0*/) { + type=tInt32, Int32=0; + if(endptr) *endptr = str; + bool stripPrefix = false; //< is true if radix==0 or radix==16 + if(radix == 0) { + radix=10; + stripPrefix = true; + } else if(radix < 2 || radix > 36) { + type = tNaN; + return 0; + } else + stripPrefix = radix == 16; + while(isWhitespace(*str)) str++; + int sign=1; + if(*str=='-') sign=-1,str++; + else if(*str=='+') str++; + if(stripPrefix && *str=='0' && (str[1]=='x' || str[1]=='X')) str+=2, radix=16; + else if(stripPrefix && *str=='0' && str[1]>='0' && str[1]<='7') str+=1, radix=8; + int32_t max = 0x7fffffff/radix; + const char *start = str; + for( ; *str; str++) { + if(*str >= '0' && *str <= '0'-1+radix) Int32 = Int32*radix+*str-'0'; + else if(*str>='a' && *str<='a'-11+radix) Int32 = Int32*radix+*str-'a'+10; + else if(*str>='A' && *str<='A'-11+radix) Int32 = Int32*radix+*str-'A'+10; + else break; + if(Int32 >= max) { + type=tDouble, Double=double(Int32); + for(str++ ; *str; str++) { + if(*str >= '0' && *str <= '0'-1+radix) Double = Double *radix+*str-'0'; + else if(*str>='a' && *str<='a'-11+radix) Double = Double *radix+*str-'a'+10; + else if(*str>='A' && *str<='A'-11+radix) Double = Double *radix+*str-'A'+10; + else break; + } + break; + } + } + if(str == start) { + type= tNaN; + return 0; + } + if(sign<0 && ((type==tInt32 && Int32==0) || (type==tDouble && Double==0.0))) { type=tnNULL,Int32=0; return radix; } + if(type==tInt32) operator=(sign<0 ? -Int32 : Int32); + else operator=(sign<0 ? -Double : Double); + if(endptr) *endptr = (char*)str; + return radix; +} + +void CNumber::parseFloat(const char * str, const char **endptr/*=0*/) { + type=tInt32, Int32=0; + if(endptr) *endptr = str; + while(isWhitespace(*str)) str++; + int sign=1; + if(*str=='-') sign=-1,str++; + else if(*str=='+') str++; + if(strncmp(str, "Infinity", 8) == 0) { type=tInfinity, Int32=sign; return; } + double d = strtod(str, (char**)endptr); + operator=(sign>0 ? d : -d); + return; +} + +CNumber CNumber::add(const CNumber &Value) const { + if(type==tNaN || Value.type==tNaN) + return CNumber(tNaN); + else if(type==tInfinity || Value.type==tInfinity) { + if(type!=tInfinity) + return Value; + else if(Value.type!=tInfinity || sign()==Value.sign()) + return *this; + else + return CNumber(tNaN); + } else if(type==tnNULL) + return Value; + else if(Value.type==tnNULL) + return *this; + else if(type==tDouble || Value.type==tDouble) + return CNumber(toDouble()+Value.toDouble()); + else { + int32_t range_max = numeric_limits::max(); + int32_t range_min = numeric_limits::min(); + if(Int32>0) range_max-=Int32; + else if(Int32<0) range_min-=Int32; + if(range_min<=Value.Int32 && Value.Int32<=range_max) + return CNumber(Int32+Value.Int32); + else + return CNumber(double(Int32)+double(Value.Int32)); + } +} + +CNumber CNumber::operator-() const { + switch(type) { + case tInt32: + if(Int32==0) + return CNumber(NegativeZero); + case tnNULL: + return CNumber(-Int32); + case tDouble: + return CNumber(-Double); + case tInfinity: + return CNumber(tInfinity, -Int32); + default: + return CNumber(tNaN); + } +} + +static inline int bits(uint32_t Value) { + uint32_t b=0, mask=0xFFFF0000UL; + for(int shift=16; shift>0 && Value!=0; shift>>=1, mask>>=shift) { + if(Value & mask) { + b += shift; + Value>>=shift; + } + } + return b; +} +static inline int bits(int32_t Value) { + return bits(uint32_t(Value<0?-Value:Value)); +} + +CNumber CNumber::multi(const CNumber &Value) const { + if(type==tNaN || Value.type==tNaN) + return CNumber(tNaN); + else if(type==tInfinity || Value.type==tInfinity) { + if(isZero() || Value.isZero()) + return CNumber(tNaN); + else + return CNumber(tInfinity, sign()==Value.sign()?1:-1); + } else if(isZero() || Value.isZero()) { + if(sign()==Value.sign()) + return CNumber(0); + else + return CNumber(NegativeZero); + } else if(type==tDouble || Value.type==tDouble) + return CNumber(toDouble()*Value.toDouble()); + else { + // Int32*Int32 + if(bits(Int32)+bits(Value.Int32) <= 29) + return CNumber(Int32*Value.Int32); + else + return CNumber(double(Int32)*double(Value.Int32)); + } +} + + +CNumber CNumber::div( const CNumber &Value ) const { + if(type==tNaN || Value.type==tNaN) return CNumber(tNaN); + int Sign = sign()*Value.sign(); + if(type==tInfinity) { + if(Value.type==tInfinity) return CNumber(tNaN); + else return CNumber(tInfinity, Sign); + } + if(Value.type==tInfinity) { + if(Sign<0) return CNumber(NegativeZero); + else return CNumber(0); + } else if(Value.isZero()) { + if(isZero()) return CNumber(tNaN); + else return CNumber(tInfinity, Sign); + } else + return CNumber(toDouble() / Value.toDouble()); +} + +CNumber CNumber::modulo( const CNumber &Value ) const { + if(type==tNaN || type==tInfinity || Value.type==tNaN || Value.isZero()) return CNumber(tNaN); + if(Value.type==tInfinity) return CNumber(*this); + if(isZero()) return CNumber(0); + if(type==tDouble || Value.type==tDouble || (Int32==numeric_limits::min() && Value == -1) /* use double to prevent integer overflow */ ) { + double n = toDouble(), d = Value.toDouble(), q; + modf(n/d, &q); + return CNumber(n - (d * q)); + } else + return CNumber(Int32 % Value.Int32); +} + +CNumber CNumber::round() const { + if(type != tDouble) return CNumber(*this); + if(Double < 0.0 && Double >= -0.5) + return CNumber(NegativeZero); + return CNumber(::floor(Double+0.5)); +} + +CNumber CNumber::floor() const { + if(type != tDouble) return CNumber(*this); + return CNumber(::floor(Double)); +} + +CNumber CNumber::ceil() const { + if(type != tDouble) return CNumber(*this); + return CNumber(::ceil(Double)); +} + +CNumber CNumber::abs() const { + if(sign()<0) return -CNumber(*this); + else return CNumber(*this); +} + +CNumber CNumber::shift(const CNumber &Value, bool Right) const { + int32_t lhs = toInt32(); + uint32_t rhs = Value.toUInt32() & 0x1F; + return CNumber(Right ? lhs>>rhs : lhs<>rhs : lhs< 9 ? ('a'-10) : '0')); + if(p==buf_end) { + char *new_buf = (char *)realloc(buf, buf_end-buf+16+1); // for '\0' + if(!new_buf) { free(buf); return 0; } + p = new_buf + (buf_end - buf); + buf_end = p + 16; + buf = new_buf; + } + } while (val > 0); + + // We now have the digit of the number in the buffer, but in reverse + // order. Thus we reverse them now. + *p-- = '\0'; + firstdig = buf; + if(*firstdig=='-') firstdig++; + do { + temp = *p; + *p = *firstdig; + *firstdig = temp; + p--; + firstdig++; + } while (firstdig < p); + return buf; +} + +static char *tiny_dtoa(double val, unsigned radix) { + char *buf, *buf_end, *p, temp; + unsigned digval; + + buf = (char*)malloc(64); + if(!buf) return 0; + buf_end = buf+64-2; // -1 for '.' , -1 for '\0' + + p = buf; + if (val < 0.0) { + *p++ = '-'; + val = -val; + } + + double val_1 = floor(val); + double val_2 = val - val_1; + + + do { + double tmp = val_1 / radix; + val_1 = floor(tmp); + digval = (unsigned)((tmp - val_1) * radix); + + *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0')); + if(p==buf_end) { + char *new_buf = (char *)realloc(buf, buf_end-buf+16+2); // +2 for '.' + '\0' + if(!new_buf) { free(buf); return 0; } + p = new_buf + (buf_end - buf); + buf_end = p + 16; + buf = new_buf; + } + } while (val_1 > 0.0); + + // We now have the digit of the number in the buffer, but in reverse + // order. Thus we reverse them now. + char *p1 = buf; + char *p2 = p-1; + do { + temp = *p2; + *p2-- = *p1; + *p1++ = temp; + } while (p1 < p2); + + if(val_2) { + *p++ = '.'; + do { + val_2 *= radix; + digval = (unsigned)(val_2); + val_2 -= digval; + + *p++ = (char) (digval + (digval > 9 ? ('a'-10) : '0')); + if(p==buf_end) { + char *new_buf = (char *)realloc(buf, buf_end-buf+16); + if(!new_buf) { free(buf); return 0; } + p = new_buf + (buf_end - buf); + buf_end = p + 16; + buf = new_buf; + } + } while (val_2 > 0.0); + + } + *p = '\0'; + return buf; +} +std::string CNumber::toString( uint32_t Radix/*=10*/ ) const { + char *str; + if(2 > Radix || Radix > 36) + Radix = 10; // todo error; + switch(type) { + case tInt32: + if( (str = tiny_ltoa(Int32, Radix)) ) { + string ret(str); free(str); + return ret; + } + break; + case tnNULL: + return "0"; + case tDouble: + if(Radix==10) { + ostringstream str; + str.unsetf(ios::floatfield); +#if (defined(_MSC_VER) && _MSC_VER >= 1600) || __cplusplus >= 201103L + str.precision(numeric_limits::max_digits10); +#else + str.precision(numeric_limits::digits10+2); +#endif + str << Double; + return str.str(); + } else if( (str = tiny_dtoa(Double, Radix)) ) { + string ret(str); free(str); + return ret; + } + break; + case tInfinity: + return Int32<0?"-Infinity":"Infinity"; + case tNaN: + return "NaN"; + } + return ""; +} + +double CNumber::toDouble() const +{ + switch(type) { + case tnNULL: + return -0.0; + case tInt32: + return double(Int32); + case tDouble: + return Double; + case tNaN: + return std::numeric_limits::quiet_NaN(); + case tInfinity: + return Int32<0 ? -std::numeric_limits::infinity():std::numeric_limits::infinity(); + } + return 0.0; +} + +#endif + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarNumber +////////////////////////////////////////////////////////////////////////// + +CScriptVarNumber::CScriptVarNumber(CTinyJS *Context, const CNumber &Data) : CScriptVarPrimitive(Context, Context->numberPrototype), data(Data) {} +CScriptVarNumber::~CScriptVarNumber() {} +CScriptVarPtr CScriptVarNumber::clone() { return new CScriptVarNumber(*this); } +bool CScriptVarNumber::isNumber() { return true; } +bool CScriptVarNumber::isInt() { return data.isInt32(); } +bool CScriptVarNumber::isDouble() { return data.isDouble(); } +bool CScriptVarNumber::isRealNumber() { return isInt() || isDouble(); } +bool CScriptVarNumber::isNaN() { return data.isNaN(); } +int CScriptVarNumber::isInfinity() { return data.isInfinity(); } + +bool CScriptVarNumber::toBoolean() { return data.toBoolean(); } +CNumber CScriptVarNumber::toNumber_Callback() { return data; } +string CScriptVarNumber::toCString(int radix/*=0*/) { return data.toString(radix); } + +string CScriptVarNumber::getVarType() { return "number"; } + +CScriptVarPtr CScriptVarNumber::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->numberPrototype); } +inline define_newScriptVar_Fnc(Number, CTinyJS *Context, const CNumber &Obj) { + if(!Obj.isInt32() && !Obj.isDouble()) { + if(Obj.isNaN()) return Context->constScriptVar(NaN); + if(Obj.isInfinity()) return Context->constScriptVar(Infinity(Obj.sign())); + if(Obj.isNegativeZero()) return Context->constScriptVar(NegativeZero); + } + return new CScriptVarNumber(Context, Obj); +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptVarBool +////////////////////////////////////////////////////////////////////////// + +CScriptVarBool::CScriptVarBool(CTinyJS *Context, bool Data) : CScriptVarPrimitive(Context, Context->booleanPrototype), data(Data) {} +CScriptVarBool::~CScriptVarBool() {} +CScriptVarPtr CScriptVarBool::clone() { return new CScriptVarBool(*this); } +bool CScriptVarBool::isBool() { return true; } + +bool CScriptVarBool::toBoolean() { return data; } +CNumber CScriptVarBool::toNumber_Callback() { return data?1:0; } +string CScriptVarBool::toCString(int radix/*=0*/) { return data ? "true" : "false"; } + +string CScriptVarBool::getVarType() { return "boolean"; } + +CScriptVarPtr CScriptVarBool::toObject() { return newScriptVar(CScriptVarPrimitivePtr(this), context->booleanPrototype); } + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObject +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Object); +CScriptVarObject::CScriptVarObject(CTinyJS *Context) : CScriptVar(Context, Context->objectPrototype) { } +CScriptVarObject::~CScriptVarObject() {} +CScriptVarPtr CScriptVarObject::clone() { return new CScriptVarObject(*this); } + +void CScriptVarObject::removeAllChildren() +{ + CScriptVar::removeAllChildren(); + value.clear(); +} + +CScriptVarPrimitivePtr CScriptVarObject::getRawPrimitive() { return value; } +bool CScriptVarObject::isObject() { return true; } + +string CScriptVarObject::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { + getParsableStringRecursionsCheck(); + string destination; + const char *nl = indent.size() ? "\n" : " "; + const char *comma = ""; + destination.append("{"); + if(Childs.size()) { + string new_indentString = indentString + indent; + for(SCRIPTVAR_CHILDS_it it = Childs.begin(); it != Childs.end(); ++it) { + if((*it)->isEnumerable()) { + destination.append(comma); comma=","; + destination.append(nl).append(new_indentString).append(getIDString((*it)->getName())); + destination.append(" : "); + destination.append((*it)->getVarPtr()->getParsableString(new_indentString, indent, uniqueID, hasRecursion)); + } + } + destination.append(nl).append(indentString); + } + destination.append("}"); + return destination; +} +string CScriptVarObject::getVarType() { return "object"; } +string CScriptVarObject::getVarTypeTagName() { return "Object"; } + +CScriptVarPtr CScriptVarObject::toObject() { return this; } + +CScriptVarPtr CScriptVarObject::valueOf_CallBack() { + if(value) + return value->valueOf_CallBack(); + return CScriptVar::valueOf_CallBack(); +} +CScriptVarPtr CScriptVarObject::toString_CallBack(CScriptResult &execute, int radix) { + if(value) + return value->toString_CallBack(execute, radix); + return newScriptVar("[object "+getVarTypeTagName()+"]"); +} + +void CScriptVarObject::setTemporaryMark_recursive( uint32_t ID) { + CScriptVar::setTemporaryMark_recursive(ID); + if(value) value->setTemporaryMark_recursive(ID); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarObjectTyped (simple Object with Typename +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(StopIteration); +CScriptVarObjectTypeTagged::~CScriptVarObjectTypeTagged() {} +CScriptVarPtr CScriptVarObjectTypeTagged::clone() { return new CScriptVarObjectTypeTagged(*this); } +std::string CScriptVarObjectTypeTagged::getVarTypeTagName() { return typeTagName; } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarError +////////////////////////////////////////////////////////////////////////// + +const char *ERROR_NAME[] = {"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError"}; + +CScriptVarError::CScriptVarError(CTinyJS *Context, ERROR_TYPES type, const char *message, const char *file, int line, int column) : CScriptVarObject(Context, Context->getErrorPrototype(type)) { + if(message && *message) addChild("message", newScriptVar(message)); + if(file && *file) addChild("fileName", newScriptVar(file)); + if(line>=0) addChild("lineNumber", newScriptVar(line+1)); + if(column>=0) addChild("column", newScriptVar(column+1)); +} + +CScriptVarError::~CScriptVarError() {} +CScriptVarPtr CScriptVarError::clone() { return new CScriptVarError(*this); } +bool CScriptVarError::isError() { return true; } + +CScriptVarPtr CScriptVarError::toString_CallBack(CScriptResult &execute, int radix) { + CScriptVarLinkPtr link; + string name = ERROR_NAME[Error]; + link = findChildWithPrototypeChain("name"); if(link) name = link->toString(execute); + string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString(execute); + string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString(execute); + int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32(); + int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32(); + ostringstream msg; + msg << name << ": " << message; + if(lineNumber >= 0) msg << " at Line:" << lineNumber+1; + if(column >=0) msg << " Column:" << column+1; + if(fileName.length()) msg << " in " << fileName; + return newScriptVar(msg.str()); +} + +CScriptException *CScriptVarError::toCScriptException() +{ + CScriptVarLinkPtr link; + string name = ERROR_NAME[Error]; + link = findChildWithPrototypeChain("name"); if(link) name = link->toString(); + int ErrorCode; + for(ErrorCode=(sizeof(ERROR_NAME)/sizeof(ERROR_NAME[0]))-1; ErrorCode>0; ErrorCode--) { + if(name == ERROR_NAME[ErrorCode]) break; + } + string message; link = findChildWithPrototypeChain("message"); if(link) message = link->toString(); + string fileName; link = findChildWithPrototypeChain("fileName"); if(link) fileName = link->toString(); + int lineNumber=-1; link = findChildWithPrototypeChain("lineNumber"); if(link) lineNumber = link->toNumber().toInt32()-1; + int column=-1; link = findChildWithPrototypeChain("column"); if(link) column = link->toNumber().toInt32()-1; + return new CScriptException((enum ERROR_TYPES)ErrorCode, message, fileName, lineNumber, column); +} + + +////////////////////////////////////////////////////////////////////////// +// CScriptVarArray +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Array); +CScriptVarArray::CScriptVarArray(CTinyJS *Context) : CScriptVarObject(Context, Context->arrayPrototype), toStringRecursion(false) { + CScriptVarLinkPtr acc = addChild("length", newScriptVar(Accessor), 0); + CScriptVarFunctionPtr getter(::newScriptVar(Context, this, &CScriptVarArray::native_Length, 0)); + getter->setFunctionData(new CScriptTokenDataFnc); + acc->getVarPtr()->addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); +} + +CScriptVarArray::~CScriptVarArray() {} +CScriptVarPtr CScriptVarArray::clone() { return new CScriptVarArray(*this); } +bool CScriptVarArray::isArray() { return true; } +string CScriptVarArray::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { + getParsableStringRecursionsCheck(); + string destination; + const char *nl = indent.size() ? "\n" : " "; + const char *comma = ""; + destination.append("["); + int len = getArrayLength(); + if(len) { + string new_indentString = indentString + indent; + for (int i=0;igetParsableString(new_indentString, indent, uniqueID, hasRecursion)); + } + destination.append(nl).append(indentString); + } + destination.append("]"); + return destination; +} +CScriptVarPtr CScriptVarArray::toString_CallBack( CScriptResult &execute, int radix/*=0*/ ) { + ostringstream destination; + if(toStringRecursion) { + return newScriptVar(""); + } + toStringRecursion = true; + try { + int len = getArrayLength(); + for (int i=0;itoString(execute); + if (isetReturnVar(newScriptVar(c->getArgument("this")->getArrayLength())); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarRegExp +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_REGEXP + +CScriptVarRegExp::CScriptVarRegExp(CTinyJS *Context, const string &Regexp, const string &Flags) : CScriptVarObject(Context, Context->regexpPrototype), regexp(Regexp), flags(Flags) { + addChild("global", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Global, 0, 0, 0), 0); + addChild("ignoreCase", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_IgnoreCase, 0, 0, 0), 0); + addChild("multiline", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Multiline, 0, 0, 0), 0); + addChild("sticky", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Sticky, 0, 0, 0), 0); + addChild("regexp", ::newScriptVarAccessor(Context, this, &CScriptVarRegExp::native_Source, 0, 0, 0), 0); + addChild("lastIndex", newScriptVar(0)); +} +CScriptVarRegExp::~CScriptVarRegExp() {} +CScriptVarPtr CScriptVarRegExp::clone() { return new CScriptVarRegExp(*this); } +bool CScriptVarRegExp::isRegExp() { return true; } +//int CScriptVarRegExp::getInt() {return strtol(regexp.c_str(),0,0); } +//bool CScriptVarRegExp::getBool() {return regexp.length()!=0;} +//double CScriptVarRegExp::getDouble() {return strtod(regexp.c_str(),0);} +//string CScriptVarRegExp::getString() { return "/"+regexp+"/"+flags; } +//string CScriptVarRegExp::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { return getString(); } +CScriptVarPtr CScriptVarRegExp::toString_CallBack(CScriptResult &execute, int radix) { + return newScriptVar("/"+regexp+"/"+flags); +} +void CScriptVarRegExp::native_Global(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(Global())); +} +void CScriptVarRegExp::native_IgnoreCase(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(IgnoreCase())); +} +void CScriptVarRegExp::native_Multiline(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(Multiline())); +} +void CScriptVarRegExp::native_Sticky(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(Sticky())); +} +void CScriptVarRegExp::native_Source(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(newScriptVar(regexp)); +} +unsigned int CScriptVarRegExp::LastIndex() { + CScriptVarPtr lastIndex = findChild("lastIndex"); + if(lastIndex) return lastIndex->toNumber().toInt32(); + return 0; +} +void CScriptVarRegExp::LastIndex(unsigned int Idx) { + addChildOrReplace("lastIndex", newScriptVar((int)Idx)); +} + +CScriptVarPtr CScriptVarRegExp::exec( const string &Input, bool Test /*= false*/ ) +{ + regex::flag_type flags = regex_constants::ECMAScript; + if(IgnoreCase()) flags |= regex_constants::icase; + bool global = Global(), sticky = Sticky(); + unsigned int lastIndex = LastIndex(); + int offset = 0; + if(global || sticky) { + if(lastIndex > Input.length()) goto failed; + offset=lastIndex; + } + { + regex_constants::match_flag_type mflag = sticky?regex_constants::match_continuous:regex_constants::match_default; + if(offset) mflag |= regex_constants::match_prev_avail; + smatch match; + if(regex_search(Input.begin()+offset, Input.end(), match, regex(regexp, flags), mflag) ) { + LastIndex(offset+match.position()+match.str().length()); + if(Test) return constScriptVar(true); + + CScriptVarArrayPtr retVar = newScriptVar(Array); + retVar->addChild("input", newScriptVar(Input)); + retVar->addChild("index", newScriptVar(match.position())); + for(smatch::size_type idx=0; idxaddChild(int2string(idx), newScriptVar(match[idx].str())); + return retVar; + } + } +failed: + if(global || sticky) + LastIndex(0); + if(Test) return constScriptVar(false); + return constScriptVar(Null); +} + +const char * CScriptVarRegExp::ErrorStr( int Error ) +{ + switch(Error) { + case regex_constants::error_badbrace: return "the expression contained an invalid count in a { } expression"; + case regex_constants::error_badrepeat: return "a repeat expression (one of '*', '?', '+', '{' in most contexts) was not preceded by an expression"; + case regex_constants::error_brace: return "the expression contained an unmatched '{' or '}'"; + case regex_constants::error_brack: return "the expression contained an unmatched '[' or ']'"; + case regex_constants::error_collate: return "the expression contained an invalid collating element name"; + case regex_constants::error_complexity: return "an attempted match failed because it was too complex"; + case regex_constants::error_ctype: return "the expression contained an invalid character class name"; + case regex_constants::error_escape: return "the expression contained an invalid escape sequence"; + case regex_constants::error_paren: return "the expression contained an unmatched '(' or ')'"; + case regex_constants::error_range: return "the expression contained an invalid character range specifier"; + case regex_constants::error_space: return "parsing a regular expression failed because there were not enough resources available"; + case regex_constants::error_stack: return "an attempted match failed because there was not enough memory available"; + case regex_constants::error_backref: return "the expression contained an invalid back reference"; + default: return ""; + } +} + +#endif /* NO_REGEXP */ + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarDefaultIterator +////////////////////////////////////////////////////////////////////////// + +//declare_dummy_t(DefaultIterator); +CScriptVarDefaultIterator::CScriptVarDefaultIterator(CTinyJS *Context, const CScriptVarPtr &Object, int Mode) + : CScriptVarObject(Context, Context->iteratorPrototype), mode(Mode), object(Object) { + object->keys(keys, true); + pos = keys.begin(); + addChild("next", ::newScriptVar(context, this, &CScriptVarDefaultIterator::native_next, 0)); +} +CScriptVarDefaultIterator::~CScriptVarDefaultIterator() {} +CScriptVarPtr CScriptVarDefaultIterator::clone() { return new CScriptVarDefaultIterator(*this); } +bool CScriptVarDefaultIterator::isIterator() {return true;} +void CScriptVarDefaultIterator::native_next(const CFunctionsScopePtr &c, void *data) { + if(pos==keys.end()) throw constScriptVar(StopIteration); + CScriptVarPtr ret, ret0, ret1; + if(mode&1) ret0 = newScriptVar(*pos); + if(mode&2) ret1 = object->findChildWithStringChars(*pos); + pos++; + if(mode==3) { + ret = newScriptVar(Array); + ret->setArrayIndex(0, ret0); + ret->setArrayIndex(1, ret1); + } else if(mode==1) + ret = ret0; + else + ret = ret1; + c->setReturnVar(ret); +} + + +#ifndef NO_GENERATORS +////////////////////////////////////////////////////////////////////////// +/// CScriptVarGenerator +////////////////////////////////////////////////////////////////////////// + +//declare_dummy_t(Generator); +CScriptVarGenerator::CScriptVarGenerator(CTinyJS *Context, const CScriptVarPtr &FunctionRoot, const CScriptVarFunctionPtr &Function) + : CScriptVarObject(Context, Context->generatorPrototype), functionRoot(FunctionRoot), function(Function), + closed(false), yieldVarIsException(false), coroutine(this) { +// addChild("next", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, 0, "Generator.next")); + // addChild("send", ::newScriptVar(context, this, &CScriptVarGenerator::native_send, (void*)1, "Generator.send")); + //addChild("close", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)0, "Generator.close")); + //addChild("throw", ::newScriptVar(context, this, &CScriptVarGenerator::native_throw, (void*)1, "Generator.throw")); +} +CScriptVarGenerator::~CScriptVarGenerator() { + if(coroutine.isStarted() && coroutine.isRunning()) { + coroutine.Stop(false); + coroutine.next(); + coroutine.Stop(); + } +} +CScriptVarPtr CScriptVarGenerator::clone() { return new CScriptVarGenerator(*this); } +bool CScriptVarGenerator::isIterator() {return true;} +bool CScriptVarGenerator::isGenerator() {return true;} + +string CScriptVarGenerator::getVarType() { return "generator"; } +string CScriptVarGenerator::getVarTypeTagName() { return "Generator"; } + +void CScriptVarGenerator::setTemporaryMark_recursive( uint32_t ID ) { + CScriptVarObject::setTemporaryMark_recursive(ID); + functionRoot->setTemporaryMark_recursive(ID); + function->setTemporaryMark_recursive(ID); + if(yieldVar) yieldVar->setTemporaryMark_recursive(ID); + for(std::vector::iterator it=generatorScopes.begin(); it != generatorScopes.end(); ++it) + (*it)->setTemporaryMark_recursive(ID); +} +void CScriptVarGenerator::native_send(const CFunctionsScopePtr &c, void *data) { + // data == 0 ==> next() + // data != 0 ==> send(...) + if(closed) + throw constScriptVar(StopIteration); + + yieldVar = data ? c->getArgument(0) : constScriptVar(Undefined); + yieldVarIsException = false; + + if(!coroutine.isStarted() && data && !yieldVar->isUndefined()) + c->throwError(TypeError, "attempt to send value to newborn generator"); + if(coroutine.next()) { + c->setReturnVar(yieldVar); + return; + } + closed = true; + throw yieldVar; +} +void CScriptVarGenerator::native_throw(const CFunctionsScopePtr &c, void *data) { + // data == 0 ==> close() + // data != 0 ==> throw(...) + if(closed || !coroutine.isStarted()) { + closed = true; + if(data) + throw c->getArgument(0); + else + return; + } + yieldVar = data ? c->getArgument(0) : CScriptVarPtr(); + yieldVarIsException = true; + closed = data==0; + if(coroutine.next()) { + c->setReturnVar(yieldVar); + return; + } + closed = true; + /* + * from http://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators + * Generators have a close() method that forces the generator to close itself. The effects of closing a generator are: + * - Any finally clauses active in the generator function are run. + * - If a finally clause throws any exception other than StopIteration, the exception is propagated to the caller of the close() method. + * - The generator terminates. + * + * but in Firefox is also "StopIteration" propagated to the caller + * define GENERATOR_CLOSE_LIKE_IN_FIREFOX to enable this behavior + */ +//#define GENERATOR_CLOSE_LIKE_IN_FIREFOX +#ifdef GENERATOR_CLOSE_LIKE_IN_FIREFOX + if(data || yieldVar) + throw yieldVar; +#else + if(data || (yieldVar && yieldVar != constScriptVar(StopIteration))) + throw yieldVar; +#endif +} + +int CScriptVarGenerator::Coroutine() +{ + context->generator_start(this); + return 0; +} + +CScriptVarPtr CScriptVarGenerator::yield( CScriptResult &execute, CScriptVar *YieldIn ) +{ + yieldVar = YieldIn; + coroutine.yield(); + if(yieldVarIsException) { + execute.set(CScriptResult::Throw, yieldVar); + return constScriptVar(Undefined); + } + return yieldVar; +} + +#endif /*NO_GENERATORS*/ + +////////////////////////////////////////////////////////////////////////// +// CScriptVarFunction +////////////////////////////////////////////////////////////////////////// + +CScriptVarFunction::CScriptVarFunction(CTinyJS *Context, CScriptTokenDataFnc *Data) : CScriptVarObject(Context, Context->functionPrototype), data(0) { + setFunctionData(Data); +} +CScriptVarFunction::~CScriptVarFunction() { setFunctionData(0); } +CScriptVarPtr CScriptVarFunction::clone() { return new CScriptVarFunction(*this); } +bool CScriptVarFunction::isObject() { return true; } +bool CScriptVarFunction::isFunction() { return true; } +bool CScriptVarFunction::isPrimitive() { return false; } + +//string CScriptVarFunction::getString() {return "[ Function ]";} +string CScriptVarFunction::getVarType() { return "function"; } +string CScriptVarFunction::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { + getParsableStringRecursionsCheck(); + string destination; + destination.append("function ").append(data->name); + // get list of arguments + destination.append(data->getArgumentsString()); + + if(isNative() || isBounded()) + destination.append("{ [native code] }"); + else { + destination.append(CScriptToken::getParsableString(data->body, indentString, indent)); + if(!data->body.size() || data->body.front().token != '{') + destination.append(";"); + } + return destination; +} + +CScriptVarPtr CScriptVarFunction::toString_CallBack(CScriptResult &execute, int radix){ + bool hasRecursion; + return newScriptVar(getParsableString("", " ", 0, hasRecursion)); +} + +CScriptTokenDataFnc *CScriptVarFunction::getFunctionData() { return data; } + +void CScriptVarFunction::setFunctionData(CScriptTokenDataFnc *Data) { + if(data) { data->unref(); data = 0; } + if(Data) { + data = Data; data->ref(); + addChildOrReplace("length", newScriptVar((int)data->arguments.size()), 0); + // can not add "name" here because name is a StingVar with length as getter + // length-getter is a function with a function name -> endless recursion + //addChildNoDup("name", newScriptVar(data->name), 0); + } +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionBounded +////////////////////////////////////////////////////////////////////////// + +CScriptVarFunctionBounded::CScriptVarFunctionBounded(CScriptVarFunctionPtr BoundedFunction, CScriptVarPtr BoundedThis, const std::vector &BoundedArguments) + : CScriptVarFunction(BoundedFunction->getContext(), new CScriptTokenDataFnc) , + boundedFunction(BoundedFunction), + boundedThis(BoundedThis), + boundedArguments(BoundedArguments) { + getFunctionData()->name = BoundedFunction->getFunctionData()->name; +} +CScriptVarFunctionBounded::~CScriptVarFunctionBounded(){} +CScriptVarPtr CScriptVarFunctionBounded::clone() { return new CScriptVarFunctionBounded(*this); } +bool CScriptVarFunctionBounded::isBounded() { return true; } +void CScriptVarFunctionBounded::setTemporaryMark_recursive(uint32_t ID) { + CScriptVarFunction::setTemporaryMark_recursive(ID); + boundedThis->setTemporaryMark_recursive(ID); + for(vector::iterator it=boundedArguments.begin(); it!=boundedArguments.end(); ++it) + (*it)->setTemporaryMark_recursive(ID); +} + +CScriptVarPtr CScriptVarFunctionBounded::callFunction( CScriptResult &execute, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis/*=0*/ ) +{ + vector newArgs=boundedArguments; + newArgs.insert(newArgs.end(), Arguments.begin(), Arguments.end()); + return context->callFunction(execute, boundedFunction, newArgs, newThis ? This : boundedThis, newThis); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNative +////////////////////////////////////////////////////////////////////////// + +CScriptVarFunctionNative::~CScriptVarFunctionNative() {} +bool CScriptVarFunctionNative::isNative() { return true; } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarFunctionNativeCallback +////////////////////////////////////////////////////////////////////////// + +CScriptVarFunctionNativeCallback::~CScriptVarFunctionNativeCallback() {} +CScriptVarPtr CScriptVarFunctionNativeCallback::clone() { return new CScriptVarFunctionNativeCallback(*this); } +void CScriptVarFunctionNativeCallback::callFunction(const CFunctionsScopePtr &c) { jsCallback(c, jsUserData); } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarAccessor +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Accessor); +CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context) : CScriptVarObject(Context, Context->objectPrototype) { } +CScriptVarAccessor::CScriptVarAccessor(CTinyJS *Context, JSCallback getterFnc, void *getterData, JSCallback setterFnc, void *setterData) + : CScriptVarObject(Context) +{ + if(getterFnc) + addChild(TINYJS_ACCESSOR_GET_VAR, ::newScriptVar(Context, getterFnc, getterData), 0); + if(setterFnc) + addChild(TINYJS_ACCESSOR_SET_VAR, ::newScriptVar(Context, setterFnc, setterData), 0); +} + +CScriptVarAccessor::CScriptVarAccessor( CTinyJS *Context, const CScriptVarFunctionPtr &getter, const CScriptVarFunctionPtr &setter) : CScriptVarObject(Context, Context->objectPrototype) { + if(getter) + addChild(TINYJS_ACCESSOR_GET_VAR, getter, 0); + if(setter) + addChild(TINYJS_ACCESSOR_SET_VAR, setter, 0); +} + +CScriptVarAccessor::~CScriptVarAccessor() {} +CScriptVarPtr CScriptVarAccessor::clone() { return new CScriptVarAccessor(*this); } +bool CScriptVarAccessor::isAccessor() { return true; } +bool CScriptVarAccessor::isPrimitive() { return false; } +string CScriptVarAccessor::getParsableString(const string &indentString, const string &indent, uint32_t uniqueID, bool &hasRecursion) { + return ""; +} +string CScriptVarAccessor::getVarType() { return "accessor"; } + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScope +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(Scope); +CScriptVarScope::~CScriptVarScope() {} +CScriptVarPtr CScriptVarScope::clone() { return CScriptVarPtr(); } +bool CScriptVarScope::isObject() { return false; } +CScriptVarPtr CScriptVarScope::scopeVar() { return this; } ///< to create var like: var a = ... +CScriptVarPtr CScriptVarScope::scopeLet() { return this; } ///< to create var like: let a = ... +CScriptVarLinkWorkPtr CScriptVarScope::findInScopes(const string &childName) { + return CScriptVar::findChild(childName); +} +CScriptVarScopePtr CScriptVarScope::getParent() { return CScriptVarScopePtr(); } ///< no Parent + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeFnc +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(ScopeFnc); +CScriptVarScopeFnc::~CScriptVarScopeFnc() {} +CScriptVarLinkWorkPtr CScriptVarScopeFnc::findInScopes(const string &childName) { + CScriptVarLinkWorkPtr ret = findChild(childName); + if( !ret ) { + if(closure) ret = CScriptVarScopePtr(closure)->findInScopes(childName); + else ret = context->getRoot()->findChild(childName); + } + return ret; +} + +void CScriptVarScopeFnc::setReturnVar(const CScriptVarPtr &var) { + addChildOrReplace(TINYJS_RETURN_VAR, var); +} + +CScriptVarPtr CScriptVarScopeFnc::getParameter(const string &name) { + return getArgument(name); +} + +CScriptVarPtr CScriptVarScopeFnc::getParameter(int Idx) { + return getArgument(Idx); +} +CScriptVarPtr CScriptVarScopeFnc::getArgument(const string &name) { + return findChildOrCreate(name); +} +CScriptVarPtr CScriptVarScopeFnc::getArgument(int Idx) { + CScriptVarLinkPtr arguments = findChildOrCreate(TINYJS_ARGUMENTS_VAR); + if(arguments) arguments = arguments->getVarPtr()->findChild(int2string(Idx)); + return arguments ? arguments->getVarPtr() : constScriptVar(Undefined); +} +int CScriptVarScopeFnc::getParameterLength() { + return getArgumentsLength(); +} +int CScriptVarScopeFnc::getArgumentsLength() { + CScriptVarLinkPtr arguments = findChild(TINYJS_ARGUMENTS_VAR); + if(arguments) arguments = arguments->getVarPtr()->findChild("length"); + return arguments ? arguments.getter()->toNumber().toInt32() : 0; +} + +void CScriptVarScopeFnc::throwError( ERROR_TYPES ErrorType, const string &message ) { + throw newScriptVarError(context, ErrorType, message.c_str()); +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeLet +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(ScopeLet); +CScriptVarScopeLet::CScriptVarScopeLet(const CScriptVarScopePtr &Parent) // constructor for LetScope + : CScriptVarScope(Parent->getContext()), parent(addChild(TINYJS_SCOPE_PARENT_VAR, Parent, 0)) + , letExpressionInitMode(false) {} + +CScriptVarScopeLet::~CScriptVarScopeLet() {} +CScriptVarPtr CScriptVarScopeLet::scopeVar() { // to create var like: var a = ... + return getParent()->scopeVar(); +} +CScriptVarScopePtr CScriptVarScopeLet::getParent() { return (CScriptVarPtr)parent; } +CScriptVarLinkWorkPtr CScriptVarScopeLet::findInScopes(const string &childName) { + CScriptVarLinkWorkPtr ret; + if(letExpressionInitMode) { + return getParent()->findInScopes(childName); + } else { + ret = findChild(childName); + if( !ret ) ret = getParent()->findInScopes(childName); + } + return ret; +} + + +////////////////////////////////////////////////////////////////////////// +/// CScriptVarScopeWith +////////////////////////////////////////////////////////////////////////// + +declare_dummy_t(ScopeWith); +CScriptVarScopeWith::~CScriptVarScopeWith() {} +CScriptVarPtr CScriptVarScopeWith::scopeLet() { // to create var like: let a = ... + return getParent()->scopeLet(); +} +CScriptVarLinkWorkPtr CScriptVarScopeWith::findInScopes(const string &childName) { + if(childName == "this") return with; + CScriptVarLinkWorkPtr ret = with->getVarPtr()->findChild(childName); + if( !ret ) { + ret = with->getVarPtr()->findChildInPrototypeChain(childName); + if(ret) { + ret(ret->getVarPtr(), ret->getName()); // recreate ret + ret.setReferencedOwner(with->getVarPtr()); // fake referenced Owner + } + } + if( !ret ) ret = getParent()->findInScopes(childName); + return ret; +} + + +////////////////////////////////////////////////////////////////////////// +/// CTinyJS +////////////////////////////////////////////////////////////////////////// + +extern "C" void _registerFunctions(CTinyJS *tinyJS); +extern "C" void _registerStringFunctions(CTinyJS *tinyJS); +extern "C" void _registerMathFunctions(CTinyJS *tinyJS); + +CTinyJS::CTinyJS() { + CScriptVarPtr var; + t = 0; + haveTry = false; + first = 0; + uniqueID = 0; + currentMarkSlot = -1; + stackBase = 0; + + + ////////////////////////////////////////////////////////////////////////// + // Object-Prototype + // must be created as first object because this prototype is the base of all objects + objectPrototype = newScriptVar(Object); + + // all objects have a prototype. Also the prototype of prototypes + objectPrototype->addChild(TINYJS___PROTO___VAR, objectPrototype, 0); + + ////////////////////////////////////////////////////////////////////////// + // Function-Prototype + // must be created as second object because this is the base of all functions (also constructors) + functionPrototype = newScriptVar(Object); + + + ////////////////////////////////////////////////////////////////////////// + // Scopes + root = ::newScriptVar(this, Scope); + scopes.push_back(root); + + ////////////////////////////////////////////////////////////////////////// + // Add built-in classes + ////////////////////////////////////////////////////////////////////////// + // Object + var = addNative("function Object()", this, &CTinyJS::native_Object, 0, SCRIPTVARLINK_CONSTANT); + objectPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + objectPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + addNative("function Object.getPrototypeOf(obj)", this, &CTinyJS::native_Object_getPrototypeOf); + addNative("function Object.preventExtensions(obj)", this, &CTinyJS::native_Object_setObjectSecure); + addNative("function Object.isExtensible(obj)", this, &CTinyJS::native_Object_isSecureObject); + addNative("function Object.seel(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)1); + addNative("function Object.isSealed(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)1); + addNative("function Object.freeze(obj)", this, &CTinyJS::native_Object_setObjectSecure, (void*)2); + addNative("function Object.isFrozen(obj)", this, &CTinyJS::native_Object_isSecureObject, (void*)2); + addNative("function Object.keys(obj)", this, &CTinyJS::native_Object_keys); + addNative("function Object.getOwnPropertyNames(obj)", this, &CTinyJS::native_Object_keys, (void*)1); + addNative("function Object.getOwnPropertyDescriptor(obj,name)", this, &CTinyJS::native_Object_getOwnPropertyDescriptor); + addNative("function Object.defineProperty(obj,name,attributes)", this, &CTinyJS::native_Object_defineProperty); + addNative("function Object.defineProperties(obj,properties)", this, &CTinyJS::native_Object_defineProperties); + addNative("function Object.create(obj,properties)", this, &CTinyJS::native_Object_defineProperties, (void*)1); + + addNative("function Object.prototype.hasOwnProperty(prop)", this, &CTinyJS::native_Object_prototype_hasOwnProperty); + objectPrototype_valueOf = addNative("function Object.prototype.valueOf()", this, &CTinyJS::native_Object_prototype_valueOf); + objectPrototype_toString = addNative("function Object.prototype.toString(radix)", this, &CTinyJS::native_Object_prototype_toString); + pseudo_refered.push_back(&objectPrototype); + pseudo_refered.push_back(&objectPrototype_valueOf); + pseudo_refered.push_back(&objectPrototype_toString); + + ////////////////////////////////////////////////////////////////////////// + // Array + var = addNative("function Array()", this, &CTinyJS::native_Array, 0, SCRIPTVARLINK_CONSTANT); + arrayPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + arrayPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + arrayPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + arrayPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&arrayPrototype); + var = addNative("function Array.__constructor__()", this, &CTinyJS::native_Array, (void*)1, SCRIPTVARLINK_CONSTANT); + var->getFunctionData()->name = "Array"; + + ////////////////////////////////////////////////////////////////////////// + // String + var = addNative("function String()", this, &CTinyJS::native_String, 0, SCRIPTVARLINK_CONSTANT); + stringPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + stringPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + stringPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + stringPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&stringPrototype); + var = addNative("function String.__constructor__()", this, &CTinyJS::native_String, (void*)1, SCRIPTVARLINK_CONSTANT); + var->getFunctionData()->name = "String"; + + ////////////////////////////////////////////////////////////////////////// + // RegExp +#ifndef NO_REGEXP + var = addNative("function RegExp()", this, &CTinyJS::native_RegExp, 0, SCRIPTVARLINK_CONSTANT); + regexpPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + regexpPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + regexpPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + regexpPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(®expPrototype); +#endif /* NO_REGEXP */ + + ////////////////////////////////////////////////////////////////////////// + // Number + var = addNative("function Number()", this, &CTinyJS::native_Number, 0, SCRIPTVARLINK_CONSTANT); + var->addChild("NaN", constNaN = newScriptVarNumber(this, NaN), SCRIPTVARLINK_CONSTANT); + var->addChild("MAX_VALUE", newScriptVarNumber(this, numeric_limits::max()), SCRIPTVARLINK_CONSTANT); + var->addChild("MIN_VALUE", newScriptVarNumber(this, numeric_limits::min()), SCRIPTVARLINK_CONSTANT); + var->addChild("POSITIVE_INFINITY", constInfinityPositive = newScriptVarNumber(this, InfinityPositive), SCRIPTVARLINK_CONSTANT); + var->addChild("NEGATIVE_INFINITY", constInfinityNegative = newScriptVarNumber(this, InfinityNegative), SCRIPTVARLINK_CONSTANT); + numberPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + numberPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + numberPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + numberPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&numberPrototype); + pseudo_refered.push_back(&constNaN); + pseudo_refered.push_back(&constInfinityPositive); + pseudo_refered.push_back(&constInfinityNegative); + var = addNative("function Number.__constructor__()", this, &CTinyJS::native_Number, (void*)1, SCRIPTVARLINK_CONSTANT); + var->getFunctionData()->name = "Number"; + + ////////////////////////////////////////////////////////////////////////// + // Boolean + var = addNative("function Boolean()", this, &CTinyJS::native_Boolean, 0, SCRIPTVARLINK_CONSTANT); + booleanPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + booleanPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + booleanPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + booleanPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&booleanPrototype); + var = addNative("function Boolean.__constructor__()", this, &CTinyJS::native_Boolean, (void*)1, SCRIPTVARLINK_CONSTANT); + var->getFunctionData()->name = "Boolean"; + + ////////////////////////////////////////////////////////////////////////// + // Iterator + var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT); + iteratorPrototype = var->findChild(TINYJS_PROTOTYPE_CLASS); + iteratorPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&iteratorPrototype); + + ////////////////////////////////////////////////////////////////////////// + // Generator +// var = addNative("function Iterator(obj,mode)", this, &CTinyJS::native_Iterator, 0, SCRIPTVARLINK_CONSTANT); +#ifndef NO_GENERATORS + generatorPrototype = newScriptVar(Object); + generatorPrototype->addChild("next", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)0, "Generator.next"), SCRIPTVARLINK_BUILDINDEFAULT); + generatorPrototype->addChild("send", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)1, "Generator.send"), SCRIPTVARLINK_BUILDINDEFAULT); + generatorPrototype->addChild("close", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)2, "Generator.close"), SCRIPTVARLINK_BUILDINDEFAULT); + generatorPrototype->addChild("throw", ::newScriptVar(this, this, &CTinyJS::native_Generator_prototype_next, (void*)3, "Generator.throw"), SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&generatorPrototype); +#endif /*NO_GENERATORS*/ + + ////////////////////////////////////////////////////////////////////////// + // Function + var = addNative("function Function(params, body)", this, &CTinyJS::native_Function, 0, SCRIPTVARLINK_CONSTANT); + var->addChildOrReplace(TINYJS_PROTOTYPE_CLASS, functionPrototype); + functionPrototype->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + addNative("function Function.prototype.call(objc)", this, &CTinyJS::native_Function_prototype_call); + addNative("function Function.prototype.apply(objc, args)", this, &CTinyJS::native_Function_prototype_apply); + addNative("function Function.prototype.bind(objc, args)", this, &CTinyJS::native_Function_prototype_bind); + functionPrototype->addChild("valueOf", objectPrototype_valueOf, SCRIPTVARLINK_BUILDINDEFAULT); + functionPrototype->addChild("toString", objectPrototype_toString, SCRIPTVARLINK_BUILDINDEFAULT); + pseudo_refered.push_back(&functionPrototype); + + ////////////////////////////////////////////////////////////////////////// + // Error + var = addNative("function Error(message, fileName, lineNumber, column)", this, &CTinyJS::native_Error, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[Error] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[Error]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[Error]->addChild("message", newScriptVar("")); + errorPrototypes[Error]->addChild("name", newScriptVar("Error")); + errorPrototypes[Error]->addChild("fileName", newScriptVar("")); + errorPrototypes[Error]->addChild("lineNumber", newScriptVar(-1)); // -1 means not viable + errorPrototypes[Error]->addChild("column", newScriptVar(-1)); // -1 means not viable + + var = addNative("function EvalError(message, fileName, lineNumber, column)", this, &CTinyJS::native_EvalError, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[EvalError] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[EvalError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[EvalError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); + errorPrototypes[EvalError]->addChild("name", newScriptVar("EvalError")); + + var = addNative("function RangeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_RangeError, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[RangeError] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[RangeError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[RangeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); + errorPrototypes[RangeError]->addChild("name", newScriptVar("RangeError")); + + var = addNative("function ReferenceError(message, fileName, lineNumber, column)", this, &CTinyJS::native_ReferenceError, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[ReferenceError] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[ReferenceError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[ReferenceError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); + errorPrototypes[ReferenceError]->addChild("name", newScriptVar("ReferenceError")); + + var = addNative("function SyntaxError(message, fileName, lineNumber, column)", this, &CTinyJS::native_SyntaxError, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[SyntaxError] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[SyntaxError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[SyntaxError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); + errorPrototypes[SyntaxError]->addChild("name", newScriptVar("SyntaxError")); + + var = addNative("function TypeError(message, fileName, lineNumber, column)", this, &CTinyJS::native_TypeError, 0, SCRIPTVARLINK_CONSTANT); + errorPrototypes[TypeError] = var->findChild(TINYJS_PROTOTYPE_CLASS); + errorPrototypes[TypeError]->addChild(TINYJS_CONSTRUCTOR_VAR, var, SCRIPTVARLINK_BUILDINDEFAULT); + errorPrototypes[TypeError]->addChildOrReplace(TINYJS___PROTO___VAR, errorPrototypes[Error], SCRIPTVARLINK_WRITABLE); + errorPrototypes[TypeError]->addChild("name", newScriptVar("TypeError")); + + + + + + + ////////////////////////////////////////////////////////////////////////// + // add global built-in vars & constants + root->addChild("undefined", constUndefined = newScriptVarUndefined(this), SCRIPTVARLINK_CONSTANT); + pseudo_refered.push_back(&constUndefined); + constNull = newScriptVarNull(this); pseudo_refered.push_back(&constNull); + root->addChild("NaN", constNaN, SCRIPTVARLINK_CONSTANT); + root->addChild("Infinity", constInfinityPositive, SCRIPTVARLINK_CONSTANT); + root->addChild("StopIteration", constStopIteration=newScriptVar(Object, var=newScriptVar(Object), "StopIteration"), SCRIPTVARLINK_CONSTANT); + constStopIteration->addChild(TINYJS_PROTOTYPE_CLASS, var, SCRIPTVARLINK_CONSTANT); pseudo_refered.push_back(&constStopIteration); + constNegativZero = newScriptVarNumber(this, NegativeZero); pseudo_refered.push_back(&constNegativZero); + constFalse = newScriptVarBool(this, false); pseudo_refered.push_back(&constFalse); + constTrue = newScriptVarBool(this, true); pseudo_refered.push_back(&constTrue); + + ////////////////////////////////////////////////////////////////////////// + // add global functions + addNative("function eval(jsCode)", this, &CTinyJS::native_eval); + native_require_read = 0; + addNative("function require(jsFile)", this, &CTinyJS::native_require); + addNative("function isNaN(objc)", this, &CTinyJS::native_isNAN); + addNative("function isFinite(objc)", this, &CTinyJS::native_isFinite); + addNative("function parseInt(string, radix)", this, &CTinyJS::native_parseInt); + addNative("function parseFloat(string)", this, &CTinyJS::native_parseFloat); + + root->addChild("JSON", newScriptVar(Object), SCRIPTVARLINK_BUILDINDEFAULT); + addNative("function JSON.parse(text, reviver)", this, &CTinyJS::native_JSON_parse); + + _registerFunctions(this); + _registerStringFunctions(this); + _registerMathFunctions(this); +} + +CTinyJS::~CTinyJS() { + ASSERT(!t); + for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it) + **it = CScriptVarPtr(); + for(int i=Error; iremoveAllChildren(); + scopes.clear(); + ClearUnreferedVars(); + root = CScriptVarPtr(); +#ifdef _DEBUG + for(CScriptVar *p = first; p; p=p->next) + printf("%p\n", p); +#endif +#if DEBUG_MEMORY + show_allocated(); +#endif +} + +////////////////////////////////////////////////////////////////////////// +/// throws an Error & Exception +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message ) { + if(execute && haveTry) { + execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), t->currentLine(), t->currentColumn())); + return; + } + throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn()); +} +void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message ) { + throw new CScriptException(ErrorType, message, t->currentFile, t->currentLine(), t->currentColumn()); +} + +void CTinyJS::throwError(CScriptResult &execute, ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){ + if(execute && haveTry) { + execute.set(CScriptResult::Throw, newScriptVarError(this, ErrorType, message.c_str(), t->currentFile.c_str(), Pos.currentLine(), Pos.currentColumn())); + return; + } + throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn()); +} +void CTinyJS::throwException(ERROR_TYPES ErrorType, const string &message, CScriptTokenizer::ScriptTokenPosition &Pos ){ + throw new CScriptException(ErrorType, message, t->currentFile, Pos.currentLine(), Pos.currentColumn()); +} + +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::trace() { + root->trace(); +} + +void CTinyJS::execute(CScriptTokenizer &Tokenizer) { + evaluateComplex(Tokenizer); +} + +void CTinyJS::execute(const char *Code, const string &File, int Line, int Column) { + evaluateComplex(Code, File, Line, Column); +} + +void CTinyJS::execute(const string &Code, const string &File, int Line, int Column) { + evaluateComplex(Code, File, Line, Column); +} + +CScriptVarLinkPtr CTinyJS::evaluateComplex(CScriptTokenizer &Tokenizer) { + t = &Tokenizer; + CScriptResult execute; + try { + do { + execute_statement(execute); + while (t->tk==';') t->match(';'); // skip empty statements + } while (t->tk!=LEX_EOF); + } catch (...) { + haveTry = false; + t=0; // clean up Tokenizer + throw; // + } + t=0; + ClearUnreferedVars(execute.value); + + uint32_t UniqueID = allocUniqueID(); + setTemporaryID_recursive(UniqueID); + if(execute.value) execute.value->setTemporaryMark_recursive(UniqueID); + for(CScriptVar *p = first; p; p=p->next) + { + if(p->getTemporaryMark() != UniqueID) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" + printf("%s %p\n", p->getVarType().c_str(), p); +#pragma GCC diagnostic pop + } + freeUniqueID(); + + if (execute.value) + return CScriptVarLinkPtr(execute.value); + // return undefined... + return CScriptVarLinkPtr(constScriptVar(Undefined)); +} +CScriptVarLinkPtr CTinyJS::evaluateComplex(const char *Code, const string &File, int Line, int Column) { + CScriptTokenizer Tokenizer(Code, File, Line, Column); + return evaluateComplex(Tokenizer); +} +CScriptVarLinkPtr CTinyJS::evaluateComplex(const string &Code, const string &File, int Line, int Column) { + CScriptTokenizer Tokenizer(Code.c_str(), File, Line, Column); + return evaluateComplex(Tokenizer); +} + +string CTinyJS::evaluate(CScriptTokenizer &Tokenizer) { + return evaluateComplex(Tokenizer)->toString(); +} +string CTinyJS::evaluate(const char *Code, const string &File, int Line, int Column) { + return evaluateComplex(Code, File, Line, Column)->toString(); +} +string CTinyJS::evaluate(const string &Code, const string &File, int Line, int Column) { + return evaluate(Code.c_str(), File, Line, Column); +} + +CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, JSCallback ptr, void *userdata, int LinkFlags) { + return addNative(funcDesc, ::newScriptVar(this, ptr, userdata), LinkFlags); +} + +CScriptVarFunctionNativePtr CTinyJS::addNative(const string &funcDesc, CScriptVarFunctionNativePtr Var, int LinkFlags) { + CScriptLex lex(funcDesc.c_str()); + CScriptVarPtr base = root; + + lex.match(LEX_R_FUNCTION); + string funcName = lex.tkStr; + lex.match(LEX_ID); + /* Check for dots, we might want to do something like function String.substring ... */ + while (lex.tk == '.') { + lex.match('.'); + CScriptVarLinkPtr link = base->findChild(funcName); + // if it doesn't exist, make an object class + if (!link) link = base->addChild(funcName, newScriptVar(Object)); + base = link->getVarPtr(); + funcName = lex.tkStr; + lex.match(LEX_ID); + } + + unique_ptr pFunctionData(new CScriptTokenDataFnc); + pFunctionData->name = funcName; + lex.match('('); + while (lex.tk!=')') { + pFunctionData->arguments.push_back(CScriptToken(LEX_ID, lex.tkStr)); + lex.match(LEX_ID); + if (lex.tk!=')') lex.match(',',')'); + } + lex.match(')'); + Var->setFunctionData(pFunctionData.release()); + Var->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE); + + base->addChild(funcName, Var, LinkFlags); + return Var; + +} + +CScriptVarLinkWorkPtr CTinyJS::parseFunctionDefinition(const CScriptToken &FncToken) { + const CScriptTokenDataFnc &Fnc = FncToken.Fnc(); +// string fncName = (FncToken.token == LEX_T_FUNCTION_OPERATOR) ? TINYJS_TEMP_NAME : Fnc.name; + CScriptVarLinkWorkPtr funcVar(newScriptVar((CScriptTokenDataFnc*)&Fnc), Fnc.name); + if(scope() != root) + funcVar->getVarPtr()->addChild(TINYJS_FUNCTION_CLOSURE_VAR, scope(), 0); + funcVar->getVarPtr()->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE)->getVarPtr()->addChild(TINYJS_CONSTRUCTOR_VAR, funcVar->getVarPtr(), SCRIPTVARLINK_WRITABLE); + return funcVar; +} + +CScriptVarLinkWorkPtr CTinyJS::parseFunctionsBodyFromString(const string &ArgumentList, const string &FncBody) { + string Fnc = "function ("+ArgumentList+"){"+FncBody+"}"; + CScriptTokenizer tokenizer(Fnc.c_str()); + return parseFunctionDefinition(tokenizer.getToken()); +} +CScriptVarPtr CTinyJS::callFunction(const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) { + CScriptResult execute; + CScriptVarPtr retVar = callFunction(execute, Function, Arguments, This, newThis); + execute.cThrow(); + return retVar; +} + +CScriptVarPtr CTinyJS::callFunction(CScriptResult &execute, const CScriptVarFunctionPtr &Function, vector &Arguments, const CScriptVarPtr &This, CScriptVarPtr *newThis) { + ASSERT(Function && Function->isFunction()); + + if(Function->isBounded()) return CScriptVarFunctionBoundedPtr(Function)->callFunction(execute, Arguments, This, newThis); + + CScriptTokenDataFnc *Fnc = Function->getFunctionData(); + CScriptVarScopeFncPtr functionRoot(::newScriptVar(this, ScopeFnc, CScriptVarPtr(Function->findChild(TINYJS_FUNCTION_CLOSURE_VAR)))); + if(Fnc->name.size()) functionRoot->addChild(Fnc->name, Function); + functionRoot->addChild("this", This); + CScriptVarPtr arguments = functionRoot->addChild(TINYJS_ARGUMENTS_VAR, newScriptVar(Object)); + + CScriptResult function_execute; + int length_proto = Fnc->arguments.size(); + int length_arguments = Arguments.size(); + int length = max(length_proto, length_arguments); + for(int arguments_idx = 0; arguments_idxaddChild(arguments_idx_str, Arguments[arguments_idx]); + } else { + value = constScriptVar(Undefined); + } + if(arguments_idx < length_proto) { + CScriptToken &FncArguments = Fnc->arguments[arguments_idx]; + if(FncArguments.token == LEX_ID) + functionRoot->addChildOrReplace(FncArguments.String(), value); + else + assign_destructuring_var(functionRoot, FncArguments.DestructuringVar(), value, function_execute); + } + } + arguments->addChild("length", newScriptVar(length_arguments)); + +#ifndef NO_GENERATORS + if(Fnc->isGenerator) { + return ::newScriptVarCScriptVarGenerator(this, functionRoot, Function); + } +#endif /*NO_GENERATORS*/ + // execute function! + // add the function's execute space to the symbol table so we can recurse + CScopeControl ScopeControl(this); + ScopeControl.addFncScope(functionRoot); + if (Function->isNative()) { + try { + CScriptVarFunctionNativePtr(Function)->callFunction(functionRoot); + CScriptVarLinkPtr ret = functionRoot->findChild(TINYJS_RETURN_VAR); + function_execute.set(CScriptResult::Return, ret ? CScriptVarPtr(ret) : constUndefined); + } catch (CScriptVarPtr v) { + if(haveTry) { + function_execute.setThrow(v, "native function '"+Fnc->name+"'"); + } else if(v->isError()) { + CScriptException *err = CScriptVarErrorPtr(v)->toCScriptException(); + if(err->fileName.empty()) err->fileName = "native function '"+Fnc->name+"'"; + throw err; + } + else + throw new CScriptException(Error, "uncaught exception: '"+v->toString(function_execute)+"' in native function '"+Fnc->name+"'"); + } + } else { + /* we just want to execute the block, but something could + * have messed up and left us with the wrong ScriptLex, so + * we want to be careful here... */ + string oldFile = t->currentFile; + t->currentFile = Fnc->file; + t->pushTokenScope(Fnc->body); + if(Fnc->body.front().token == '{') + execute_block(function_execute); + else { + CScriptVarPtr ret = execute_base(function_execute); + if(function_execute) function_execute.set(CScriptResult::Return, ret); + } + t->currentFile = oldFile; + + // because return will probably have called this, and set execute to false + } + if(function_execute.isReturnNormal()) { + if(newThis) *newThis = functionRoot->findChild("this"); + if(function_execute.isReturn()) { + CScriptVarPtr ret = function_execute.value; + return ret; + } + } else + execute = function_execute; + return constScriptVar(Undefined); +} +#ifndef NO_GENERATORS +void CTinyJS::generator_start(CScriptVarGenerator *Generator) +{ + // push current Generator + generatorStack.push_back(Generator); + + // safe callers stackBase & set generators one + Generator->callersStackBase = stackBase; + stackBase = 0; + + // safe callers ScopeSize + Generator->callersScopeSize = scopes.size(); + + // safe callers Tokenizer & set generators one + Generator->callersTokenizer = t; + CScriptTokenizer generatorTokenizer; + t = &generatorTokenizer; + + // safe callers haveTry + Generator->callersHaveTry = haveTry; + haveTry = true; + + // push generator's FunctionRoot + CScopeControl ScopeControl(this); + ScopeControl.addFncScope(Generator->getFunctionRoot()); + + // call generator-function + CScriptTokenDataFnc *Fnc = Generator->getFunction()->getFunctionData(); + CScriptResult function_execute; + TOKEN_VECT eof(1, CScriptToken()); + t->pushTokenScope(eof); + t->pushTokenScope(Fnc->body); + t->currentFile = Fnc->file; + try { + if(Fnc->body.front().token == '{') + execute_block(function_execute); + else { + execute_base(function_execute); + } + if(function_execute.isThrow()) + Generator->setException(function_execute.value); + else + Generator->setException(constStopIteration); +// } catch(CScriptVarPtr &e) { +// Generator->setException(e); + } catch(CScriptCoroutine::StopIteration_t &) { + Generator->setException(CScriptVarPtr()); +// } catch(CScriptException *e) { +// Generator->setException(newScriptVarError(this, *e)); + } catch(...) { + // pop current Generator + generatorStack.pop_back(); + + // restore callers stackBase + stackBase = Generator->callersStackBase; + + // restore callers Scope (restored by ScopeControl + // scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end()); + + // restore callers Tokenizer + t = Generator->callersTokenizer; + + // restore callers haveTry + haveTry = Generator->callersHaveTry; + + Generator->setException(constStopIteration); + // re-throw + throw; + } + // pop current Generator + generatorStack.pop_back(); + + // restore callers stackBase + stackBase = Generator->callersStackBase; + + // restore callers Scope (restored by ScopeControl + // scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end()); + + // restore callers Tokenizer + t = Generator->callersTokenizer; + + // restore callers haveTry + haveTry = Generator->callersHaveTry; +} +CScriptVarPtr CTinyJS::generator_yield(CScriptResult &execute, CScriptVar *YieldIn) +{ + if(!execute) return constUndefined; + CScriptVarGenerator *Generator = generatorStack.back(); + if(Generator->isClosed()) { + throwError(execute, TypeError, "yield from closing generator function"); + return constUndefined; + } + + // pop current Generator + generatorStack.pop_back(); + + // safe generators and restore callers stackBase + void *generatorStckBase = stackBase; + stackBase = Generator->callersStackBase; + + // safe generators and restore callers scopes + Generator->generatorScopes.assign(scopes.begin()+Generator->callersScopeSize, scopes.end()); + scopes.erase(scopes.begin()+Generator->callersScopeSize, scopes.end()); + + // safe generators and restore callers Tokenizer + CScriptTokenizer *generatorTokenizer = t; + t = Generator->callersTokenizer; + + // safe generators and restore callers haveTry + bool generatorsHaveTry = haveTry; + haveTry = Generator->callersHaveTry; + + CScriptVarPtr ret; + try { + ret = Generator->yield(execute, YieldIn); + } catch(...) { + // normaly catch(CScriptCoroutine::CScriptCoroutineFinish_t &) + // force StopIteration with call CScriptCoroutine::Stop() before CScriptCoroutine::next() + // but catch(...) is for paranoia + + // push current Generator + generatorStack.push_back(Generator); + + // safe callers and restore generators stackBase + Generator->callersStackBase = stackBase; + stackBase = generatorStckBase; + + // safe callers and restore generator Scopes + Generator->callersScopeSize = scopes.size(); + scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end()); + Generator->generatorScopes.clear(); + + // safe callers and restore generator Tokenizer + Generator->callersTokenizer = t; + t = generatorTokenizer; + + // safe callers and restore generator haveTry + Generator->callersHaveTry = haveTry; + haveTry = generatorsHaveTry; + + // re-throw + throw; + } + // push current Generator + generatorStack.push_back(Generator); + + // safe callers and restore generators stackBase + Generator->callersStackBase = stackBase; + stackBase = generatorStckBase; + + // safe callers and restore generator Scopes + Generator->callersScopeSize = scopes.size(); + scopes.insert(scopes.end(), Generator->generatorScopes.begin(), Generator->generatorScopes.end()); + Generator->generatorScopes.clear(); + + // safe callers and restore generator Tokenizer + Generator->callersTokenizer = t; + t = generatorTokenizer; + + // safe callers and restore generator haveTry + Generator->callersHaveTry = haveTry; + haveTry = generatorsHaveTry; + + return ret; +} +#endif /*NO_GENERATORS*/ + + + +CScriptVarPtr CTinyJS::mathsOp(CScriptResult &execute, const CScriptVarPtr &A, const CScriptVarPtr &B, int op) { + if(!execute) return constUndefined; + if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) { + // check type first + if( (A->getVarType() == B->getVarType()) ^ (op == LEX_TYPEEQUAL)) return constFalse; + // check value second + return mathsOp(execute, A, B, op == LEX_TYPEEQUAL ? LEX_EQUAL : LEX_NEQUAL); + } + if (!A->isPrimitive() && !B->isPrimitive()) { // Objects both + // check pointers + switch (op) { + case LEX_EQUAL: return constScriptVar(A==B); + case LEX_NEQUAL: return constScriptVar(A!=B); + } + } + + CScriptVarPtr a = A->toPrimitive_hintNumber(execute); + CScriptVarPtr b = B->toPrimitive_hintNumber(execute); + if(!execute) return constUndefined; + // do maths... + bool a_isString = a->isString(); + bool b_isString = b->isString(); + // both a String or one a String and op='+' + if( (a_isString && b_isString) || ((a_isString || b_isString) && op == '+')) { + string da = a->isNull() ? "" : a->toString(execute); + string db = b->isNull() ? "" : b->toString(execute); + switch (op) { + case '+': + try{ + return newScriptVar(da+db); + } catch(exception& e) { + throwError(execute, Error, e.what()); + return constUndefined; + } + case LEX_EQUAL: return constScriptVar(da==db); + case LEX_NEQUAL: return constScriptVar(da!=db); + case '<': return constScriptVar(da': return constScriptVar(da>db); + case LEX_GEQUAL: return constScriptVar(da>=db); + } + } + // special for undefined and null --> every true: undefined==undefined, undefined==null, null==undefined and null=null + else if( (a->isUndefined() || a->isNull()) && (b->isUndefined() || b->isNull()) ) { + switch (op) { + case LEX_EQUAL: return constScriptVar(true); + case LEX_NEQUAL: + case LEX_GEQUAL: + case LEX_LEQUAL: + case '<': + case '>': return constScriptVar(false); + } + } + CNumber da = a->toNumber(); + CNumber db = b->toNumber(); + switch (op) { + case '+': return a->newScriptVar(da+db); + case '-': return a->newScriptVar(da-db); + case '*': return a->newScriptVar(da*db); + case '/': return a->newScriptVar(da/db); + case '%': return a->newScriptVar(da%db); + case '&': return a->newScriptVar(da.toInt32()&db.toInt32()); + case '|': return a->newScriptVar(da.toInt32()|db.toInt32()); + case '^': return a->newScriptVar(da.toInt32()^db.toInt32()); + case '~': return a->newScriptVar(~da); + case LEX_LSHIFT: return a->newScriptVar(da<newScriptVar(da>>db); + case LEX_RSHIFTU: return a->newScriptVar(da.ushift(db)); + case LEX_EQUAL: return a->constScriptVar(da==db); + case LEX_NEQUAL: return a->constScriptVar(da!=db); + case '<': return a->constScriptVar(daconstScriptVar(da<=db); + case '>': return a->constScriptVar(da>db); + case LEX_GEQUAL: return a->constScriptVar(da>=db); + default: throw new CScriptException("This operation not supported on the int datatype"); + } +} + +void CTinyJS::assign_destructuring_var(const CScriptVarPtr &Scope, const CScriptTokenDataDestructuringVar &Objc, const CScriptVarPtr &Val, CScriptResult &execute) { + if(!execute) return; + if(Objc.vars.size() == 1) { + if(Scope) + Scope->addChildOrReplace(Objc.vars.front().second, Val); + else { + CScriptVarLinkWorkPtr v(findInScopes(Objc.vars.front().second)); + ASSERT(v==true); + if(v) v->setVarPtr(Val); + } + } else { + vector Path(1, Val); + for(DESTRUCTURING_VARS_cit it=Objc.vars.begin()+1; it!=Objc.vars.end(); ++it) { + if(it->second == "}" || it->second == "]") + Path.pop_back(); + else { + if(it->second.empty()) continue; // skip empty entries + CScriptVarLinkWorkPtr var = Path.back()->findChildWithStringChars(it->first); + if(var) var = var.getter(execute); else var = constUndefined; + if(!execute) return; + if(it->second == "{" || it->second == "[") { + Path.push_back(var); + } else if(Scope) + Scope->addChildOrReplace(it->second, var); + else { + CScriptVarLinkWorkPtr v(findInScopes(it->second)); + ASSERT(v==true); + if(v) v->setVarPtr(var); + } + } + } + } +} + +void CTinyJS::execute_var_init( bool hideLetScope, CScriptResult &execute ) +{ + for(;;) { + t->check(LEX_T_DESTRUCTURING_VAR); + CScriptTokenDataDestructuringVar &Objc = t->getToken().DestructuringVar(); + t->match(LEX_T_DESTRUCTURING_VAR); + if(t->tk == '=') { + t->match('='); + if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(true); + CScriptVarPtr Val = execute_assignment(execute); + if(hideLetScope) CScriptVarScopeLetPtr(scope())->setletExpressionInitMode(false); + assign_destructuring_var(0, Objc, Val, execute); + } + if (t->tk == ',') + t->match(','); + else + break; + } +} +void CTinyJS::execute_destructuring(CScriptTokenDataObjectLiteral &Objc, const CScriptVarPtr &Val, CScriptResult &execute) { + for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) { + if(it->value.empty()) continue; + CScriptVarPtr rhs = Val->findChildWithStringChars(it->id).getter(execute); + if(!rhs) rhs=constUndefined; + if(it->value.front().token == LEX_T_OBJECT_LITERAL && it->value.front().Object().destructuring) { + execute_destructuring(it->value.front().Object(), rhs, execute); + } else { + t->pushTokenScope(it->value); + CScriptVarLinkWorkPtr lhs = execute_condition(execute); + if(lhs->isWritable()) { + if (!lhs->isOwned()) { + CScriptVarPtr fakedOwner = lhs.getReferencedOwner(); + if(fakedOwner) { + if(!fakedOwner->isExtensible()) + continue; + lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs); + } else + lhs = root->addChildOrReplace(lhs->getName(), lhs); + } + lhs.setter(execute, rhs); + } + } + } +} + +CScriptVarLinkWorkPtr CTinyJS::execute_literals(CScriptResult &execute) { + switch(t->tk) { + case LEX_ID: + if(execute) { + CScriptVarLinkWorkPtr a(findInScopes(t->tkStr())); + if (!a) { + /* Variable doesn't exist! JavaScript says we should create it + * (we won't add it here. This is done in the assignment operator)*/ + if(t->tkStr() == "this") + a = root; // fake this + else + a = CScriptVarLinkPtr(constScriptVar(Undefined), t->tkStr()); + } +/* + prvention for assignment to this is now done by the tokenizer + else if(t->tkStr() == "this") + a(a->getVarPtr()); // prevent assign to this +*/ + t->match(LEX_ID); + return a; + } + t->match(LEX_ID); + break; + case LEX_INT: + { + CScriptVarPtr a = newScriptVar(t->getToken().Int()); + a->setExtensible(false); + t->match(LEX_INT); + return a; + } + break; + case LEX_FLOAT: + { + CScriptVarPtr a = newScriptVar(t->getToken().Float()); + t->match(LEX_FLOAT); + return a; + } + break; + case LEX_STR: + { + CScriptVarPtr a = newScriptVar(t->getToken().String()); + t->match(LEX_STR); + return a; + } + break; +#ifndef NO_REGEXP + case LEX_REGEXP: + { + string::size_type pos = t->getToken().String().find_last_of('/'); + string source = t->getToken().String().substr(1, pos-1); + string flags = t->getToken().String().substr(pos+1); + CScriptVarPtr a = newScriptVar(source, flags); + t->match(LEX_REGEXP); + return a; + } + break; +#endif /* NO_REGEXP */ + case LEX_T_OBJECT_LITERAL: + if(execute) { + CScriptTokenDataObjectLiteral &Objc = t->getToken().Object(); + t->match(LEX_T_OBJECT_LITERAL); + if(Objc.destructuring) { + t->match('='); + CScriptVarPtr a = execute_assignment(execute); + if(execute) execute_destructuring(Objc, a, execute); + return a; + } else { + CScriptVarPtr a = Objc.type==CScriptTokenDataObjectLiteral::OBJECT ? newScriptVar(Object) : newScriptVar(Array); + for(vector::iterator it=Objc.elements.begin(); execute && it!=Objc.elements.end(); ++it) { + if(it->value.empty()) continue; + CScriptToken &tk = it->value.front(); + if(tk.token==LEX_T_GET || tk.token==LEX_T_SET) { + CScriptTokenDataFnc &Fnc = tk.Fnc(); + if((tk.token == LEX_T_GET && Fnc.arguments.size()==0) || (tk.token == LEX_T_SET && Fnc.arguments.size()==1)) { + CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(tk); + CScriptVarLinkWorkPtr child = a->findChild(Fnc.name); + if(child && !child->getVarPtr()->isAccessor()) child.clear(); + if(!child) child = a->addChildOrReplace(Fnc.name, newScriptVar(Accessor)); + child->getVarPtr()->addChildOrReplace((tk.token==LEX_T_GET?TINYJS_ACCESSOR_GET_VAR:TINYJS_ACCESSOR_SET_VAR), funcVar->getVarPtr()); + } + } else { + t->pushTokenScope(it->value); + a->addChildOrReplace(it->id, execute_assignment(execute)); + while(0); + } + } + return a; + } + } else + t->match(LEX_T_OBJECT_LITERAL); + break; + case LEX_R_LET: // let as expression + if(execute) { + CScopeControl ScopeControl(this); + t->match(LEX_R_LET); + t->match('('); + t->check(LEX_T_FORWARD); + ScopeControl.addLetScope(); + execute_statement(execute); // execute forwarder + execute_var_init(true, execute); + t->match(')'); + return execute_assignment(execute); + } else { + t->skip(t->getToken().Int()); + } + break; + case LEX_T_FUNCTION_OPERATOR: + if(execute) { + CScriptVarLinkWorkPtr a = parseFunctionDefinition(t->getToken()); + t->match(LEX_T_FUNCTION_OPERATOR); + return a; + } + t->match(LEX_T_FUNCTION_OPERATOR); + break; +#ifndef NO_GENERATORS + case LEX_R_YIELD: + if (execute) { + t->match(LEX_R_YIELD); + CScriptVarPtr result = constUndefined; + if (t->tk != ';') + result = execute_base(execute); + if(execute) + return generator_yield(execute, result.getVar()); + else + return constUndefined; + } else + t->skip(t->getToken().Int()); + break; +#endif /*NO_GENERATORS*/ + case LEX_R_NEW: // new -> create a new object + if (execute) { + t->match(LEX_R_NEW); + CScriptVarLinkWorkPtr parent = execute_literals(execute); + CScriptVarLinkWorkPtr objClass = execute_member(parent, execute).getter(execute); + if (execute) { + CScriptVarPtr Constructor = objClass->getVarPtr(); + if(Constructor->isFunction()) { + CScriptVarPtr obj(newScriptVar(Object)); + CScriptVarLinkPtr prototype = Constructor->findChild(TINYJS_PROTOTYPE_CLASS); + if(!prototype || prototype->getVarPtr()->isUndefined() || prototype->getVarPtr()->isNull()) { + prototype = Constructor->addChild(TINYJS_PROTOTYPE_CLASS, newScriptVar(Object), SCRIPTVARLINK_WRITABLE); + obj->addChildOrReplace(TINYJS___PROTO___VAR, prototype, SCRIPTVARLINK_WRITABLE); + } + CScriptVarLinkPtr __constructor__ = Constructor->findChild("__constructor__"); + if(__constructor__ && __constructor__->getVarPtr()->isFunction()) + Constructor = __constructor__; + if (stackBase) { + int dummy; + if(&dummy < stackBase) + throwError(execute, Error, "too much recursion"); + } + vector arguments; + if (t->tk == '(') { + t->match('('); + while(t->tk!=')') { + CScriptVarPtr value = execute_assignment(execute).getter(execute); + if (execute) + arguments.push_back(value); + if (t->tk!=')') t->match(',', ')'); + } + t->match(')'); + } + if(execute) { + CScriptVarPtr returnVar = callFunction(execute, Constructor, arguments, obj, &obj); + if(returnVar->isObject()) + return CScriptVarLinkWorkPtr(returnVar); + return CScriptVarLinkWorkPtr(obj); + } + } else + throwError(execute, TypeError, objClass->getName() + " is not a constructor"); + } else + if (t->tk == '(') t->skip(t->getToken().Int()); + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_TRUE: + t->match(LEX_R_TRUE); + return constScriptVar(true); + case LEX_R_FALSE: + t->match(LEX_R_FALSE); + return constScriptVar(false); + case LEX_R_NULL: + t->match(LEX_R_NULL); + return constScriptVar(Null); + case '(': + if(execute) { + t->match('('); + CScriptVarLinkWorkPtr a = execute_base(execute).getter(execute); + t->match(')'); + return a; + } else + t->skip(t->getToken().Int()); + break; + case LEX_T_EXCEPTION_VAR: + t->match(LEX_T_EXCEPTION_VAR); + if(execute.value) return execute.value; + break; + default: + t->match(LEX_EOF); + break; + } + return constScriptVar(Undefined); + +} +CScriptVarLinkWorkPtr CTinyJS::execute_member(CScriptVarLinkWorkPtr &parent, CScriptResult &execute) { + CScriptVarLinkWorkPtr a; + parent.swap(a); + if(t->tk == '.' || t->tk == '[') { + while(t->tk == '.' || t->tk == '[') { + parent.swap(a); + a = parent.getter(execute); // a is now the "getted" var + if(execute && (a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull())) { + throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute)); + } + string name; + if(t->tk == '.') { + t->match('.'); + name = t->tkStr(); + t->match(LEX_ID); + } else { + if(execute) { + t->match('['); + name = execute_base(execute)->toString(execute); + t->match(']'); + } else + t->skip(t->getToken().Int()); + } + if (execute) { + CScriptVarPtr aVar = a; + a = aVar->findChildWithPrototypeChain(name); + if(!a) { + a(constScriptVar(Undefined), name); + a.setReferencedOwner(aVar); + } + } + } + } + return a; +} + +CScriptVarLinkWorkPtr CTinyJS::execute_function_call(CScriptResult &execute) { + CScriptVarLinkWorkPtr parent = execute_literals(execute); + CScriptVarLinkWorkPtr a = execute_member(parent, execute); + while (t->tk == '(') { + if (execute) { + if(a->getVarPtr()->isUndefined() || a->getVarPtr()->isNull()) + throwError(execute, ReferenceError, a->getName() + " is " + a->toString(execute)); + CScriptVarPtr fnc = a.getter(execute)->getVarPtr(); + if (!fnc->isFunction()) + throwError(execute, TypeError, a->getName() + " is not a function"); + if (stackBase) { + int dummy; + if(&dummy < stackBase) + throwError(execute, Error, "too much recursion"); + } + + t->match('('); // path += '('; + + // grab in all parameters + vector arguments; + while(t->tk!=')') { + CScriptVarLinkWorkPtr value = execute_assignment(execute).getter(execute); +// path += (*value)->getString(); + if (execute) { + arguments.push_back(value); + } + if (t->tk!=')') { t->match(','); /*path+=',';*/ } + } + t->match(')'); //path+=')'; + // setup a return variable + CScriptVarLinkWorkPtr returnVar; + if(execute) { + if (!parent) + parent = findInScopes("this"); + // if no parent use the root-scope + CScriptVarPtr This(parent ? parent->getVarPtr() : (CScriptVarPtr )root); + a = callFunction(execute, fnc, arguments, This); + } + } else { + // function, but not executing - just parse args and be done + t->match('('); + while (t->tk != ')') { + CScriptVarLinkWorkPtr value = execute_base(execute); + // if (t->tk!=')') t->match(','); + } + t->match(')'); + } + a = execute_member(parent = a, execute); + } + return a; +} +// R->L: Precedence 3 (in-/decrement) ++ -- +// R<-L: Precedence 4 (unary) ! ~ + - typeof void delete +bool CTinyJS::execute_unary_rhs(CScriptResult &execute, CScriptVarLinkWorkPtr& a) { + t->match(t->tk); + a = execute_unary(execute).getter(execute); + if(execute) CheckRightHandVar(execute, a); + return execute; +} +CScriptVarLinkWorkPtr CTinyJS::execute_unary(CScriptResult &execute) { + CScriptVarLinkWorkPtr a; + switch(t->tk) { + case '-': + if(execute_unary_rhs(execute, a)) + a(newScriptVar(-a->getVarPtr()->toNumber(execute))); + break; + case '+': + if(execute_unary_rhs(execute, a)) + a = newScriptVar(a->getVarPtr()->toNumber(execute)); + break; + case '!': + if(execute_unary_rhs(execute, a)) + a = constScriptVar(!a->getVarPtr()->toBoolean()); + break; + case '~': + if(execute_unary_rhs(execute, a)) + a = newScriptVar(~a->getVarPtr()->toNumber(execute)); + break; + case LEX_R_TYPEOF: + if(execute_unary_rhs(execute, a)) + a = newScriptVar(a->getVarPtr()->getVarType()); + break; + case LEX_R_VOID: + if(execute_unary_rhs(execute, a)) + a = constScriptVar(Undefined); + break; + case LEX_R_DELETE: + t->match(LEX_R_DELETE); // delete + a = execute_unary(execute); // no getter - delete can remove the accessor + if (execute) { + // !!! no right-hand-check by delete + if(a->isOwned() && a->isConfigurable() && a->getName() != "this") { + a->getOwner()->removeLink(a); // removes the link from owner + a = constScriptVar(true); + } + else + a = constScriptVar(false); + } + break; + case LEX_PLUSPLUS: + case LEX_MINUSMINUS: + { + int op = t->tk; + t->match(op); // pre increment/decrement + CScriptTokenizer::ScriptTokenPosition ErrorPos = t->getPos(); + a = execute_function_call(execute); + if (execute) { + if(a->getName().empty()) + throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", ErrorPos); + else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty()) + throwError(execute, ReferenceError, a->getName() + " is not defined", ErrorPos); + CScriptVarPtr res = newScriptVar(a.getter(execute)->getVarPtr()->toNumber(execute).add(op==LEX_PLUSPLUS ? 1 : -1)); + if(a->isWritable()) { + if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible()) + a.getReferencedOwner()->addChildOrReplace(a->getName(), res); + else + a.setter(execute, res); + } + a = res; + } + } + break; + default: + a = execute_function_call(execute); + break; + } + // post increment/decrement + if (t->tk==LEX_PLUSPLUS || t->tk==LEX_MINUSMINUS) { + int op = t->tk; + t->match(op); + if (execute) { + if(a->getName().empty()) + throwError(execute, SyntaxError, string("invalid ")+(op==LEX_PLUSPLUS ? "increment" : "decrement")+" operand", t->getPrevPos()); + else if(!a->isOwned() && !a.hasReferencedOwner() && !a->getName().empty()) + throwError(execute, ReferenceError, a->getName() + " is not defined", t->getPrevPos()); + CNumber num = a.getter(execute)->getVarPtr()->toNumber(execute); + CScriptVarPtr res = newScriptVar(num.add(op==LEX_PLUSPLUS ? 1 : -1)); + if(a->isWritable()) { + if(!a->isOwned() && a.hasReferencedOwner() && a.getReferencedOwner()->isExtensible()) + a.getReferencedOwner()->addChildOrReplace(a->getName(), res); + else + a.setter(execute, res); + } + a = newScriptVar(num); + } + } + return a; +} + +// L->R: Precedence 5 (term) * / % +CScriptVarLinkWorkPtr CTinyJS::execute_term(CScriptResult &execute) { + CScriptVarLinkWorkPtr a = execute_unary(execute); + if (t->tk=='*' || t->tk=='/' || t->tk=='%') { + CheckRightHandVar(execute, a); + while (t->tk=='*' || t->tk=='/' || t->tk=='%') { + int op = t->tk; + t->match(t->tk); + CScriptVarLinkWorkPtr b = execute_unary(execute); // L->R + if (execute) { + CheckRightHandVar(execute, b); + a = mathsOp(execute, a.getter(execute), b.getter(execute), op); + } + } + } + return a; +} + +// L->R: Precedence 6 (addition/subtraction) + - +CScriptVarLinkWorkPtr CTinyJS::execute_expression(CScriptResult &execute) { + CScriptVarLinkWorkPtr a = execute_term(execute); + if (t->tk=='+' || t->tk=='-') { + CheckRightHandVar(execute, a); + while (t->tk=='+' || t->tk=='-') { + int op = t->tk; + t->match(t->tk); + CScriptVarLinkWorkPtr b = execute_term(execute); // L->R + if (execute) { + CheckRightHandVar(execute, b); + a = mathsOp(execute, a.getter(execute), b.getter(execute), op); + } + } + } + return a; +} + +// L->R: Precedence 7 (bitwise shift) << >> >>> +CScriptVarLinkWorkPtr CTinyJS::execute_binary_shift(CScriptResult &execute) { + CScriptVarLinkWorkPtr a = execute_expression(execute); + if (t->tk==LEX_LSHIFT || t->tk==LEX_RSHIFT || t->tk==LEX_RSHIFTU) { + CheckRightHandVar(execute, a); + while (t->tk>=LEX_SHIFTS_BEGIN && t->tk<=LEX_SHIFTS_END) { + int op = t->tk; + t->match(t->tk); + + CScriptVarLinkWorkPtr b = execute_expression(execute); // L->R + if (execute) { + CheckRightHandVar(execute, a); + // not in-place, so just replace + a = mathsOp(execute, a.getter(execute), b.getter(execute), op); + } + } + } + return a; +} +// L->R: Precedence 8 (relational) < <= > <= in instanceof +// L->R: Precedence 9 (equality) == != === !=== +CScriptVarLinkWorkPtr CTinyJS::execute_relation(CScriptResult &execute, int set, int set_n) { + CScriptVarLinkWorkPtr a = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute); + if ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END) + || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) { + CheckRightHandVar(execute, a); + a = a.getter(execute); + while ((set==LEX_EQUAL && t->tk>=LEX_RELATIONS_1_BEGIN && t->tk<=LEX_RELATIONS_1_END) + || (set=='<' && (t->tk==LEX_LEQUAL || t->tk==LEX_GEQUAL || t->tk=='<' || t->tk=='>' || t->tk == LEX_R_IN || t->tk == LEX_R_INSTANCEOF))) { + int op = t->tk; + t->match(t->tk); + CScriptVarLinkWorkPtr b = set_n ? execute_relation(execute, set_n, 0) : execute_binary_shift(execute); // L->R + if (execute) { + CheckRightHandVar(execute, b); + string nameOf_b = b->getName(); + b = b.getter(execute); + if(op == LEX_R_IN) { + if(!b->getVarPtr()->isObject()) + throwError(execute, TypeError, "invalid 'in' operand "+nameOf_b); + a(constScriptVar( (bool)b->getVarPtr()->findChildWithPrototypeChain(a->toString(execute)))); + } else if(op == LEX_R_INSTANCEOF) { + CScriptVarLinkPtr prototype = b->getVarPtr()->findChild(TINYJS_PROTOTYPE_CLASS); + if(!prototype) + throwError(execute, TypeError, "invalid 'instanceof' operand "+nameOf_b); + else { + unsigned int uniqueID = allocUniqueID(); + CScriptVarPtr object = a->getVarPtr()->findChild(TINYJS___PROTO___VAR); + while( object && object!=prototype->getVarPtr() && object->getTemporaryMark() != uniqueID) { + object->setTemporaryMark(uniqueID); // prevents recursions + object = object->findChild(TINYJS___PROTO___VAR); + } + freeUniqueID(); + a(constScriptVar(object && object==prototype->getVarPtr())); + } + } else + a = mathsOp(execute, a, b, op); + } + } + } + return a; +} + +// L->R: Precedence 10 (bitwise-and) & +// L->R: Precedence 11 (bitwise-xor) ^ +// L->R: Precedence 12 (bitwise-or) | +CScriptVarLinkWorkPtr CTinyJS::execute_binary_logic(CScriptResult &execute, int op, int op_n1, int op_n2) { + CScriptVarLinkWorkPtr a = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute); + if (t->tk==op) { + CheckRightHandVar(execute, a); + a = a.getter(execute); + while (t->tk==op) { + t->match(t->tk); + CScriptVarLinkWorkPtr b = op_n1 ? execute_binary_logic(execute, op_n1, op_n2, 0) : execute_relation(execute); // L->R + if (execute) { + CheckRightHandVar(execute, b); + a = mathsOp(execute, a, b.getter(execute), op); + } + } + } + return a; +} +// L->R: Precedence 13 ==> (logical-and) && +// L->R: Precedence 14 ==> (logical-or) || +CScriptVarLinkWorkPtr CTinyJS::execute_logic(CScriptResult &execute, int op /*= LEX_OROR*/, int op_n /*= LEX_ANDAND*/) { + CScriptVarLinkWorkPtr a = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute); + if (t->tk==op) { + if(execute) { + CScriptVarLinkWorkPtr b; + CheckRightHandVar(execute, a); + a(a.getter(execute)); // rebuild a + do { + if(execute && (op==LEX_ANDAND ? a->toBoolean() : !a->toBoolean())) { + t->match(t->tk); + b = op_n ? execute_logic(execute, op_n, 0) : execute_binary_logic(execute); + CheckRightHandVar(execute, b); a(b.getter(execute)); // rebuild a + } else + t->skip(t->getToken().Int()); + } while(t->tk==op); + } else + t->skip(t->getToken().Int()); + } + return a; +} + +// L<-R: Precedence 15 (condition) ?: +CScriptVarLinkWorkPtr CTinyJS::execute_condition(CScriptResult &execute) { + CScriptVarLinkWorkPtr a = execute_logic(execute); + if (t->tk=='?') { + CheckRightHandVar(execute, a); + bool cond = execute && a.getter(execute)->toBoolean(); + if(execute) { + if(cond) { + t->match('?'); +// a = execute_condition(execute); + a = execute_assignment(execute); + t->check(':'); + t->skip(t->getToken().Int()); + } else { + CScriptVarLinkWorkPtr b; + t->skip(t->getToken().Int()); + t->match(':'); + return execute_assignment(execute); +// return execute_condition(execute); + } + } else { + t->skip(t->getToken().Int()); + t->skip(t->getToken().Int()); + } + } + return a; +} + +// L<-R: Precedence 16 (assignment) = += -= *= /= %= <<= >>= >>>= &= |= ^= +// now we can return CScriptVarLinkPtr execute_assignment returns always no setters/getters +// force life of the Owner is no more needed +CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptResult &execute) { + return execute_assignment(execute_condition(execute), execute); +} +CScriptVarLinkPtr CTinyJS::execute_assignment(CScriptVarLinkWorkPtr lhs, CScriptResult &execute) { + if (t->tk=='=' || (t->tk>=LEX_ASSIGNMENTS_BEGIN && t->tk<=LEX_ASSIGNMENTS_END) ) { + int op = t->tk; + CScriptTokenizer::ScriptTokenPosition leftHandPos = t->getPos(); + t->match(t->tk); + CScriptVarLinkWorkPtr rhs = execute_assignment(execute).getter(execute); // L<-R + if (execute) { + if (!lhs->isOwned() && !lhs.hasReferencedOwner() && lhs->getName().empty()) { + throw new CScriptException(ReferenceError, "invalid assignment left-hand side (at runtime)", t->currentFile, leftHandPos.currentLine(), leftHandPos.currentColumn()); + } else if (op != '=' && !lhs->isOwned()) { + throwError(execute, ReferenceError, lhs->getName() + " is not defined"); + } + else if(lhs->isWritable()) { + if (op=='=') { + if (!lhs->isOwned()) { + CScriptVarPtr fakedOwner = lhs.getReferencedOwner(); + if(fakedOwner) { + if(!fakedOwner->isExtensible()) + return rhs->getVarPtr(); + lhs = fakedOwner->addChildOrReplace(lhs->getName(), lhs); + } else + lhs = root->addChildOrReplace(lhs->getName(), lhs); + } + lhs.setter(execute, rhs); + return rhs->getVarPtr(); + } else { + CScriptVarPtr result; + static int assignments[] = {'+', '-', '*', '/', '%', LEX_LSHIFT, LEX_RSHIFT, LEX_RSHIFTU, '&', '|', '^'}; + result = mathsOp(execute, lhs, rhs, assignments[op-LEX_PLUSEQUAL]); + lhs.setter(execute, result); + return result; + } + } else { + // lhs is not writable we ignore lhs & use rhs + return rhs->getVarPtr(); + } + } + } + else + CheckRightHandVar(execute, lhs); + return lhs.getter(execute); +} +// L->R: Precedence 17 (comma) , +CScriptVarLinkPtr CTinyJS::execute_base(CScriptResult &execute) { + CScriptVarLinkPtr a; + for(;;) + { + a = execute_assignment(execute); // L->R + if (t->tk == ',') { + t->match(','); + } else + break; + } + return a; +} +void CTinyJS::execute_block(CScriptResult &execute) { + if(execute) { + t->match('{'); + CScopeControl ScopeControl(this); + if(t->tk==LEX_T_FORWARD) // add a LetScope only if needed + ScopeControl.addLetScope(); + while (t->tk && t->tk!='}') + execute_statement(execute); + t->match('}'); + // scopes.pop_back(); + } + else + t->skip(t->getToken().Int()); +} +void CTinyJS::execute_statement(CScriptResult &execute) { + switch(t->tk) { + case '{': /* A block of code */ + execute_block(execute); + break; + case ';': /* Empty statement - to allow things like ;;; */ + t->match(';'); + break; + case LEX_T_FORWARD: + { + CScriptVarPtr in_scope = scope()->scopeLet(); + STRING_SET_t *varNames = t->getToken().Forwarder().varNames; + for(int i=0; ifindChild(*it); + if(!a) in_scope->addChild(*it, constScriptVar(Undefined), i==CScriptTokenDataForwards::CONSTS ? SCRIPTVARLINK_CONSTDEFAULT : SCRIPTVARLINK_VARDEFAULT); + } + in_scope = scope()->scopeVar(); + } + CScriptTokenDataForwards::FNC_SET_t &functions = t->getToken().Forwarder().functions; + for(CScriptTokenDataForwards::FNC_SET_it it=functions.begin(); it!=functions.end(); ++it) { + CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(*it); + in_scope->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT); + } + t->match(LEX_T_FORWARD); + } + break; + case LEX_R_VAR: + case LEX_R_LET: + case LEX_R_CONST: + if(execute) + { + CScopeControl ScopeControl(this); + bool isLet = t->tk==LEX_R_LET, let_ext=false; + t->match(t->tk); + if(isLet && t->tk=='(') { + let_ext = true; + t->match('('); + t->check(LEX_T_FORWARD); + ScopeControl.addLetScope(); + execute_statement(execute); // forwarder + } + execute_var_init(let_ext, execute); + if(let_ext) { + t->match(')'); + execute_statement(execute); + } else + t->match(';'); + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_WITH: + if(execute) { + t->match(LEX_R_WITH); + t->match('('); + CScriptVarLinkPtr var = execute_base(execute); + t->match(')'); + CScopeControl ScopeControl(this); + ScopeControl.addWithScope(var); + execute_statement(execute); + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_IF: + if(execute) { + t->match(LEX_R_IF); + t->match('('); + bool cond = execute_base(execute)->toBoolean(); + t->match(')'); + if(cond && execute) { + t->match(LEX_T_SKIP); + execute_statement(execute); + } else { + t->check(LEX_T_SKIP); + t->skip(t->getToken().Int()); + } + if (t->tk==LEX_R_ELSE) { + if(!cond && execute) { + t->match(LEX_R_ELSE); + execute_statement(execute); + } + else + t->skip(t->getToken().Int()); + } + } else + t->skip(t->getToken().Int()); + break; + case LEX_T_FOR_IN: + { + CScriptTokenDataLoop &LoopData = t->getToken().Loop(); + t->match(LEX_T_FOR_IN); + if(!execute) break; + + CScopeControl ScopeControl(this); + if(LoopData.init.size()) { + t->pushTokenScope(LoopData.init); + ScopeControl.addLetScope(); + execute_statement(execute); // forwarder + } + if(!execute) break; + + t->pushTokenScope(LoopData.iter); + CScriptVarPtr for_in_var = execute_base(execute); + + if(!execute) break; + + CScriptVarPtr Iterator(for_in_var->toIterator(execute, LoopData.type!=CScriptTokenDataLoop::FOR_IN ? 2:1)); + CScriptVarFunctionPtr Iterator_next(Iterator->findChildWithPrototypeChain("next").getter(execute)); + if(execute && !Iterator_next) throwError(execute, TypeError, "'" + for_in_var->toString(execute) + "' is not iterable", t->getPrevPos()); + if(!execute) break; + CScriptResult tmp_execute; + for(;;) { + bool old_haveTry = haveTry; + haveTry = true; + tmp_execute.set(CScriptResult::Normal, Iterator); + t->pushTokenScope(LoopData.condition); + execute_statement(tmp_execute); + haveTry = old_haveTry; + if(tmp_execute.isThrow()){ + if(tmp_execute.value != constStopIteration) { + if(!haveTry) + throw new CScriptException("uncaught exception: '"+tmp_execute.value->toString(CScriptResult())+"'", t->currentFile, t->currentLine(), t->currentColumn()); + else + execute = tmp_execute; + } + break; + } + t->pushTokenScope(LoopData.body); + execute_statement(execute); + if(!execute) { + bool Continue = false; + if(execute.isBreakContinue() + && + (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) { + Continue = execute.isContinue(); + execute.set(CScriptResult::Normal, false); + } + if(!Continue) break; + } + } + } + break; + case LEX_T_LOOP: + { + CScriptTokenDataLoop &LoopData = t->getToken().Loop(); + t->match(LEX_T_LOOP); + if(!execute) break; + + CScopeControl ScopeControl(this); + if(LoopData.type == CScriptTokenDataLoop::FOR) { + CScriptResult tmp_execute; + t->pushTokenScope(LoopData.init); + if(t->tk == LEX_T_FORWARD) { + ScopeControl.addLetScope(); + execute_statement(tmp_execute); // forwarder + } + if(t->tk==LEX_R_VAR || t->tk==LEX_R_LET) + execute_statement(tmp_execute); // initialisation + else if (t->tk != ';') + execute_base(tmp_execute); // initialisation + if(!execute(tmp_execute)) break; + } + + bool loopCond = true; // Empty Condition -->always true + if(LoopData.type != CScriptTokenDataLoop::DO && LoopData.condition.size()) { + t->pushTokenScope(LoopData.condition); + loopCond = execute_base(execute)->toBoolean(); + if(!execute) break; + } + while (loopCond && execute) { + t->pushTokenScope(LoopData.body); + execute_statement(execute); + if(!execute) { + bool Continue = false; + if(execute.isBreakContinue() + && + (execute.target.empty() || find(LoopData.labels.begin(), LoopData.labels.end(), execute.target) != LoopData.labels.end())) { + Continue = execute.isContinue(); + execute.set(CScriptResult::Normal, false); + } + if(!Continue) break; + } + if(LoopData.type == CScriptTokenDataLoop::FOR && execute && LoopData.iter.size()) { + t->pushTokenScope(LoopData.iter); + execute_base(execute); + } + if(execute && LoopData.condition.size()) { + t->pushTokenScope(LoopData.condition); + loopCond = execute_base(execute)->toBoolean(); + } + } + } + break; + case LEX_R_BREAK: + case LEX_R_CONTINUE: + if (execute) + { + CScriptResult::TYPE type = t->tk==LEX_R_BREAK ? CScriptResult::Break : CScriptResult::Continue; + string label; + t->match(t->tk); + if(t->tk == LEX_ID) { + label = t->tkStr(); + t->match(LEX_ID); + } + t->match(';'); + execute.set(type, label); + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_RETURN: + if (execute) { + t->match(LEX_R_RETURN); + CScriptVarPtr result = constUndefined; + if (t->tk != ';') + result = execute_base(execute); + t->match(';'); + execute.set(CScriptResult::Return, result); + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_FUNCTION: + if(execute) { + CScriptVarLinkWorkPtr funcVar = parseFunctionDefinition(t->getToken()); + scope()->scopeVar()->addChildOrReplace(funcVar->getName(), funcVar, SCRIPTVARLINK_VARDEFAULT); + } + case LEX_T_FUNCTION_PLACEHOLDER: + t->match(t->tk); + break; + case LEX_T_TRY: + if(execute) { + CScriptTokenDataTry &TryData = t->getToken().Try(); + + bool old_haveTry = haveTry; + haveTry = true; + + // execute try-block + t->pushTokenScope(TryData.tryBlock); + execute_block(execute); + + bool isThrow = execute.isThrow(); + + if(isThrow && execute.value) { + // execute catch-blocks only if value set (spezial case Generator.close() -> only finally-blocks are executed) + for(CScriptTokenDataTry::CatchBlock_it catchBlock = TryData.catchBlocks.begin(); catchBlock!=TryData.catchBlocks.end(); catchBlock++) { + CScriptResult catch_execute; + CScopeControl ScopeControl(this); + ScopeControl.addLetScope(); + t->pushTokenScope(catchBlock->condition); // condition; + execute_statement(catch_execute); // forwarder + assign_destructuring_var(0, *catchBlock->indentifiers, execute.value, catch_execute); + bool condition = true; + if(catchBlock->condition.size()>1) + condition = execute_base(catch_execute)->toBoolean(); + if(!catch_execute) { + execute = catch_execute; + break; + } else if(condition) { + t->pushTokenScope(catchBlock->block); // condition; + execute_block(catch_execute); + execute = catch_execute; + break; + } + } + } + if(TryData.finallyBlock.size()) { + CScriptResult finally_execute; // alway execute finally-block + t->pushTokenScope(TryData.finallyBlock); // finally; + execute_block(finally_execute); + execute(finally_execute); + } + // restore haveTry + haveTry = old_haveTry; + if(execute.isThrow() && !haveTry) { // (exception in catch or finally or no catch-clause found) and no parent try-block + if(execute.value->isError()) + throw CScriptVarErrorPtr(execute.value)->toCScriptException(); + throw new CScriptException("uncaught exception: '"+execute.value->toString()+"'", execute.throw_at_file, execute.throw_at_line, execute.throw_at_column); + } + + } + t->match(LEX_T_TRY); + break; + case LEX_R_THROW: + if(execute) { + CScriptTokenizer::ScriptTokenPosition tokenPos = t->getPos(); + // int tokenStart = t->getToken().pos; + t->match(LEX_R_THROW); + CScriptVarPtr a = execute_base(execute); + if(execute) { + if(haveTry) + execute.setThrow(a, t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn()); + else + throw new CScriptException("uncaught exception: '"+a->toString(execute)+"'", t->currentFile, tokenPos.currentLine(), tokenPos.currentColumn()); + } + } else + t->skip(t->getToken().Int()); + break; + case LEX_R_SWITCH: + if(execute) { + t->match(LEX_R_SWITCH); + t->match('('); + CScriptVarPtr SwitchValue = execute_base(execute); + t->match(')'); + if(execute) { + t->match('{'); + CScopeControl ScopeControl(this); + if(t->tk == LEX_T_FORWARD) { + ScopeControl.addLetScope(); // add let-scope only if needed + execute_statement(execute); // execute forwarder + } + CScriptTokenizer::ScriptTokenPosition defaultStart = t->getPos(); + bool hasDefault = false, found = false; + while (t->tk) { + switch(t->tk) { + case LEX_R_CASE: + if(!execute) + t->skip(t->getToken().Int()); // skip up to'}' + else if(found) { // execute && found + t->match(LEX_R_CASE); + t->skip(t->getToken().Int()); // skip up to ':' + t->match(':'); // skip ':' and execute all after ':' + } else { // execute && !found + t->match(LEX_R_CASE); + t->match(LEX_T_SKIP); // skip 'L_T_SKIP' + CScriptVarLinkPtr CaseValue = execute_base(execute); + CaseValue = mathsOp(execute, CaseValue, SwitchValue, LEX_TYPEEQUAL); + if(execute) { + found = CaseValue->toBoolean(); + if(found) t->match(':'); // skip ':' and execute all after ':' + else t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}' + } else + t->skip(t->getToken().Int()); // skip up to next 'case'/'default' or '}' + } + break; + case LEX_R_DEFAULT: + if(!execute) + t->skip(t->getToken().Int()); // skip up to'}' NOTE: no extra 'L_T_SKIP' for skipping tp ':' + else { + t->match(LEX_R_DEFAULT); + if(found) + t->match(':'); // skip ':' and execute all after ':' + else { + hasDefault = true; // in fist pass: skip default-area + defaultStart = t->getPos(); // remember pos of default + t->skip(t->getToken().Int()); // skip up to next 'case' or '}' + } + } + break; + case '}': + if(execute && !found && hasDefault) { // if not found & have default -> execute default + found = true; + t->setPos(defaultStart); + t->match(':'); + } else + goto end_while; // goto isn't fine but C supports no "break lable;" + break; + default: + ASSERT(found); + execute_statement(execute); + break; + } + } +end_while: + t->match('}'); + if(execute.isBreak() && execute.target.empty()) { + execute.set(CScriptResult::Normal); + } + } else + t->skip(t->getToken().Int()); + } else + t->skip(t->getToken().Int()); + break; + case LEX_T_DUMMY_LABEL: + t->match(LEX_T_DUMMY_LABEL); + t->match(':'); + break; + case LEX_T_LABEL: + { + STRING_VECTOR_t Labels; + while(t->tk == LEX_T_LABEL) { + Labels.push_back(t->tkStr()); + t->match(LEX_T_LABEL); + t->match(':'); + } + if(execute) { + execute_statement(execute); + if(execute.isBreak() && find(Labels.begin(), Labels.end(), execute.target) != Labels.end()) { // break this label + execute.set(CScriptResult::Normal, false); + } + } + else + execute_statement(execute); + } + break; + case LEX_EOF: + t->match(LEX_EOF); + break; + default: + if(t->tk!=LEX_T_SKIP || execute) { + if(t->tk==LEX_T_SKIP) t->match(LEX_T_SKIP); + /* Execute a simple statement that only contains basic arithmetic... */ + CScriptVarPtr ret = execute_base(execute); + if(execute) execute.set(CScriptResult::Normal, CScriptVarPtr(ret)); + t->match(';'); + } else + t->skip(t->getToken().Int()); + break; + } +} + + +/// Finds a child, looking recursively up the scopes +CScriptVarLinkPtr CTinyJS::findInScopes(const string &childName) { + return scope()->findInScopes(childName); +} + +////////////////////////////////////////////////////////////////////////// +/// Object +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Object(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(c->getArgument(0)->toObject()); +} +void CTinyJS::native_Object_getPrototypeOf(const CFunctionsScopePtr &c, void *data) { + if(c->getArgumentsLength()>=1) { + CScriptVarPtr obj = c->getArgument(0); + if(obj->isObject()) { + c->setReturnVar(obj->findChild(TINYJS___PROTO___VAR)); + return; + } + } + c->throwError(TypeError, "argument is not an object"); +} + +void CTinyJS::native_Object_setObjectSecure(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument(0); + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + if(data==(void*)2) + obj->freeze(); + else if(data==(void*)1) + obj->seal(); + else + obj->preventExtensions(); + c->setReturnVar(obj); +} + +void CTinyJS::native_Object_isSecureObject(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument(0); + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + bool ret; + if(data==(void*)2) + ret = obj->isFrozen(); + else if(data==(void*)1) + ret = obj->isSealed(); + else + ret = obj->isExtensible(); + c->setReturnVar(constScriptVar(ret)); +} + +void CTinyJS::native_Object_keys(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument(0); + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + CScriptVarPtr returnVar = c->newScriptVar(Array); + c->setReturnVar(returnVar); + + STRING_SET_t keys; + obj->keys(keys, data==0); + + uint32_t idx=0; + for(STRING_SET_it it=keys.begin(); it!=keys.end(); ++it) + returnVar->setArrayIndex(idx++, newScriptVar(*it)); +} + +void CTinyJS::native_Object_getOwnPropertyDescriptor(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument(0); + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + c->setReturnVar(obj->getOwnPropertyDescriptor(c->getArgument(1)->toString())); +} + +void CTinyJS::native_Object_defineProperty(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument(0); + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + string name = c->getArgument(1)->toString(); + CScriptVarPtr attributes = c->getArgument(2); + if(!attributes->isObject()) c->throwError(TypeError, "attributes is not an object"); + const char *err = obj->defineProperty(name, attributes); + if(err) c->throwError(TypeError, err); + c->setReturnVar(obj); +} + +void CTinyJS::native_Object_defineProperties(const CFunctionsScopePtr &c, void *data) { + bool ObjectCreate = data!=0; + CScriptVarPtr obj = c->getArgument(0); + if(ObjectCreate) { + if(!obj->isObject() && !obj->isNull()) c->throwError(TypeError, "argument is not an object or null"); + obj = newScriptVar(Object, obj); + } else + if(!obj->isObject()) c->throwError(TypeError, "argument is not an object"); + c->setReturnVar(obj); + if(c->getArrayLength()<2) { + if(ObjectCreate) return; + c->throwError(TypeError, "Object.defineProperties requires 2 arguments"); + } + + CScriptVarPtr properties = c->getArgument(1); + + STRING_SET_t names; + properties->keys(names, true); + + for(STRING_SET_it it=names.begin(); it!=names.end(); ++it) { + CScriptVarPtr attributes = properties->findChildWithStringChars(*it).getter(); + if(!attributes->isObject()) c->throwError(TypeError, "descriptor for "+*it+" is not an object"); + const char *err = obj->defineProperty(*it, attributes); + if(err) c->throwError(TypeError, err); + } +} + + +////////////////////////////////////////////////////////////////////////// +/// Object.prototype +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Object_prototype_hasOwnProperty(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr This = c->getArgument("this"); + string PropStr = c->getArgument("prop")->toString(); + CScriptVarLinkPtr Prop = This->findChild(PropStr); + bool res = Prop && !Prop->getVarPtr()->isUndefined(); + if(!res) { + CScriptVarStringPtr This_asString = This->getRawPrimitive(); + if(This_asString) { + uint32_t Idx = isArrayIndex(PropStr); + res = Idx!=uint32_t(-1) && IdxstringLength(); + } + } + c->setReturnVar(c->constScriptVar(res)); +} +void CTinyJS::native_Object_prototype_valueOf(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(c->getArgument("this")->valueOf_CallBack()); +} +void CTinyJS::native_Object_prototype_toString(const CFunctionsScopePtr &c, void *data) { + CScriptResult execute; + int radix = 10; + if(c->getArgumentsLength()>=1) radix = c->getArgument("radix")->toNumber().toInt32(); + c->setReturnVar(c->getArgument("this")->toString_CallBack(execute, radix)); + if(!execute) { + // TODO + } +} + +////////////////////////////////////////////////////////////////////////// +/// Array +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Array(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr returnVar = c->newScriptVar(Array); + c->setReturnVar(returnVar); + int length = c->getArgumentsLength(); + CScriptVarPtr Argument_0_Var = c->getArgument(0); + if(data!=0 && length == 1 && Argument_0_Var->isNumber()) { + CNumber Argument_0 = Argument_0_Var->toNumber(); + uint32_t new_size = Argument_0.toUInt32(); + if(Argument_0.isFinite() && Argument_0 == new_size) + returnVar->setArrayIndex(new_size-1, constScriptVar(Undefined)); + else + c->throwError(RangeError, "invalid array length"); + } else for(int i=0; isetArrayIndex(i, c->getArgument(i)); +} + +////////////////////////////////////////////////////////////////////////// +/// String +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_String(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr arg; + if(c->getArgumentsLength()==0) + arg = newScriptVar(""); + else + arg = newScriptVar(c->getArgument(0)->toString()); + if(data) + c->setReturnVar(arg->toObject()); + else + c->setReturnVar(arg); +} + + +////////////////////////////////////////////////////////////////////////// +/// RegExp +////////////////////////////////////////////////////////////////////////// +#ifndef NO_REGEXP + +void CTinyJS::native_RegExp(const CFunctionsScopePtr &c, void *data) { + int arglen = c->getArgumentsLength(); + string RegExp, Flags; + if(arglen>=1) { + RegExp = c->getArgument(0)->toString(); + try { regex(RegExp, regex_constants::ECMAScript); } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + if(arglen>=2) { + Flags = c->getArgument(1)->toString(); + string::size_type pos = Flags.find_first_not_of("gimy"); + if(pos != string::npos) { + c->throwError(SyntaxError, string("invalid regular expression flag ")+Flags[pos]); + } + } + } + c->setReturnVar(newScriptVar(RegExp, Flags)); +} +#endif /* NO_REGEXP */ + +////////////////////////////////////////////////////////////////////////// +/// Number +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Number(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr arg; + if(c->getArgumentsLength()==0) + arg = newScriptVar(0); + else + arg = newScriptVar(c->getArgument(0)->toNumber()); + if(data) + c->setReturnVar(arg->toObject()); + else + c->setReturnVar(arg); +} + + +////////////////////////////////////////////////////////////////////////// +/// Boolean +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Boolean(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr arg; + if(c->getArgumentsLength()==0) + arg = constScriptVar(false); + else + arg = constScriptVar(c->getArgument(0)->toBoolean()); + if(data) + c->setReturnVar(arg->toObject()); + else + c->setReturnVar(arg); +} + +////////////////////////////////////////////////////////////////////////// +/// Iterator +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Iterator(const CFunctionsScopePtr &c, void *data) { + if(c->getArgumentsLength()<1) c->throwError(TypeError, "missing argument 0 when calling function Iterator"); + c->setReturnVar(c->getArgument(0)->toIterator(c->getArgument(1)->toBoolean()?1:3)); +} + +////////////////////////////////////////////////////////////////////////// +/// Generator +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_GENERATORS +void CTinyJS::native_Generator_prototype_next(const CFunctionsScopePtr &c, void *data) { + CScriptVarGeneratorPtr Generator(c->getArgument("this")); + if(!Generator) { + static const char *fnc[] = {"next","send","close","throw"}; + c->throwError(TypeError, string(fnc[(int)data])+" method called on incompatible Object"); + } + if((int)data >=2) + Generator->native_throw(c, (void*)(((int)data)-2)); + else + Generator->native_send(c, data); +} +#endif /*NO_GENERATORS*/ + + +////////////////////////////////////////////////////////////////////////// +/// Function +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_Function(const CFunctionsScopePtr &c, void *data) { + int length = c->getArgumentsLength(); + string params, body; + if(length>=1) + body = c->getArgument(length-1)->toString(); + if(length>=2) { + params = c->getArgument(0)->toString(); + for(int i=1; igetArgument(i)->toString()); + } + } + c->setReturnVar(parseFunctionsBodyFromString(params,body)); +} + +void CTinyJS::native_Function_prototype_call(const CFunctionsScopePtr &c, void *data) { + int length = c->getArgumentsLength(); + CScriptVarPtr Fnc = c->getArgument("this"); + if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.call called on incompatible Object"); + CScriptVarPtr This = c->getArgument(0); + vector Args; + for(int i=1; igetArgument(i)); + c->setReturnVar(callFunction(Fnc, Args, This)); +} +void CTinyJS::native_Function_prototype_apply(const CFunctionsScopePtr &c, void *data) { + int length=0; + CScriptVarPtr Fnc = c->getArgument("this"); + if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.apply called on incompatible Object"); + // Argument_0 + CScriptVarPtr This = c->getArgument(0)->toObject(); + if(This->isNull() || This->isUndefined()) This=root; + // Argument_1 + CScriptVarPtr Array = c->getArgument(1); + if(!Array->isNull() && !Array->isUndefined()) { + CScriptVarLinkWorkPtr Length = Array->findChild("length"); + if(!Length) c->throwError(TypeError, "second argument to Function.prototype.apply must be an array or an array like object"); + length = Length.getter()->toNumber().toInt32(); + } + vector Args; + for(int i=0; ifindChild(int2string(i)); + if(value) Args.push_back(value); + else Args.push_back(constScriptVar(Undefined)); + } + c->setReturnVar(callFunction(Fnc, Args, This)); +} +void CTinyJS::native_Function_prototype_bind(const CFunctionsScopePtr &c, void *data) { + int length = c->getArgumentsLength(); + CScriptVarPtr Fnc = c->getArgument("this"); + if(!Fnc->isFunction()) c->throwError(TypeError, "Function.prototype.bind called on incompatible Object"); + CScriptVarPtr This = c->getArgument(0); + if(This->isUndefined() || This->isNull()) This = root; + vector Args; + for(int i=1; igetArgument(i)); + c->setReturnVar(newScriptVarFunctionBounded(Fnc, This, Args)); +} + + +////////////////////////////////////////////////////////////////////////// +/// Error +////////////////////////////////////////////////////////////////////////// + +static CScriptVarPtr _newError(CTinyJS *context, ERROR_TYPES type, const CFunctionsScopePtr &c) { + int i = c->getArgumentsLength(); + string message, fileName; + int line=-1, column=-1; + if(i>0) message = c->getArgument(0)->toString(); + if(i>1) fileName = c->getArgument(1)->toString(); + if(i>2) line = c->getArgument(2)->toNumber().toInt32(); + if(i>3) column = c->getArgument(3)->toNumber().toInt32(); + return ::newScriptVarError(context, type, message.c_str(), fileName.c_str(), line, column); +} +void CTinyJS::native_Error(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, Error,c)); } +void CTinyJS::native_EvalError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, EvalError,c)); } +void CTinyJS::native_RangeError(const CFunctionsScopePtr &c, void *data) { c->setReturnVar(_newError(this, RangeError,c)); } +void CTinyJS::native_ReferenceError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, ReferenceError,c)); } +void CTinyJS::native_SyntaxError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, SyntaxError,c)); } +void CTinyJS::native_TypeError(const CFunctionsScopePtr &c, void *data){ c->setReturnVar(_newError(this, TypeError,c)); } + +////////////////////////////////////////////////////////////////////////// +/// global functions +////////////////////////////////////////////////////////////////////////// + +void CTinyJS::native_eval(const CFunctionsScopePtr &c, void *data) { + string Code = c->getArgument("jsCode")->toString(); + CScriptVarScopePtr scEvalScope = scopes.back(); // save scope + scopes.pop_back(); // go back to the callers scope + CScriptResult execute; + CScriptTokenizer *oldTokenizer = t; t=0; + try { + CScriptTokenizer Tokenizer(Code.c_str(), "eval"); + t = &Tokenizer; + do { + execute_statement(execute); + while (t->tk==';') t->match(';'); // skip empty statements + } while (t->tk!=LEX_EOF); + } catch (CScriptException *e) { // script exceptions + t = oldTokenizer; // restore tokenizer + scopes.push_back(scEvalScope); // restore Scopes; + if(haveTry) { // an Error in eval is always catchable + CScriptVarPtr E = newScriptVarError(this, e->errorType, e->message.c_str(), e->fileName.c_str(), e->lineNumber, e->column); + delete e; + throw E; + } else + throw e; + } catch (...) { // all other exceptions + t = oldTokenizer; // restore tokenizer + scopes.push_back(scEvalScope); // restore Scopes; + throw; // re-throw + } + t = oldTokenizer; // restore tokenizer + scopes.push_back(scEvalScope); // restore Scopes; + if(execute.value) + c->setReturnVar(execute.value); +} + +static int _native_require_read(const string &Fname, std::string &Data) { + std::ifstream in(Fname.c_str(), std::ios::in | std::ios::binary); + if (in) { + in.seekg(0, std::ios::end); + Data.resize((string::size_type)in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&Data[0], Data.size()); + in.close(); + return(0); + } + return errno; +} + +void CTinyJS::native_require(const CFunctionsScopePtr &c, void *data) { + string File = c->getArgument("jsFile")->toString(); + string Code; + int ErrorNo; + if(!native_require_read) + native_require_read = _native_require_read; // use builtin if no callback + + if((ErrorNo = native_require_read(File, Code))) { + ostringstream msg; + msg << "can't read \"" << File << "\" (Error=" << ErrorNo << ")"; + c->throwError(Error, msg.str()); + } + c->addChildOrReplace("jsCode", c->newScriptVar(Code)); + native_eval(c, data); +} + +void CTinyJS::native_isNAN(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isNaN())); +} + +void CTinyJS::native_isFinite(const CFunctionsScopePtr &c, void *data) { + c->setReturnVar(constScriptVar(c->getArgument("objc")->toNumber().isFinite())); +} + +void CTinyJS::native_parseInt(const CFunctionsScopePtr &c, void *) { + CNumber result; + result.parseInt(c->getArgument("string")->toString(), c->getArgument("radix")->toNumber().toInt32()); + c->setReturnVar(c->newScriptVar(result)); +} + +void CTinyJS::native_parseFloat(const CFunctionsScopePtr &c, void *) { + CNumber result; + result.parseFloat(c->getArgument("string")->toString()); + c->setReturnVar(c->newScriptVar(result)); +} + + + +void CTinyJS::native_JSON_parse(const CFunctionsScopePtr &c, void *data) { + string Code = "§" + c->getArgument("text")->toString(); + // "§" is a spezal-token - it's for the tokenizer and means the code begins not in Statement-level + CScriptVarLinkWorkPtr returnVar; + CScriptTokenizer *oldTokenizer = t; t=0; + try { + CScriptTokenizer Tokenizer(Code.c_str(), "JSON.parse", 0, -1); + t = &Tokenizer; + CScriptResult execute; + returnVar = execute_literals(execute); + t->match(LEX_EOF); + } catch (CScriptException *e) { + t = oldTokenizer; + throw e; + } + t = oldTokenizer; + + if(returnVar) + c->setReturnVar(returnVar); +} + +void CTinyJS::setTemporaryID_recursive(uint32_t ID) { + for(vector::iterator it = pseudo_refered.begin(); it!=pseudo_refered.end(); ++it) + if(**it) (**it)->setTemporaryMark_recursive(ID); + for(int i=Error; isetTemporaryMark_recursive(ID); + root->setTemporaryMark_recursive(ID); +} + +void CTinyJS::ClearUnreferedVars(const CScriptVarPtr &extra/*=CScriptVarPtr()*/) { + uint32_t UniqueID = allocUniqueID(); + setTemporaryID_recursive(UniqueID); + if(extra) extra->setTemporaryMark_recursive(UniqueID); + CScriptVar *p = first; + + while(p) + { + if(p->getTemporaryMark() != UniqueID) + { + CScriptVarPtr var = p; + var->removeAllChildren(); + p = var->next; + } + else + p = p->next; + } + freeUniqueID(); +} + diff --git a/src/TinyJS/TinyJS_Functions.cpp b/src/TinyJS/TinyJS_Functions.cpp new file mode 100755 index 0000000..1b74035 --- /dev/null +++ b/src/TinyJS/TinyJS_Functions.cpp @@ -0,0 +1,162 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * Authored By Gordon Williams + * + * Copyright (C) 2009 Pur3 Ltd + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include +#include +#include +#include +#include "TinyJS/TinyJS.h" + +using namespace std; +// ----------------------------------------------- Actual Functions + +static void scTrace(const CFunctionsScopePtr &c, void * userdata) { + CTinyJS *js = (CTinyJS*)userdata; + if(c->getArgumentsLength()) + c->getArgument(0)->trace(); + else + js->getRoot()->trace("root"); +} + +static void scObjectDump(const CFunctionsScopePtr &c, void *) { + c->getArgument("this")->trace("> "); +} + +static void scObjectClone(const CFunctionsScopePtr &c, void *) { + CScriptVarPtr obj = c->getArgument("this"); + c->setReturnVar(obj->clone()); +} +/* +static void scIntegerValueOf(const CFunctionsScopePtr &c, void *) { + string str = c->getArgument("str")->toString(); + + int val = 0; + if (str.length()==1) + val = str.operator[](0); + c->setReturnVar(c->newScriptVar(val)); +} +*/ +static void scJSONStringify(const CFunctionsScopePtr &c, void *) { + uint32_t UniqueID = c->getContext()->allocUniqueID(); + bool hasRecursion=false; + c->setReturnVar(c->newScriptVar(c->getArgument("obj")->getParsableString("", " ", UniqueID, hasRecursion))); + c->getContext()->freeUniqueID(); + if(hasRecursion) c->throwError(TypeError, "cyclic object value"); +} + +static void scArrayContains(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument("obj"); + CScriptVarPtr arr = c->getArgument("this"); + + int l = arr->getArrayLength(); + CScriptVarPtr equal = c->constScriptVar(Undefined); + for (int i=0;imathsOp(arr->getArrayIndex(i), LEX_EQUAL); + if(equal->toBoolean()) { + c->setReturnVar(c->constScriptVar(true)); + return; + } + } + c->setReturnVar(c->constScriptVar(false)); +} + +static void scArrayRemove(const CFunctionsScopePtr &c, void *data) { + CScriptVarPtr obj = c->getArgument("obj"); + CScriptVarPtr arr = c->getArgument("this"); + int i; + vector removedIndices; + + int l = arr->getArrayLength(); + CScriptVarPtr equal = c->constScriptVar(Undefined); + for (i=0;imathsOp(arr->getArrayIndex(i), LEX_EQUAL); + if(equal->toBoolean()) { + removedIndices.push_back(i); + } + } + if(removedIndices.size()) { + vector::iterator remove_it = removedIndices.begin(); + int next_remove = *remove_it; + int next_insert = *remove_it++; + for (i=next_remove;ifindChild(int2string(i)); + if(i == next_remove) { + if(link) arr->removeLink(link); + if(remove_it != removedIndices.end()) + next_remove = *remove_it++; + } else { + if(link) { + arr->setArrayIndex(next_insert++, link); + arr->removeLink(link); + } + } + } + } +} + +static void scArrayJoin(const CFunctionsScopePtr &c, void *data) { + string sep = c->getArgument("separator")->toString(); + CScriptVarPtr arr = c->getArgument("this"); + + ostringstream sstr; + int l = arr->getArrayLength(); + for (int i=0;i0) sstr << sep; + sstr << arr->getArrayIndex(i)->toString(); + } + + c->setReturnVar(c->newScriptVar(sstr.str())); +} + +// ----------------------------------------------- Register Functions +void registerFunctions(CTinyJS *tinyJS) { +} +extern "C" void _registerFunctions(CTinyJS *tinyJS) { + tinyJS->addNative("function trace()", scTrace, tinyJS, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Object.prototype.dump()", scObjectDump, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Object.prototype.clone()", scObjectClone, 0, SCRIPTVARLINK_BUILDINDEFAULT); + +// tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // value of a single character + tinyJS->addNative("function JSON.stringify(obj, replacer)", scJSONStringify, 0, SCRIPTVARLINK_BUILDINDEFAULT); // convert to JSON. replacer is ignored at the moment + tinyJS->addNative("function Array.prototype.contains(obj)", scArrayContains, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Array.prototype.remove(obj)", scArrayRemove, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Array.prototype.join(separator)", scArrayJoin, 0, SCRIPTVARLINK_BUILDINDEFAULT); +} + diff --git a/src/TinyJS/TinyJS_MathFunctions.cpp b/src/TinyJS/TinyJS_MathFunctions.cpp new file mode 100644 index 0000000..6f7eae9 --- /dev/null +++ b/src/TinyJS/TinyJS_MathFunctions.cpp @@ -0,0 +1,441 @@ +/* + * TinyJS + * + * A single-file Javascript-alike engine + * + * - Math and Trigonometry functions + * + * Authored By O.Z.L.B. + * + * Copyright (C) 2011 O.Z.L.B. + * + + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored / Changed By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include "TinyJS/TinyJS.h" + +using namespace std; + +#define k_E exp(1.0) +#define k_PI 3.1415926535897932384626433832795 +#define k_LN2 log((double)2) +#define k_LN10 log((double)10) +#define k_LOG2E (log(k_E)/log((double)2)) +#define k_LOG10E log10(k_E) +#define k_SQRT1_2 sqrt((double)0.5) +#define k_SQRT2 sqrt((double)2) + +#define F_ABS(a) ((a)>=0 ? (a) : (-(a))) +#define F_MIN(a,b) ((a)>(b) ? (b) : (a)) +#define F_MAX(a,b) ((a)>(b) ? (a) : (b)) +#define F_SGN(a) ((a)>0 ? 1 : ((a)<0 ? -1 : 0 )) +#define F_RNG(a,min,max) ((a)<(min) ? min : ((a)>(max) ? max : a )) + +#ifdef _MSC_VER +namespace +{ + double asinh( const double &value ) { + double returned; + + if(value>0) + returned = log(value + sqrt(value * value + 1)); + else + returned = -log(-value + sqrt(value * value + 1)); + + return(returned); + } + + double acosh( const double &value ) { + double returned; + + if(value>0) + returned = log(value + sqrt(value * value - 1)); + else + returned = -log(-value + sqrt(value * value - 1)); + + return(returned); + } + + double atanh( double value ) { + bool neg = value<0; + if(neg) value=-value; + double value_x2 = 2.0*value; + if(value>=0.5) + value = log(1.0+value_x2/(1.0-value))/2.0; + else + value = log(1.0+value_x2+value_x2*value/(1.0-value))/2.0; + return(neg ? -value : value); + } +} +#endif + +#define PARAMETER_TO_NUMBER(v,n) CNumber v = c->getArgument(n)->toNumber() +#define RETURN_NAN_IS_NAN(v) do{ if(v.isNaN()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_NAN_IS_NAN_OR_INFINITY(v) do{ if(v.isNaN() || v.isInfinity()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_INFINITY_IS_INFINITY(v) do{ if(v.isInfinity()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN_ZERO_IS_ZERO(v) do{ if(v.isZero()) { c->setReturnVar(c->newScriptVar(v)); return; } }while(0) +#define RETURN(a) do{ c->setReturnVar(c->newScriptVar(a)); return; }while(0) +#define RETURNconst(a) c->setReturnVar(c->constScriptVar(a)) + +//Math.abs(x) - returns absolute of given value +static void scMathAbs(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.sign()<0?-a:a); +} + +//Math.round(a) - returns nearest round of given value +static void scMathRound(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.round()); +} + +//Math.ceil(a) - returns nearest round of given value +static void scMathCeil(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN(a.ceil()); +} + +//Math.floor(a) - returns nearest round of given value +static void scMathFloor(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN(a.floor()); +} + +//Math.min(a,b) - returns minimum of two given values +static void scMathMin(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + CNumber ret(InfinityPositive); + for(int i=0; ia) ret=a; + } + RETURN(ret); +} + +//Math.max(a,b) - returns maximum of two given values +static void scMathMax(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + CNumber ret(InfinityNegative); + for(int i=0; ib) RETURNconst(NaN); + if(xb) RETURN(b); + RETURN(x); +} + +//Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive) +static void scMathSign(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN(a.isZero() ? 0 : a.sign()); +} +static void scMathRandom(const CFunctionsScopePtr &c, void *) { + static int inited=0; + if(!inited) { + inited = 1; + srand((unsigned int)time(NULL)); + } + RETURN(double(rand())/RAND_MAX); +} + +//Math.toDegrees(a) - returns degree value of a given angle in radians +static void scMathToDegrees(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN( (180.0/k_PI)*a ); +} + +//Math.toRadians(a) - returns radians value of a given angle in degrees +static void scMathToRadians(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_INFINITY_IS_INFINITY(a); + RETURN( (k_PI/180.0)*a ); +} + +//Math.sin(a) - returns trig. sine of given angle in radians +static void scMathSin(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); RETURN_ZERO_IS_ZERO(a); + RETURN( sin(a.toDouble()) ); +} + +//Math.asin(a) - returns trig. arcsine of given angle in radians +static void scMathASin(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_ZERO_IS_ZERO(a); + if(abs(a)>1) RETURNconst(NaN); + RETURN( asin(a.toDouble()) ); +} + +//Math.cos(a) - returns trig. cosine of given angle in radians +static void scMathCos(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); + if(a.isZero()) RETURN(1); + RETURN( cos(a.toDouble()) ); +} + +//Math.acos(a) - returns trig. arccosine of given angle in radians +static void scMathACos(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); + if(abs(a)>1) RETURNconst(NaN); + else if(a==1) RETURN(0); + RETURN( acos(a.toDouble()) ); +} + +//Math.tan(a) - returns trig. tangent of given angle in radians +static void scMathTan(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN_OR_INFINITY(a); RETURN_ZERO_IS_ZERO(a); + RETURN( tan(a.toDouble()) ); +} + +//Math.atan(a) - returns trig. arctangent of given angle in radians +static void scMathATan(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); RETURN_ZERO_IS_ZERO(a); + int infinity=a.isInfinity(); + if(infinity) RETURN(k_PI/(infinity*2)); + RETURN( atan(a.toDouble()) ); +} + +//Math.atan2(a,b) - returns trig. arctangent of given angle in radians +static void scMathATan2(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + PARAMETER_TO_NUMBER(b,"b"); RETURN_NAN_IS_NAN(b); + int sign_a = a.sign(); + int sign_b = b.sign(); + if(a.isZero()) + RETURN(sign_a>0 ? (sign_b>0 ? 0.0 : k_PI) : (sign_b>0 ? -0.0 : -k_PI)); + else if(b.isZero()) + RETURN((sign_a>0 ? k_PI : -k_PI)/2.0); + int infinity_a=a.isInfinity(); + int infinity_b=b.isInfinity(); + if(infinity_a) { + if(infinity_b>0) RETURN(k_PI/(infinity_a*4)); + else if(infinity_b<0) RETURN(3.0*k_PI/(infinity_a*4)); + else RETURN(k_PI/(infinity_a*2)); + } else if(infinity_b>0) + RETURN(sign_a>0 ? 0.0 : -0.0); + else if(infinity_b<0) + RETURN(sign_a>0 ? k_PI : -k_PI); + RETURN( atan2(a.toDouble(), b.toDouble()) ); +} + + +//Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians +static void scMathSinh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(abs(a)>1) RETURNconst(NaN); + RETURN( sinh(a.toDouble()) ); +} + +//Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians +static void scMathASinh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_INFINITY_IS_INFINITY(a); + RETURN_ZERO_IS_ZERO(a); + RETURN( asinh(a.toDouble()) ); +} + +//Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians +static void scMathCosh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( cosh(a.toDouble()) ); +} + +//Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians +static void scMathACosh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_INFINITY_IS_INFINITY(a); + if(abs(a)<1) RETURNconst(NaN); + RETURN( acosh(a.toDouble()) ); +} + +//Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians +static void scMathTanh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(a.isInfinity()) RETURN(a.sign()); + RETURN( tanh(a.toDouble()) ); +} + +//Math.atanh(a) - returns trig. hyperbolic arctangent of given angle in radians +static void scMathATanh(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + CNumber abs_a = abs(a); + if(abs_a > 1) RETURNconst(NaN); + if(abs_a == 1) RETURNconst(Infinity(a.sign())); + RETURN( atanh(a.toDouble()) ); +} + +//Math.log(a) - returns natural logaritm (base E) of given value +static void scMathLog(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURNconst(InfinityNegative); + if(a.sign()<0) RETURNconst(NaN); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( log( a.toDouble()) ); +} + +//Math.log10(a) - returns logaritm(base 10) of given value +static void scMathLog10(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURNconst(InfinityNegative); + if(a.sign()<0) RETURNconst(NaN); + if(a.isInfinity()) RETURNconst(InfinityPositive); + RETURN( log10( a.toDouble()) ); +} + +//Math.exp(a) - returns e raised to the power of a given number +static void scMathExp(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + if(a.isZero()) RETURN(1); + int a_i = a.isInfinity(); + if(a_i>0) RETURNconst(InfinityPositive); + else if(a_i<0) RETURN(0); + RETURN( exp(a.toDouble()) ); +} + +//Math.pow(a,b) - returns the result of a number raised to a power (a)^(b) +static void scMathPow(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + PARAMETER_TO_NUMBER(b,"b"); RETURN_NAN_IS_NAN(b); + if(b.isZero()) RETURN(1); + RETURN_NAN_IS_NAN(a); + if(b==1) RETURN(a); + + int sign; + CNumber a_abs = abs(a); + if((sign = b.isInfinity())) { + if( a_abs==1 ) RETURNconst(NaN); + else if( (a_abs>1) ^ (sign<0) ) RETURN(b); else RETURN(0); + } else if((sign = a.isInfinity())) { + if(sign>0) { + if(b.sign()>0) RETURN(a); else RETURN(0); + } else { + bool b_is_odd_int = ((b+1)/2).isInteger(); + if(b.sign()>0) RETURNconst(b_is_odd_int?InfinityNegative:InfinityPositive); + else RETURN(b_is_odd_int?CNumber(NegativeZero):CNumber(0)); + } + } else if(a.isZero()) { + if(a.isNegativeZero()) { + bool b_is_odd_int = ((b+1)/2).isInteger(); + if(b.sign()>0) RETURN(b_is_odd_int?CNumber(NegativeZero):CNumber(0)); + else RETURNconst(b_is_odd_int?InfinityNegative:InfinityPositive); + } else + if(b.sign()>0) RETURN(a); else RETURNconst(InfinityPositive); + } + if(a.sign()<0 && !b.isInteger()) RETURNconst(NaN); + + RETURN( pow(a.toDouble(), b.toDouble()) ); +} + +//Math.sqr(a) - returns square of given value +static void scMathSqr(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); + RETURN( a*a ); +} + +//Math.sqrt(a) - returns square root of given value +static void scMathSqrt(const CFunctionsScopePtr &c, void *userdata) { + PARAMETER_TO_NUMBER(a,"a"); RETURN_NAN_IS_NAN(a); + RETURN_ZERO_IS_ZERO(a); + if(a.sign()<0) RETURNconst(NaN); + RETURN_INFINITY_IS_INFINITY(a); + RETURN( sqrt(a.toDouble()) ); +} + +// ----------------------------------------------- Register Functions +void registerMathFunctions(CTinyJS *tinyJS) {} +extern "C" void _registerMathFunctions(CTinyJS *tinyJS) { + + CScriptVarPtr Math = tinyJS->getRoot()->addChild("Math", tinyJS->newScriptVar(Object), SCRIPTVARLINK_CONSTANT); + + // --- Math and Trigonometry functions --- + tinyJS->addNative("function Math.abs(a)", scMathAbs, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.round(a)", scMathRound, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.ceil(a)", scMathCeil, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.floor(a)", scMathFloor, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.min()", scMathMin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.max()", scMathMax, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.range(x,a,b)", scMathRange, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sign(a)", scMathSign, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.random(a)", scMathRandom, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + +// atan2, ceil, floor, random, round, + + Math->addChild("LN2", tinyJS->newScriptVar(k_LN2), SCRIPTVARLINK_READONLY); + Math->addChild("LN10", tinyJS->newScriptVar(k_LN10), SCRIPTVARLINK_READONLY); + Math->addChild("LOG2E", tinyJS->newScriptVar(k_LOG2E), SCRIPTVARLINK_READONLY); + Math->addChild("LOG10E", tinyJS->newScriptVar(k_LOG10E), SCRIPTVARLINK_READONLY); + Math->addChild("SQRT1_2", tinyJS->newScriptVar(k_SQRT1_2), SCRIPTVARLINK_READONLY); + Math->addChild("SQRT2", tinyJS->newScriptVar(k_SQRT2), SCRIPTVARLINK_READONLY); + Math->addChild("PI", tinyJS->newScriptVar(k_PI), SCRIPTVARLINK_READONLY); +// tinyJS->addNative("function Math.PI()", scMathPI, 0); + tinyJS->addNative("function Math.toDegrees(a)", scMathToDegrees, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.toRadians(a)", scMathToRadians, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sin(a)", scMathSin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.asin(a)", scMathASin, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.cos(a)", scMathCos, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.acos(a)", scMathACos, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.tan(a)", scMathTan, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atan(a)", scMathATan, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atan2(a,b)", scMathATan2, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sinh(a)", scMathSinh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.asinh(a)", scMathASinh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.cosh(a)", scMathCosh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.acosh(a)", scMathACosh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.tanh(a)", scMathTanh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.atanh(a)", scMathATanh, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + Math->addChild("E", tinyJS->newScriptVar(k_E), SCRIPTVARLINK_READONLY); + tinyJS->addNative("function Math.log(a)", scMathLog, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.log10(a)", scMathLog10, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.exp(a)", scMathExp, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.pow(a,b)", scMathPow, 0, SCRIPTVARLINK_BUILDINDEFAULT); + + tinyJS->addNative("function Math.sqr(a)", scMathSqr, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function Math.sqrt(a)", scMathSqrt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + +} diff --git a/src/TinyJS/TinyJS_StringFunctions.cpp b/src/TinyJS/TinyJS_StringFunctions.cpp new file mode 100644 index 0000000..3286e20 --- /dev/null +++ b/src/TinyJS/TinyJS_StringFunctions.cpp @@ -0,0 +1,567 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "TinyJS/TinyJS.h" + +#ifndef NO_REGEXP +# if defined HAVE_TR1_REGEX +# include + using namespace std::tr1; +# elif defined HAVE_BOOST_REGEX +# include + using namespace boost; +# else +# include +# endif +#endif +using namespace std; +// ----------------------------------------------- Actual Functions + +#define CheckObjectCoercible(var) do { \ + if(var->isUndefined() || var->isNull())\ + c->throwError(TypeError, "can't convert undefined to object");\ + }while(0) + +static string this2string(const CFunctionsScopePtr &c) { + CScriptVarPtr This = c->getArgument("this"); + CheckObjectCoercible(This); + return This->toString(); +} + +static void scStringCharAt(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + int p = c->getArgument("pos")->toNumber().toInt32(); + if (p>=0 && p<(int)str.length()) + c->setReturnVar(c->newScriptVar(str.substr(p, 1))); + else + c->setReturnVar(c->newScriptVar("")); +} + +static void scStringCharCodeAt(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + int p = c->getArgument("pos")->toNumber().toInt32(); + if (p>=0 && p<(int)str.length()) + c->setReturnVar(c->newScriptVar((unsigned char)str.at(p))); + else + c->setReturnVar(c->constScriptVar(NaN)); +} + +static void scStringConcat(const CFunctionsScopePtr &c, void *userdata) { + int length = c->getArgumentsLength(); + string str = this2string(c); + for(int i=(int)userdata; igetArgument(i)->toString()); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringIndexOf(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string search = c->getArgument("search")->toString(); + CNumber pos_n = c->getArgument("pos")->toNumber(); + string::size_type pos; + pos = (userdata) ? string::npos : 0; + if(pos_n.sign()<0) pos = 0; + else if(pos_n.isInfinity()) pos = string::npos; + else if(pos_n.isFinite()) pos = pos_n.toInt32(); + string::size_type p = (userdata==0) ? str.find(search, pos) : str.rfind(search, pos); + int val = (p==string::npos) ? -1 : p; + c->setReturnVar(c->newScriptVar(val)); +} + +static void scStringLocaleCompare(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string compareString = c->getArgument("compareString")->toString(); + int val = 0; + if(strcompareString) val = 1; + c->setReturnVar(c->newScriptVar(val)); +} + +static void scStringQuote(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + c->setReturnVar(c->newScriptVar(getJSString(str))); +} + +#ifndef NO_REGEXP +// helper-function for replace search +static bool regex_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end, smatch &match) { + regex::flag_type flags = regex_constants::ECMAScript; + if(ignoreCase) flags |= regex_constants::icase; + regex_constants::match_flag_type mflag = sticky?regex_constants::match_continuous:regex_constants::format_default; + if(str.begin() != search_begin) mflag |= regex_constants::match_prev_avail; + if(regex_search(search_begin, str.end(), match, regex(substr, flags), mflag)) { + match_begin = match[0].first; + match_end = match[0].second; + return true; + } + return false; +} +static bool regex_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end) { + smatch match; + return regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end, match); +} +#endif /* NO_REGEXP */ + +static bool charcmp (char i, char j) { return (i==j); } +static bool charicmp (char i, char j) { return (toupper(i)==toupper(j)); } +// helper-function for replace search +static bool string_search(const string &str, const string::const_iterator &search_begin, const string &substr, bool ignoreCase, bool sticky, string::const_iterator &match_begin, string::const_iterator &match_end) { + bool (*cmp)(char,char) = ignoreCase ? charicmp : charcmp; + if(sticky) { + match_begin = match_end = search_begin; + string::const_iterator s1e=str.end(); + string::const_iterator s2=substr.begin(), s2e=substr.end(); + while(match_end!=s1e && s2!=s2e && cmp(*match_end++, *s2++)); + return s2==s2e; + } + match_begin = search(search_begin, str.end(), substr.begin(), substr.end(), cmp); + if(match_begin==str.end()) return false; + match_end = match_begin + substr.length(); + return true; +} +//************************************ +// Method: getRegExpData +// FullName: getRegExpData +// Access: public static +// Returns: bool true if regexp-param=RegExp-Object / other false +// Qualifier: +// Parameter: const CFunctionsScopePtr & c +// Parameter: const string & regexp - parameter name of the regexp +// Parameter: bool noUndefined - true an undefined regexp aims in "" else in "undefined" +// Parameter: const string & flags - parameter name of the flags +// Parameter: string & substr - rgexp.source +// Parameter: bool & global +// Parameter: bool & ignoreCase +// Parameter: bool & sticky +//************************************ +static CScriptVarPtr getRegExpData(const CFunctionsScopePtr &c, const string ®exp, bool noUndefined, const char *flags_argument, string &substr, bool &global, bool &ignoreCase, bool &sticky) { + CScriptVarPtr regexpVar = c->getArgument(regexp); + if(regexpVar->isRegExp()) { +#ifndef NO_REGEXP + CScriptVarRegExpPtr RegExp(regexpVar); + substr = RegExp->Regexp(); + ignoreCase = RegExp->IgnoreCase(); + global = RegExp->Global(); + sticky = RegExp->Sticky(); + return RegExp; +#endif /* NO_REGEXP */ + } else { + substr.clear(); + if(!noUndefined || !regexpVar->isUndefined()) substr = regexpVar->toString(); + CScriptVarPtr flagVar; + if(flags_argument && (flagVar = c->getArgument(flags_argument)) && !flagVar->isUndefined()) { + string flags = flagVar->toString(); + string::size_type pos = flags.find_first_not_of("gimy"); + if(pos != string::npos) { + c->throwError(SyntaxError, string("invalid regular expression flag ")+flags[pos]); + } + global = flags.find_first_of('g')!=string::npos; + ignoreCase = flags.find_first_of('i')!=string::npos; + sticky = flags.find_first_of('y')!=string::npos; + } else + global = ignoreCase = sticky = false; + } + return CScriptVarPtr(); +} + +static void scStringReplace(const CFunctionsScopePtr &c, void *) { + const string str = this2string(c); + CScriptVarPtr newsubstrVar = c->getArgument("newsubstr"); + string substr, ret_str; + bool global, ignoreCase, sticky; + bool isRegExp = getRegExpData(c, "substr", false, "flags", substr, global, ignoreCase, sticky); + if(isRegExp && !newsubstrVar->isFunction()) { +#ifndef NO_REGEXP + regex::flag_type flags = regex_constants::ECMAScript; + if(ignoreCase) flags |= regex_constants::icase; + regex_constants::match_flag_type mflags = regex_constants::match_default; + if(!global) mflags |= regex_constants::format_first_only; + if(sticky) mflags |= regex_constants::match_continuous; + ret_str = regex_replace(str, regex(substr, flags), newsubstrVar->toString(), mflags); +#endif /* NO_REGEXP */ + } else { + bool (*search)(const string &, const string::const_iterator &, const string &, bool, bool, string::const_iterator &, string::const_iterator &); +#ifndef NO_REGEXP + if(isRegExp) + search = regex_search; + else +#endif /* NO_REGEXP */ + search = string_search; + string newsubstr; + vector arguments; + if(!newsubstrVar->isFunction()) + newsubstr = newsubstrVar->toString(); + global = global && substr.length(); + string::const_iterator search_begin=str.begin(), match_begin, match_end; + if(search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)) { + do { + ret_str.append(search_begin, match_begin); + if(newsubstrVar->isFunction()) { + arguments.push_back(c->newScriptVar(string(match_begin, match_end))); + newsubstr = c->getContext()->callFunction(newsubstrVar, arguments, c)->toString(); + arguments.pop_back(); + } + ret_str.append(newsubstr); +#if 1 /* Fix from "vcmpeq" (see Issue 14) currently untested */ + if (match_begin == match_end) { + if (search_begin != str.end()) + ++search_begin; + else + break; + } else { + search_begin = match_end; + } +#else + search_begin = match_end; +#endif + } while(global && search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)); + } + ret_str.append(search_begin, str.end()); + } + c->setReturnVar(c->newScriptVar(ret_str)); +} +#ifndef NO_REGEXP +static void scStringMatch(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + + string flags="flags", substr, newsubstr, match; + bool global, ignoreCase, sticky; + CScriptVarRegExpPtr RegExp = getRegExpData(c, "regexp", true, "flags", substr, global, ignoreCase, sticky); + if(!global) { + if(!RegExp) + RegExp = ::newScriptVar(c->getContext(), substr, flags); + if(RegExp) { + try { + c->setReturnVar(RegExp->exec(str)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } + } else { + try { + CScriptVarArrayPtr retVar = c->newScriptVar(Array); + int idx=0; + string::size_type offset=0; + global = global && substr.length(); + string::const_iterator search_begin=str.begin(), match_begin, match_end; + if(regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)) { + do { + offset = match_begin-str.begin(); + retVar->addChild(int2string(idx++), c->newScriptVar(string(match_begin, match_end))); +#if 1 /* Fix from "vcmpeq" (see Issue 14) currently untested */ + if (match_begin == match_end) { + if (search_begin != str.end()) + ++search_begin; + else + break; + } else { + search_begin = match_end; + } +#else + search_begin = match_end; +#endif + } while(global && regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)); + } + if(idx) { + retVar->addChild("input", c->newScriptVar(str)); + retVar->addChild("index", c->newScriptVar((int)offset)); + c->setReturnVar(retVar); + } else + c->setReturnVar(c->constScriptVar(Null)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } +} +#endif /* NO_REGEXP */ + +static void scStringSearch(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + + string substr; + bool global, ignoreCase, sticky; + getRegExpData(c, "regexp", true, "flags", substr, global, ignoreCase, sticky); + string::const_iterator search_begin=str.begin(), match_begin, match_end; +#ifndef NO_REGEXP + try { + c->setReturnVar(c->newScriptVar(regex_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)?match_begin-search_begin:-1)); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } +#else /* NO_REGEXP */ + c->setReturnVar(c->newScriptVar(string_search(str, search_begin, substr, ignoreCase, sticky, match_begin, match_end)?match_begin-search_begin:-1)); +#endif /* NO_REGEXP */ +} + +static void scStringSlice(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + int length = c->getArgumentsLength()-((int)userdata & 1); + bool slice = ((int)userdata & 2) == 0; + int start = c->getArgument("start")->toNumber().toInt32(); + int end = (int)str.size(); + if(slice && start<0) start = str.size()+start; + if(length>1) { + end = c->getArgument("end")->toNumber().toInt32(); + if(slice && end<0) end = str.size()+end; + } + if(!slice && end < start) { end^=start; start^=end; end^=start; } + if(start<0) start = 0; + if(start>=(int)str.size()) + c->setReturnVar(c->newScriptVar("")); + else if(end <= start) + c->setReturnVar(c->newScriptVar("")); + else + c->setReturnVar(c->newScriptVar(str.substr(start, end-start))); +} + +static void scStringSplit(const CFunctionsScopePtr &c, void *) { + const string str = this2string(c); + + string seperator; + bool global, ignoreCase, sticky; +#ifndef NO_REGEXP + CScriptVarRegExpPtr RegExp = getRegExpData(c, "separator", true, 0, seperator, global, ignoreCase, sticky); +#else + getRegExpData(c, "separator", true, 0, seperator, global, ignoreCase, sticky); +#endif + + CScriptVarPtr sep_var = c->getArgument("separator"); + CScriptVarPtr limit_var = c->getArgument("limit"); + int limit = limit_var->isUndefined() ? 0x7fffffff : limit_var->toNumber().toInt32(); + + CScriptVarPtr result(newScriptVar(c->getContext(), Array)); + c->setReturnVar(result); + if(limit == 0) + return; + else if(!str.size() || sep_var->isUndefined()) { + result->setArrayIndex(0, c->newScriptVar(str)); + return; + } + if(seperator.size() == 0) { + for(int i=0; isetArrayIndex(i, c->newScriptVar(str.substr(i,1))); + return; + } + int length = 0; + string::const_iterator search_begin=str.begin(), match_begin, match_end; +#ifndef NO_REGEXP + smatch match; +#endif + bool found=true; + while(found) { +#ifndef NO_REGEXP + if(RegExp) { + try { + found = regex_search(str, search_begin, seperator, ignoreCase, sticky, match_begin, match_end, match); + } catch(regex_error e) { + c->throwError(SyntaxError, string(e.what())+" - "+CScriptVarRegExp::ErrorStr(e.code())); + } + } else /* NO_REGEXP */ +#endif + found = string_search(str, search_begin, seperator, ignoreCase, sticky, match_begin, match_end); + string f; + if(found) { + result->setArrayIndex(length++, c->newScriptVar(string(search_begin, match_begin))); + if(length>=limit) break; +#ifndef NO_REGEXP + for(uint32_t i=1; isetArrayIndex(length++, c->newScriptVar(string(match[i].first, match[i].second))); + else + result->setArrayIndex(length++, c->constScriptVar(Undefined)); + if(length>=limit) break; + } + if(length>=limit) break; +#endif + search_begin = match_end; + } else { + result->setArrayIndex(length++, c->newScriptVar(string(search_begin,str.end()))); + if(length>=limit) break; + } + } +} + +static void scStringSubstr(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + int length = c->getArgumentsLength()-(int)userdata; + int start = c->getArgument("start")->toNumber().toInt32(); + if(start<0 || start>=(int)str.size()) + c->setReturnVar(c->newScriptVar("")); + else if(length>1) { + int length = c->getArgument("length")->toNumber().toInt32(); + c->setReturnVar(c->newScriptVar(str.substr(start, length))); + } else + c->setReturnVar(c->newScriptVar(str.substr(start))); +} + +static void scStringToLowerCase(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + transform(str.begin(), str.end(), str.begin(), ::tolower); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringToUpperCase(const CFunctionsScopePtr &c, void *) { + string str = this2string(c); + transform(str.begin(), str.end(), str.begin(), ::toupper); + c->setReturnVar(c->newScriptVar(str)); +} + +static void scStringTrim(const CFunctionsScopePtr &c, void *userdata) { + string str = this2string(c); + string::size_type start = 0; + string::size_type end = string::npos; + if((((int)userdata) & 2) == 0) { + start = str.find_first_not_of(" \t\r\n"); + if(start == string::npos) start = 0; + } + if((((int)userdata) & 1) == 0) { + end = str.find_last_not_of(" \t\r\n"); + if(end != string::npos) end = 1+end-start; + } + c->setReturnVar(c->newScriptVar(str.substr(start, end))); +} + + + +static void scCharToInt(const CFunctionsScopePtr &c, void *) { + string str = c->getArgument("ch")->toString();; + int val = 0; + if (str.length()>0) + val = (int)str.c_str()[0]; + c->setReturnVar(c->newScriptVar(val)); +} + + +static void scStringFromCharCode(const CFunctionsScopePtr &c, void *) { + char str[2]; + str[0] = c->getArgument("char")->toNumber().toInt32(); + str[1] = 0; + c->setReturnVar(c->newScriptVar(str)); +} + +////////////////////////////////////////////////////////////////////////// +// RegExp-Stuff +////////////////////////////////////////////////////////////////////////// + +#ifndef NO_REGEXP + +static void scRegExpTest(const CFunctionsScopePtr &c, void *) { + CScriptVarRegExpPtr This = c->getArgument("this"); + if(This) + c->setReturnVar(This->exec(c->getArgument("str")->toString(), true)); + else + c->throwError(TypeError, "Object is not a RegExp-Object in test(str)"); +} +static void scRegExpExec(const CFunctionsScopePtr &c, void *) { + CScriptVarRegExpPtr This = c->getArgument("this"); + if(This) + c->setReturnVar(This->exec(c->getArgument("str")->toString())); + else + c->throwError(TypeError, "Object is not a RegExp-Object in exec(str)"); +} +#endif /* NO_REGEXP */ + +// ----------------------------------------------- Register Functions +void registerStringFunctions(CTinyJS *tinyJS) {} +extern "C" void _registerStringFunctions(CTinyJS *tinyJS) { + CScriptVarPtr fnc; + // charAt + tinyJS->addNative("function String.prototype.charAt(pos)", scStringCharAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.charAt(this,pos)", scStringCharAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // charCodeAt + tinyJS->addNative("function String.prototype.charCodeAt(pos)", scStringCharCodeAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.charCodeAt(this,pos)", scStringCharCodeAt, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // concat + tinyJS->addNative("function String.prototype.concat()", scStringConcat, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.concat(this)", scStringConcat, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // indexOf + tinyJS->addNative("function String.prototype.indexOf(search,pos)", scStringIndexOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the position of a string in a string, -1 if not + tinyJS->addNative("function String.indexOf(this,search,pos)", scStringIndexOf, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the position of a string in a string, -1 if not + // lastIndexOf + tinyJS->addNative("function String.prototype.lastIndexOf(search,pos)", scStringIndexOf, (void*)-1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + tinyJS->addNative("function String.lastIndexOf(this,search,pos)", scStringIndexOf, (void*)-1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + // localeCompare + tinyJS->addNative("function String.prototype.localeCompare(compareString)", scStringLocaleCompare, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.localeCompare(this,compareString)", scStringLocaleCompare, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // quote + tinyJS->addNative("function String.prototype.quote()", scStringQuote, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.quote(this)", scStringQuote, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#ifndef NO_REGEXP + // match + tinyJS->addNative("function String.prototype.match(regexp, flags)", scStringMatch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.match(this, regexp, flags)", scStringMatch, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#endif /* !REGEXP */ + // replace + tinyJS->addNative("function String.prototype.replace(substr, newsubstr, flags)", scStringReplace, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.replace(this, substr, newsubstr, flags)", scStringReplace, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // search + tinyJS->addNative("function String.prototype.search(regexp, flags)", scStringSearch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.search(this, regexp, flags)", scStringSearch, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // slice + tinyJS->addNative("function String.prototype.slice(start,end)", scStringSlice, 0, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + tinyJS->addNative("function String.slice(this,start,end)", scStringSlice, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); // find the last position of a string in a string, -1 if not + // split + tinyJS->addNative("function String.prototype.split(separator,limit)", scStringSplit, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.split(this,separator,limit)", scStringSplit, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // substr + tinyJS->addNative("function String.prototype.substr(start,length)", scStringSubstr, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.substr(this,start,length)", scStringSubstr, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // substring + tinyJS->addNative("function String.prototype.substring(start,end)", scStringSlice, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.substring(this,start,end)", scStringSlice, (void*)3, SCRIPTVARLINK_BUILDINDEFAULT); + // toLowerCase toLocaleLowerCase currently the same function + tinyJS->addNative("function String.prototype.toLowerCase()", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLowerCase(this)", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.prototype.toLocaleLowerCase()", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLocaleLowerCase(this)", scStringToLowerCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // toUpperCase toLocaleUpperCase currently the same function + tinyJS->addNative("function String.prototype.toUpperCase()", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toUpperCase(this)", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.prototype.toLocaleUpperCase()", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.toLocaleUpperCase(this)", scStringToUpperCase, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // trim + tinyJS->addNative("function String.prototype.trim()", scStringTrim, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trim(this)", scStringTrim, 0, SCRIPTVARLINK_BUILDINDEFAULT); + // trimLeft + tinyJS->addNative("function String.prototype.trimLeft()", scStringTrim, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trimLeft(this)", scStringTrim, (void*)1, SCRIPTVARLINK_BUILDINDEFAULT); + // trimRight + tinyJS->addNative("function String.prototype.trimRight()", scStringTrim, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function String.trimRight(this)", scStringTrim, (void*)2, SCRIPTVARLINK_BUILDINDEFAULT); + + tinyJS->addNative("function charToInt(ch)", scCharToInt, 0, SCRIPTVARLINK_BUILDINDEFAULT); // convert a character to an int - get its value + + tinyJS->addNative("function String.prototype.fromCharCode(char)", scStringFromCharCode, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#ifndef NO_REGEXP + tinyJS->addNative("function RegExp.prototype.test(str)", scRegExpTest, 0, SCRIPTVARLINK_BUILDINDEFAULT); + tinyJS->addNative("function RegExp.prototype.exec(str)", scRegExpExec, 0, SCRIPTVARLINK_BUILDINDEFAULT); +#endif /* NO_REGEXP */ +} + diff --git a/src/TinyJS/TinyJS_Threading.cpp b/src/TinyJS/TinyJS_Threading.cpp new file mode 100644 index 0000000..487bcc3 --- /dev/null +++ b/src/TinyJS/TinyJS_Threading.cpp @@ -0,0 +1,239 @@ +#include "TinyJS/TinyJS_Threading.h" + +#undef HAVE_THREADING +#if !defined(NO_THREADING) && !defined(HAVE_CUSTOM_THREADING_IMPL) +# define HAVE_THREADING +# ifdef HAVE_CXX_THREADS +# include +# else +# if defined(WIN32) && !defined(HAVE_PTHREAD) +# include +# else +# include +# ifndef HAVE_PTHREAD +# define HAVE_PTHREAD +# endif +# endif +# endif +#endif + +#ifdef HAVE_THREADING + +////////////////////////////////////////////////////////////////////////// +// Mutex +////////////////////////////////////////////////////////////////////////// +#ifndef HAVE_CXX_THREADS + +#ifndef HAVE_PTHREAD +// simple mutex 4 windows +//# define pthread_mutex_t HANDLE +//# define pthread_mutex_init(m, a) *(m) = CreateMutex(NULL, false, NULL) +//# define pthread_mutex_destroy(m) CloseHandle(*(m)); +//# define pthread_mutex_lock(m) WaitForSingleObject(*(m), INFINITE); +//# define pthread_mutex_unlock(m) ReleaseMutex(*(m)); +# define pthread_mutex_t CRITICAL_SECTION +# define pthread_mutex_init(m, a) { InitializeCriticalSection(m); 0; } +# define pthread_mutex_destroy(m) do {} while(0) +# define pthread_mutex_lock(m) EnterCriticalSection(m) +# define pthread_mutex_unlock(m) LeaveCriticalSection(m) + +#endif + +class CScriptMutex_impl : public CScriptMutex::CScriptMutex_t { +public: + CScriptMutex_impl() { + pthread_mutex_init(&mutex, NULL); + } + ~CScriptMutex_impl() { + pthread_mutex_destroy(&mutex); + } + void lock() { + pthread_mutex_lock(&mutex); + } + void unlock() { + pthread_mutex_unlock(&mutex); + } + void *getRealMutex() { return &mutex; } + pthread_mutex_t mutex; +}; + + +CScriptMutex::CScriptMutex() { + mutex = new CScriptMutex_impl; +} + +CScriptMutex::~CScriptMutex() { + delete mutex; +} + +#endif /*HAVE_CXX_THREADS*/ + +////////////////////////////////////////////////////////////////////////// +// CondVar +////////////////////////////////////////////////////////////////////////// + +#ifndef HAVE_CXX_THREADS + +#ifndef HAVE_PTHREAD +// simple conditional Variable 4 windows +# define pthread_cond_t CONDITION_VARIABLE +# define pthread_cond_init(c, a) InitializeConditionVariable(c) +# define pthread_cond_destroy(c) do {} while(0) +# define pthread_cond_wait(c, m) SleepConditionVariableCS(c, m, INFINITE) +# define pthread_cond_signal(c) WakeConditionVariable(c); +#endif + +class CScriptCondVar_impl : public CScriptCondVar::CScriptCondVar_t { +public: + CScriptCondVar_impl(CScriptCondVar *_this) : This(_this) { + pthread_cond_init(&cond, NULL); + } + ~CScriptCondVar_impl() { + pthread_cond_destroy(&cond); + } + CScriptCondVar *This; + void notify_one() { + pthread_cond_signal(&cond); + } + void wait(CScriptUniqueLock &Lock) { + pthread_cond_wait(&cond, (pthread_mutex_t *)Lock.mutex->getRealMutex()); + } + pthread_cond_t cond; +}; + +CScriptCondVar::CScriptCondVar() { + condVar = new CScriptCondVar_impl(this); +} +CScriptCondVar::~CScriptCondVar() { + delete condVar; +} + +#endif /*HAVE_CXX_THREADS*/ + + + +////////////////////////////////////////////////////////////////////////// +// Threading +////////////////////////////////////////////////////////////////////////// + + +#ifdef HAVE_CXX_THREADS +# define pthread_attr_t int +# define pthread_attr_init(a) do {} while(0) +# define pthread_attr_destroy(a) do {} while(0) +# define pthread_t std::thread +# define pthread_create(t, attr, fnc, a) *(t) = std::thread(fnc, this); +# define pthread_join(t, v) t.join(); +#elif !defined(HAVE_PTHREAD) +// simple pthreads 4 windows +# define pthread_attr_t SIZE_T +# define pthread_attr_init(attr) (*attr=0) +# define pthread_attr_destroy(a) do {} while(0) +# define pthread_attr_setstacksize(attr, stack) *attr=stack; + +# define pthread_t HANDLE +# define pthread_create(t, attr, fnc, a) *(t) = CreateThread(NULL, attr ? *((pthread_attr_t*)attr) : 0, (LPTHREAD_START_ROUTINE)fnc, a, 0, NULL) +# define pthread_join(t, v) WaitForSingleObject(t, INFINITE), GetExitCodeThread(t,(LPDWORD)v), CloseHandle(t) +#endif + +class CScriptThread_impl : public CScriptThread::CScriptThread_t { +public: + CScriptThread_impl(CScriptThread *_this) : retvar((void*)-1), activ(false), running(false), started(false), This(_this) {} + ~CScriptThread_impl() {} + void Run() { + if(started) return; + activ = true; +// pthread_attr_t attribute; +// pthread_attr_init(&attribute); +// pthread_attr_setstacksize(&attribute,1024); + pthread_create(&thread, NULL /*&attribute*/, (void*(*)(void*))ThreadFnc, this); +// pthread_attr_destroy(&attribute); + while(!started); + } + int Stop(bool Wait) { + if(!running) return started ? retValue() : -1; + activ = false; + if(Wait) { + pthread_join(thread, &retvar); + } + return (int)retvar; + } + int retValue() { return (int)retvar; } + bool isActiv() { return activ; } + bool isRunning() { return running; } + bool isStarted() { return started; } + static void *ThreadFnc(CScriptThread_impl *This) { + This->running = This->started = true; + This->retvar = (void*)This->This->ThreadFnc(); + This->running = false; + This->This->ThreadFncFinished(); + return This->retvar; + } + void *retvar; + bool activ; + bool running; + bool started; + CScriptThread *This; + pthread_t thread; +}; + +CScriptThread::CScriptThread() { + thread = new CScriptThread_impl(this); +} +CScriptThread::~CScriptThread() { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" + delete thread; +#pragma GCC diagnostic pop +} +void CScriptThread::ThreadFncFinished() {} + +CScriptCoroutine::StopIteration_t CScriptCoroutine::StopIteration; + +bool CScriptCoroutine::next() +{ + if(!isStarted()) { + Run(); + wake_main.wait(); + } else if(isRunning()) { + wake_thread.post(); + wake_main.wait(); + } else + return false; + if(!isRunning()) return false; + return true; +} +bool CScriptCoroutine::yield_no_throw() { + wake_main.post(); + wake_thread.wait(); + return isActiv(); +} +void CScriptCoroutine::yield() { + wake_main.post(); + wake_thread.wait(); + if(!isActiv()) { + throw StopIteration; + } +} +int CScriptCoroutine::ThreadFnc() { + int ret=-1; + try { + ret = Coroutine(); + } catch(StopIteration_t &) { + return 0; + } catch(std::exception & e) { + printf("CScriptCoroutine has received an uncaught exception: %s\n", e.what()); + return -1; + } catch(...) { + printf("CScriptCoroutine has received an uncaught and unknown exception\n"); + return -1; + } + return ret; +} +void CScriptCoroutine::ThreadFncFinished() { + wake_main.post(); +} + + +#endif // HAVE_THREADING + diff --git a/src/TinyJS/pool_allocator.cpp b/src/TinyJS/pool_allocator.cpp new file mode 100644 index 0000000..3a34fba --- /dev/null +++ b/src/TinyJS/pool_allocator.cpp @@ -0,0 +1,258 @@ +/* + * 42TinyJS + * + * A fork of TinyJS with the goal to makes a more JavaScript/ECMA compliant engine + * + * Authored By Armin Diedering + * + * Copyright (C) 2010-2014 ardisoft + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "TinyJS/pool_allocator.h" +#include +#include +#include + +struct block { + block* next; +}; +struct block_head { + block_head* next; +}; + +static void set_next(void* p, void* next) { + static_cast(p)->next = static_cast(next); +} + +static void* get_next(void* p) { + return static_cast(p)->next; +} + +fixed_size_allocator::fixed_size_allocator( size_t numObjects, size_t objectSize, const char *for_class ) +{ + num_objects = numObjects; + object_size = objectSize >= sizeof(block) ? objectSize : sizeof(block); + + head_of_free_list = head = 0; + +#ifdef DEBUG_POOL_ALLOCATOR + if(for_class) name = for_class; + allocs= + frees= + max = + current= + blocks = +#endif + refs = 0; +} + +fixed_size_allocator::~fixed_size_allocator() +{ + while(head) { + char *p = (char*)head; + head = head->next; + delete [] p; + } +#ifdef DEBUG_POOL_ALLOCATOR +# ifndef LOG_POOL_ALLOCATOR_MEMORY_USAGE + if(refs) { +# endif + fprintf(stderr, "allocator [%s](%d) destroyed\n", name.c_str(), object_size); + fprintf(stderr, " allocs:%i, ", allocs); + fprintf(stderr, "frees:%i, ", frees); + fprintf(stderr, "max:%i, ", max); + fprintf(stderr, "blocks:%i\n", blocks); + if(refs) fprintf(stderr, "************ %i x not freed ************\n", refs); + fprintf(stderr, "\n"); +# ifndef LOG_POOL_ALLOCATOR_MEMORY_USAGE + } +# endif +#endif +} + +void* fixed_size_allocator::_alloc( size_t ) { + refs++; +#ifdef DEBUG_POOL_ALLOCATOR + allocs++;current++; + if(current>max)max=current; +#endif + void* p = head_of_free_list; + if(p) { + head_of_free_list = get_next(p); + } else { + + char* new_block = new char[sizeof(block_head) + num_objects * object_size]; + ((block_head*)new_block)->next = head; + head = (block_head*)new_block; + new_block += sizeof(block_head); + for(std::size_t i = object_size; i < (num_objects - 1) * object_size; i += object_size) { + set_next(&new_block[i], &new_block[i + object_size]); + } + set_next(&new_block[(num_objects - 1) * object_size], 0); + p = new_block; + head_of_free_list = &new_block[object_size]; + +#ifdef DEBUG_POOL_ALLOCATOR + blocks++; +#endif + } + return p; +} +#include +#ifndef ASSERT +# define ASSERT(X) assert(X) +#endif + +bool fixed_size_allocator::_free( void* p, size_t ) { + if(p == 0) return refs==0; + refs--; +#ifdef DEBUG_POOL_ALLOCATOR + ASSERT(refs>=0); + frees++;current--; +#endif + block* dead_object = static_cast(p); + + dead_object->next = static_cast(head_of_free_list); + head_of_free_list = dead_object; + return refs==0; +} +typedef std::vector allocator_pool_t; +typedef allocator_pool_t::iterator allocator_pool_it; + +static bool compare_allocator_pool(fixed_size_allocator *allocator, size_t Size) { + return allocator->objectSize() < Size; +} + +static class _allocator_pool +{ +public: + _allocator_pool() : allocator_pool(0) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok = last_access = 0; +#endif + } + ~_allocator_pool() { + if(allocator_pool && !allocator_pool->empty()) + for(allocator_pool_it it = allocator_pool->begin(); it!=allocator_pool->end(); it++) + delete *it; + delete allocator_pool; +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + if(last_access) fprintf(stderr, "last_ok:%i(%i)=%i%%\n", last_ok, last_access, (last_ok*100)/last_access); +#endif + } + allocator_pool_it *findAllocator(size_t size, allocator_pool_it &it) { + if(!allocator_pool) return 0; + it = lower_bound(allocator_pool->begin(), allocator_pool->end(), size, compare_allocator_pool); + if(it != allocator_pool->end() && (*it)->objectSize() == size) + return ⁢ + return 0; + } + fixed_size_allocator *checkLastAllocator(size_t size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_access++; +#endif + if(last_allocate_allocator && last_allocate_allocator->objectSize()==size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok++; +#endif + return last_allocate_allocator; + } + else if(last_free_allocator && last_free_allocator->objectSize()==size) { +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + last_ok++; +#endif + return last_free_allocator; + } else + return 0; + } + void removeAllocator(allocator_pool_it it) { + if(last_allocate_allocator == *it) last_allocate_allocator = 0; + if(last_free_allocator == *it) last_free_allocator = 0; + delete *it; allocator_pool->erase(it); + if(allocator_pool->empty()) { + delete allocator_pool; allocator_pool=0; + } + } + allocator_pool_t *allocator_pool; + fixed_size_allocator *last_allocate_allocator; + fixed_size_allocator *last_free_allocator; +#ifdef LOG_POOL_ALLOCATOR_MEMORY_USAGE + int last_ok; + int last_access; +#endif +}allocator_pool; + +//#define WITH_TIME_LOGGER +#include "TinyJS/time_logger.h" + +TimeLoggerCreate(alloc, false) +TimeLoggerCreate(free, false) +#ifdef NO_THREADING +# define LOCK do{}while(0) +#else +CScriptMutex fixed_size_allocator::locker; +class lock_help { +public: + lock_help() { fixed_size_allocator::locker.lock(); } + ~lock_help() { fixed_size_allocator::locker.unlock(); } +}; +#define LOCK lock_help lock +#endif +void* fixed_size_allocator::alloc(size_t size, const char *for_class) { + TimeLoggerHelper(alloc); + LOCK; + if(!allocator_pool.allocator_pool) { + allocator_pool.allocator_pool = new allocator_pool_t(); + allocator_pool.last_allocate_allocator = allocator_pool.last_free_allocator = 0; + } + fixed_size_allocator *last = allocator_pool.checkLastAllocator(size); + if(last) + return last->_alloc(size); + else { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) + return (allocator_pool.last_allocate_allocator = *(it))->_alloc(size); + else { + return (allocator_pool.last_allocate_allocator = (*allocator_pool.allocator_pool->insert(it, new fixed_size_allocator(64, size, for_class))))->_alloc(size); + } + } +} +void fixed_size_allocator::free(void *p, size_t size) { + TimeLoggerHelper(free); + LOCK; + if(!allocator_pool.allocator_pool) { + ASSERT(0/* free called but not allocator defined*/); + return; + } + fixed_size_allocator *last = allocator_pool.checkLastAllocator(size); + if(last) { + if( last->_free(p, size) ) { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) + allocator_pool.removeAllocator(it); + } + } else { + allocator_pool_it it; if(allocator_pool.findAllocator(size, it)) { + if( (allocator_pool.last_free_allocator = *it)->_free(p, size) ) + allocator_pool.removeAllocator(it); + } else + ASSERT(0/* free called but not allocator defined*/); + } +} diff --git a/src/program.cpp b/src/program.cpp new file mode 100644 index 0000000..759b9dc --- /dev/null +++ b/src/program.cpp @@ -0,0 +1,50 @@ +#include "TinyJS/TinyJS.h" +#include +#include +#include + + +void js_print(const CFunctionsScopePtr &v, void *) { + printf("> %s\n", v->getArgument("text")->toString().c_str()); +} + +void js_dump(const CFunctionsScopePtr &v, void *) { + v->getContext()->getRoot()->trace("> "); +} + + +char *topOfStack; +#define sizeOfStack 1*1024*1024 /* for example 1 MB depend of Compiler-Options */ +#define sizeOfSafeStack 50*1024 /* safety area */ + +int main(int , char **) +{ + char dummy; + topOfStack = &dummy; + CTinyJS *js = new CTinyJS(); + 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->setStackBase(topOfStack-(sizeOfStack-sizeOfSafeStack)); + try { + 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("%s\n", e->toString().c_str()); + delete e; + } + int lineNumber = 0; + while (js->evaluate("lets_quit") == "0") { + std::string buffer; + if(!std::getline(std::cin, buffer)) break; + try { + js->execute(buffer, "console.input", lineNumber++); + } catch (CScriptException *e) { + printf("%s\n", e->toString().c_str()); + delete e; + } + } + delete js; + return 0; +} \ No newline at end of file