/*JSON{ "type" : "method", "class" : "Array", "name" : "reduce", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_array_reduce", "params" : [ ["callback","JsVar","Function used to reduce the array"], ["initialValue","JsVar","if specified, the initial value to pass to the function"] ], "return" : ["JsVar","The value returned by the last function called"] } Execute `previousValue=initialValue` and then `previousValue = callback(previousValue, currentValue, index, array)` for each element in the array, and finally return previousValue. */ JsVar *jswrap_array_reduce(JsVar *parent, JsVar *funcVar, JsVar *initialValue) { const char *name = "reduce"; if (!jsvIsIterable(parent)) { jsExceptionHere(JSET_ERROR, "Array.%s can only be called on something iterable", name); return 0; } if (!jsvIsFunction(funcVar)) { jsExceptionHere(JSET_ERROR, "Array.%s's first argument should be a function", name); return 0; } JsVar *previousValue = initialValue ? jsvLockAgain(initialValue) : 0; JsvIterator it; jsvIteratorNew(&it, parent); if (!previousValue) { bool isDone = false; while (!isDone && jsvIteratorHasElement(&it)) { JsVar *index = jsvIteratorGetKey(&it); if (jsvIsInt(index)) { previousValue = jsvIteratorGetValue(&it); isDone = true; } jsvUnLock(index); jsvIteratorNext(&it); } if (!previousValue) { jsExceptionHere(JSET_ERROR, "Array.%s without initial value required non-empty array", name); } } while (jsvIteratorHasElement(&it)) { JsVar *index = jsvIteratorGetKey(&it); if (jsvIsInt(index)) { JsVarInt idxValue = jsvGetInteger(index); JsVar *args[4]; args[0] = previousValue; args[1] = jsvIteratorGetValue(&it); args[2] = jsvNewFromInteger(idxValue); // child is a variable name, create a new variable for the index args[3] = parent; previousValue = jspeFunctionCall(funcVar, 0, 0, false, 4, args); jsvUnLock(args[0]); jsvUnLock(args[1]); jsvUnLock(args[2]); } jsvUnLock(index); jsvIteratorNext(&it); } jsvIteratorFree(&it); return previousValue; }
/*JSON{ "type" : "method", "class" : "Function", "name" : "apply", "generate" : "jswrap_function_apply_or_call", "params" : [ ["this","JsVar","The value to use as the 'this' argument when executing the function"], ["args","JsVar","Optional Array of Arguments"] ], "return" : ["JsVar","The return value of executing this function"] } This executes the function with the supplied 'this' argument and parameters */ JsVar *jswrap_function_apply_or_call(JsVar *parent, JsVar *thisArg, JsVar *argsArray) { unsigned int i; JsVar **args = 0; unsigned int argC = 0; if (jsvIsIterable(argsArray)) { argC = (unsigned int)jsvGetLength(argsArray); if (argC>64) { jsExceptionHere(JSET_ERROR, "Array passed to Function.apply is too big! Maximum 64 arguments, got %d", argC); return 0; } args = (JsVar**)alloca((size_t)argC * sizeof(JsVar*)); for (i=0;i<argC;i++) args[i] = 0; // TODO: Use jsvGetArrayItems? JsvIterator it; jsvIteratorNew(&it, argsArray); while (jsvIteratorHasElement(&it)) { JsVarInt idx = jsvGetIntegerAndUnLock(jsvIteratorGetKey(&it)); if (idx>=0 && idx<(int)argC) { assert(!args[idx]); // just in case there were dups args[idx] = jsvIteratorGetValue(&it); } jsvIteratorNext(&it); } jsvIteratorFree(&it); } else if (!jsvIsUndefined(argsArray)) { jsExceptionHere(JSET_ERROR, "Second argument to Function.apply must be iterable, got %t", argsArray); return 0; } JsVar *r = jspeFunctionCall(parent, 0, thisArg, false, (int)argC, args); jsvUnLockMany(argC, args); return r; }
/*JSON{ "type" : "method", "class" : "ArrayBufferView", "name" : "set", "generate" : "jswrap_arraybufferview_set", "params" : [ ["arr","JsVar","Floating point index to access"], ["offset","int32","The offset in this array at which to write the values (optional)"] ] } Copy the contents of `array` into this one, mapping `this[x+offset]=array[x];` */ void jswrap_arraybufferview_set(JsVar *parent, JsVar *arr, int offset) { if (!(jsvIsString(arr) || jsvIsArray(arr) || jsvIsArrayBuffer(arr))) { jsExceptionHere(JSET_ERROR, "Expecting first argument to be an array, not %t", arr); return; } JsvIterator itsrc; jsvIteratorNew(&itsrc, arr); JsvArrayBufferIterator itdst; jsvArrayBufferIteratorNew(&itdst, parent, (size_t)offset); bool useInts = !JSV_ARRAYBUFFER_IS_FLOAT(itdst.type) || jsvIsString(arr); while (jsvIteratorHasElement(&itsrc) && jsvArrayBufferIteratorHasElement(&itdst)) { if (useInts) { jsvArrayBufferIteratorSetIntegerValue(&itdst, jsvIteratorGetIntegerValue(&itsrc)); } else { JsVar *value = jsvIteratorGetValue(&itsrc); jsvArrayBufferIteratorSetValue(&itdst, value); jsvUnLock(value); } jsvArrayBufferIteratorNext(&itdst); jsvIteratorNext(&itsrc); } jsvArrayBufferIteratorFree(&itdst); jsvIteratorFree(&itsrc); }
/*JSON{ "type" : "method", "class" : "ArrayBufferView", "name" : "map", "generate" : "jswrap_arraybufferview_map", "params" : [ ["function","JsVar","Function used to map one item to another"], ["thisArg","JsVar","if specified, the function is called with 'this' set to thisArg (optional)"] ], "return" : ["JsVar","An array containing the results"], "return_object" : "ArrayBufferView" } Return an array which is made from the following: ```A.map(function) = [function(A[0]), function(A[1]), ...]``` **Note:** This returns an ArrayBuffer of the same type it was called on. To get an Array, use `Array.prototype.map` */ JsVar *jswrap_arraybufferview_map(JsVar *parent, JsVar *funcVar, JsVar *thisVar) { if (!jsvIsArrayBuffer(parent)) { jsExceptionHere(JSET_ERROR, "ArrayBufferView.map can only be called on an ArrayBufferView"); return 0; } if (!jsvIsFunction(funcVar)) { jsExceptionHere(JSET_ERROR, "ArrayBufferView.map's first argument should be a function"); return 0; } if (!jsvIsUndefined(thisVar) && !jsvIsObject(thisVar)) { jsExceptionHere(JSET_ERROR, "ArrayBufferView.map's second argument should be undefined, or an object"); return 0; } // create ArrayBuffer result JsVarDataArrayBufferViewType arrayBufferType = parent->varData.arraybuffer.type; JsVar *arrayBufferLength = jsvNewFromInteger((JsVarInt)jsvGetArrayBufferLength(parent)); JsVar *array = jswrap_typedarray_constructor(arrayBufferType, arrayBufferLength, 0, 0); jsvUnLock(arrayBufferLength); if (!array) return 0; // now iterate JsvIterator it; // TODO: if we really are limited to ArrayBuffers, this could be an ArrayBufferIterator. jsvIteratorNew(&it, parent); JsvArrayBufferIterator itdst; jsvArrayBufferIteratorNew(&itdst, array, 0); while (jsvIteratorHasElement(&it)) { JsVar *index = jsvIteratorGetKey(&it); if (jsvIsInt(index)) { JsVarInt idxValue = jsvGetInteger(index); JsVar *args[3], *mapped; args[0] = jsvIteratorGetValue(&it); args[1] = jsvNewFromInteger(idxValue); // child is a variable name, create a new variable for the index args[2] = parent; mapped = jspeFunctionCall(funcVar, 0, thisVar, false, 3, args); jsvUnLock(args[0]); jsvUnLock(args[1]); if (mapped) { jsvArrayBufferIteratorSetValue(&itdst, mapped); jsvUnLock(mapped); } } jsvUnLock(index); jsvIteratorNext(&it); jsvArrayBufferIteratorNext(&itdst); } jsvIteratorFree(&it); jsvArrayBufferIteratorFree(&itdst); return array; }
JsVar *jswrap_typedarray_constructor(JsVarDataArrayBufferViewType type, JsVar *arr, JsVarInt byteOffset, JsVarInt length) { JsVar *arrayBuffer = 0; // Only allow use of byteOffset/length if we're passing an ArrayBuffer - NOT A VIEW. bool copyData = false; if (jsvIsArrayBuffer(arr) && arr->varData.arraybuffer.type==ARRAYBUFFERVIEW_ARRAYBUFFER) { arrayBuffer = jsvLockAgain(arr); } else if (jsvIsNumeric(arr)) { length = jsvGetInteger(arr); byteOffset = 0; arrayBuffer = jswrap_arraybuffer_constructor((int)JSV_ARRAYBUFFER_GET_SIZE(type)*length); } else if (jsvIsArray(arr) || jsvIsArrayBuffer(arr)) { length = (JsVarInt)jsvGetLength(arr); byteOffset = 0; arrayBuffer = jswrap_arraybuffer_constructor((int)JSV_ARRAYBUFFER_GET_SIZE(type)*length); copyData = true; // so later on we'll populate this } if (!arrayBuffer) { jsExceptionHere(JSET_ERROR, "Unsupported first argument of type %t\n", arr); return 0; } if (length==0) { length = ((JsVarInt)jsvGetArrayBufferLength(arrayBuffer)-byteOffset) / (JsVarInt)JSV_ARRAYBUFFER_GET_SIZE(type); if (length<0) length=0; } JsVar *typedArr = jsvNewWithFlags(JSV_ARRAYBUFFER); if (typedArr) { typedArr->varData.arraybuffer.type = type; typedArr->varData.arraybuffer.byteOffset = (unsigned short)byteOffset; typedArr->varData.arraybuffer.length = (unsigned short)length; jsvSetFirstChild(typedArr, jsvGetRef(jsvRef(arrayBuffer))); if (copyData) { // if we were given an array, populate this ArrayBuffer JsvIterator it; jsvIteratorNew(&it, arr, JSIF_DEFINED_ARRAY_ElEMENTS); while (jsvIteratorHasElement(&it)) { JsVar *idx = jsvIteratorGetKey(&it); if (jsvIsInt(idx)) { JsVar *val = jsvIteratorGetValue(&it); // TODO: This is horrible! We need to try and iterate properly... jsvArrayBufferSet(typedArr, (size_t)jsvGetInteger(idx), val); jsvUnLock(val); } jsvUnLock(idx); jsvIteratorNext(&it); } jsvIteratorFree(&it); } } jsvUnLock(arrayBuffer); return typedArr; }
JsVar *_jswrap_array_map_or_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar, bool isMap) { const char *name = isMap ? "map":"forEach"; if (!jsvIsIterable(parent)) { jsError("Array.%s can only be called on something iterable", name); return 0; } if (!jsvIsFunction(funcVar)) { jsError("Array.%s's first argument should be a function", name); return 0; } if (!jsvIsUndefined(thisVar) && !jsvIsObject(thisVar)) { jsError("Array.%s's second argument should be undefined, or an object", name); return 0; } JsVar *array = 0; if (isMap) array = jsvNewWithFlags(JSV_ARRAY); if (array || !isMap) { JsvIterator it; jsvIteratorNew(&it, parent); while (jsvIteratorHasElement(&it)) { JsVar *index = jsvIteratorGetKey(&it); if (jsvIsInt(index)) { JsVarInt idxValue = jsvGetInteger(index); JsVar *args[3], *mapped; args[0] = jsvIteratorGetValue(&it); args[1] = jsvNewFromInteger(idxValue); // child is a variable name, create a new variable for the index args[2] = parent; mapped = jspeFunctionCall(funcVar, 0, thisVar, false, 3, args); jsvUnLock(args[0]); jsvUnLock(args[1]); if (mapped) { if (isMap) { JsVar *name = jsvNewFromInteger(idxValue); if (name) { // out of memory? jsvMakeIntoVariableName(name, mapped); jsvAddName(array, name); jsvUnLock(name); } } jsvUnLock(mapped); } } jsvUnLock(index); jsvIteratorNext(&it); } jsvIteratorFree(&it); } return array; }
/** Iterate over the contents of var, calling callback for each. Contents may be: * * numeric -> output * * a string -> output each character * * array/arraybuffer -> call itself on each element * * object -> call itself object.count times, on object.data */ bool jsvIterateCallback(JsVar *data, void (*callback)(int item, void *callbackData), void *callbackData) { bool ok = true; if (jsvIsNumeric(data)) { callback((int)jsvGetInteger(data), callbackData); } else if (jsvIsObject(data)) { JsVar *countVar = jsvObjectGetChild(data, "count", 0); JsVar *dataVar = jsvObjectGetChild(data, "data", 0); if (countVar && dataVar && jsvIsNumeric(countVar)) { int n = (int)jsvGetInteger(countVar); while (ok && n-- > 0) { ok = jsvIterateCallback(dataVar, callback, callbackData); } } else { jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); } jsvUnLock(countVar); jsvUnLock(dataVar); } else if (jsvIsString(data)) { JsvStringIterator it; jsvStringIteratorNew(&it, data, 0); while (jsvStringIteratorHasChar(&it) && ok) { char ch = jsvStringIteratorGetChar(&it); callback(ch, callbackData); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); } else if (jsvIsIterable(data)) { JsvIterator it; jsvIteratorNew(&it, data); while (jsvIteratorHasElement(&it) && ok) { JsVar *el = jsvIteratorGetValue(&it); ok = jsvIterateCallback(el, callback, callbackData); jsvUnLock(el); jsvIteratorNext(&it); } jsvIteratorFree(&it); } else { jsWarn("Expecting a number or something iterable, got %t", data); ok = false; } return ok; }
/** * \brief Iterate over the contents of the content of a variable, calling callback for each. * Contents may be: * * numeric -> output * * a string -> output each character * * array/arraybuffer -> call itself on each element * object -> call itself object.count times, on object.data */ bool jsvIterateCallback( JsVar *data, // The data to iterate over. void (*callback)(int item, void *callbackData), // The callback function invoke. void *callbackData // Data to be passed to the callback function ) { bool ok = true; // Handle the data being a single numeric. if (jsvIsNumeric(data)) { callback((int)jsvGetInteger(data), callbackData); } // Handle the data being an object. else if (jsvIsObject(data)) { JsVar *countVar = jsvObjectGetChild(data, "count", 0); JsVar *dataVar = jsvObjectGetChild(data, "data", 0); if (countVar && dataVar && jsvIsNumeric(countVar)) { int n = (int)jsvGetInteger(countVar); while (ok && n-- > 0) { ok = jsvIterateCallback(dataVar, callback, callbackData); } } else { jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); } jsvUnLock2(countVar, dataVar); } // Handle the data being a string else if (jsvIsString(data)) { JsvStringIterator it; jsvStringIteratorNew(&it, data, 0); while (jsvStringIteratorHasChar(&it) && ok) { char ch = jsvStringIteratorGetChar(&it); callback(ch, callbackData); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); } // Handle the data being an array buffer else if (jsvIsArrayBuffer(data)) { JsvArrayBufferIterator it; jsvArrayBufferIteratorNew(&it, data, 0); if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { // faster for single byte arrays. while (jsvArrayBufferIteratorHasElement(&it)) { callback((int)(unsigned char)jsvStringIteratorGetChar(&it.it), callbackData); jsvArrayBufferIteratorNext(&it); } } else { while (jsvArrayBufferIteratorHasElement(&it)) { callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); jsvArrayBufferIteratorNext(&it); } } jsvArrayBufferIteratorFree(&it); } // Handle the data being iterable else if (jsvIsIterable(data)) { JsvIterator it; jsvIteratorNew(&it, data); while (jsvIteratorHasElement(&it) && ok) { JsVar *el = jsvIteratorGetValue(&it); ok = jsvIterateCallback(el, callback, callbackData); jsvUnLock(el); jsvIteratorNext(&it); } jsvIteratorFree(&it); } else { jsWarn("Expecting a number or something iterable, got %t", data); ok = false; } return ok; }
JsVar *_jswrap_array_iterate_with_callback(const char *name, JsVar *parent, JsVar *funcVar, JsVar *thisVar, bool wantArray, bool isBoolCallback, bool expectedValue) { if (!jsvIsIterable(parent)) { jsExceptionHere(JSET_ERROR, "Array.%s can only be called on something iterable", name); return 0; } if (!jsvIsFunction(funcVar)) { jsExceptionHere(JSET_ERROR, "Array.%s's first argument should be a function", name); return 0; } if (!jsvIsUndefined(thisVar) && !jsvIsObject(thisVar)) { jsExceptionHere(JSET_ERROR, "Array.%s's second argument should be undefined, or an object", name); return 0; } JsVar *result = 0; if (wantArray) result = jsvNewEmptyArray(); bool isDone = false; if (result || !wantArray) { JsvIterator it; jsvIteratorNew(&it, parent); while (jsvIteratorHasElement(&it) && !isDone) { JsVar *index = jsvIteratorGetKey(&it); if (jsvIsInt(index)) { JsVarInt idxValue = jsvGetInteger(index); JsVar *args[3], *cb_result; args[0] = jsvIteratorGetValue(&it); args[1] = jsvNewFromInteger(idxValue); // child is a variable name, create a new variable for the index args[2] = parent; cb_result = jspeFunctionCall(funcVar, 0, thisVar, false, 3, args); jsvUnLockMany(2,args); if (cb_result) { bool matched; if (isBoolCallback) matched = (jsvGetBool(cb_result) == expectedValue); if (wantArray) { if (isBoolCallback) { // filter if (matched) { jsvArrayPushAndUnLock(result, jsvIteratorGetValue(&it)); } } else { // map JsVar *name = jsvNewFromInteger(idxValue); if (name) { // out of memory? jsvMakeIntoVariableName(name, cb_result); jsvAddName(result, name); jsvUnLock(name); } } } else { // break the loop early if expecting a particular value and didn't get it if (isBoolCallback && !matched) isDone = true; } jsvUnLock(cb_result); } } jsvUnLock(index); jsvIteratorNext(&it); } jsvIteratorFree(&it); } /* boolean result depends on whether the loop terminated early for 'some' or completed for 'every' */ if (!wantArray && isBoolCallback) { result = jsvNewFromBool(isDone != expectedValue); } return result; }
/** Iterate over the contents of the content of a variable, calling callback for each. Contents may be: * numeric -> output * a string -> output each character * array/arraybuffer -> call itself on each element * {data:..., count:...} -> call itself object.count times, on object.data * {callback:...} -> call the given function, call itself on return value */ bool jsvIterateCallback( JsVar *data, jsvIterateCallbackFn callback, void *callbackData ) { bool ok = true; // Handle the data being a single numeric. if (jsvIsNumeric(data)) { callback((int)jsvGetInteger(data), callbackData); } // Handle the data being an object. else if (jsvIsObject(data)) { JsVar *callbackVar = jsvObjectGetChild(data, "callback", 0); if (jsvIsFunction(callbackVar)) { JsVar *result = jspExecuteFunction(callbackVar,0,0,NULL); jsvUnLock(callbackVar); if (result) { bool r = jsvIterateCallback(result, callback, callbackData); jsvUnLock(result); return r; } return true; } jsvUnLock(callbackVar); JsVar *countVar = jsvObjectGetChild(data, "count", 0); JsVar *dataVar = jsvObjectGetChild(data, "data", 0); if (countVar && dataVar && jsvIsNumeric(countVar)) { int n = (int)jsvGetInteger(countVar); while (ok && n-- > 0) { ok = jsvIterateCallback(dataVar, callback, callbackData); } } else { jsExceptionHere(JSET_TYPEERROR, "If specifying an object, it must be of the form {data : ..., count : N} or {callback : fn} - got %j", data); ok = false; } jsvUnLock2(countVar, dataVar); } // Handle the data being a string else if (jsvIsString(data)) { JsvStringIterator it; jsvStringIteratorNew(&it, data, 0); while (jsvStringIteratorHasChar(&it) && ok) { char ch = jsvStringIteratorGetChar(&it); callback(ch, callbackData); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); } // Handle the data being an array buffer else if (jsvIsArrayBuffer(data)) { JsvArrayBufferIterator it; jsvArrayBufferIteratorNew(&it, data, 0); if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { JsvStringIterator *sit = &it.it; // faster for single byte arrays - read using the string iterator. while (jsvStringIteratorHasChar(sit)) { callback((int)(unsigned char)jsvStringIteratorGetChar(sit), callbackData); jsvStringIteratorNextInline(sit); } } else { while (jsvArrayBufferIteratorHasElement(&it)) { callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); jsvArrayBufferIteratorNext(&it); } } jsvArrayBufferIteratorFree(&it); } // Handle the data being iterable else if (jsvIsIterable(data)) { JsvIterator it; jsvIteratorNew(&it, data, JSIF_EVERY_ARRAY_ELEMENT); while (jsvIteratorHasElement(&it) && ok) { JsVar *el = jsvIteratorGetValue(&it); ok = jsvIterateCallback(el, callback, callbackData); jsvUnLock(el); jsvIteratorNext(&it); } jsvIteratorFree(&it); } else { jsExceptionHere(JSET_TYPEERROR, "Expecting a number or something iterable, got %t", data); ok = false; } return ok; }