jswrap_object.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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 Objects and Functions
  14. * ----------------------------------------------------------------------------
  15. */
  16. #include "jswrap_object.h"
  17. #include "jsparse.h"
  18. #include "jsinteractive.h"
  19. /*JSON{ "type":"class",
  20. "class" : "Hardware",
  21. "check" : "jsvIsRoot(var)",
  22. "description" : ["This is the built-in class for the Espruino device. It is the 'root scope', as 'Window' is for JavaScript on the desktop." ]
  23. }*/
  24. /*JSON{ "type":"class",
  25. "class" : "Object",
  26. "check" : "jsvIsObject(var)",
  27. "description" : ["This is the built-in class for Objects" ]
  28. }*/
  29. /*JSON{ "type":"class",
  30. "class" : "Function",
  31. "check" : "jsvIsFunction(var)",
  32. "description" : ["This is the built-in class for Functions" ]
  33. }*/
  34. /*JSON{ "type":"class",
  35. "class" : "Integer",
  36. "check" : "jsvIsInt(var)",
  37. "description" : ["This is the built-in class for Integer values" ]
  38. }*/
  39. /*JSON{ "type":"class",
  40. "class" : "Double",
  41. "check" : "jsvIsFloat(var)",
  42. "description" : ["This is the built-in class for Floating Point values" ]
  43. }*/
  44. /*JSON{ "type":"property", "class": "Object", "name" : "length",
  45. "description" : "Find the length of the object",
  46. "generate" : "jswrap_object_length",
  47. "return" : ["JsVar", "The value of the string"]
  48. }*/
  49. JsVar *jswrap_object_length(JsVar *parent) {
  50. if (jsvIsArray(parent)) {
  51. return jsvNewFromInteger(jsvGetArrayLength(parent));
  52. } else if (jsvIsArrayBuffer(parent)) {
  53. return jsvNewFromInteger((JsVarInt)jsvGetArrayBufferLength(parent));
  54. } else if (jsvIsString(parent)) {
  55. return jsvNewFromInteger((JsVarInt)jsvGetStringLength(parent));
  56. }
  57. return 0;
  58. }
  59. /*JSON{ "type":"method", "class": "Object", "name" : "toString",
  60. "description" : "Convert the Object to a string",
  61. "generate" : "jswrap_object_toString",
  62. "params" : [ [ "radix", "JsVar", "If the object is an integer, the radix (between 2 and 36) to use. NOTE: Setting a radix does not work on floating point numbers."] ],
  63. "return" : ["JsVar", "A String representing the object"]
  64. }*/
  65. JsVar *jswrap_object_toString(JsVar *parent, JsVar *arg0) {
  66. if (jsvIsInt(arg0) && jsvIsInt(parent)) {
  67. JsVarInt radix = jsvGetInteger(arg0);
  68. if (radix>=2 && radix<=36) {
  69. char buf[JS_NUMBER_BUFFER_SIZE];
  70. itoa(parent->varData.integer, buf, (unsigned int)radix);
  71. return jsvNewFromString(buf);
  72. }
  73. }
  74. return jsvAsString(parent, false);
  75. }
  76. /*JSON{ "type":"method", "class": "Object", "name" : "clone",
  77. "description" : "Copy this object completely",
  78. "generate" : "jswrap_object_clone",
  79. "return" : ["JsVar", "A copy of this Object"]
  80. }*/
  81. JsVar *jswrap_object_clone(JsVar *parent) {
  82. return jsvCopy(parent);
  83. }
  84. /*JSON{ "type":"staticmethod", "class": "Object", "name" : "keys",
  85. "description" : "Return all enumerable keys of the given object",
  86. "generate" : "jswrap_object_keys",
  87. "params" : [ [ "object", "JsVar", "The object to return keys for"] ],
  88. "return" : ["JsVar", "An array of strings - one for each key on the given object"]
  89. }*/
  90. JsVar *jswrap_object_keys(JsVar *obj) {
  91. if (jsvIsIterable(obj)) {
  92. bool (*checkerFunction)(JsVar*) = 0;
  93. if (jsvIsFunction(obj)) checkerFunction = jsvIsInternalFunctionKey;
  94. else if (jsvIsObject(obj)) checkerFunction = jsvIsInternalObjectKey;
  95. JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
  96. if (!arr) return 0;
  97. JsvIterator it;
  98. jsvIteratorNew(&it, obj);
  99. while (jsvIteratorHasElement(&it)) {
  100. JsVar *key = jsvIteratorGetKey(&it);
  101. if (!(checkerFunction && checkerFunction(key))) {
  102. JsVar *name = jsvCopyNameOnly(key,false,false);
  103. if (name) {
  104. jsvArrayPushAndUnLock(arr, name);
  105. }
  106. }
  107. jsvUnLock(key);
  108. jsvIteratorNext(&it);
  109. }
  110. jsvIteratorFree(&it);
  111. return arr;
  112. } else {
  113. jsWarn("Object.keys called on non-object");
  114. return 0;
  115. }
  116. }
  117. /*JSON{ "type":"method", "class": "Object", "name" : "on",
  118. "description" : ["Register an event listener for this object, for instance ```http.on('data', function(d) {...})```. See Node.js's EventEmitter."],
  119. "generate" : "jswrap_object_on",
  120. "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"],
  121. [ "listener", "JsVar", "The listener to call when this event is received"] ]
  122. }*/
  123. void jswrap_object_on(JsVar *parent, JsVar *event, JsVar *listener) {
  124. if (!jsvIsObject(parent)) {
  125. jsWarn("Parent must be a proper object - not a String, Integer, etc.");
  126. return;
  127. }
  128. if (!jsvIsString(event)) {
  129. jsWarn("First argument to EventEmitter.on(..) must be a string");
  130. return;
  131. }
  132. if (!jsvIsFunction(listener) && !jsvIsString(listener)) {
  133. jsWarn("Second argument to EventEmitter.on(..) must be a function or a String (containing code)");
  134. return;
  135. }
  136. char eventName[16] = "#on";
  137. jsvGetString(event, &eventName[3], sizeof(eventName)-4);
  138. JsVar *eventList = jsvFindChildFromString(parent, eventName, true);
  139. JsVar *eventListeners = jsvSkipName(eventList);
  140. if (jsvIsUndefined(eventListeners)) {
  141. // just add
  142. jsvSetValueOfName(eventList, listener);
  143. } else {
  144. if (jsvIsArray(eventListeners)) {
  145. // we already have an array, just add to it
  146. jsvArrayPush(eventListeners, listener);
  147. } else {
  148. // not an array - we need to make it an array
  149. JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
  150. jsvArrayPush(arr, eventListeners);
  151. jsvArrayPush(arr, listener);
  152. jsvSetValueOfName(eventList, arr);
  153. jsvUnLock(arr);
  154. }
  155. }
  156. jsvUnLock(eventListeners);
  157. jsvUnLock(eventList);
  158. }
  159. /*JSON{ "type":"method", "class": "Object", "name" : "emit",
  160. "description" : ["Call the event listeners for this object, for instance ```http.emit('data', 'Foo')```. See Node.js's EventEmitter."],
  161. "generate" : "jswrap_object_emit",
  162. "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"],
  163. [ "v1", "JsVar", "Optional argument 1"],
  164. [ "v2", "JsVar", "Optional argument 2"] ]
  165. }*/
  166. void jswrap_object_emit(JsVar *parent, JsVar *event, JsVar *v1, JsVar *v2) {
  167. if (!jsvIsObject(parent)) {
  168. jsWarn("Parent must be a proper object - not a String, Integer, etc.");
  169. return;
  170. }
  171. if (!jsvIsString(event)) {
  172. jsWarn("First argument to EventEmitter.emit(..) must be a string");
  173. return;
  174. }
  175. char eventName[16] = "#on";
  176. jsvGetString(event, &eventName[3], sizeof(eventName)-4);
  177. jsiQueueObjectCallbacks(parent, eventName, v1, v2);
  178. }
  179. /*JSON{ "type":"method", "class": "Object", "name" : "removeAllListeners",
  180. "description" : ["Removes all listeners, or those of the specified event."],
  181. "generate" : "jswrap_object_removeAllListeners",
  182. "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"] ]
  183. }*/
  184. void jswrap_object_removeAllListeners(JsVar *parent, JsVar *event) {
  185. if (!jsvIsObject(parent)) {
  186. jsWarn("Parent must be a proper object - not a String, Integer, etc.");
  187. return;
  188. }
  189. if (jsvIsString(event)) {
  190. // remove the whole child containing listeners
  191. char eventName[16] = "#on";
  192. jsvGetString(event, &eventName[3], sizeof(eventName)-4);
  193. JsVar *eventList = jsvFindChildFromString(parent, eventName, true);
  194. if (eventList) {
  195. jsvRemoveChild(parent, eventList);
  196. jsvUnLock(eventList);
  197. }
  198. } else if (jsvIsUndefined(event)) {
  199. // Eep. We must remove everything beginning with '#on'
  200. JsObjectIterator it;
  201. jsvObjectIteratorNew(&it, parent);
  202. while (jsvObjectIteratorHasElement(&it)) {
  203. JsVar *key = jsvObjectIteratorGetKey(&it);
  204. jsvObjectIteratorNext(&it);
  205. if (jsvIsString(key) &&
  206. key->varData.str[0]=='#' &&
  207. key->varData.str[1]=='o' &&
  208. key->varData.str[2]=='n') {
  209. // begins with #on - we must kill it
  210. jsvRemoveChild(parent, key);
  211. }
  212. jsvUnLock(key);
  213. }
  214. jsvObjectIteratorFree(&it);
  215. } else {
  216. jsWarn("First argument to EventEmitter.removeAllListeners(..) must be a string, or undefined");
  217. return;
  218. }
  219. }
  220. // ------------------------------------------------------------------------------
  221. /*JSON{ "type":"method", "class": "Function", "name" : "replaceWith",
  222. "description" : ["This replaces the function with the one in the argument - while keeping the old function's scope. This allows inner functions to be edited, and is used when edit() is called on an inner function."],
  223. "generate" : "jswrap_function_replaceWith",
  224. "params" : [ [ "newFunc", "JsVar", "The new function to replace this function with"] ]
  225. }*/
  226. void jswrap_function_replaceWith(JsVar *oldFunc, JsVar *newFunc) {
  227. if (!jsvIsFunction(newFunc)) {
  228. jsWarn("First argument of replaceWith should be a function - ignoring");
  229. return;
  230. }
  231. // Grab scope - the one thing we want to keep
  232. JsVar *scope = jsvFindChildFromString(oldFunc, JSPARSE_FUNCTION_SCOPE_NAME, false);
  233. // so now remove all existing entries
  234. jsvRemoveAllChildren(oldFunc);
  235. // now re-add scope
  236. jsvAddName(oldFunc, scope);
  237. jsvUnLock(scope);
  238. // now re-add other entries
  239. JsObjectIterator it;
  240. jsvObjectIteratorNew(&it, newFunc);
  241. while (jsvObjectIteratorHasElement(&it)) {
  242. JsVar *el = jsvObjectIteratorGetKey(&it);
  243. jsvObjectIteratorNext(&it);
  244. if (!jsvIsStringEqual(el, JSPARSE_FUNCTION_SCOPE_NAME)) {
  245. JsVar *copy = jsvCopy(el);
  246. if (copy) {
  247. jsvAddName(oldFunc, copy);
  248. jsvUnLock(copy);
  249. }
  250. }
  251. }
  252. jsvObjectIteratorFree(&it);
  253. }
  254. /*JSON{ "type":"method", "class": "Function", "name" : "call",
  255. "description" : ["This executes the function with the supplied 'this' argument and parameters"],
  256. "generate" : "jswrap_function_call",
  257. "params" : [ [ "this", "JsVar", "The value to use as the 'this' argument when executing the function"],
  258. [ "a", "JsVar", "Optional Parameter 1"],
  259. [ "b", "JsVar", "Optional Parameter 2"],
  260. [ "c", "JsVar", "Optional Parameter 3"],
  261. [ "d", "JsVar", "Optional Parameter 4"]
  262. ],
  263. "return" : [ "JsVar", "The return value of executing this function" ]
  264. }*/
  265. JsVar *jswrap_function_call(JsVar *parent, JsVar *thisArg, JsVar *a, JsVar *b, JsVar *c, JsVar *d) {
  266. JsVar *args[4] = {a,b,c,d};
  267. int argC = 0;
  268. while (argC<4 && args[argC]!=0) argC++;
  269. return jspeFunctionCall(parent, 0, thisArg, false, argC, args);
  270. }
  271. /*JSON{ "type":"method", "class": "Function", "name" : "apply",
  272. "description" : ["This executes the function with the supplied 'this' argument and parameters"],
  273. "generate" : "jswrap_function_apply",
  274. "params" : [ [ "this", "JsVar", "The value to use as the 'this' argument when executing the function"],
  275. [ "args", "JsVar", "Optional Array of Aruments"]
  276. ],
  277. "return" : [ "JsVar", "The return value of executing this function" ]
  278. }*/
  279. JsVar *jswrap_function_apply(JsVar *parent, JsVar *thisArg, JsVar *argsArray) {
  280. unsigned int i;
  281. JsVar **args = 0;
  282. size_t argC = 0;
  283. if (jsvIsArray(argsArray)) {
  284. argC = (unsigned int)jsvGetArrayLength(argsArray);
  285. if (argC>64) argC=64; // sanity
  286. #ifdef RT_USING_JS
  287. args = (JsVar**)rt_malloc((size_t)argC * sizeof(JsVar*));
  288. #else
  289. args = (JsVar**)alloca((size_t)argC * sizeof(JsVar*));
  290. #endif
  291. for (i=0;i<argC;i++) args[i] = 0;
  292. JsArrayIterator it;
  293. jsvArrayIteratorNew(&it, argsArray);
  294. while (jsvArrayIteratorHasElement(&it)) {
  295. JsVarInt idx = jsvGetIntegerAndUnLock(jsvArrayIteratorGetIndex(&it));
  296. if (idx>=0 && idx<argC) {
  297. assert(!args[idx]); // just in case there were dups
  298. args[idx] = jsvArrayIteratorGetElement(&it);
  299. }
  300. jsvArrayIteratorNext(&it);
  301. }
  302. jsvArrayIteratorFree(&it);
  303. } else if (!jsvIsUndefined(argsArray)) {
  304. jsWarn("Second argument to Function.apply must be an array");
  305. }
  306. JsVar *r = jspeFunctionCall(parent, 0, thisArg, false, (int)argC, args);
  307. for (i=0;i<argC;i++) jsvUnLock(args[i]);
  308. #ifdef RT_USING_JS
  309. rt_free(args);
  310. #else
  311. #error "please define RT_USING_JS on rtconfig.h"
  312. #endif
  313. return r;
  314. }