Version 0.28 : Ternary operator

Rudimentary call stack on error
                   Added String Character functions
                   Added shift operators
master
pur3mail 13 years ago
parent 2e930cd8cf
commit 8c790516ea

@ -88,14 +88,18 @@
Add built-in array functions Add built-in array functions
Version 0.27 : Added OZLB's TinyJS.setVariable (with some tweaks) Version 0.27 : Added OZLB's TinyJS.setVariable (with some tweaks)
Added OZLB's Maths Functions Added OZLB's Maths Functions
Version 0.28 : Ternary operator
Rudimentary call stack on error
Added String Character functions
Added shift operators
NOTE: This doesn't support constructors for objects NOTE: This doesn't support constructors for objects
Constructing an array with an initial length 'Array(5)' doesn't work
Recursive loops of data such as a.foo = a; fail to be garbage collected Recursive loops of data such as a.foo = a; fail to be garbage collected
'length' cannot be set length variable cannot be set
There is no ternary operator implemented yet The postfix increment operator returns the current value, not the previous as it should.
The postfix increment operator returns the current value, not the There is no prefix increment operator
previous as it should.
Arrays are implemented as a linked list - hence a lookup time is O(n) Arrays are implemented as a linked list - hence a lookup time is O(n)
TODO: TODO:
@ -341,6 +345,7 @@ string CScriptLex::getTokenStr(int token) {
case LEX_LSHIFTEQUAL : return "<<="; case LEX_LSHIFTEQUAL : return "<<=";
case LEX_GEQUAL : return ">="; case LEX_GEQUAL : return ">=";
case LEX_RSHIFT : return ">>"; case LEX_RSHIFT : return ">>";
case LEX_RSHIFTUNSIGNED : return ">>";
case LEX_RSHIFTEQUAL : return ">>="; case LEX_RSHIFTEQUAL : return ">>=";
case LEX_PLUSEQUAL : return "+="; case LEX_PLUSEQUAL : return "+=";
case LEX_MINUSEQUAL : return "-="; case LEX_MINUSEQUAL : return "-=";
@ -545,9 +550,12 @@ void CScriptLex::getNextToken() {
} else if (tk=='>' && currCh=='>') { } else if (tk=='>' && currCh=='>') {
tk = LEX_RSHIFT; tk = LEX_RSHIFT;
getNextCh(); getNextCh();
if (currCh=='=') { // <<= if (currCh=='=') { // >>=
tk = LEX_RSHIFTEQUAL; tk = LEX_RSHIFTEQUAL;
getNextCh(); getNextCh();
} else if (currCh=='>') { // >>>
tk = LEX_RSHIFTUNSIGNED;
getNextCh();
} }
} else if (tk=='+' && currCh=='=') { } else if (tk=='+' && currCh=='=') {
tk = LEX_PLUSEQUAL; tk = LEX_PLUSEQUAL;
@ -602,9 +610,9 @@ string CScriptLex::getSubString(int lastPosition) {
CScriptLex *CScriptLex::getSubLex(int lastPosition) { CScriptLex *CScriptLex::getSubLex(int lastPosition) {
int lastCharIdx = tokenLastEnd+1; int lastCharIdx = tokenLastEnd+1;
if (lastCharIdx < dataEnd) if (lastCharIdx < dataEnd)
return new CScriptLex( this, lastPosition, lastCharIdx); return new CScriptLex(this, lastPosition, lastCharIdx);
else else
return new CScriptLex( this, lastPosition, dataEnd ); return new CScriptLex(this, lastPosition, dataEnd );
} }
string CScriptLex::getPosition(int pos) { string CScriptLex::getPosition(int pos) {
@ -1290,6 +1298,9 @@ void CTinyJS::execute(const string &code) {
CScriptLex *oldLex = l; CScriptLex *oldLex = l;
vector<CScriptVar*> oldScopes = scopes; vector<CScriptVar*> oldScopes = scopes;
l = new CScriptLex(code); l = new CScriptLex(code);
#ifdef TINYJS_CALL_STACK
call_stack.clear();
#endif
scopes.clear(); scopes.clear();
scopes.push_back(root); scopes.push_back(root);
try { try {
@ -1297,9 +1308,15 @@ void CTinyJS::execute(const string &code) {
while (l->tk) statement(execute); while (l->tk) statement(execute);
} catch (CScriptException *e) { } catch (CScriptException *e) {
ostringstream msg; ostringstream msg;
msg << "Error " << e->text << " at " << l->getPosition(); msg << "Error " << e->text;
#ifdef TINYJS_CALL_STACK
for (int i=(int)call_stack.size()-1;i>=0;i--)
msg << "\n" << i << ": " << call_stack.at(i);
#endif
msg << " at " << l->getPosition();
delete l; delete l;
l = oldLex; l = oldLex;
throw new CScriptException(msg.str()); throw new CScriptException(msg.str());
} }
delete l; delete l;
@ -1312,6 +1329,9 @@ CScriptVarLink CTinyJS::evaluateComplex(const string &code) {
vector<CScriptVar*> oldScopes = scopes; vector<CScriptVar*> oldScopes = scopes;
l = new CScriptLex(code); l = new CScriptLex(code);
#ifdef TINYJS_CALL_STACK
call_stack.clear();
#endif
scopes.clear(); scopes.clear();
scopes.push_back(root); scopes.push_back(root);
CScriptVarLink *v = 0; CScriptVarLink *v = 0;
@ -1323,10 +1343,16 @@ CScriptVarLink CTinyJS::evaluateComplex(const string &code) {
if (l->tk!=LEX_EOF) l->match(';'); if (l->tk!=LEX_EOF) l->match(';');
} while (l->tk!=LEX_EOF); } while (l->tk!=LEX_EOF);
} catch (CScriptException *e) { } catch (CScriptException *e) {
ostringstream msg; ostringstream msg;
msg << "Error " << e->text << " at " << l->getPosition(); msg << "Error " << e->text;
delete l; #ifdef TINYJS_CALL_STACK
l = oldLex; for (int i=(int)call_stack.size()-1;i>=0;i--)
msg << "\n" << i << ": " << call_stack.at(i);
#endif
msg << " at " << l->getPosition();
delete l;
l = oldLex;
throw new CScriptException(msg.str()); throw new CScriptException(msg.str());
} }
delete l; delete l;
@ -1475,6 +1501,9 @@ CScriptVarLink *CTinyJS::factor(bool &execute) {
// add the function's execute space to the symbol table so we can recurse // add the function's execute space to the symbol table so we can recurse
CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR); CScriptVarLink *returnVarLink = functionRoot->addChild(TINYJS_RETURN_VAR);
scopes.push_back(functionRoot); scopes.push_back(functionRoot);
#ifdef TINYJS_CALL_STACK
call_stack.push_back(a->name + " from " + l->getPosition());
#endif
if (a->var->isNative()) { if (a->var->isNative()) {
ASSERT(a->var->jsCallback); ASSERT(a->var->jsCallback);
@ -1500,7 +1529,9 @@ CScriptVarLink *CTinyJS::factor(bool &execute) {
if (exception) if (exception)
throw exception; throw exception;
} }
#ifdef TINYJS_CALL_STACK
if (!call_stack.empty()) call_stack.pop_back();
#endif
scopes.pop_back(); scopes.pop_back();
/* get the real return var before we remove it from our function */ /* get the real return var before we remove it from our function */
returnVar = new CScriptVarLink(returnVarLink->var); returnVar = new CScriptVarLink(returnVarLink->var);
@ -1548,7 +1579,7 @@ CScriptVarLink *CTinyJS::factor(bool &execute) {
l->match(LEX_ID); l->match(LEX_ID);
} else if (l->tk == '[') { // ------------------------------------- Array Access } else if (l->tk == '[') { // ------------------------------------- Array Access
l->match('['); l->match('[');
CScriptVarLink *index = expression(execute); CScriptVarLink *index = base(execute);
l->match(']'); l->match(']');
if (execute) { if (execute) {
CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString()); CScriptVarLink *child = a->var->findChildOrCreate(index->var->getString());
@ -1719,8 +1750,25 @@ CScriptVarLink *CTinyJS::expression(bool &execute) {
return a; return a;
} }
CScriptVarLink *CTinyJS::shift(bool &execute) {
CScriptVarLink *a = expression(execute);
if (l->tk==LEX_LSHIFT || l->tk==LEX_RSHIFT || l->tk==LEX_RSHIFTUNSIGNED) {
int op = l->tk;
l->match(op);
CScriptVarLink *b = base(execute);
int shift = execute ? b->var->getInt() : 0;
CLEAN(b);
if (execute) {
if (op==LEX_LSHIFT) a->var->setInt(a->var->getInt() << shift);
if (op==LEX_RSHIFT) a->var->setInt(a->var->getInt() >> shift);
if (op==LEX_RSHIFTUNSIGNED) a->var->setInt(((unsigned int)a->var->getInt()) >> shift);
}
}
return a;
}
CScriptVarLink *CTinyJS::condition(bool &execute) { CScriptVarLink *CTinyJS::condition(bool &execute) {
CScriptVarLink *a = expression(execute); CScriptVarLink *a = shift(execute);
CScriptVarLink *b; CScriptVarLink *b;
while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL || while (l->tk==LEX_EQUAL || l->tk==LEX_NEQUAL ||
l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL || l->tk==LEX_TYPEEQUAL || l->tk==LEX_NTYPEEQUAL ||
@ -1728,7 +1776,7 @@ CScriptVarLink *CTinyJS::condition(bool &execute) {
l->tk=='<' || l->tk=='>') { l->tk=='<' || l->tk=='>') {
int op = l->tk; int op = l->tk;
l->match(l->tk); l->match(l->tk);
b = expression(execute); b = shift(execute);
if (execute) { if (execute) {
CScriptVar *res = a->var->mathsOp(b->var, op); CScriptVar *res = a->var->mathsOp(b->var, op);
CREATE_LINK(a,res); CREATE_LINK(a,res);
@ -1775,8 +1823,36 @@ CScriptVarLink *CTinyJS::logic(bool &execute) {
return a; return a;
} }
CScriptVarLink *CTinyJS::ternary(bool &execute) {
CScriptVarLink *lhs = logic(execute);
bool noexec = false;
if (l->tk=='?') {
l->match('?');
if (!execute) {
CLEAN(lhs);
CLEAN(base(noexec));
l->match(':');
CLEAN(base(noexec));
} else {
bool first = lhs->var->getBool();
CLEAN(lhs);
if (first) {
lhs = base(execute);
l->match(':');
CLEAN(base(noexec));
} else {
CLEAN(base(noexec));
l->match(':');
lhs = base(execute);
}
}
}
return lhs;
}
CScriptVarLink *CTinyJS::base(bool &execute) { CScriptVarLink *CTinyJS::base(bool &execute) {
CScriptVarLink *lhs = logic(execute); CScriptVarLink *lhs = ternary(execute);
if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) { if (l->tk=='=' || l->tk==LEX_PLUSEQUAL || l->tk==LEX_MINUSEQUAL) {
/* If we're assigning to this and we don't have a parent, /* If we're assigning to this and we don't have a parent,
* add it to the symbol table root as per JavaScript. */ * add it to the symbol table root as per JavaScript. */
@ -1846,27 +1922,31 @@ void CTinyJS::statement(bool &execute) {
* hand side. Maybe just have a flag called can_create_var that we * hand side. Maybe just have a flag called can_create_var that we
* set and then we parse as if we're doing a normal equals.*/ * set and then we parse as if we're doing a normal equals.*/
l->match(LEX_R_VAR); l->match(LEX_R_VAR);
CScriptVarLink *a = 0; while (l->tk != ';') {
if (execute) CScriptVarLink *a = 0;
a = scopes.back()->findChildOrCreate(l->tkStr); if (execute)
l->match(LEX_ID); a = scopes.back()->findChildOrCreate(l->tkStr);
// now do stuff defined with dots l->match(LEX_ID);
while (l->tk == '.') { // now do stuff defined with dots
l->match('.'); while (l->tk == '.') {
if (execute) { l->match('.');
CScriptVarLink *lastA = a; if (execute) {
a = lastA->var->findChildOrCreate(l->tkStr); CScriptVarLink *lastA = a;
} a = lastA->var->findChildOrCreate(l->tkStr);
l->match(LEX_ID); }
} l->match(LEX_ID);
// sort out initialiser }
if (l->tk == '=') { // sort out initialiser
l->match('='); if (l->tk == '=') {
CScriptVarLink *var = base(execute); l->match('=');
if (execute) CScriptVarLink *var = base(execute);
a->replaceWith(var); if (execute)
CLEAN(var); a->replaceWith(var);
} CLEAN(var);
}
if (l->tk != ';')
l->match(',');
}
l->match(';'); l->match(';');
} else if (l->tk==LEX_R_IF) { } else if (l->tk==LEX_R_IF) {
l->match(LEX_R_IF); l->match(LEX_R_IF);

@ -26,6 +26,9 @@
#ifndef TINYJS_H #ifndef TINYJS_H
#define TINYJS_H #define TINYJS_H
// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for debugging
#define TINYJS_CALL_STACK
#ifdef _WIN32 #ifdef _WIN32
#ifdef _DEBUG #ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC #define _CRTDBG_MAP_ALLOC
@ -40,6 +43,7 @@
#define TRACE printf #define TRACE printf
#endif // TRACE #endif // TRACE
const int TINYJS_LOOP_MAX_ITERATIONS = 8192; const int TINYJS_LOOP_MAX_ITERATIONS = 8192;
enum LEX_TYPES { enum LEX_TYPES {
@ -58,6 +62,7 @@ enum LEX_TYPES {
LEX_LSHIFTEQUAL, LEX_LSHIFTEQUAL,
LEX_GEQUAL, LEX_GEQUAL,
LEX_RSHIFT, LEX_RSHIFT,
LEX_RSHIFTUNSIGNED,
LEX_RSHIFTEQUAL, LEX_RSHIFTEQUAL,
LEX_PLUSEQUAL, LEX_PLUSEQUAL,
LEX_MINUSEQUAL, LEX_MINUSEQUAL,
@ -319,6 +324,10 @@ public:
private: private:
CScriptLex *l; /// current lexer CScriptLex *l; /// current lexer
std::vector<CScriptVar*> scopes; /// stack of scopes when parsing std::vector<CScriptVar*> scopes; /// stack of scopes when parsing
#ifdef TINYJS_CALL_STACK
std::vector<std::string> call_stack; /// Names of places called so we can show when erroring
#endif
CScriptVar *stringClass; /// Built in string class CScriptVar *stringClass; /// Built in string class
CScriptVar *objectClass; /// Built in object class CScriptVar *objectClass; /// Built in object class
CScriptVar *arrayClass; /// Built in array class CScriptVar *arrayClass; /// Built in array class
@ -328,8 +337,10 @@ private:
CScriptVarLink *unary(bool &execute); CScriptVarLink *unary(bool &execute);
CScriptVarLink *term(bool &execute); CScriptVarLink *term(bool &execute);
CScriptVarLink *expression(bool &execute); CScriptVarLink *expression(bool &execute);
CScriptVarLink *shift(bool &execute);
CScriptVarLink *condition(bool &execute); CScriptVarLink *condition(bool &execute);
CScriptVarLink *logic(bool &execute); CScriptVarLink *logic(bool &execute);
CScriptVarLink *ternary(bool &execute);
CScriptVarLink *base(bool &execute); CScriptVarLink *base(bool &execute);
void block(bool &execute); void block(bool &execute);
void statement(bool &execute); void statement(bool &execute);

@ -93,6 +93,15 @@ void scStringCharAt(CScriptVar *c, void *) {
c->getReturnVar()->setString(""); c->getReturnVar()->setString("");
} }
void scStringCharCodeAt(CScriptVar *c, void *) {
string str = c->getParameter("this")->getString();
int p = c->getParameter("pos")->getInt();
if (p>=0 && p<(int)str.length())
c->getReturnVar()->setInt(str.at(p));
else
c->getReturnVar()->setInt(0);
}
void scStringSplit(CScriptVar *c, void *) { void scStringSplit(CScriptVar *c, void *) {
string str = c->getParameter("this")->getString(); string str = c->getParameter("this")->getString();
string sep = c->getParameter("separator")->getString(); string sep = c->getParameter("separator")->getString();
@ -111,6 +120,13 @@ void scStringSplit(CScriptVar *c, void *) {
result->setArrayIndex(length++, new CScriptVar(str)); result->setArrayIndex(length++, new CScriptVar(str));
} }
void scStringFromCharCode(CScriptVar *c, void *) {
char str[2];
str[0] = c->getParameter("char")->getInt();
str[1] = 0;
c->getReturnVar()->setString(str);
}
void scIntegerParseInt(CScriptVar *c, void *) { void scIntegerParseInt(CScriptVar *c, void *) {
string str = c->getParameter("str")->getString(); string str = c->getParameter("str")->getString();
int val = strtol(str.c_str(),0,0); int val = strtol(str.c_str(),0,0);
@ -206,6 +222,8 @@ void registerFunctions(CTinyJS *tinyJS) {
tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not tinyJS->addNative("function String.indexOf(search)", scStringIndexOf, 0); // find the position of a string in a string, -1 if not
tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0); tinyJS->addNative("function String.substring(lo,hi)", scStringSubstring, 0);
tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0); tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0);
tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0);
tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0);
tinyJS->addNative("function String.split(separator)", scStringSplit, 0); tinyJS->addNative("function String.split(separator)", scStringSplit, 0);
tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int
tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character

@ -0,0 +1,5 @@
// test for shift
var a = (2<<2);
var b = (16>>3);
var c = (-1 >>> 16);
result = a==8 && b==2 && c == 0xFFFF;

@ -0,0 +1,3 @@
// test for ternary
result = (true?3:4)==3 && (false?5:6)==6;
Loading…
Cancel
Save