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

@ -26,6 +26,9 @@
#ifndef 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 _DEBUG
#define _CRTDBG_MAP_ALLOC
@ -40,6 +43,7 @@
#define TRACE printf
#endif // TRACE
const int TINYJS_LOOP_MAX_ITERATIONS = 8192;
enum LEX_TYPES {
@ -58,6 +62,7 @@ enum LEX_TYPES {
LEX_LSHIFTEQUAL,
LEX_GEQUAL,
LEX_RSHIFT,
LEX_RSHIFTUNSIGNED,
LEX_RSHIFTEQUAL,
LEX_PLUSEQUAL,
LEX_MINUSEQUAL,
@ -319,6 +324,10 @@ public:
private:
CScriptLex *l; /// current lexer
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 *objectClass; /// Built in object class
CScriptVar *arrayClass; /// Built in array class
@ -328,8 +337,10 @@ private:
CScriptVarLink *unary(bool &execute);
CScriptVarLink *term(bool &execute);
CScriptVarLink *expression(bool &execute);
CScriptVarLink *shift(bool &execute);
CScriptVarLink *condition(bool &execute);
CScriptVarLink *logic(bool &execute);
CScriptVarLink *ternary(bool &execute);
CScriptVarLink *base(bool &execute);
void block(bool &execute);
void statement(bool &execute);

@ -93,6 +93,15 @@ void scStringCharAt(CScriptVar *c, void *) {
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 *) {
string str = c->getParameter("this")->getString();
string sep = c->getParameter("separator")->getString();
@ -111,6 +120,13 @@ void scStringSplit(CScriptVar *c, void *) {
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 *) {
string str = c->getParameter("str")->getString();
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.substring(lo,hi)", scStringSubstring, 0);
tinyJS->addNative("function String.charAt(pos)", scStringCharAt, 0);
tinyJS->addNative("function String.charCodeAt(pos)", scStringCharCodeAt, 0);
tinyJS->addNative("function String.fromCharCode(char)", scStringFromCharCode, 0);
tinyJS->addNative("function String.split(separator)", scStringSplit, 0);
tinyJS->addNative("function Integer.parseInt(str)", scIntegerParseInt, 0); // string to int
tinyJS->addNative("function Integer.valueOf(str)", scIntegerValueOf, 0); // value of a single character

@ -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