jswrap_array.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
  3. *
  4. * Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. *
  10. * ----------------------------------------------------------------------------
  11. * This file is designed to be parsed during the build process
  12. *
  13. * JavaScript methods for Arrays
  14. * ----------------------------------------------------------------------------
  15. */
  16. #include "jswrap_array.h"
  17. #include "jsparse.h"
  18. #define min(a,b) (((a)<(b))?(a):(b))
  19. #define max(a,b) (((a)>(b))?(a):(b))
  20. /*JSON{ "type":"class",
  21. "class" : "Array",
  22. "check" : "jsvIsArray(var)",
  23. "description" : ["This is the built-in JavaScript class for arrays.",
  24. "Arrays can be defined with ```[]```, ```new Array()```, or ```new Array(length)```" ]
  25. }*/
  26. /*JSON{ "type":"constructor", "class": "Array", "name": "Array",
  27. "description" : "Create an Array. Either give it one integer argument (>=0) which is the length of the array, or any number of arguments ",
  28. "generate" : "jswrap_array_constructor",
  29. "params" : [ [ "args", "JsVarArray", "The length of the array OR any number of items to add to the array" ] ],
  30. "return" : [ "JsVar", "An Array" ]
  31. }*/
  32. JsVar *jswrap_array_constructor(JsVar *args) {
  33. assert(args);
  34. if (jsvGetArrayLength(args)==1) {
  35. JsVar *firstArg = jsvSkipNameAndUnLock(jsvArrayGetLast(args)); // also the first!
  36. if (jsvIsInt(firstArg) && jsvGetInteger(firstArg)>=0) {
  37. JsVarInt count = jsvGetInteger(firstArg);
  38. // we cheat - no need to fill the array - just the last element
  39. if (count>0) {
  40. JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
  41. if (!arr) return 0; // out of memory
  42. JsVar *idx = jsvMakeIntoVariableName(jsvNewFromInteger(count-1), 0);
  43. if (idx) { // could be out of memory
  44. jsvAddName(arr, idx);
  45. jsvUnLock(idx);
  46. }
  47. jsvUnLock(firstArg);
  48. return arr;
  49. }
  50. }
  51. jsvUnLock(firstArg);
  52. }
  53. // Otherwise, we just return the array!
  54. return jsvLockAgain(args);
  55. }
  56. /*JSON{ "type":"method", "class": "Array", "name" : "contains",
  57. "description" : "Return true if this array contains the given value",
  58. "generate" : "jswrap_array_contains",
  59. "params" : [ [ "value", "JsVar", "The value to check for"] ],
  60. "return" : ["bool", "Whether value is in the array or not"]
  61. }*/
  62. bool jswrap_array_contains(JsVar *parent, JsVar *value) {
  63. // ArrayIndexOf will return 0 if not found
  64. JsVar *arrElement = jsvGetArrayIndexOf(parent, value, false/*not exact*/);
  65. bool contains = arrElement!=0;
  66. jsvUnLock(arrElement);
  67. return contains;
  68. }
  69. /*JSON{ "type":"method", "class": "Array", "name" : "indexOf",
  70. "description" : "Return the index of the value in the array, or -1",
  71. "generate" : "jswrap_array_indexOf",
  72. "params" : [ [ "value", "JsVar", "The value to check for"] ],
  73. "return" : ["JsVar", "the index of the value in the array, or -1"]
  74. }*/
  75. JsVar *jswrap_array_indexOf(JsVar *parent, JsVar *value) {
  76. JsVar *idxName = jsvGetArrayIndexOf(parent, value, false/*not exact*/);
  77. // but this is the name - we must turn it into a var
  78. if (idxName == 0) return jsvNewFromInteger(-1); // not found!
  79. JsVar *idx = jsvCopyNameOnly(idxName, false/* no children */, false/* Make sure this is not a name*/);
  80. jsvUnLock(idxName);
  81. return idx;
  82. }
  83. /*JSON{ "type":"method", "class": "Array", "name" : "join",
  84. "description" : "Join all elements of this array together into one string, using 'separator' between them. eg. ```[1,2,3].join(' ')=='1 2 3'```",
  85. "generate" : "jswrap_array_join",
  86. "params" : [ [ "separator", "JsVar", "The separator"] ],
  87. "return" : ["JsVar", "A String representing the Joined array"]
  88. }*/
  89. JsVar *jswrap_array_join(JsVar *parent, JsVar *filler) {
  90. if (jsvIsUndefined(filler))
  91. filler = jsvNewFromString(","); // the default it seems
  92. else
  93. filler = jsvAsString(filler, false);
  94. if (!filler) return 0; // out of memory
  95. JsVar *str = jsvArrayJoin(parent, filler);
  96. jsvUnLock(filler);
  97. return str;
  98. }
  99. /*JSON{ "type":"method", "class": "Array", "name" : "push",
  100. "description" : "Push a new value onto the end of this array'",
  101. "generate_full" : "jsvArrayPush(parent, value)",
  102. "params" : [ [ "value", "JsVar", "The value to add"] ],
  103. "return" : ["int", "The new size of the array"]
  104. }*/
  105. /*JSON{ "type":"method", "class": "Array", "name" : "pop",
  106. "description" : "Pop a new value off of the end of this array",
  107. "generate_full" : "jsvArrayPop(parent)",
  108. "return" : ["JsVar", "The value that is popped off"]
  109. }*/
  110. JsVar *_jswrap_array_map_or_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar, bool isMap) {
  111. if (!jsvIsFunction(funcVar)) {
  112. jsError("Array.map's first argument should be a function");
  113. return 0;
  114. }
  115. if (!jsvIsUndefined(thisVar) && !jsvIsObject(thisVar)) {
  116. jsError("Arraymap's second argument should be undefined, or an object");
  117. return 0;
  118. }
  119. JsVar *array = 0;
  120. if (isMap)
  121. array = jsvNewWithFlags(JSV_ARRAY);
  122. if (array || !isMap) {
  123. JsVarRef childRef = parent->firstChild;
  124. while (childRef) {
  125. JsVar *child = jsvLock(childRef);
  126. if (jsvIsInt(child)) {
  127. JsVar *args[3], *mapped;
  128. args[0] = jsvLock(child->firstChild);
  129. // child is a variable name, create a new variable for the index
  130. args[1] = jsvNewFromInteger(jsvGetInteger(child));
  131. args[2] = parent;
  132. mapped = jspeFunctionCall(funcVar, 0, thisVar, false, 3, args);
  133. jsvUnLock(args[0]);
  134. jsvUnLock(args[1]);
  135. if (mapped) {
  136. if (isMap) {
  137. JsVar *name = jsvCopyNameOnly(child, false/*linkChildren*/, true/*keepAsName*/);
  138. if (name) { // out of memory?
  139. name->firstChild = jsvGetRef(jsvRef(mapped));
  140. jsvAddName(array, name);
  141. jsvUnLock(name);
  142. }
  143. }
  144. jsvUnLock(mapped);
  145. }
  146. }
  147. childRef = child->nextSibling;
  148. jsvUnLock(child);
  149. }
  150. }
  151. return array;
  152. }
  153. /*JSON{ "type":"method", "class": "Array", "name" : "map",
  154. "description" : "Return an array which is made from the following: ```A.map(function) = [function(A[0]), function(A[1]), ...]```",
  155. "generate" : "jswrap_array_map",
  156. "params" : [ [ "function", "JsVar", "Function used to map one item to another"] ,
  157. [ "thisArg", "JsVar", "if specified, the function is called with 'this' set to thisArg (optional)"] ],
  158. "return" : ["JsVar", "The value that is popped off"]
  159. }*/
  160. JsVar *jswrap_array_map(JsVar *parent, JsVar *funcVar, JsVar *thisVar) {
  161. return _jswrap_array_map_or_forEach(parent, funcVar, thisVar, true);
  162. }
  163. /*JSON{ "type":"method", "class": "Array", "name" : "splice",
  164. "description" : "Both remove and add items to an array",
  165. "generate" : "jswrap_array_splice",
  166. "params" : [ [ "index", "int", "Index at which to start changing the array. If negative, will begin that many elements from the end"],
  167. [ "howMany", "JsVar", "An integer indicating the number of old array elements to remove. If howMany is 0, no elements are removed."],
  168. [ "element1", "JsVar", "A new item to add (optional)" ],
  169. [ "element2", "JsVar", "A new item to add (optional)" ],
  170. [ "element3", "JsVar", "A new item to add (optional)" ],
  171. [ "element4", "JsVar", "A new item to add (optional)" ],
  172. [ "element5", "JsVar", "A new item to add (optional)" ],
  173. [ "element6", "JsVar", "A new item to add (optional)" ] ],
  174. "return" : ["JsVar", "An array containing the removed elements. If only one element is removed, an array of one element is returned."]
  175. }*/
  176. JsVar *jswrap_array_splice(JsVar *parent, JsVarInt index, JsVar *howManyVar, JsVar *element1, JsVar *element2, JsVar *element3, JsVar *element4, JsVar *element5, JsVar *element6) {
  177. JsVarInt len = jsvGetArrayLength(parent);
  178. if (index<0) index+=len;
  179. if (index<0) index=0;
  180. if (index>len) index=len;
  181. JsVarInt howMany = len; // how many to delete!
  182. if (jsvIsInt(howManyVar)) howMany = jsvGetInteger(howManyVar);
  183. if (howMany > len-index) howMany = len-index;
  184. JsVarInt newItems = 0;
  185. if (element1) newItems++;
  186. if (element2) newItems++;
  187. if (element3) newItems++;
  188. if (element4) newItems++;
  189. if (element5) newItems++;
  190. if (element6) newItems++;
  191. JsVarInt shift = newItems-howMany;
  192. bool needToAdd = false;
  193. JsVar *result = jsvNewWithFlags(JSV_ARRAY);
  194. JsArrayIterator it;
  195. jsvArrayIteratorNew(&it, parent);
  196. while (jsvArrayIteratorHasElement(&it) && !needToAdd) {
  197. bool goToNext = true;
  198. JsVar *idxVar = jsvArrayIteratorGetIndex(&it);
  199. if (idxVar && jsvIsInt(idxVar)) {
  200. JsVarInt idx = jsvGetInteger(idxVar);
  201. if (idx<index) {
  202. // do nothing...
  203. } else if (idx<index+howMany) { // must delete
  204. if (result) { // append to result array
  205. JsVar *el = jsvArrayIteratorGetElement(&it);
  206. jsvArrayPushAndUnLock(result, el);
  207. }
  208. // delete
  209. goToNext = false;
  210. JsVar *toRemove = jsvArrayIteratorGetIndex(&it);
  211. jsvArrayIteratorNext(&it);
  212. jsvRemoveChild(parent, toRemove);
  213. jsvUnLock(toRemove);
  214. } else { // we're greater than the amount we need to remove now
  215. needToAdd = true;
  216. goToNext = false;
  217. }
  218. }
  219. jsvUnLock(idxVar);
  220. if (goToNext) jsvArrayIteratorNext(&it);
  221. }
  222. // now we add everything
  223. JsVar *beforeIndex = jsvArrayIteratorGetIndex(&it);
  224. if (element1) jsvArrayInsertBefore(parent, beforeIndex, element1);
  225. if (element2) jsvArrayInsertBefore(parent, beforeIndex, element2);
  226. if (element3) jsvArrayInsertBefore(parent, beforeIndex, element3);
  227. if (element4) jsvArrayInsertBefore(parent, beforeIndex, element4);
  228. if (element5) jsvArrayInsertBefore(parent, beforeIndex, element5);
  229. if (element6) jsvArrayInsertBefore(parent, beforeIndex, element6);
  230. jsvUnLock(beforeIndex);
  231. // And finally renumber
  232. while (jsvArrayIteratorHasElement(&it)) {
  233. JsVar *idxVar = jsvArrayIteratorGetIndex(&it);
  234. if (idxVar && jsvIsInt(idxVar)) {
  235. jsvSetInteger(idxVar, jsvGetInteger(idxVar)+shift);
  236. }
  237. jsvUnLock(idxVar);
  238. jsvArrayIteratorNext(&it);
  239. }
  240. // free
  241. jsvArrayIteratorFree(&it);
  242. return result;
  243. }
  244. /*JSON{ "type":"method", "class": "Array", "name" : "slice",
  245. "description" : "Return a copy of a portion of the calling array",
  246. "generate" : "jswrap_array_slice",
  247. "params" : [ [ "start", "JsVar", "Start index"],
  248. [ "end", "JsVar", "End index (optional)"] ],
  249. "return" : ["JsVar", "A new array"]
  250. }*/
  251. JsVar *jswrap_array_slice(JsVar *parent, JsVar *startVar, JsVar *endVar) {
  252. JsVarInt len = jsvGetArrayLength(parent);
  253. JsVarInt start = 0;
  254. JsVarInt end = len;
  255. if (!jsvIsUndefined(startVar))
  256. start = jsvGetInteger(startVar);
  257. if (!jsvIsUndefined(endVar))
  258. end = jsvGetInteger(endVar);
  259. JsVarInt k = 0;
  260. JsVarInt final = len;
  261. JsVar *array = jsvNewWithFlags(JSV_ARRAY);
  262. if (!array) return 0;
  263. if (start<0) k = max((len + start), 0);
  264. else k = min(start, len);
  265. if (end<0) final = max((len + end), 0);
  266. else final = min(end, len);
  267. bool isDone = false;
  268. JsArrayIterator it;
  269. jsvArrayIteratorNew(&it, parent);
  270. while (jsvArrayIteratorHasElement(&it) && !isDone) {
  271. JsVarInt idx = jsvGetInteger(jsvArrayIteratorGetIndex(&it));
  272. if (idx < k) {
  273. jsvArrayIteratorNext(&it);
  274. } else {
  275. if (k < final) {
  276. jsvArrayPushAndUnLock(array, jsvArrayIteratorGetElement(&it));
  277. jsvArrayIteratorNext(&it);
  278. k++;
  279. } else {
  280. isDone = true;
  281. }
  282. }
  283. }
  284. jsvArrayIteratorFree(&it);
  285. return array;
  286. }
  287. /*JSON{ "type":"method", "class": "Array", "name" : "forEach",
  288. "description" : "Executes a provided function once per array element.",
  289. "generate" : "jswrap_array_forEach",
  290. "params" : [ [ "function", "JsVar", "Function to be executed"] ,
  291. [ "thisArg", "JsVar", "if specified, the function is called with 'this' set to thisArg (optional)"] ]
  292. }*/
  293. void jswrap_array_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar) {
  294. _jswrap_array_map_or_forEach(parent, funcVar, thisVar, false);
  295. }