Added getter and setter support

This commit is contained in:
Gordon Williams 2018-05-24 14:49:12 +01:00
parent fb8c1d8777
commit 5aad237f72
11 changed files with 379 additions and 99 deletions

View File

@ -6,6 +6,7 @@
Fix bug if using an undefined member of an object for for..in (fix #1437)
Allow for..in to iterate over prototype chains down from Array and Object
Add for(var i of array) to iterate over elements
Added getter and setter support
1v99 : Increase jslMatch error buffer size to handle "UNFINISHED TEMPLATE LITERAL" string (#1426)
nRF5x: Make FlashWrite cope with flash writes > 4k

View File

@ -81,57 +81,6 @@ bool jspHasError() {
return JSP_HAS_ERROR;
}
void jspReplaceWith(JsVar *dst, JsVar *src) {
// If this is an index in an array buffer, write directly into the array buffer
if (jsvIsArrayBufferName(dst)) {
size_t idx = (size_t)jsvGetInteger(dst);
JsVar *arrayBuffer = jsvLock(jsvGetFirstChild(dst));
jsvArrayBufferSet(arrayBuffer, idx, src);
jsvUnLock(arrayBuffer);
return;
}
// if destination isn't there, isn't a 'name', or is used, give an error
if (!jsvIsName(dst)) {
jsExceptionHere(JSET_ERROR, "Unable to assign value to non-reference %t", dst);
return;
}
jsvSetValueOfName(dst, src);
/* If dst is flagged as a new child, it means that
* it was previously undefined, and we need to add it to
* the given object when it is set.
*/
if (jsvIsNewChild(dst)) {
// Get what it should have been a child of
JsVar *parent = jsvLock(jsvGetNextSibling(dst));
if (!jsvIsString(parent)) {
// if we can't find a char in a string we still return a NewChild,
// but we can't add character back in
if (!jsvHasChildren(parent)) {
jsExceptionHere(JSET_ERROR, "Field or method \"%s\" does not already exist, and can't create it on %t", dst, parent);
} else {
// Remove the 'new child' flagging
jsvUnRef(parent);
jsvSetNextSibling(dst, 0);
jsvUnRef(parent);
jsvSetPrevSibling(dst, 0);
// Add to the parent
jsvAddName(parent, dst);
}
}
jsvUnLock(parent);
}
}
void jspReplaceWithOrAddToRoot(JsVar *dst, JsVar *src) {
/* If we're assigning to this and we don't have a parent,
* add it to the symbol table root */
if (!jsvGetRefs(dst) && jsvIsName(dst)) {
if (!jsvIsArrayBufferName(dst) && !jsvIsNewChild(dst))
jsvAddName(execInfo.root, dst);
}
jspReplaceWith(dst, src);
}
bool jspeiAddScope(JsVar *scope) {
if (execInfo.scopeCount >= JSPARSE_MAX_SCOPES) {
jsExceptionHere(JSET_ERROR, "Maximum number of scopes exceeded");
@ -1273,13 +1222,30 @@ NO_INLINE JsVar *jspeFactorObject() {
} else {
JSP_MATCH_WITH_RETURN(LEX_ID, contents);
}
JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(varName), contents);
if (JSP_SHOULD_EXECUTE) {
varName = jsvAsArrayIndexAndUnLock(varName);
JsVar *contentsName = jsvFindChildFromVar(contents, varName, true);
if (contentsName) {
JsVar *value = jsvSkipNameAndUnLock(jspeAssignmentExpression()); // value can be 0 (could be undefined!)
jsvUnLock2(jsvSetValueOfName(contentsName, value), value);
#ifndef SAVE_ON_FLASH
bool isGetter, isSetter;
if (lex->tk==LEX_ID && jsvIsString(varName)) {
isGetter = jsvIsStringEqual(varName, "get");
isSetter = jsvIsStringEqual(varName, "set");
if (isGetter || isSetter) {
jsvUnLock(varName);
varName = jslGetTokenValueAsVar(lex);
JSP_ASSERT_MATCH(LEX_ID);
JsVar *method = jspeFunctionDefinition(false);
jsvAddGetterOrSetter(contents, varName, isGetter, method);
jsvUnLock(method);
}
} else
#endif
{
JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(varName), contents);
if (JSP_SHOULD_EXECUTE) {
varName = jsvAsArrayIndexAndUnLock(varName);
JsVar *contentsName = jsvFindChildFromVar(contents, varName, true);
if (contentsName) {
JsVar *value = jsvSkipNameAndUnLock(jspeAssignmentExpression()); // value can be 0 (could be undefined!)
jsvUnLock2(jsvSetValueOfName(contentsName, value), value);
}
}
}
jsvUnLock(varName);
@ -1551,18 +1517,35 @@ NO_INLINE JsVar *jspeClassDefinition(bool parseNamedClass) {
if (isStatic) JSP_ASSERT_MATCH(LEX_R_STATIC);
JsVar *funcName = jslGetTokenValueAsVar(lex);
JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID,jsvUnLock3(classFunction,classInternalName,classPrototype),0);
JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID,jsvUnLock4(funcName,classFunction,classInternalName,classPrototype),0);
#ifndef SAVE_ON_FLASH
bool isGetter, isSetter;
if (lex->tk==LEX_ID) {
isGetter = jsvIsStringEqual(funcName, "get");
isSetter = jsvIsStringEqual(funcName, "set");
if (isGetter || isSetter) {
jsvUnLock(funcName);
funcName = jslGetTokenValueAsVar(lex);
JSP_ASSERT_MATCH(LEX_ID);
}
}
#endif
JsVar *method = jspeFunctionDefinition(false);
if (classFunction && classPrototype) {
if (jsvIsStringEqual(funcName, "get") || jsvIsStringEqual(funcName, "set")) {
jsExceptionHere(JSET_SYNTAXERROR, "'get' and 'set' and not supported in Espruino");
} else if (jsvIsStringEqual(funcName, "constructor")) {
JsVar *obj = isStatic ? classFunction : classPrototype;
if (jsvIsStringEqual(funcName, "constructor")) {
jswrap_function_replaceWith(classFunction, method);
#ifndef SAVE_ON_FLASH
} else if (isGetter || isSetter) {
jsvAddGetterOrSetter(obj, funcName, isGetter, method);
#endif
} else {
funcName = jsvMakeIntoVariableName(funcName, 0);
jsvSetValueOfName(funcName, method);
jsvAddName(isStatic ? classFunction : classPrototype, funcName);
jsvAddName(obj, funcName);
}
}
jsvUnLock2(method,funcName);
}
@ -1748,7 +1731,7 @@ NO_INLINE JsVar *__jspePostfixExpression(JsVar *a) {
jsvUnLock(one);
// in-place add/subtract
jspReplaceWith(a, res);
jsvReplaceWith(a, res);
jsvUnLock(res);
// but then use the old value
jsvUnLock(a);
@ -1770,7 +1753,7 @@ NO_INLINE JsVar *jspePostfixExpression() {
JsVar *res = jsvMathsOpSkipNames(a, one, op==LEX_PLUSPLUS ? '+' : '-');
jsvUnLock(one);
// in-place add/subtract
jspReplaceWith(a, res);
jsvReplaceWith(a, res);
jsvUnLock(res);
}
} else
@ -1982,7 +1965,7 @@ NO_INLINE JsVar *__jspeAssignmentExpression(JsVar *lhs) {
if (JSP_SHOULD_EXECUTE && lhs) {
if (op=='=') {
jspReplaceWithOrAddToRoot(lhs, rhs);
jsvReplaceWithOrAddToRoot(lhs, rhs);
} else {
if (op==LEX_PLUSEQUAL) op='+';
else if (op==LEX_MINUSEQUAL) op='-';
@ -2011,7 +1994,7 @@ NO_INLINE JsVar *__jspeAssignmentExpression(JsVar *lhs) {
if (op) {
/* Fallback which does a proper add */
JsVar *res = jsvMathsOpSkipNames(lhs,rhs,op);
jspReplaceWith(lhs, res);
jsvReplaceWith(lhs, res);
jsvUnLock(res);
}
}
@ -2127,7 +2110,7 @@ NO_INLINE JsVar *jspeStatementVar() {
JSP_MATCH_WITH_CLEANUP_AND_RETURN('=', jsvUnLock(a), lastDefined);
var = jsvSkipNameAndUnLock(jspeAssignmentExpression());
if (JSP_SHOULD_EXECUTE)
jspReplaceWith(a, var);
jsvReplaceWith(a, var);
jsvUnLock(var);
}
jsvUnLock(lastDefined);
@ -2400,7 +2383,7 @@ NO_INLINE JsVar *jspeStatementFor() {
}
if (isForOf || iteratorValue) { // could be out of memory
assert(!jsvIsName(iteratorValue));
jspReplaceWithOrAddToRoot(forStatement, iteratorValue);
jsvReplaceWithOrAddToRoot(forStatement, iteratorValue);
if (iteratorValue!=loopIndexVar) jsvUnLock(iteratorValue);
jslSeekToP(&forBodyStart);
@ -2600,7 +2583,7 @@ NO_INLINE JsVar *jspeStatementReturn() {
if (JSP_SHOULD_EXECUTE) {
JsVar *resultVar = jspeiFindInScopes(JSPARSE_RETURN_VAR);
if (resultVar) {
jspReplaceWith(resultVar, result);
jsvReplaceWith(resultVar, result);
jsvUnLock(resultVar);
execInfo.execute |= EXEC_RETURN; // Stop anything else in this function executing
} else {
@ -2655,7 +2638,7 @@ NO_INLINE JsVar *jspeStatementFunctionDecl(bool isClass) {
funcVar = jsvSkipNameAndUnLock(funcVar);
jswrap_function_replaceWith(existingFunc, funcVar);
} else {
jspReplaceWith(existingName, funcVar);
jsvReplaceWith(existingName, funcVar);
}
jsvUnLock(funcName);
funcName = existingName;

View File

@ -192,6 +192,5 @@ JsVar *jspCallNamedFunction(JsVar *object, char* name, int argCount, JsVar **arg
// These are exported for the Web IDE's compiler. See exportPtrs in jswrap_process.c
JsVar *jspeiFindInScopes(const char *name);
void jspReplaceWith(JsVar *dst, JsVar *src);
#endif /* JSPARSE_H_ */

View File

@ -91,6 +91,14 @@ bool jsvIsNameIntInt(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==J
bool jsvIsNameIntBool(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_NAME_INT_BOOL; }
/// What happens when we access a variable that doesn't exist. We get a NAME where the next + previous siblings point to the object that may one day contain them
bool jsvIsNewChild(const JsVar *v) { return jsvIsName(v) && jsvGetNextSibling(v) && jsvGetNextSibling(v)==jsvGetPrevSibling(v); }
/// Returns true if v is a getter/setter
bool jsvIsGetterOrSetter(const JsVar *v) {
#ifdef SAVE_ON_FLASH
return false;
#else
return v && (v->flags&JSV_VARTYPEMASK)==JSV_GET_SET;
#endif
}
/// Are var.varData.ref.* (excl pad) used for data (so we expect them not to be empty)
bool jsvIsRefUsedForData(const JsVar *v) { return jsvIsStringExt(v) || (jsvIsString(v)&&!jsvIsName(v)) || jsvIsFloat(v) || jsvIsNativeFunction(v) || jsvIsArrayBuffer(v) || jsvIsArrayBufferName(v); }
@ -359,7 +367,7 @@ bool jsvHasStringExt(const JsVar *v) {
}
bool jsvHasChildren(const JsVar *v) {
return jsvIsFunction(v) || jsvIsObject(v) || jsvIsArray(v) || jsvIsRoot(v);
return jsvIsFunction(v) || jsvIsObject(v) || jsvIsArray(v) || jsvIsRoot(v) || jsvIsGetterOrSetter(v);
}
/// Is this variable a type that uses firstChild to point to a single Variable (ie. it doesn't have multiple children)
@ -641,6 +649,12 @@ ALWAYS_INLINE JsVar *jsvLock(JsVarRef ref) {
return var;
}
/// Lock this reference and return a pointer - UNSAFE for null refs
JsVar *jsvLockSafe(JsVarRef ref) {
if (!ref) return 0;
return jsvLock(ref);
}
/// Lock this pointer and return a pointer - UNSAFE for null pointer
ALWAYS_INLINE JsVar *jsvLockAgain(JsVar *var) {
assert(var);
@ -1190,7 +1204,7 @@ size_t jsvGetString(const JsVar *v, char *str, size_t len) {
/* don't use strncpy here because we don't
* want to pad the entire buffer with zeros */
len--;
int l = 0;
size_t l = 0;
while (s[l] && l<len) {
str[l] = s[l];
l++;
@ -1408,7 +1422,7 @@ size_t jsvGetStringLength(const JsVar *v) {
// Go to next
jsvUnLock(newVar); // note use of if (ref), not var
var = newVar = ref ? jsvLock(ref) : 0;
var = newVar = jsvLockSafe(ref);
}
jsvUnLock(newVar); // note use of if (ref), not var
return strLength;
@ -1863,6 +1877,127 @@ JsVarInt jsvGetIntegerAndUnLock(JsVar *v) { return _jsvGetIntegerAndUnLock(v); }
JsVarFloat jsvGetFloatAndUnLock(JsVar *v) { return _jsvGetFloatAndUnLock(v); }
bool jsvGetBoolAndUnLock(JsVar *v) { return _jsvGetBoolAndUnLock(v); }
#ifndef SAVE_ON_FLASH
// Executes the given getter, or if there are problems returns undefined
JsVar *jsvExecuteGetter(JsVar *getset) {
assert(jsvIsGetterOrSetter(getset));
if (!jsvIsGetterOrSetter(getset)) return 0; // wasn't an object?
JsVar *fn = jsvObjectGetChild(getset, "get", 0);
if (!jsvIsFunction(fn)) {
jsvUnLock(fn);
return 0;
}
JsVar *this = jsvObjectGetChild(getset, "this", 0);
JsVar *result = jspExecuteFunction(fn, this, 0, NULL);
jsvUnLock2(fn, this);
return result;
}
// Executes the given setter
void jsvExecuteSetter(JsVar *getset, JsVar *value) {
assert(jsvIsGetterOrSetter(getset));
if (!jsvIsGetterOrSetter(getset)) return; // wasn't an object?
JsVar *fn = jsvObjectGetChild(getset, "set", 0);
if (!jsvIsFunction(fn)) {
jsvUnLock(fn);
return;
}
if (!fn) return;
JsVar *this = jsvObjectGetChild(getset, "this", 0);
jsvUnLock3(jspExecuteFunction(fn, this, 1, &value), fn, this);
}
/// Add a named getter or setter to an object
void jsvAddGetterOrSetter(JsVar *obj, JsVar *varName, bool isGetter, JsVar *method) {
// check for existing getter/setter, make one if needed
JsVar *getsetName = jsvFindChildFromVar(obj, varName, true);
if (jsvIsName(getsetName)) {
JsVar *getset = jsvGetValueOfName(getsetName);
if (!jsvIsGetterOrSetter(getset)) {
jsvUnLock(getset);
getset = jsvNewWithFlags(JSV_GET_SET);
jsvSetValueOfName(getsetName, getset);
}
if (jsvIsGetterOrSetter(getset)) {
jsvObjectSetChild(getset, "this", obj);
jsvObjectSetChild(getset, isGetter?"get":"set", method);
}
jsvUnLock(getset);
}
jsvUnLock(getsetName);
}
#endif
/* Set the value of the given variable. This is sort of like
* jsvSetValueOfName except it deals with all the non-standard
* stuff like ArrayBuffers, variables that haven't been allocated
* yet, setters, etc.
*/
void jsvReplaceWith(JsVar *dst, JsVar *src) {
// If this is an index in an array buffer, write directly into the array buffer
if (jsvIsArrayBufferName(dst)) {
size_t idx = (size_t)jsvGetInteger(dst);
JsVar *arrayBuffer = jsvLock(jsvGetFirstChild(dst));
jsvArrayBufferSet(arrayBuffer, idx, src);
jsvUnLock(arrayBuffer);
return;
}
// if destination isn't there, isn't a 'name', or is used, give an error
if (!jsvIsName(dst)) {
jsExceptionHere(JSET_ERROR, "Unable to assign value to non-reference %t", dst);
return;
}
bool setValue = true;
#ifndef SAVE_ON_FLASH
JsVar *v = jsvGetValueOfName(dst);
if (jsvIsGetterOrSetter(v)) {
jsvExecuteSetter(v,src);
setValue = false;
}
jsvUnLock(v);
#endif
if (setValue) jsvSetValueOfName(dst, src);
/* If dst is flagged as a new child, it means that
* it was previously undefined, and we need to add it to
* the given object when it is set.
*/
if (jsvIsNewChild(dst)) {
// Get what it should have been a child of
JsVar *parent = jsvLock(jsvGetNextSibling(dst));
if (!jsvIsString(parent)) {
// if we can't find a char in a string we still return a NewChild,
// but we can't add character back in
if (!jsvHasChildren(parent)) {
jsExceptionHere(JSET_ERROR, "Field or method \"%s\" does not already exist, and can't create it on %t", dst, parent);
} else {
// Remove the 'new child' flagging
jsvUnRef(parent);
jsvSetNextSibling(dst, 0);
jsvUnRef(parent);
jsvSetPrevSibling(dst, 0);
// Add to the parent
jsvAddName(parent, dst);
}
}
jsvUnLock(parent);
}
}
/* See jsvReplaceWith - this does the same but will
* shove the variable in execInfo.root if it hasn't
* been defined yet */
void jsvReplaceWithOrAddToRoot(JsVar *dst, JsVar *src) {
/* If we're assigning to this and we don't have a parent,
* add it to the symbol table root */
if (!jsvGetRefs(dst) && jsvIsName(dst)) {
if (!jsvIsArrayBufferName(dst) && !jsvIsNewChild(dst))
jsvAddName(execInfo.root, dst);
}
jsvReplaceWith(dst, src);
}
/** Get the item at the given location in the array buffer and return the result */
size_t jsvGetArrayBufferLength(const JsVar *arrayBuffer) {
assert(jsvIsArrayBuffer(arrayBuffer));
@ -1939,12 +2074,27 @@ bool jsvIsVariableDefined(JsVar *a) {
(jsvGetFirstChild(a)!=0);
}
/* If this is a simple name (that links to another var) the
* return that var, else 0. */
JsVar *jsvGetValueOfName(JsVar *a) {
if (!a) return 0;
if (jsvIsArrayBufferName(a)) return jsvArrayBufferGetFromName(a);
if (jsvIsNameInt(a)) return jsvNewFromInteger((JsVarInt)jsvGetFirstChildSigned(a));
if (jsvIsNameIntBool(a)) return jsvNewFromBool(jsvGetFirstChild(a)!=0);
assert(!jsvIsNameWithValue(a));
if (jsvIsName(a))
return jsvLockSafe(jsvGetFirstChild(a));
return 0;
}
/* Check for and trigger a ReferenceError on a variable if it's a name that doesn't exist */
void jsvCheckReferenceError(JsVar *a) {
if (jsvIsName(a) && jsvGetRefs(a)==0 && !jsvIsNewChild(a) && !jsvGetFirstChild(a))
jsExceptionHere(JSET_REFERENCEERROR, "%q is not defined", a);
}
/** If a is a name skip it and go to what it points to - and so on (if repeat=true).
* ALWAYS locks - so must unlock what it returns. It MAY
* return 0. Throws a ReferenceError if variable is not defined,
@ -1965,6 +2115,13 @@ static JsVar *jsvSkipNameInternal(JsVar *a, bool repeat) {
}
pa = jsvLock(n);
assert(pa!=a);
#ifndef SAVE_ON_FLASH
if (jsvIsGetterOrSetter(pa)) {
JsVar *v = jsvExecuteGetter(pa);
jsvUnLock(pa);
pa = v;
}
#endif
if (!repeat) return pa;
}
return pa;
@ -2289,7 +2446,7 @@ void jsvAddName(JsVar *parent, JsVar *namedChild) {
while (insertAfter && jsvCompareInteger(namedChild, insertAfter)<0) {
JsVarRef prev = jsvGetPrevSibling(insertAfter);
jsvUnLock(insertAfter);
insertAfter = prev ? jsvLock(prev) : 0;
insertAfter = jsvLockSafe(prev);
}
}
@ -3309,6 +3466,7 @@ void _jsvTrace(JsVar *var, int indent, JsVar *baseVar, int level) {
char endBracket = ' ';
if (jsvIsObject(var)) { jsiConsolePrint("Object { "); endBracket = '}'; }
else if (jsvIsGetterOrSetter(var)) { jsiConsolePrint("Getter/Setter { "); endBracket = '}'; }
else if (jsvIsArray(var)) { jsiConsolePrintf("Array(%d) [ ", var->varData.integer); endBracket = ']'; }
else if (jsvIsNativeFunction(var)) { jsiConsolePrintf("NativeFunction 0x%x (%d) { ", var->varData.native.ptr, var->varData.native.argTypes); endBracket = '}'; }
else if (jsvIsFunction(var)) {
@ -3347,7 +3505,7 @@ void _jsvTrace(JsVar *var, int indent, JsVar *baseVar, int level) {
}
if (jsvHasSingleChild(var)) {
JsVar *child = jsvGetFirstChild(var) ? jsvLock(jsvGetFirstChild(var)) : 0;
JsVar *child = jsvLockSafe(jsvGetFirstChild(var));
_jsvTrace(child, indent+2, baseVar, level+1);
jsvUnLock(child);
} else if (jsvHasChildren(var)) {

View File

@ -32,12 +32,15 @@ typedef enum {
// UNDEFINED is now just stored using '0' as the variable Ref
JSV_NULL = JSV_ROOT+1, ///< it seems null is its own data type
JSV_ARRAY = JSV_NULL+1, ///< A JavaScript Array Buffer - Implemented just like a String at the moment
JSV_ARRAYBUFFER = JSV_ARRAY+1,
JSV_OBJECT = JSV_ARRAYBUFFER+1,
JSV_FUNCTION = JSV_OBJECT+1,
JSV_FUNCTION_RETURN = JSV_FUNCTION+1, ///< A simple function that starts with `return` (which is implicit)
JSV_INTEGER = JSV_FUNCTION_RETURN+1, ///< integer number (note JSV_NUMERICMASK)
JSV_ARRAY, ///< A JavaScript Array Buffer - Implemented just like a String at the moment
JSV_ARRAYBUFFER, ///< An arraybuffer (see varData.arraybuffer)
JSV_OBJECT,
#ifndef SAVE_ON_FLASH
JSV_GET_SET, ///< Getter/setter (an object with get/set fields)
#endif
JSV_FUNCTION,
JSV_FUNCTION_RETURN, ///< A simple function that starts with `return` (which is implicit)
JSV_INTEGER, ///< integer number (note JSV_NUMERICMASK)
_JSV_NUMERIC_START = JSV_INTEGER, ///< --------- Start of numeric variable types
JSV_FLOAT = JSV_INTEGER+1, ///< floating point double (note JSV_NUMERICMASK)
JSV_BOOLEAN = JSV_FLOAT+1, ///< boolean (note JSV_NUMERICMASK)
@ -344,6 +347,9 @@ ALWAYS_INLINE JsVar *_jsvGetAddressOf(JsVarRef ref);
/// Lock this reference and return a pointer - UNSAFE for null refs
ALWAYS_INLINE JsVar *jsvLock(JsVarRef ref);
/// Lock this reference and return a pointer, or 0
JsVar *jsvLockSafe(JsVarRef ref);
/// Lock this pointer and return a pointer - UNSAFE for null pointer
ALWAYS_INLINE JsVar *jsvLockAgain(JsVar *var);
@ -407,6 +413,8 @@ extern bool jsvIsNameIntInt(const JsVar *v);
extern bool jsvIsNameIntBool(const JsVar *v);
/// What happens when we access a variable that doesn't exist. We get a NAME where the next + previous siblings point to the object that may one day contain them
extern bool jsvIsNewChild(const JsVar *v);
/// Returns true if v is a getter/setter
extern bool jsvIsGetterOrSetter(const JsVar *v);
/// Are var.varData.ref.* (excl pad) used for data (so we expect them not to be empty)
extern bool jsvIsRefUsedForData(const JsVar *v);
@ -524,7 +532,26 @@ long long jsvGetLongIntegerAndUnLock(JsVar *v);
static ALWAYS_INLINE char jsvStringCharToUpper(char ch) { return (char)((ch >= 97 && ch <= 122) ? ch - 32 : ch); } // a-z
static ALWAYS_INLINE char jsvStringCharToLower(char ch) { return (char)((ch >= 65 && ch <= 90) ? ch + 32 : ch); } // A-Z
#ifndef SAVE_ON_FLASH
// Executes the given getter, or if there are problems returns undefined
JsVar *jsvExecuteGetter(JsVar *getset);
// Executes the given setter
void jsvExecuteSetter(JsVar *getset, JsVar *value);
/// Add a named getter or setter to an object
void jsvAddGetterOrSetter(JsVar *obj, JsVar *varName, bool isGetter, JsVar *method);
#endif
/* Set the value of the given variable. This is sort of like
* jsvSetValueOfName except it deals with all the non-standard
* stuff like ArrayBuffers, variables that haven't been allocated
* yet, setters, etc.
*/
void jsvReplaceWith(JsVar *dst, JsVar *src);
/* See jsvReplaceWith - this does the same but will
* shove the variable in execInfo.root if it hasn't
* been defined yet */
void jsvReplaceWithOrAddToRoot(JsVar *dst, JsVar *src);
/** Get the item at the given location in the array buffer and return the result */
size_t jsvGetArrayBufferLength(const JsVar *arrayBuffer);
@ -546,6 +573,10 @@ JsVar *jsvGetFunctionArgumentLength(JsVar *function);
* if ok, but has the value `undefined`. */
bool jsvIsVariableDefined(JsVar *a);
/* If this is a simple name (that links to another var) the
* return that var, else 0. */
JsVar *jsvGetValueOfName(JsVar *name);
/* Check for and trigger a ReferenceError on a variable if it's a name that doesn't exist */
void jsvCheckReferenceError(JsVar *a);

View File

@ -263,8 +263,8 @@ void jsvStringIteratorAppendString(JsvStringIterator *it, JsVar *str, size_t sta
// --------------------------------------------------------------------------------------------
void jsvObjectIteratorNew(JsvObjectIterator *it, JsVar *obj) {
assert(jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj));
it->var = jsvGetFirstChild(obj) ? jsvLock(jsvGetFirstChild(obj)) : 0;
assert(jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj) || jsvIsGetterOrSetter(obj));
it->var = jsvLockSafe(jsvGetFirstChild(obj));
}
/// Clone the iterator
@ -279,7 +279,7 @@ void jsvObjectIteratorNext(JsvObjectIterator *it) {
if (it->var) {
JsVarRef next = jsvGetNextSibling(it->var);
jsvUnLock(it->var);
it->var = next ? jsvLock(next) : 0;
it->var = jsvLockSafe(next);
}
}
@ -293,7 +293,7 @@ void jsvObjectIteratorRemoveAndGotoNext(JsvObjectIterator *it, JsVar *parent) {
JsVarRef next = jsvGetNextSibling(it->var);
jsvRemoveChild(parent, it->var);
jsvUnLock(it->var);
it->var = next ? jsvLock(next) : 0;
it->var = jsvLockSafe(next);
}
}
@ -521,7 +521,7 @@ void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) {
/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */
void jsvIteratorNew(JsvIterator *it, JsVar *obj, JsvIteratorFlags flags) {
if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj)) {
if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj) || jsvIsGetterOrSetter(obj)) {
it->type = JSVI_OBJECT;
if (jsvIsArray(obj) && (flags&JSIF_EVERY_ARRAY_ELEMENT)) {
it->type = JSVI_FULLARRAY;

View File

@ -275,10 +275,11 @@ static bool jsfGetJSONForObjectItWithCallback(JsvObjectIterator *it, JSONFlags f
size_t sinceNewLine = 0;
while (jsvObjectIteratorHasValue(it) && !jspIsInterrupted()) {
JsVar *index = jsvObjectIteratorGetKey(it);
JsVar *item = jsvObjectIteratorGetValue(it);
JsVar *item = jsvGetValueOfName(index);
bool hidden = jsvIsInternalObjectKey(index) ||
((flags & JSON_IGNORE_FUNCTIONS) && jsvIsFunction(item)) ||
((flags&JSON_NO_UNDEFINED) && jsvIsUndefined(item));
((flags&JSON_NO_UNDEFINED) && jsvIsUndefined(item)) ||
jsvIsGetterOrSetter(item);
if (!hidden) {
sinceNewLine++;
if (!first) cbprintf(user_callback, user_data, (flags&JSON_PRETTY)?", ":",");

View File

@ -356,18 +356,27 @@ JsVar *jswrap_object_getOwnPropertyDescriptor(JsVar *parent, JsVar *name) {
return 0;
}
//jsvTrace(varName, 5);
JsVar *var = jsvSkipName(varName);
bool isBuiltIn = jsvIsNewChild(varName);
JsvIsInternalChecker checkerFunction = jsvGetInternalFunctionCheckerFor(parent);
jsvObjectSetChild(obj, "value", var);
jsvObjectSetChildAndUnLock(obj, "writable", jsvNewFromBool(true));
jsvObjectSetChildAndUnLock(obj, "enumerable", jsvNewFromBool(!checkerFunction || !checkerFunction(varName)));
jsvObjectSetChildAndUnLock(obj, "configurable", jsvNewFromBool(!isBuiltIn));
#ifndef SAVE_ON_FLASH
JsVar *getset = jsvGetValueOfName(varName);
if (jsvIsGetterOrSetter(getset)) {
jsvObjectSetChildAndUnLock(obj, "get", jsvObjectGetChild(getset,"get",0));
jsvObjectSetChildAndUnLock(obj, "set", jsvObjectGetChild(getset,"set",0));
} else {
#endif
jsvObjectSetChildAndUnLock(obj, "value", jsvSkipName(varName));
#ifndef SAVE_ON_FLASH
}
jsvUnLock(getset);
#endif
jsvUnLock2(var, varName);
jsvUnLock(varName);
return obj;
}
@ -434,8 +443,8 @@ Add a new property to the Object. 'Desc' is an object with the following fields:
* `enumerable` (bool = false) - can this property be enumerated
* `value` (anything) - the value of this property
* `writable` (bool = false) - can the value be changed with the assignment operator?
* `get` (function) - the getter function, or undefined if no getter
* `set` (function) - the setter function, or undefined if no setter
* `get` (function) - the getter function, or undefined if no getter (only supported on some platforms)
* `set` (function) - the setter function, or undefined if no setter (only supported on some platforms)
*
**Note:** `configurable`, `enumerable`, `writable`, `get`, and `set` are not implemented and will be ignored.
*/
@ -450,7 +459,26 @@ JsVar *jswrap_object_defineProperty(JsVar *parent, JsVar *propName, JsVar *desc)
}
JsVar *name = jsvAsArrayIndex(propName);
JsVar *value = jsvObjectGetChild(desc, "value", 0);
JsVar *value = 0;
JsVar *getter = jsvObjectGetChild(desc, "get", 0);
JsVar *setter = jsvObjectGetChild(desc, "set", 0);
if (getter || setter) {
#ifdef SAVE_ON_FLASH
jsExceptionHere(JSET_ERROR, "get/set unsupported in this build");
#else
// also see jsvAddGetterOrSetter(contents, varName, isGetter, method);
value = jsvNewWithFlags(JSV_GET_SET);
if (value) {
if (getter) jsvObjectSetChild(value, "get", getter);
if (setter) jsvObjectSetChild(value, "set", setter);
jsvObjectSetChild(value, "this", parent);
}
#endif
jsvUnLock2(getter,setter);
}
if (!value) value = jsvObjectGetChild(desc, "value", 0);
jsvObjectSetChildVar(parent, name, value);
jsvUnLock2(name, value);

View File

@ -66,7 +66,7 @@ const void *exportPtrs[] = {
jsvGetFloat,
jsvGetInteger,
jsvGetBool,
jspReplaceWith,
jsvReplaceWith,
jspeFunctionCall,
jspGetNamedVariable,
jspGetNamedField,

36
tests/test_getter.js Normal file
View File

@ -0,0 +1,36 @@
// Examples from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
var results = [];
var obj = {
log: ['a', 'b', 'c'],
get latest() {
if (this.log.length == 0) {
return undefined;
}
return this.log[this.log.length - 1];
}
}
results.push(obj.latest);
// expected output: "c"
var obj = {
log: ['example','test'],
get latest() {
if (this.log.length == 0) return undefined;
return this.log[this.log.length - 1];
}
}
results.push(obj.latest); // "test".
delete obj.latest;
results.push(obj.latest); // undefined
var o = {a: 0};
Object.defineProperty(o, 'b', { get: function() { return this.a + 1; } });
results.push(o.b) // Runs the getter, which yields a + 1 (which is 1)
result = results=="c,test,,1";

43
tests/test_setter.js Normal file
View File

@ -0,0 +1,43 @@
// Examples from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
var results = [];
var language = {
set current(name) {
this.log.push(name);
},
log: []
}
language.current = 'EN';
language.current = 'FA';
results.push(JSON.stringify(language.log));
// expected output: Array ["EN", "FA"]
var language = {
set current(name) {
this.log.push(name);
},
log: []
}
language.current = 'EN';
results.push(JSON.stringify(language.log)); // ['EN']
language.current = 'FA';
results.push(JSON.stringify(language.log)); // ['EN', 'FA']
delete language.current;
language.current = 'EN';
results.push(JSON.stringify(language.log)); // ['EN', 'FA']
var o = {a: 0};
Object.defineProperty(o, 'b', { set: function(x) { this.a = x / 2; } });
o.b = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
results.push(o.a) // 5
print(results+"");
result = results=='["EN","FA"],["EN"],["EN","FA"],["EN","FA"],5';