/*JSON{ "type" : "method", "class" : "String", "name" : "trim", "generate" : "jswrap_string_trim", "return" : ["JsVar","A String with Whitespace removed from the beginning and end"], "return_object" : "String" } Return a new string with any whitespace (tabs, space, form feed, newline, carriage return, etc) removed from the beginning and end. */ JsVar *jswrap_string_trim(JsVar *parent) { JsVar *s = jsvAsString(parent); if (!s) return s; unsigned int start = 0; int end = -1; // work out beginning and end JsvStringIterator it; jsvStringIteratorNew(&it, s, 0); while (jsvStringIteratorHasChar(&it)) { bool ws = isWhitespace(jsvStringIteratorGetChar(&it)); if (!ws) { if (end<0) start = (unsigned int)jsvStringIteratorGetIndex(&it); end = (int)jsvStringIteratorGetIndex(&it); // last } jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); // work out length unsigned int len = 0; if (end>=(int)start) len = 1+(unsigned int)end-start; JsVar *res = jsvNewFromStringVar(s, start, len); jsvUnLock(s); return res; }
JsVar *jslNewStringFromLexer(JslCharPos *charFrom, size_t charTo) { // Original method - just copy it verbatim size_t maxLength = charTo + 1 - jsvStringIteratorGetIndex(&charFrom->it); assert(maxLength>0); // will fail if 0 // Try and create a flat string first JsVar *var = 0; if (maxLength > JSV_FLAT_STRING_BREAK_EVEN) { var = jsvNewFlatStringOfLength((unsigned int)maxLength); if (var) { // Flat string char *flatPtr = jsvGetFlatStringPointer(var); *(flatPtr++) = charFrom->currCh; JsvStringIterator it = jsvStringIteratorClone(&charFrom->it); while (jsvStringIteratorHasChar(&it) && (--maxLength>0)) { *(flatPtr++) = jsvStringIteratorGetChar(&it); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); return var; } } // Non-flat string... var = jsvNewFromEmptyString(); if (!var) { // out of memory return 0; } //jsvAppendStringVar(var, lex->sourceVar, charFrom->it->index, (int)(charTo-charFrom)); JsVar *block = jsvLockAgain(var); block->varData.str[0] = charFrom->currCh; size_t blockChars = 1; size_t l = maxLength; // now start appending JsvStringIterator it = jsvStringIteratorClone(&charFrom->it); while (jsvStringIteratorHasChar(&it) && (--maxLength>0)) { char ch = jsvStringIteratorGetChar(&it); if (blockChars >= jsvGetMaxCharactersInVar(block)) { jsvSetCharactersInVar(block, blockChars); JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0); if (!next) break; // out of memory // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) jsvSetLastChild(block, jsvGetRef(next)); jsvUnLock(block); block = next; blockChars=0; // it's new, so empty } block->varData.str[blockChars++] = ch; jsvStringIteratorNext(&it); } jsvSetCharactersInVar(block, blockChars); jsvUnLock(block); // Just make sure we only assert if there's a bug here. If we just ran out of memory or at end of string it's ok assert((l == jsvGetStringLength(var)) || (jsErrorFlags&JSERR_MEMORY) || !jsvStringIteratorHasChar(&it)); jsvStringIteratorFree(&it); return var; }
JsVar *jsvIteratorGetKey(JsvIterator *it) { switch (it->type) { case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj); case JSVI_STRING : return jsvMakeIntoVariableName(jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str)), 0); // some things expect a veriable name case JSVI_ARRAYBUFFER : return jsvMakeIntoVariableName(jsvArrayBufferIteratorGetIndex(&it->it.buf), 0); // some things expect a veriable name default: assert(0); return 0; } }
/* Match failed - report error message */ static void jslMatchError(int expected_tk) { char gotStr[30]; char expStr[30]; jslGetTokenString(gotStr, sizeof(gotStr)); jslTokenAsString(expected_tk, expStr, sizeof(expStr)); size_t oldPos = lex->tokenLastStart; lex->tokenLastStart = jsvStringIteratorGetIndex(&lex->tokenStart.it)-1; jsExceptionHere(JSET_SYNTAXERROR, "Got %s expected %s", gotStr, expStr); lex->tokenLastStart = oldPos; // Sod it, skip this token anyway - stops us looping jslGetNextToken(); }
static JsVar *matchfound(JsvStringIterator *txtIt, matchInfo info) { JsVar *rmatch = jsvNewEmptyArray(); size_t endIndex = jsvStringIteratorGetIndex(txtIt); JsVar *matchStr = jsvNewFromStringVar(info.sourceStr, info.startIndex, endIndex-info.startIndex); jsvSetArrayItem(rmatch, 0, matchStr); jsvUnLock(matchStr); int i; for (i=0;i<info.groups;i++) { matchStr = jsvNewFromStringVar(info.sourceStr, info.groupStart[i], info.groupEnd[i]-info.groupStart[i]); jsvSetArrayItem(rmatch, i+1, matchStr); jsvUnLock(matchStr); } jsvObjectSetChildAndUnLock(rmatch, "index", jsvNewFromInteger((JsVarInt)info.startIndex)); jsvObjectSetChild(rmatch, "input", info.sourceStr); return rmatch; }
/// Match, and return true on success, false on failure bool jslMatch(JsLex *lex, int expected_tk) { if (lex->tk != expected_tk) { char gotStr[16]; char expStr[16]; jslGetTokenString(lex, gotStr, sizeof(gotStr)); jslTokenAsString(expected_tk, expStr, sizeof(expStr)); size_t oldPos = lex->tokenLastStart; lex->tokenLastStart = jsvStringIteratorGetIndex(&lex->tokenStart.it)-1; jsExceptionHere(JSET_SYNTAXERROR, "Got %s expected %s", gotStr, expStr); lex->tokenLastStart = oldPos; // Sod it, skip this token anyway - stops us looping jslGetNextToken(lex); return false; } jslGetNextToken(lex); return true; }
/// Match, and return true on success, false on failure bool jslMatch(JsLex *lex, int expected_tk) { if (lex->tk!=expected_tk) { char buf[JS_ERROR_BUF_SIZE]; size_t bufpos = 0; strncpy(&buf[bufpos], "Got ", JS_ERROR_BUF_SIZE-bufpos); bufpos = strlen(buf); jslGetTokenString(lex, &buf[bufpos], JS_ERROR_BUF_SIZE-bufpos); bufpos = strlen(buf); strncpy(&buf[bufpos], " expected ", JS_ERROR_BUF_SIZE-bufpos); bufpos = strlen(buf); jslTokenAsString(expected_tk, &buf[bufpos], JS_ERROR_BUF_SIZE-bufpos); jsErrorAt(buf, lex, jsvStringIteratorGetIndex(&lex->tokenStart.it)); // Sod it, skip this token anyway - stops us looping jslGetNextToken(lex); return false; } jslGetNextToken(lex); return true; }
JsVar *jslNewFromLexer(struct JsLex *lex, JslCharPos *charFrom, size_t charTo) { // Create a var JsVar *var = jsvNewFromEmptyString(); if (!var) { // out of memory return 0; } //jsvAppendStringVar(var, lex->sourceVar, charFrom->it->index, (int)(charTo-charFrom)); size_t maxLength = charTo - jsvStringIteratorGetIndex(&charFrom->it); JsVar *block = jsvLockAgain(var); block->varData.str[0] = charFrom->currCh; size_t blockChars = 1; // now start appending JsvStringIterator it = jsvStringIteratorClone(&charFrom->it); while (jsvStringIteratorHasChar(&it) && (maxLength-->0)) { char ch = jsvStringIteratorGetChar(&it); if (blockChars >= jsvGetMaxCharactersInVar(block)) { jsvSetCharactersInVar(block, blockChars); JsVar *next = jsvNewWithFlags(JSV_STRING_EXT); if (!next) break; // out of memory // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) block->lastChild = jsvGetRef(next); jsvUnLock(block); block = next; blockChars=0; // it's new, so empty } block->varData.str[blockChars++] = ch; jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); jsvSetCharactersInVar(block, blockChars); jsvUnLock(block); return var; }
/// Return the line number at the current character position (this isn't fast as it searches the string) unsigned int jslGetLineNumber() { size_t line; size_t col; jsvGetLineAndCol(lex->sourceVar, jsvStringIteratorGetIndex(&lex->tokenStart.it)-1, &line, &col); return (unsigned int)line; }
void jslGetNextToken() { jslGetNextToken_start: // Skip whitespace while (isWhitespace(lex->currCh)) jslGetNextCh(); // Search for comments if (lex->currCh=='/') { // newline comments if (jslNextCh()=='/') { while (lex->currCh && lex->currCh!='\n') jslGetNextCh(); jslGetNextCh(); goto jslGetNextToken_start; } // block comments if (jslNextCh()=='*') { while (lex->currCh && !(lex->currCh=='*' && jslNextCh()=='/')) jslGetNextCh(); if (!lex->currCh) { lex->tk = LEX_UNFINISHED_COMMENT; return; /* an unfinished multi-line comment. When in interactive console, detect this and make sure we accept new lines */ } jslGetNextCh(); jslGetNextCh(); goto jslGetNextToken_start; } } lex->tk = LEX_EOF; lex->tokenl = 0; // clear token string if (lex->tokenValue) { jsvUnLock(lex->tokenValue); lex->tokenValue = 0; } // record beginning of this token lex->tokenLastStart = jsvStringIteratorGetIndex(&lex->tokenStart.it) - 1; /* we don't lock here, because we know that the string itself will be locked * because of lex->sourceVar */ lex->tokenStart.it = lex->it; lex->tokenStart.currCh = lex->currCh; // tokens if (((unsigned char)lex->currCh) < jslJumpTableStart || ((unsigned char)lex->currCh) > jslJumpTableEnd) { // if unhandled by the jump table, just pass it through as a single character jslSingleChar(); } else { switch(jslJumpTable[((unsigned char)lex->currCh) - jslJumpTableStart]) { case JSLJT_ID: { while (isAlpha(lex->currCh) || isNumeric(lex->currCh) || lex->currCh=='$') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } lex->tk = LEX_ID; // We do fancy stuff here to reduce number of compares (hopefully GCC creates a jump table) switch (lex->token[0]) { case 'b': if (jslIsToken("break", 1)) lex->tk = LEX_R_BREAK; break; case 'c': if (jslIsToken("case", 1)) lex->tk = LEX_R_CASE; else if (jslIsToken("catch", 1)) lex->tk = LEX_R_CATCH; else if (jslIsToken("continue", 1)) lex->tk = LEX_R_CONTINUE; break; case 'd': if (jslIsToken("default", 1)) lex->tk = LEX_R_DEFAULT; else if (jslIsToken("delete", 1)) lex->tk = LEX_R_DELETE; else if (jslIsToken("do", 1)) lex->tk = LEX_R_DO; else if (jslIsToken("debugger", 1)) lex->tk = LEX_R_DEBUGGER; break; case 'e': if (jslIsToken("else", 1)) lex->tk = LEX_R_ELSE; break; case 'f': if (jslIsToken("false", 1)) lex->tk = LEX_R_FALSE; else if (jslIsToken("finally", 1)) lex->tk = LEX_R_FINALLY; else if (jslIsToken("for", 1)) lex->tk = LEX_R_FOR; else if (jslIsToken("function", 1)) lex->tk = LEX_R_FUNCTION; break; case 'i': if (jslIsToken("if", 1)) lex->tk = LEX_R_IF; else if (jslIsToken("in", 1)) lex->tk = LEX_R_IN; else if (jslIsToken("instanceof", 1)) lex->tk = LEX_R_INSTANCEOF; break; case 'n': if (jslIsToken("new", 1)) lex->tk = LEX_R_NEW; else if (jslIsToken("null", 1)) lex->tk = LEX_R_NULL; break; case 'r': if (jslIsToken("return", 1)) lex->tk = LEX_R_RETURN; break; case 's': if (jslIsToken("switch", 1)) lex->tk = LEX_R_SWITCH; break; case 't': if (jslIsToken("this", 1)) lex->tk = LEX_R_THIS; else if (jslIsToken("throw", 1)) lex->tk = LEX_R_THROW; else if (jslIsToken("true", 1)) lex->tk = LEX_R_TRUE; else if (jslIsToken("try", 1)) lex->tk = LEX_R_TRY; else if (jslIsToken("typeof", 1)) lex->tk = LEX_R_TYPEOF; break; case 'u': if (jslIsToken("undefined", 1)) lex->tk = LEX_R_UNDEFINED; break; case 'w': if (jslIsToken("while", 1)) lex->tk = LEX_R_WHILE; break; case 'v': if (jslIsToken("var", 1)) lex->tk = LEX_R_VAR; else if (jslIsToken("void", 1)) lex->tk = LEX_R_VOID; break; default: break; } break; case JSLJT_NUMBER: { // TODO: check numbers aren't the wrong format bool canBeFloating = true; if (lex->currCh=='.') { jslGetNextCh(); if (isNumeric(lex->currCh)) { // it is a float lex->tk = LEX_FLOAT; jslTokenAppendChar('.'); } else { // it wasn't a number after all lex->tk = '.'; break; } } else { if (lex->currCh=='0') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); if ((lex->currCh=='x' || lex->currCh=='X') || (lex->currCh=='b' || lex->currCh=='B') || (lex->currCh=='o' || lex->currCh=='O')) { canBeFloating = false; jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } lex->tk = LEX_INT; while (isNumeric(lex->currCh) || (!canBeFloating && isHexadecimal(lex->currCh))) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } if (canBeFloating && lex->currCh=='.') { lex->tk = LEX_FLOAT; jslTokenAppendChar('.'); jslGetNextCh(); } } // parse fractional part if (lex->tk == LEX_FLOAT) { while (isNumeric(lex->currCh)) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } // do fancy e-style floating point if (canBeFloating && (lex->currCh=='e'||lex->currCh=='E')) { lex->tk = LEX_FLOAT; jslTokenAppendChar(lex->currCh); jslGetNextCh(); if (lex->currCh=='-' || lex->currCh=='+') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } while (isNumeric(lex->currCh)) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } } break; case JSLJT_STRING: { char delim = lex->currCh; lex->tokenValue = jsvNewFromEmptyString(); if (!lex->tokenValue) { lex->tk = LEX_EOF; return; } JsvStringIterator it; jsvStringIteratorNew(&it, lex->tokenValue, 0); // strings... jslGetNextCh(); while (lex->currCh && lex->currCh!=delim) { if (lex->currCh == '\\') { jslGetNextCh(); char ch = lex->currCh; switch (lex->currCh) { case 'n' : ch = 0x0A; jslGetNextCh(); break; case 'b' : ch = 0x08; jslGetNextCh(); break; case 'f' : ch = 0x0C; jslGetNextCh(); break; case 'r' : ch = 0x0D; jslGetNextCh(); break; case 't' : ch = 0x09; jslGetNextCh(); break; case 'v' : ch = 0x0B; jslGetNextCh(); break; case 'u' : case 'x' : { // hex digits char buf[5] = "0x??"; if (lex->currCh == 'u') { // We don't support unicode, so we just take the bottom 8 bits // of the unicode character jslGetNextCh(); jslGetNextCh(); } jslGetNextCh(); buf[2] = lex->currCh; jslGetNextCh(); buf[3] = lex->currCh; jslGetNextCh(); ch = (char)stringToInt(buf); } break; default: if (lex->currCh>='0' && lex->currCh<='7') { // octal digits char buf[5] = "0"; buf[1] = lex->currCh; int n=2; jslGetNextCh(); if (lex->currCh>='0' && lex->currCh<='7') { buf[n++] = lex->currCh; jslGetNextCh(); if (lex->currCh>='0' && lex->currCh<='7') { buf[n++] = lex->currCh; jslGetNextCh(); } } buf[n]=0; ch = (char)stringToInt(buf); } else { // for anything else, just push the character through jslGetNextCh(); } break; } jslTokenAppendChar(ch); jsvStringIteratorAppend(&it, ch); } else { jslTokenAppendChar(lex->currCh); jsvStringIteratorAppend(&it, lex->currCh); jslGetNextCh(); } } jsvStringIteratorFree(&it); lex->tk = lex->currCh==delim ? LEX_STR : LEX_UNFINISHED_STR; jslGetNextCh(); } break; case JSLJT_EXCLAMATION: jslSingleChar(); if (lex->currCh=='=') { // != lex->tk = LEX_NEQUAL; jslGetNextCh(); if (lex->currCh=='=') { // !== lex->tk = LEX_NTYPEEQUAL; jslGetNextCh(); } } break; case JSLJT_PLUS: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_PLUSEQUAL; jslGetNextCh(); } else if (lex->currCh=='+') { lex->tk = LEX_PLUSPLUS; jslGetNextCh(); } break; case JSLJT_MINUS: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MINUSEQUAL; jslGetNextCh(); } else if (lex->currCh=='-') { lex->tk = LEX_MINUSMINUS; jslGetNextCh(); } break; case JSLJT_AND: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_ANDEQUAL; jslGetNextCh(); } else if (lex->currCh=='&') { lex->tk = LEX_ANDAND; jslGetNextCh(); } break; case JSLJT_OR: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_OREQUAL; jslGetNextCh(); } else if (lex->currCh=='|') { lex->tk = LEX_OROR; jslGetNextCh(); } break; case JSLJT_TOPHAT: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_XOREQUAL; jslGetNextCh(); } break; case JSLJT_STAR: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MULEQUAL; jslGetNextCh(); } break; case JSLJT_FORWARDSLASH: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_DIVEQUAL; jslGetNextCh(); } break; case JSLJT_PERCENT: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MODEQUAL; jslGetNextCh(); } break; case JSLJT_EQUAL: jslSingleChar(); if (lex->currCh=='=') { // == lex->tk = LEX_EQUAL; jslGetNextCh(); if (lex->currCh=='=') { // === lex->tk = LEX_TYPEEQUAL; jslGetNextCh(); } } break; case JSLJT_LESSTHAN: jslSingleChar(); if (lex->currCh=='=') { // <= lex->tk = LEX_LEQUAL; jslGetNextCh(); } else if (lex->currCh=='<') { // << lex->tk = LEX_LSHIFT; jslGetNextCh(); if (lex->currCh=='=') { // <<= lex->tk = LEX_LSHIFTEQUAL; jslGetNextCh(); } } break; case JSLJT_GREATERTHAN: jslSingleChar(); if (lex->currCh=='=') { // >= lex->tk = LEX_GEQUAL; jslGetNextCh(); } else if (lex->currCh=='>') { // >> lex->tk = LEX_RSHIFT; jslGetNextCh(); if (lex->currCh=='=') { // >>= lex->tk = LEX_RSHIFTEQUAL; jslGetNextCh(); } else if (lex->currCh=='>') { // >>> jslGetNextCh(); if (lex->currCh=='=') { // >>>= lex->tk = LEX_RSHIFTUNSIGNEDEQUAL; jslGetNextCh(); } else { lex->tk = LEX_RSHIFTUNSIGNED; } } } break; case JSLJT_SINGLECHAR: jslSingleChar(); break; default: assert(0);break; } } } }
/* matchhere: search for regexp at beginning of text. Only handles up to '|' character. Modifies txtIt */ static JsVar *matchhere(char *regexp, JsvStringIterator *txtIt, matchInfo info) { if (jspIsInterrupted()) return 0; if (regexp[0] == '\0' || // end of regex regexp[0] == '|') // end of this 'or' section of regex return matchfound(txtIt, info); if (regexp[0] == '^') { // must be beginning of String if (jsvStringIteratorGetIndex(txtIt)!=0) return 0; // no match if (!jspCheckStackPosition()) return 0; return matchhere(regexp+1, txtIt, info); } // Marker for end of String if (regexp[0] == '$') { if (!jsvStringIteratorHasChar(txtIt)) return matchhere(regexp+1, txtIt, info); else return nomatchfound(regexp+1, info); // not the end, it's a fail } if (regexp[0] == '(') { info.groupStart[info.groups] = jsvStringIteratorGetIndex(txtIt); info.groupEnd[info.groups] = info.groupStart[info.groups]; if (info.groups<MAX_GROUPS) info.groups++; if (!jspCheckStackPosition()) return 0; return matchhere(regexp+1, txtIt, info); } if (regexp[0] == ')') { if (info.groups>0) info.groupEnd[info.groups-1] = jsvStringIteratorGetIndex(txtIt); if (!jspCheckStackPosition()) return 0; return matchhere(regexp+1, txtIt, info); } int charLength; bool charMatched = matchcharacter(regexp, txtIt, &charLength, &info); if (regexp[charLength] == '*' || regexp[charLength] == '+') { char op = regexp[charLength]; if (!charMatched && op=='+') { // with '+' operator it has to match at least once return nomatchfound(®exp[charLength+1], info); } char *regexpAfterStar = regexp+charLength+1; JsvStringIterator txtIt2; // Try and match everything after right now txtIt2 = jsvStringIteratorClone(txtIt); JsVar *lastrmatch = matchhere(regexpAfterStar, &txtIt2, info); jsvStringIteratorFree(&txtIt2); // Otherwise try and match more than one while (jsvStringIteratorHasChar(txtIt) && charMatched) { // We had this character matched, so move on and see if we can match with the new one jsvStringIteratorNext(txtIt); charMatched = matchcharacter(regexp, txtIt, &charLength, &info); // See if we can match after the character... txtIt2 = jsvStringIteratorClone(txtIt); JsVar *rmatch = matchhere(regexpAfterStar, &txtIt2, info); jsvStringIteratorFree(&txtIt2); // can't match with this - use the last one if (rmatch) { jsvUnLock(lastrmatch); lastrmatch = rmatch; } } return lastrmatch; } // This character is matched if (jsvStringIteratorHasChar(txtIt) && charMatched) { jsvStringIteratorNext(txtIt); if (!jspCheckStackPosition()) return 0; return matchhere(regexp+charLength, txtIt, info); } // No match return nomatchfound(®exp[charLength], info); }
/*JSON{ "type" : "method", "class" : "String", "name" : "replace", "generate" : "jswrap_string_replace", "params" : [ ["subStr","JsVar","The string to search for"], ["newSubStr","JsVar","The string to replace it with"] ], "return" : ["JsVar","This string with `subStr` replaced"] } Search and replace ONE occurrance of `subStr` with `newSubStr` and return the result. This doesn't alter the original string. Regular expressions not supported. */ JsVar *jswrap_string_replace(JsVar *parent, JsVar *subStr, JsVar *newSubStr) { JsVar *str = jsvAsString(parent); #ifndef SAVE_ON_FLASH // Use RegExp if one is passed in if (jsvIsInstanceOf(subStr, "RegExp")) { JsVar *replace; if (jsvIsFunction(newSubStr) || jsvIsString(newSubStr)) replace = jsvLockAgain(newSubStr); else replace = jsvAsString(newSubStr); jsvObjectSetChildAndUnLock(subStr, "lastIndex", jsvNewFromInteger(0)); bool global = jswrap_regexp_hasFlag(subStr,'g'); JsVar *match; match = jswrap_regexp_exec(subStr, str); while (match && !jsvIsNull(match) && !jspIsInterrupted()) { // get info about match JsVar *matchStr = jsvGetArrayItem(match,0); JsVarInt idx = jsvGetIntegerAndUnLock(jsvObjectGetChild(match,"index",0)); JsVarInt len = (JsVarInt)jsvGetStringLength(matchStr); // do the replacement JsVar *newStr = jsvNewFromStringVar(str, 0, (size_t)idx); JsvStringIterator dst; jsvStringIteratorNew(&dst, newStr, 0); jsvStringIteratorGotoEnd(&dst); if (jsvIsFunction(replace)) { unsigned int argCount = 0; JsVar *args[13]; args[argCount++] = jsvLockAgain(matchStr); JsVar *v; while ((v = jsvGetArrayItem(match, (JsVarInt)argCount))) args[argCount++] = v; args[argCount++] = jsvObjectGetChild(match,"index",0); args[argCount++] = jsvObjectGetChild(match,"input",0); JsVar *result = jsvAsStringAndUnLock(jspeFunctionCall(replace, 0, 0, false, (JsVarInt)argCount, args)); jsvUnLockMany(argCount, args); jsvStringIteratorAppendString(&dst, result, 0); jsvUnLock(result); } else { JsvStringIterator src; jsvStringIteratorNew(&src, replace, 0); while (jsvStringIteratorHasChar(&src)) { char ch = jsvStringIteratorGetChar(&src); if (ch=='$') { jsvStringIteratorNext(&src); ch = jsvStringIteratorGetChar(&src); JsVar *group = 0; if (ch>'0' && ch<='9') group = jsvGetArrayItem(match, ch-'0'); if (group) { jsvStringIteratorAppendString(&dst, group, 0); jsvUnLock(group); } else { jsvStringIteratorAppend(&dst, '$'); jsvStringIteratorAppend(&dst, ch); } } else { jsvStringIteratorAppend(&dst, ch); } jsvStringIteratorNext(&src); } jsvStringIteratorFree(&src); } JsVarInt lastIndex = 1+(JsVarInt)jsvStringIteratorGetIndex(&dst); jsvStringIteratorAppendString(&dst, str, (size_t)(idx+len)); jsvStringIteratorFree(&dst); jsvUnLock2(str,matchStr); str = newStr; // search again if global jsvUnLock(match); match = 0; if (global) { jsvObjectSetChildAndUnLock(subStr, "lastIndex", jsvNewFromInteger(lastIndex)); match = jswrap_regexp_exec(subStr, str); } } jsvUnLock(match); jsvUnLock(replace); // reset lastIndex if global if (global) jsvObjectSetChildAndUnLock(subStr, "lastIndex", jsvNewFromInteger(0)); return str; } #endif newSubStr = jsvAsString(newSubStr); subStr = jsvAsString(subStr); int idx = jswrap_string_indexOf(parent, subStr, 0, false); if (idx>=0) { JsVar *newStr = jsvNewFromStringVar(str, 0, (size_t)idx); jsvAppendStringVar(newStr, newSubStr, 0, JSVAPPENDSTRINGVAR_MAXLENGTH); jsvAppendStringVar(newStr, str, (size_t)idx+jsvGetStringLength(subStr), JSVAPPENDSTRINGVAR_MAXLENGTH); jsvUnLock(str); str = newStr; } jsvUnLock2(subStr, newSubStr); return str; }
JsVar *jslNewTokenisedStringFromLexer(JslCharPos *charFrom, size_t charTo) { // New method - tokenise functions // save old lex JsLex *oldLex = lex; JsLex newLex; lex = &newLex; // work out length size_t length = 0; jslInit(oldLex->sourceVar); jslSeekToP(charFrom); int lastTk = LEX_EOF; while (lex->tk!=LEX_EOF && jsvStringIteratorGetIndex(&lex->it)<=charTo+1) { if ((lex->tk==LEX_ID || lex->tk==LEX_FLOAT || lex->tk==LEX_INT) && ( lastTk==LEX_ID || lastTk==LEX_FLOAT || lastTk==LEX_INT)) { jsExceptionHere(JSET_SYNTAXERROR, "ID/number following ID/number isn't valid JS"); length = 0; break; } if (lex->tk==LEX_ID || lex->tk==LEX_INT || lex->tk==LEX_FLOAT || lex->tk==LEX_STR || lex->tk==LEX_TEMPLATE_LITERAL) { length += jsvStringIteratorGetIndex(&lex->it)-jsvStringIteratorGetIndex(&lex->tokenStart.it); } else { length++; } lastTk = lex->tk; jslGetNextToken(); } // Try and create a flat string first JsVar *var = jsvNewStringOfLength((unsigned int)length, NULL); if (var) { // out of memory JsvStringIterator dstit; jsvStringIteratorNew(&dstit, var, 0); // now start appending jslSeekToP(charFrom); while (lex->tk!=LEX_EOF && jsvStringIteratorGetIndex(&lex->it)<=charTo+1) { if (lex->tk==LEX_ID || lex->tk==LEX_INT || lex->tk==LEX_FLOAT || lex->tk==LEX_STR || lex->tk==LEX_TEMPLATE_LITERAL) { jsvStringIteratorSetCharAndNext(&dstit, lex->tokenStart.currCh); JsvStringIterator it = jsvStringIteratorClone(&lex->tokenStart.it); while (jsvStringIteratorGetIndex(&it)+1 < jsvStringIteratorGetIndex(&lex->it)) { jsvStringIteratorSetCharAndNext(&dstit, jsvStringIteratorGetChar(&it)); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); } else { jsvStringIteratorSetCharAndNext(&dstit, (char)lex->tk); } lastTk = lex->tk; jslGetNextToken(); } jsvStringIteratorFree(&dstit); } // restore lex jslKill(); lex = oldLex; return var; }
void jslGetNextToken() { jslGetNextToken_start: // Skip whitespace while (isWhitespace(lex->currCh)) jslGetNextCh(); // Search for comments if (lex->currCh=='/') { // newline comments if (jslNextCh()=='/') { while (lex->currCh && lex->currCh!='\n') jslGetNextCh(); jslGetNextCh(); goto jslGetNextToken_start; } // block comments if (jslNextCh()=='*') { jslGetNextCh(); jslGetNextCh(); while (lex->currCh && !(lex->currCh=='*' && jslNextCh()=='/')) jslGetNextCh(); if (!lex->currCh) { lex->tk = LEX_UNFINISHED_COMMENT; return; /* an unfinished multi-line comment. When in interactive console, detect this and make sure we accept new lines */ } jslGetNextCh(); jslGetNextCh(); goto jslGetNextToken_start; } } int lastToken = lex->tk; lex->tk = LEX_EOF; lex->tokenl = 0; // clear token string if (lex->tokenValue) { jsvUnLock(lex->tokenValue); lex->tokenValue = 0; } // record beginning of this token lex->tokenLastStart = jsvStringIteratorGetIndex(&lex->tokenStart.it) - 1; /* we don't lock here, because we know that the string itself will be locked * because of lex->sourceVar */ lex->tokenStart.it = lex->it; lex->tokenStart.currCh = lex->currCh; // tokens if (((unsigned char)lex->currCh) < jslJumpTableStart || ((unsigned char)lex->currCh) > jslJumpTableEnd) { // if unhandled by the jump table, just pass it through as a single character jslSingleChar(); } else { switch(jslJumpTable[((unsigned char)lex->currCh) - jslJumpTableStart]) { case JSLJT_ID: { while (isAlpha(lex->currCh) || isNumeric(lex->currCh) || lex->currCh=='$') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } lex->tk = LEX_ID; // We do fancy stuff here to reduce number of compares (hopefully GCC creates a jump table) switch (lex->token[0]) { case 'b': if (jslIsToken("break", 1)) lex->tk = LEX_R_BREAK; break; case 'c': if (jslIsToken("case", 1)) lex->tk = LEX_R_CASE; else if (jslIsToken("catch", 1)) lex->tk = LEX_R_CATCH; else if (jslIsToken("class", 1)) lex->tk = LEX_R_CLASS; else if (jslIsToken("const", 1)) lex->tk = LEX_R_CONST; else if (jslIsToken("continue", 1)) lex->tk = LEX_R_CONTINUE; break; case 'd': if (jslIsToken("default", 1)) lex->tk = LEX_R_DEFAULT; else if (jslIsToken("delete", 1)) lex->tk = LEX_R_DELETE; else if (jslIsToken("do", 1)) lex->tk = LEX_R_DO; else if (jslIsToken("debugger", 1)) lex->tk = LEX_R_DEBUGGER; break; case 'e': if (jslIsToken("else", 1)) lex->tk = LEX_R_ELSE; else if (jslIsToken("extends", 1)) lex->tk = LEX_R_EXTENDS; break; case 'f': if (jslIsToken("false", 1)) lex->tk = LEX_R_FALSE; else if (jslIsToken("finally", 1)) lex->tk = LEX_R_FINALLY; else if (jslIsToken("for", 1)) lex->tk = LEX_R_FOR; else if (jslIsToken("function", 1)) lex->tk = LEX_R_FUNCTION; break; case 'i': if (jslIsToken("if", 1)) lex->tk = LEX_R_IF; else if (jslIsToken("in", 1)) lex->tk = LEX_R_IN; else if (jslIsToken("instanceof", 1)) lex->tk = LEX_R_INSTANCEOF; break; case 'l': if (jslIsToken("let", 1)) lex->tk = LEX_R_LET; break; case 'n': if (jslIsToken("new", 1)) lex->tk = LEX_R_NEW; else if (jslIsToken("null", 1)) lex->tk = LEX_R_NULL; break; case 'o': if (jslIsToken("of", 1)) lex->tk = LEX_R_OF; break; case 'r': if (jslIsToken("return", 1)) lex->tk = LEX_R_RETURN; break; case 's': if (jslIsToken("static", 1)) lex->tk = LEX_R_STATIC; else if (jslIsToken("super", 1)) lex->tk = LEX_R_SUPER; else if (jslIsToken("switch", 1)) lex->tk = LEX_R_SWITCH; break; case 't': if (jslIsToken("this", 1)) lex->tk = LEX_R_THIS; else if (jslIsToken("throw", 1)) lex->tk = LEX_R_THROW; else if (jslIsToken("true", 1)) lex->tk = LEX_R_TRUE; else if (jslIsToken("try", 1)) lex->tk = LEX_R_TRY; else if (jslIsToken("typeof", 1)) lex->tk = LEX_R_TYPEOF; break; case 'u': if (jslIsToken("undefined", 1)) lex->tk = LEX_R_UNDEFINED; break; case 'w': if (jslIsToken("while", 1)) lex->tk = LEX_R_WHILE; break; case 'v': if (jslIsToken("var", 1)) lex->tk = LEX_R_VAR; else if (jslIsToken("void", 1)) lex->tk = LEX_R_VOID; break; default: break; } break; case JSLJT_NUMBER: { // TODO: check numbers aren't the wrong format bool canBeFloating = true; if (lex->currCh=='.') { jslGetNextCh(); if (isNumeric(lex->currCh)) { // it is a float lex->tk = LEX_FLOAT; jslTokenAppendChar('.'); } else { // it wasn't a number after all lex->tk = '.'; break; } } else { if (lex->currCh=='0') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); if ((lex->currCh=='x' || lex->currCh=='X') || (lex->currCh=='b' || lex->currCh=='B') || (lex->currCh=='o' || lex->currCh=='O')) { canBeFloating = false; jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } lex->tk = LEX_INT; while (isNumeric(lex->currCh) || (!canBeFloating && isHexadecimal(lex->currCh))) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } if (canBeFloating && lex->currCh=='.') { lex->tk = LEX_FLOAT; jslTokenAppendChar('.'); jslGetNextCh(); } } // parse fractional part if (lex->tk == LEX_FLOAT) { while (isNumeric(lex->currCh)) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } // do fancy e-style floating point if (canBeFloating && (lex->currCh=='e'||lex->currCh=='E')) { lex->tk = LEX_FLOAT; jslTokenAppendChar(lex->currCh); jslGetNextCh(); if (lex->currCh=='-' || lex->currCh=='+') { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } while (isNumeric(lex->currCh)) { jslTokenAppendChar(lex->currCh); jslGetNextCh(); } } } break; case JSLJT_STRING: jslLexString(); break; case JSLJT_EXCLAMATION: jslSingleChar(); if (lex->currCh=='=') { // != lex->tk = LEX_NEQUAL; jslGetNextCh(); if (lex->currCh=='=') { // !== lex->tk = LEX_NTYPEEQUAL; jslGetNextCh(); } } break; case JSLJT_PLUS: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_PLUSEQUAL; jslGetNextCh(); } else if (lex->currCh=='+') { lex->tk = LEX_PLUSPLUS; jslGetNextCh(); } break; case JSLJT_MINUS: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MINUSEQUAL; jslGetNextCh(); } else if (lex->currCh=='-') { lex->tk = LEX_MINUSMINUS; jslGetNextCh(); } break; case JSLJT_AND: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_ANDEQUAL; jslGetNextCh(); } else if (lex->currCh=='&') { lex->tk = LEX_ANDAND; jslGetNextCh(); } break; case JSLJT_OR: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_OREQUAL; jslGetNextCh(); } else if (lex->currCh=='|') { lex->tk = LEX_OROR; jslGetNextCh(); } break; case JSLJT_TOPHAT: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_XOREQUAL; jslGetNextCh(); } break; case JSLJT_STAR: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MULEQUAL; jslGetNextCh(); } break; case JSLJT_FORWARDSLASH: // yay! JS is so awesome. if (lastToken==LEX_EOF || lastToken=='!' || lastToken=='%' || lastToken=='&' || lastToken=='*' || lastToken=='+' || lastToken=='-' || lastToken=='/' || lastToken=='<' || lastToken=='=' || lastToken=='>' || lastToken=='?' || (lastToken>=_LEX_OPERATOR_START && lastToken<=_LEX_OPERATOR_END) || (lastToken>=_LEX_R_LIST_START && lastToken<=_LEX_R_LIST_END) || // keywords lastToken==LEX_R_CASE || lastToken==LEX_R_NEW || lastToken=='[' || lastToken=='{' || lastToken=='}' || lastToken=='(' || lastToken==',' || lastToken==';' || lastToken==':' || lastToken==LEX_ARROW_FUNCTION) { // EOF operator keyword case new [ { } ( , ; : => // phew. We're a regex jslLexRegex(); } else { jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_DIVEQUAL; jslGetNextCh(); } } break; case JSLJT_PERCENT: jslSingleChar(); if (lex->currCh=='=') { lex->tk = LEX_MODEQUAL; jslGetNextCh(); } break; case JSLJT_EQUAL: jslSingleChar(); if (lex->currCh=='=') { // == lex->tk = LEX_EQUAL; jslGetNextCh(); if (lex->currCh=='=') { // === lex->tk = LEX_TYPEEQUAL; jslGetNextCh(); } } else if (lex->currCh=='>') { // => lex->tk = LEX_ARROW_FUNCTION; jslGetNextCh(); } break; case JSLJT_LESSTHAN: jslSingleChar(); if (lex->currCh=='=') { // <= lex->tk = LEX_LEQUAL; jslGetNextCh(); } else if (lex->currCh=='<') { // << lex->tk = LEX_LSHIFT; jslGetNextCh(); if (lex->currCh=='=') { // <<= lex->tk = LEX_LSHIFTEQUAL; jslGetNextCh(); } } break; case JSLJT_GREATERTHAN: jslSingleChar(); if (lex->currCh=='=') { // >= lex->tk = LEX_GEQUAL; jslGetNextCh(); } else if (lex->currCh=='>') { // >> lex->tk = LEX_RSHIFT; jslGetNextCh(); if (lex->currCh=='=') { // >>= lex->tk = LEX_RSHIFTEQUAL; jslGetNextCh(); } else if (lex->currCh=='>') { // >>> jslGetNextCh(); if (lex->currCh=='=') { // >>>= lex->tk = LEX_RSHIFTUNSIGNEDEQUAL; jslGetNextCh(); } else { lex->tk = LEX_RSHIFTUNSIGNED; } } } break; case JSLJT_SINGLECHAR: jslSingleChar(); break; default: assert(0);break; } } } }