· 6 years ago · Aug 25, 2019, 09:24 PM
1/*
2 * TinyJS
3 *
4 * A single-file Javascript-alike engine
5 *
6 * Authored By Gordon Williams <gw@pur3.co.uk>
7 *
8 * Copyright (C) 2009 Pur3 Ltd
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy of
11 * this software and associated documentation files (the "Software"), to deal in
12 * the Software without restriction, including without limitation the rights to
13 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
14 * of the Software, and to permit persons to whom the Software is furnished to do
15 * so, subject to the following conditions:
16
17 * The above copyright notice and this permission notice shall be included in all
18 * copies or substantial portions of the Software.
19
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 * SOFTWARE.
27 */
28
29#ifndef TINYJS_H
30#define TINYJS_H
31
32// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging
33#define TINYJS_CALL_STACK
34
35#ifdef _WIN32
36#ifdef _DEBUG
37#define _CRTDBG_MAP_ALLOC
38#include <stdlib.h>
39#include <crtdbg.h>
40#endif
41#endif
42#include <string>
43#include <vector>
44
45#ifndef TRACE
46#define TRACE printf
47#endif // TRACE
48
49
50const int TINYJS_LOOP_MAX_ITERATIONS = 8192;
51
52enum LEX_TYPES {
53 LEX_EOF = 0,
54 LEX_ID = 256,
55 LEX_INT,
56 LEX_FLOAT,
57 LEX_STR,
58
59 LEX_EQUAL,
60 LEX_TYPEEQUAL,
61 LEX_NEQUAL,
62 LEX_NTYPEEQUAL,
63 LEX_LEQUAL,
64 LEX_LSHIFT,
65 LEX_LSHIFTEQUAL,
66 LEX_GEQUAL,
67 LEX_RSHIFT,
68 LEX_RSHIFTUNSIGNED,
69 LEX_RSHIFTEQUAL,
70 LEX_PLUSEQUAL,
71 LEX_MINUSEQUAL,
72 LEX_PLUSPLUS,
73 LEX_MINUSMINUS,
74 LEX_ANDEQUAL,
75 LEX_ANDAND,
76 LEX_OREQUAL,
77 LEX_OROR,
78 LEX_XOREQUAL,
79 // reserved words
80#define LEX_R_LIST_START LEX_R_IF
81 LEX_R_IF,
82 LEX_R_ELSE,
83 LEX_R_DO,
84 LEX_R_WHILE,
85 LEX_R_FOR,
86 LEX_R_BREAK,
87 LEX_R_CONTINUE,
88 LEX_R_FUNCTION,
89 LEX_R_RETURN,
90 LEX_R_VAR,
91 LEX_R_TRUE,
92 LEX_R_FALSE,
93 LEX_R_NULL,
94 LEX_R_UNDEFINED,
95 LEX_R_NEW,
96
97 LEX_R_LIST_END /* always the last entry */
98};
99
100enum SCRIPTVAR_FLAGS {
101 SCRIPTVAR_UNDEFINED = 0,
102 SCRIPTVAR_FUNCTION = 1,
103 SCRIPTVAR_OBJECT = 2,
104 SCRIPTVAR_ARRAY = 4,
105 SCRIPTVAR_DOUBLE = 8, // floating point double
106 SCRIPTVAR_INTEGER = 16, // integer number
107 SCRIPTVAR_STRING = 32, // string
108 SCRIPTVAR_NULL = 64, // it seems null is its own data type
109
110 SCRIPTVAR_NATIVE = 128, // to specify this is a native function
111 SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL |
112 SCRIPTVAR_DOUBLE |
113 SCRIPTVAR_INTEGER,
114 SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE |
115 SCRIPTVAR_INTEGER |
116 SCRIPTVAR_STRING |
117 SCRIPTVAR_FUNCTION |
118 SCRIPTVAR_OBJECT |
119 SCRIPTVAR_ARRAY |
120 SCRIPTVAR_NULL,
121
122};
123
124#define TINYJS_RETURN_VAR "return"
125#define TINYJS_PROTOTYPE_CLASS "prototype"
126#define TINYJS_TEMP_NAME ""
127#define TINYJS_BLANK_DATA ""
128
129/// convert the given string into a quoted string suitable for javascript
130std::string getJSString(const std::string &str);
131
132class CScriptException {
133public:
134 std::string text;
135 CScriptException(const std::string &exceptionText);
136};
137
138class CScriptLex
139{
140public:
141 CScriptLex(const std::string &input);
142 CScriptLex(CScriptLex *owner, int startChar, int endChar);
143 ~CScriptLex(void);
144
145 char currCh, nextCh;
146 int tk; ///< The type of the token that we have
147 int tokenStart; ///< Position in the data at the beginning of the token we have here
148 int tokenEnd; ///< Position in the data at the last character of the token we have here
149 int tokenLastEnd; ///< Position in the data at the last character of the last token
150 std::string tkStr; ///< Data contained in the token we have here
151
152 void match(int expected_tk); ///< Lexical match wotsit
153 static std::string getTokenStr(int token); ///< Get the string representation of the given token
154 void reset(); ///< Reset this lex so we can start again
155
156 std::string getSubString(int pos); ///< Return a sub-string from the given position up until right now
157 CScriptLex *getSubLex(int lastPosition); ///< Return a sub-lexer from the given position up until right now
158
159 std::string getPosition(int pos=-1); ///< Return a string representing the position in lines and columns of the character pos given
160
161protected:
162 /* When we go into a loop, we use getSubLex to get a lexer for just the sub-part of the
163 relevant string. This doesn't re-allocate and copy the string, but instead copies
164 the data pointer and sets dataOwned to false, and dataStart/dataEnd to the relevant things. */
165 char *data; ///< Data string to get tokens from
166 int dataStart, dataEnd; ///< Start and end position in data string
167 bool dataOwned; ///< Do we own this data string?
168
169 int dataPos; ///< Position in data (we CAN go past the end of the string here)
170
171 void getNextCh();
172 void getNextToken(); ///< Get the text token from our text string
173};
174
175class CScriptVar;
176
177typedef void (*JSCallback)(CScriptVar *var, void *userdata);
178
179class CScriptVarLink
180{
181public:
182 std::string name;
183 CScriptVarLink *nextSibling;
184 CScriptVarLink *prevSibling;
185 CScriptVar *var;
186 bool owned;
187
188 CScriptVarLink(CScriptVar *var, const std::string &name = TINYJS_TEMP_NAME);
189 CScriptVarLink(const CScriptVarLink &link); ///< Copy constructor
190 ~CScriptVarLink();
191 void replaceWith(CScriptVar *newVar); ///< Replace the Variable pointed to
192 void replaceWith(CScriptVarLink *newVar); ///< Replace the Variable pointed to (just dereferences)
193 int getIntName(); ///< Get the name as an integer (for arrays)
194 void setIntName(int n); ///< Set the name as an integer (for arrays)
195};
196
197/// Variable class (containing a doubly-linked list of children)
198class CScriptVar
199{
200public:
201 CScriptVar(); ///< Create undefined
202 CScriptVar(const std::string &varData, int varFlags); ///< User defined
203 CScriptVar(const std::string &str); ///< Create a string
204 CScriptVar(double varData);
205 CScriptVar(int val);
206 ~CScriptVar(void);
207
208 CScriptVar *getReturnVar(); ///< If this is a function, get the result value (for use by native functions)
209 void setReturnVar(CScriptVar *var); ///< Set the result value. Use this when setting complex return data as it avoids a deepCopy()
210 CScriptVar *getParameter(const std::string &name); ///< If this is a function, get the parameter with the given name (for use by native functions)
211
212 CScriptVarLink *findChild(const std::string &childName); ///< Tries to find a child with the given name, may return 0
213 CScriptVarLink *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
214 CScriptVarLink *findChildOrCreateByPath(const std::string &path); ///< Tries to find a child with the given path (separated by dots)
215 CScriptVarLink *addChild(const std::string &childName, CScriptVar *child=NULL);
216 CScriptVarLink *addChildNoDup(const std::string &childName, CScriptVar *child=NULL); ///< add a child overwriting any with the same name
217 void removeChild(CScriptVar *child);
218 void removeLink(CScriptVarLink *link); ///< Remove a specific link (this is faster than finding via a child)
219 void removeAllChildren();
220 CScriptVar *getArrayIndex(int idx); ///< The the value at an array index
221 void setArrayIndex(int idx, CScriptVar *value); ///< Set the value at an array index
222 int getArrayLength(); ///< If this is an array, return the number of items in it (else 0)
223 int getChildren(); ///< Get the number of children
224
225 int getInt();
226 bool getBool() { return getInt() != 0; }
227 double getDouble();
228 const std::string &getString();
229 std::string getParsableString(); ///< get Data as a parsable javascript string
230 void setInt(int num);
231 void setDouble(double val);
232 void setString(const std::string &str);
233 void setUndefined();
234 void setArray();
235 bool equals(CScriptVar *v);
236
237 bool isInt() { return (flags&SCRIPTVAR_INTEGER)!=0; }
238 bool isDouble() { return (flags&SCRIPTVAR_DOUBLE)!=0; }
239 bool isString() { return (flags&SCRIPTVAR_STRING)!=0; }
240 bool isNumeric() { return (flags&SCRIPTVAR_NUMERICMASK)!=0; }
241 bool isFunction() { return (flags&SCRIPTVAR_FUNCTION)!=0; }
242 bool isObject() { return (flags&SCRIPTVAR_OBJECT)!=0; }
243 bool isArray() { return (flags&SCRIPTVAR_ARRAY)!=0; }
244 bool isNative() { return (flags&SCRIPTVAR_NATIVE)!=0; }
245 bool isUndefined() { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; }
246 bool isNull() { return (flags & SCRIPTVAR_NULL)!=0; }
247 bool isBasic() { return firstChild==0; } ///< Is this *not* an array/object/etc
248
249 CScriptVar *mathsOp(CScriptVar *b, int op); ///< do a maths op with another script variable
250 void copyValue(CScriptVar *val); ///< copy the value from the value given
251 CScriptVar *deepCopy(); ///< deep copy this node and return the result
252
253 void trace(std::string indentStr = "", const std::string &name = ""); ///< Dump out the contents of this using trace
254 std::string getFlagsAsString(); ///< For debugging - just dump a string version of the flags
255 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)
256 void setCallback(JSCallback callback, void *userdata); ///< Set the callback for native functions
257
258 CScriptVarLink *firstChild;
259 CScriptVarLink *lastChild;
260
261 /// For memory management/garbage collection
262 CScriptVar *ref(); ///< Add reference to this variable
263 void unref(); ///< Remove a reference, and delete this variable if required
264 int getRefs(); ///< Get the number of references to this script variable
265protected:
266 int refs; ///< The number of references held to this - used for garbage collection
267
268 std::string data; ///< The contents of this variable if it is a string
269 long intData; ///< The contents of this variable if it is an int
270 double doubleData; ///< The contents of this variable if it is a double
271 int flags; ///< the flags determine the type of the variable - int/double/string/etc
272 JSCallback jsCallback; ///< Callback for native functions
273 void *jsCallbackUserData; ///< user data passed as second argument to native functions
274
275 void init(); ///< initialisation of data members
276
277 /** Copy the basic data and flags from the variable given, with no
278 * children. Should be used internally only - by copyValue and deepCopy */
279 void copySimpleData(CScriptVar *val);
280
281 friend class CTinyJS;
282};
283
284class CTinyJS {
285public:
286 CTinyJS();
287 ~CTinyJS();
288
289 void execute(const std::string &code);
290 /** Evaluate the given code and return a link to a javascript object,
291 * useful for (dangerous) JSON parsing. If nothing to return, will return
292 * 'undefined' variable type. CScriptVarLink is returned as this will
293 * automatically unref the result as it goes out of scope. If you want to
294 * keep it, you must use ref() and unref() */
295 CScriptVarLink evaluateComplex(const std::string &code);
296 /** Evaluate the given code and return a string. If nothing to return, will return
297 * 'undefined' */
298 std::string evaluate(const std::string &code);
299
300 /// add a native function to be called from TinyJS
301 /** example:
302 \code
303 void scRandInt(CScriptVar *c, void *userdata) { ... }
304 tinyJS->addNative("function randInt(min, max)", scRandInt, 0);
305 \endcode
306
307 or
308
309 \code
310 void scSubstring(CScriptVar *c, void *userdata) { ... }
311 tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0);
312 \endcode
313 */
314 void addNative(const std::string &funcDesc, JSCallback ptr, void *userdata);
315
316 /// Get the given variable specified by a path (var1.var2.etc), or return 0
317 CScriptVar *getScriptVariable(const std::string &path);
318 /// Get the value of the given variable, or return 0
319 const std::string *getVariable(const std::string &path);
320 /// set the value of the given variable, return trur if it exists and gets set
321 bool setVariable(const std::string &path, const std::string &varData);
322
323 /// Send all variables to stdout
324 void trace();
325
326 CScriptVar *root; /// root of symbol table
327private:
328 CScriptLex *l; /// current lexer
329 std::vector<CScriptVar*> scopes; /// stack of scopes when parsing
330#ifdef TINYJS_CALL_STACK
331 std::vector<std::string> call_stack; /// Names of places called so we can show when erroring
332#endif
333
334 CScriptVar *stringClass; /// Built in string class
335 CScriptVar *objectClass; /// Built in object class
336 CScriptVar *arrayClass; /// Built in array class
337
338 // parsing - in order of precedence
339 CScriptVarLink *functionCall(bool &execute, CScriptVarLink *function, CScriptVar *parent);
340 CScriptVarLink *factor(bool &execute);
341 CScriptVarLink *unary(bool &execute);
342 CScriptVarLink *term(bool &execute);
343 CScriptVarLink *expression(bool &execute);
344 CScriptVarLink *shift(bool &execute);
345 CScriptVarLink *condition(bool &execute);
346 CScriptVarLink *logic(bool &execute);
347 CScriptVarLink *ternary(bool &execute);
348 CScriptVarLink *base(bool &execute);
349 void block(bool &execute);
350 void statement(bool &execute);
351 // parsing utility functions
352 CScriptVarLink *parseFunctionDefinition();
353 void parseFunctionArguments(CScriptVar *funcVar);
354
355 CScriptVarLink *findInScopes(const std::string &childName); ///< Finds a child, looking recursively up the scopes
356 /// Look up in any parent classes of the given object
357 CScriptVarLink *findInParentClasses(CScriptVar *object, const std::string &name);
358};
359
360#endif