summaryrefslogtreecommitdiff
path: root/components/external/espruino/src
diff options
context:
space:
mode:
Diffstat (limited to 'components/external/espruino/src')
-rw-r--r--components/external/espruino/src/jsdevices.c336
-rw-r--r--components/external/espruino/src/jsdevices.h127
-rw-r--r--components/external/espruino/src/jshardware.h223
-rw-r--r--components/external/espruino/src/jsinteractive.c1686
-rw-r--r--components/external/espruino/src/jsinteractive.h105
-rw-r--r--components/external/espruino/src/jslex.c501
-rw-r--r--components/external/espruino/src/jslex.h59
-rw-r--r--components/external/espruino/src/jsparse.c2213
-rw-r--r--components/external/espruino/src/jsparse.h126
-rw-r--r--components/external/espruino/src/jspin.c150
-rw-r--r--components/external/espruino/src/jspin.h228
-rw-r--r--components/external/espruino/src/jsutils.c439
-rw-r--r--components/external/espruino/src/jsutils.h374
-rw-r--r--components/external/espruino/src/jsvar.c2542
-rw-r--r--components/external/espruino/src/jsvar.h612
-rw-r--r--components/external/espruino/src/jswrap_array.c328
-rw-r--r--components/external/espruino/src/jswrap_array.h23
-rw-r--r--components/external/espruino/src/jswrap_arraybuffer.c315
-rw-r--r--components/external/espruino/src/jswrap_arraybuffer.h19
-rw-r--r--components/external/espruino/src/jswrap_functions.c102
-rw-r--r--components/external/espruino/src/jswrap_functions.h19
-rw-r--r--components/external/espruino/src/jswrap_interactive.c203
-rw-r--r--components/external/espruino/src/jswrap_interactive.h24
-rw-r--r--components/external/espruino/src/jswrap_io.c437
-rw-r--r--components/external/espruino/src/jswrap_io.h29
-rw-r--r--components/external/espruino/src/jswrap_json.c167
-rw-r--r--components/external/espruino/src/jswrap_json.h33
-rw-r--r--components/external/espruino/src/jswrap_modules.c195
-rw-r--r--components/external/espruino/src/jswrap_modules.h21
-rw-r--r--components/external/espruino/src/jswrap_object.c332
-rw-r--r--components/external/espruino/src/jswrap_object.h27
-rw-r--r--components/external/espruino/src/jswrap_onewire.c332
-rw-r--r--components/external/espruino/src/jswrap_onewire.h23
-rw-r--r--components/external/espruino/src/jswrap_pin.c73
-rw-r--r--components/external/espruino/src/jswrap_pin.h21
-rw-r--r--components/external/espruino/src/jswrap_process.c94
-rw-r--r--components/external/espruino/src/jswrap_process.h19
-rw-r--r--components/external/espruino/src/jswrap_serial.c197
-rw-r--r--components/external/espruino/src/jswrap_serial.h21
-rw-r--r--components/external/espruino/src/jswrap_spi_i2c.c455
-rw-r--r--components/external/espruino/src/jswrap_spi_i2c.h24
-rw-r--r--components/external/espruino/src/jswrap_string.c173
-rw-r--r--components/external/espruino/src/jswrap_string.h23
-rw-r--r--components/external/espruino/src/jswrapper.h36
44 files changed, 13486 insertions, 0 deletions
diff --git a/components/external/espruino/src/jsdevices.c b/components/external/espruino/src/jsdevices.c
new file mode 100644
index 0000000000..4c7c9715f9
--- /dev/null
+++ b/components/external/espruino/src/jsdevices.c
@@ -0,0 +1,336 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Common low-level device handling (Events, IO buffers)
+ * ----------------------------------------------------------------------------
+ */
+#include "jsdevices.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+
+#ifdef LINUX
+ #include <signal.h>
+#endif//LINUX
+#ifdef USE_TRIGGER
+#include "trigger.h"
+#endif
+#ifdef USE_CC3000
+#include "board_spi.h"
+#endif
+// ----------------------------------------------------------------------------
+// BUFFERS
+
+// ----------------------------------------------------------------------------
+// DATA TRANSMIT BUFFER
+typedef struct {
+ IOEventFlags flags; // Where this data should be transmitted
+ unsigned char data; // data to transmit
+} PACKED_FLAGS TxBufferItem;
+
+TxBufferItem txBuffer[TXBUFFERMASK+1];
+volatile unsigned char txHead=0, txTail=0;
+// ----------------------------------------------------------------------------
+
+// Queue a character for transmission
+void jshTransmit(IOEventFlags device, unsigned char data) {
+#ifdef RT_USING_JS
+ rt_device_write(rt_console_get_device(), 0, &data, 1);
+#else
+#ifndef LINUX
+#ifdef USB
+ if (device==EV_USBSERIAL && !jshIsUSBSERIALConnected()) {
+ jshTransmitClearDevice(EV_USBSERIAL); // clear out stuff already waiting
+ return;
+ }
+#endif
+ if (device==EV_NONE) return;
+ unsigned char txHeadNext = (txHead+1)&TXBUFFERMASK;
+ if (txHeadNext==txTail) {
+ jsiSetBusy(BUSY_TRANSMIT, true);
+ while (txHeadNext==txTail) {
+ // wait for send to finish as buffer is about to overflow
+#ifdef USB
+ // just in case USB was unplugged while we were waiting!
+ if (!jshIsUSBSERIALConnected()) jshTransmitClearDevice(EV_USBSERIAL);
+#endif
+ }
+ jsiSetBusy(BUSY_TRANSMIT, false);
+ }
+ txBuffer[txHead].flags = device;
+ txBuffer[txHead].data = (char)data;
+ txHead = txHeadNext;
+
+ jshUSARTKick(device); // set up interrupts if required
+
+#else // if PC, just put to stdout
+ if (device==DEFAULT_CONSOLE_DEVICE) {
+ fputc(data, stdout);
+ fflush(stdout);
+ }
+#endif
+#endif
+}
+
+// Try and get a character for transmission - could just return -1 if nothing
+int jshGetCharToTransmit(IOEventFlags device) {
+ unsigned char ptr = txTail;
+ while (txHead != ptr) {
+ if (IOEVENTFLAGS_GETTYPE(txBuffer[ptr].flags) == device) {
+ unsigned char data = txBuffer[ptr].data;
+ if (ptr != txTail) { // so we weren't right at the back of the queue
+ // we need to work back from ptr (until we hit tail), shifting everything forwards
+ unsigned char this = ptr;
+ unsigned char last = (unsigned char)((this+TXBUFFERMASK)&TXBUFFERMASK);
+ while (this!=txTail) { // if this==txTail, then last is before it, so stop here
+ txBuffer[this] = txBuffer[last];
+ this = last;
+ last = (unsigned char)((this+TXBUFFERMASK)&TXBUFFERMASK);
+ }
+ }
+ txTail = (unsigned char)((txTail+1)&TXBUFFERMASK); // advance the tail
+ return data; // return data
+ }
+ ptr = (unsigned char)((ptr+1)&TXBUFFERMASK);
+ }
+ return -1; // no data :(
+}
+
+void jshTransmitFlush() {
+ jsiSetBusy(BUSY_TRANSMIT, true);
+ while (jshHasTransmitData()) ; // wait for send to finish
+ jsiSetBusy(BUSY_TRANSMIT, false);
+}
+
+// Clear everything from a device
+void jshTransmitClearDevice(IOEventFlags device) {
+ while (jshGetCharToTransmit(device)>=0);
+}
+
+bool jshHasTransmitData() {
+#ifndef LINUX
+ return txHead != txTail;
+#else
+ return false;
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// IO EVENT BUFFER
+IOEvent ioBuffer[IOBUFFERMASK+1];
+volatile unsigned char ioHead=0, ioTail=0;
+// ----------------------------------------------------------------------------
+
+
+void jshIOEventOverflowed() {
+ // Error here - just light LED as we can't do much else right now
+}
+
+
+void jshPushIOCharEvent(IOEventFlags channel, char charData) {
+ if (charData==3 && channel==jsiGetConsoleDevice()) {
+ // Ctrl-C - force interrupt
+#ifdef LINUX
+ raise(SIGINT);
+#endif
+ jspSetInterrupted(true);
+ return;
+ }
+ if (DEVICE_IS_USART(channel) && jshGetEventsUsed() > IOBUFFER_XOFF)
+ jshSetFlowControlXON(channel, false);
+ // Check for existing buffer (we must have at least 2 in the queue to avoid dropping chars though!)
+ unsigned char nextTail = (unsigned char)((ioTail+1) & IOBUFFERMASK);
+ if (ioHead!=ioTail && ioHead!=nextTail) {
+ // we can do this because we only read in main loop, and we're in an interrupt here
+ unsigned char lastHead = (unsigned char)((ioHead+IOBUFFERMASK) & IOBUFFERMASK); // one behind head
+ if (IOEVENTFLAGS_GETTYPE(ioBuffer[lastHead].flags) == channel &&
+ IOEVENTFLAGS_GETCHARS(ioBuffer[lastHead].flags) < IOEVENT_MAXCHARS) {
+ // last event was for this event type, and it has chars left
+ unsigned char c = (unsigned char)IOEVENTFLAGS_GETCHARS(ioBuffer[lastHead].flags);
+ ioBuffer[lastHead].data.chars[c] = charData;
+ IOEVENTFLAGS_SETCHARS(ioBuffer[lastHead].flags, c+1);
+ return;
+ }
+ }
+ // Make new buffer
+ unsigned char nextHead = (unsigned char)((ioHead+1) & IOBUFFERMASK);
+ if (ioTail == nextHead) {
+ jshIOEventOverflowed();
+ return; // queue full - dump this event!
+ }
+ ioBuffer[ioHead].flags = channel;
+ IOEVENTFLAGS_SETCHARS(ioBuffer[ioHead].flags, 1);
+ ioBuffer[ioHead].data.chars[0] = charData;
+ ioHead = nextHead;
+}
+
+void jshPushIOWatchEvent(IOEventFlags channel) {
+ JsSysTime time = jshGetSystemTime();
+ bool state = jshGetWatchedPinState(channel);
+
+/* // This is some simple debounce code - however it requires that the event
+ // is not taken out of ioBuffer by the main thread, which will require
+ // a bit of fiddling in jsinteractive.c. In fact it might be worth just
+ // doing debounce outside of the interrupt
+ if (true) { // debounce
+ // scan back and see if we have an event for this pin
+ unsigned char prevHead = ioHead;
+ while (prevHead!=ioTail && (IOEVENTFLAGS_GETTYPE(ioBuffer[prevHead].flags)!=channel))
+ prevHead = (unsigned char)((prevHead+IOBUFFERMASK) & IOBUFFERMASK); // step back
+ // if we have an event
+ if (prevHead!=ioTail && (IOEVENTFLAGS_GETTYPE(ioBuffer[prevHead].flags)==channel)) {
+ // just use it (with the same timestamp)...
+ ioBuffer[prevHead].flags = channel | (state?EV_EXTI_IS_HIGH:0);
+ return;
+ }
+ }*/
+
+#ifdef USE_TRIGGER
+ if (trigHandleEXTI(channel | (state?EV_EXTI_IS_HIGH:0), time))
+ return;
+#endif
+#ifdef USE_CC3000
+ IOEvent event;
+ event.flags = channel;
+ if (!state && jshIsEventForPin(&event, WLAN_IRQ_PIN)) {
+ cc3000_irq_handler();
+ }
+#endif
+ // Otherwise add this event
+ jshPushIOEvent(channel | (state?EV_EXTI_IS_HIGH:0), time);
+}
+
+void jshPushIOEvent(IOEventFlags channel, JsSysTime time) {
+
+ unsigned char nextHead = (unsigned char)((ioHead+1) & IOBUFFERMASK);
+ if (ioTail == nextHead) {
+ jshIOEventOverflowed();
+ return; // queue full - dump this event!
+ }
+ ioBuffer[ioHead].flags = channel;
+ ioBuffer[ioHead].data.time = time;
+ ioHead = nextHead;
+}
+
+// returns true on success
+bool jshPopIOEvent(IOEvent *result) {
+ if (ioHead==ioTail) return false;
+ *result = ioBuffer[ioTail];
+ ioTail = (unsigned char)((ioTail+1) & IOBUFFERMASK);
+ return true;
+}
+
+bool jshHasEvents() {
+ return ioHead!=ioTail;
+}
+
+int jshGetEventsUsed() {
+ int spaceUsed = (ioHead >= ioTail) ? ((int)ioHead-(int)ioTail) : /*or rolled*/((int)ioHead+IOBUFFERMASK+1-(int)ioTail);
+ return spaceUsed;
+}
+
+bool jshHasEventSpaceForChars(int n) {
+ int spacesNeeded = 4 + (n/IOEVENT_MAXCHARS); // be sensible - leave a little spare
+ int spaceUsed = jshGetEventsUsed();
+ int spaceLeft = IOBUFFERMASK+1-spaceUsed;
+ return spaceLeft > spacesNeeded;
+}
+
+// ----------------------------------------------------------------------------
+// DEVICES
+const char *jshGetDeviceString(IOEventFlags device) {
+ switch (device) {
+#ifdef USB
+ case EV_USBSERIAL: return "USB";
+#endif
+ case EV_SERIAL1: return "Serial1";
+ case EV_SERIAL2: return "Serial2";
+ case EV_SERIAL3: return "Serial3";
+#if USARTS>=4
+ case EV_SERIAL4: return "Serial4";
+#endif
+#if USARTS>=5
+ case EV_SERIAL5: return "Serial5";
+#endif
+#if USARTS>=6
+ case EV_SERIAL6: return "Serial6";
+#endif
+#if SPIS>=1
+ case EV_SPI1: return "SPI1";
+#endif
+#if SPIS>=2
+ case EV_SPI2: return "SPI2";
+#endif
+#if SPIS>=3
+ case EV_SPI3: return "SPI3";
+#endif
+#if I2CS>=1
+ case EV_I2C1: return "I2C1";
+#endif
+#if I2CS>=2
+ case EV_I2C2: return "I2C2";
+#endif
+#if I2CS>=3
+ case EV_I2C3: return "I2C3";
+#endif
+ default: return "";
+ }
+}
+
+IOEventFlags jshFromDeviceString(const char *device) {
+ if (device[0]=='U') {
+#ifdef USB
+ if (strcmp(device, "USB")==0) return EV_USBSERIAL;
+#endif
+ }
+ else if (device[0]=='S') {
+ if (device[1]=='e' && device[2]=='r' && device[3]=='i' && device[4]=='a' && device[5]=='l') {
+ if (strcmp(device, "Serial1")==0) return EV_SERIAL1;
+ if (strcmp(device, "Serial2")==0) return EV_SERIAL2;
+ if (strcmp(device, "Serial3")==0) return EV_SERIAL3;
+#if USARTS>=4
+ if (strcmp(device, "Serial4")==0) return EV_SERIAL4;
+#endif
+#if USARTS>=5
+ if (strcmp(device, "Serial5")==0) return EV_SERIAL5;
+#endif
+#if USARTS>=6
+ if (strcmp(device, "Serial6")==0) return EV_SERIAL6;
+#endif
+ }
+ if (device[1]=='P' && device[2]=='I') {
+#if SPIS>=1
+ if (strcmp(device, "SPI1")==0) return EV_SPI1;
+#endif
+#if SPIS>=2
+ if (strcmp(device, "SPI2")==0) return EV_SPI2;
+#endif
+#if SPIS>=3
+ if (strcmp(device, "SPI3")==0) return EV_SPI3;
+#endif
+ }
+ }
+ else if (device[0]=='I' && device[1]=='2' && device[2]=='C') {
+#if I2CS>=1
+ if (strcmp(device, "I2C1")==0) return EV_I2C1;
+#endif
+#if I2CS>=2
+ if (strcmp(device, "I2C2")==0) return EV_I2C2;
+#endif
+#if I2CS>=3
+ if (strcmp(device, "I2C3")==0) return EV_I2C3;
+#endif
+ }
+ return EV_NONE;
+}
+
+/// Set whether the host should transmit or not
+void jshSetFlowControlXON(IOEventFlags device, bool hostShouldTransmit) {
+}
+
diff --git a/components/external/espruino/src/jsdevices.h b/components/external/espruino/src/jsdevices.h
new file mode 100644
index 0000000000..020a7ee332
--- /dev/null
+++ b/components/external/espruino/src/jsdevices.h
@@ -0,0 +1,127 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Common low-level device handling (Events, IO buffers)
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSDEVICES_H_
+#define JSDEVICES_H_
+
+#include "jsutils.h"
+#include "platform_config.h"
+
+typedef enum {
+ // device type
+ EV_NONE,
+ EV_EXTI0,
+ EV_EXTI1,
+ EV_EXTI2,
+ EV_EXTI3,
+ EV_EXTI4,
+ EV_EXTI5,
+ EV_EXTI6,
+ EV_EXTI7,
+ EV_EXTI8,
+ EV_EXTI9,
+ EV_EXTI10,
+ EV_EXTI11,
+ EV_EXTI12,
+ EV_EXTI13,
+ EV_EXTI14,
+ EV_EXTI15,
+ EV_EXTI_MAX = EV_EXTI15,
+ EV_USBSERIAL,
+ EV_SERIAL1,
+ EV_SERIAL2,
+ EV_SERIAL3,
+ EV_SERIAL4,
+ EV_SERIAL5,
+ EV_SERIAL6,
+ EV_SERIAL_MAX = EV_SERIAL6,
+ EV_SPI1,
+ EV_SPI2,
+ EV_SPI3,
+ EV_SPI_MAX = EV_SPI3,
+ EV_I2C1,
+ EV_I2C2,
+ EV_I2C3,
+ EV_I2C_MAX = EV_I2C3,
+ EV_DEVICE_MAX = EV_I2C_MAX,
+ // EV_DEVICE_MAX should not be >64 - see DEVICE_INITIALISED_FLAGS
+ // Also helps if we're under 32 so we can fit IOEventFlags into a byte
+ EV_TYPE_MASK = NEXT_POWER_2(EV_DEVICE_MAX) - 1,
+ EV_CHARS_MASK = 7 * NEXT_POWER_2(EV_DEVICE_MAX),
+ // -----------------------------------------
+ // if the pin we're watching is high, the handler sets this
+ EV_EXTI_IS_HIGH = NEXT_POWER_2(EV_DEVICE_MAX),
+} PACKED_FLAGS IOEventFlags;
+
+#define DEVICE_IS_USART(X) (((X)>=EV_USBSERIAL)&& ((X)<=EV_SERIAL_MAX))
+#define DEVICE_IS_SPI(X) (((X)>=EV_SPI1) || ((X)<=EV_SPI_MAX))
+#define DEVICE_IS_I2C(X) (((X)>=EV_I2C1) || ((X)<=EV_I2C_MAX))
+#define DEVICE_IS_EXTI(X) (((X)>=EV_EXTI0) || ((X)<=EV_EXTI_MAX))
+
+#define IOEVENTFLAGS_GETTYPE(X) ((X)&EV_TYPE_MASK)
+#define IOEVENTFLAGS_GETCHARS(X) ((((X)&EV_CHARS_MASK)>>5)+1)
+#define IOEVENTFLAGS_SETCHARS(X,CHARS) ((X)=(((X)&(IOEventFlags)~EV_CHARS_MASK) | (((CHARS)-1)<<5)))
+#define IOEVENT_MAXCHARS 8
+
+typedef union {
+ JsSysTime time; // time event occurred
+ char chars[IOEVENT_MAXCHARS];
+} PACKED_FLAGS IOEventData;
+
+// IO Events - these happen when a pin changes
+typedef struct IOEvent {
+ IOEventFlags flags; // where this came from, and # of chars in it
+ IOEventData data;
+} PACKED_FLAGS IOEvent;
+
+void jshPushIOEvent(IOEventFlags channel, JsSysTime time);
+void jshPushIOWatchEvent(IOEventFlags channel); // push an even when a pin changes state
+/// Push a single character event (for example USART RX)
+void jshPushIOCharEvent(IOEventFlags channel, char charData);
+/// Push many character events at once (for example USB RX)
+static inline void jshPushIOCharEvents(IOEventFlags channel, char *data, unsigned int count) {
+ // TODO: optimise me!
+ unsigned int i;
+ for (i=0;i<count;i++) jshPushIOCharEvent(channel, data[i]);
+}
+bool jshPopIOEvent(IOEvent *result); ///< returns true on success
+/// Do we have any events pending? Will jshPopIOEvent return true?
+bool jshHasEvents();
+
+/// How many event blocks are left? compare this to IOBUFFERMASK
+int jshGetEventsUsed();
+
+/// Do we have enough space for N characters?
+bool jshHasEventSpaceForChars(int n);
+
+const char *jshGetDeviceString(IOEventFlags device);
+IOEventFlags jshFromDeviceString(const char *device);
+
+// ----------------------------------------------------------------------------
+// DATA TRANSMIT BUFFER
+/// Queue a character for transmission
+void jshTransmit(IOEventFlags device, unsigned char data);
+/// Wait for transmit to finish
+void jshTransmitFlush();
+/// Clear everything from a device
+void jshTransmitClearDevice(IOEventFlags device);
+/// Do we have anything we need to send?
+bool jshHasTransmitData();
+/// Try and get a character for transmission - could just return -1 if nothing
+int jshGetCharToTransmit(IOEventFlags device);
+
+
+/// Set whether the host should transmit or not
+void jshSetFlowControlXON(IOEventFlags device, bool hostShouldTransmit);
+
+#endif /* JSDEVICES_H_ */
diff --git a/components/external/espruino/src/jshardware.h b/components/external/espruino/src/jshardware.h
new file mode 100644
index 0000000000..e2016d2bb9
--- /dev/null
+++ b/components/external/espruino/src/jshardware.h
@@ -0,0 +1,223 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Hardware interface Layer
+ * NOTE: The definitions of these functions are inside:
+ * targets/{target}/jshardware.c
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef JSHARDWARE_H_
+#define JSHARDWARE_H_
+
+#include "jsutils.h"
+#include "jsvar.h"
+#include "jsdevices.h"
+#include "jspin.h"
+#ifndef LINUX
+#include "jspininfo.h"
+#else
+#include <inttypes.h>
+#endif
+
+void jshInit();
+void jshKill();
+void jshIdle(); // stuff to do on idle
+
+/// Get this IC's serial number. Passed max # of chars and a pointer to write to. Returns # of chars
+int jshGetSerialNumber(unsigned char *data, int maxChars);
+
+bool jshIsUSBSERIALConnected(); // is the serial device connected?
+
+/// Get the system time (in ticks)
+JsSysTime jshGetSystemTime();
+/// Convert a time in Milliseconds to one in ticks
+JsSysTime jshGetTimeFromMilliseconds(JsVarFloat ms);
+/// Convert ticks to a time in Milliseconds
+JsVarFloat jshGetMillisecondsFromTime(JsSysTime time);
+
+// software IO functions...
+void jshInterruptOff();
+void jshInterruptOn();
+void jshDelayMicroseconds(int microsec);
+void jshPinSetValue(Pin pin, bool value);
+bool jshPinGetValue(Pin pin);
+// ------
+
+typedef enum {
+ JSHPINSTATE_UNDEFINED,
+ JSHPINSTATE_GPIO_OUT,
+ JSHPINSTATE_GPIO_OUT_OPENDRAIN,
+ JSHPINSTATE_GPIO_IN,
+ JSHPINSTATE_GPIO_IN_PULLUP,
+ JSHPINSTATE_GPIO_IN_PULLDOWN,
+ JSHPINSTATE_ADC_IN,
+ JSHPINSTATE_AF_OUT,
+ JSHPINSTATE_USART_IN,
+ JSHPINSTATE_USART_OUT,
+ JSHPINSTATE_DAC_OUT,
+ JSHPINSTATE_I2C,
+ JSHPINSTATE_MASK = NEXT_POWER_2(JSHPINSTATE_I2C)-1,
+
+ JSHPINSTATE_PIN_IS_ON = JSHPINSTATE_MASK+1,
+} PACKED_FLAGS JshPinState;
+
+#define JSHPINSTATE_IS_OUTPUT(state) ( \
+ state==JSHPINSTATE_GPIO_OUT || \
+ state==JSHPINSTATE_GPIO_OUT_OPENDRAIN || \
+ state==JSHPINSTATE_AF_OUT || \
+ state==JSHPINSTATE_USART_OUT || \
+ state==JSHPINSTATE_DAC_OUT || \
+ state==JSHPINSTATE_I2C \
+)
+
+/// Is the pin state manual (has the user asked us explicitly to change it?)
+bool jshGetPinStateIsManual(Pin pin);
+/// Is the pin state manual (has the user asked us explicitly to change it?)
+void jshSetPinStateIsManual(Pin pin, bool manual);
+/// Set the pin state
+void jshPinSetState(Pin pin, JshPinState state);
+/** Get the pin state (only accurate for simple IO - won't return JSHPINSTATE_USART_OUT for instance).
+ * Note that you should use JSHPINSTATE_MASK as other flags may have been added */
+JshPinState jshPinGetState(Pin pin);
+
+
+bool jshPinInput(Pin pin);
+JsVarFloat jshPinAnalog(Pin pin);
+void jshPinOutput(Pin pin, bool value);
+void jshPinAnalogOutput(Pin pin, JsVarFloat value, JsVarFloat freq); // if freq<=0, the default is used
+void jshPinPulse(Pin pin, bool value, JsVarFloat time);
+void jshPinWatch(Pin pin, bool shouldWatch);
+/// returns false if timer queue was full...
+bool jshPinOutputAtTime(JsSysTime time, Pin pin, bool value);
+
+/** Check the pin associated with this EXTI - return true if it is a 1 */
+bool jshGetWatchedPinState(IOEventFlags device);
+
+bool jshIsEventForPin(IOEvent *event, Pin pin);
+
+/** Is the given device initialised? */
+bool jshIsDeviceInitialised(IOEventFlags device);
+
+
+#define DEFAULT_BAUD_RATE 9600
+#define DEFAULT_BYTESIZE 8
+#define DEFAULT_PARITY 0
+#define DEFAULT_STOPBITS 1
+
+typedef struct {
+ int baudRate; // FIXME uint32_t ???
+ Pin pinRX;
+ Pin pinTX;
+ unsigned char bytesize;
+ unsigned char parity;
+ unsigned char stopbits;
+} PACKED_FLAGS JshUSARTInfo;
+
+static inline void jshUSARTInitInfo(JshUSARTInfo *inf) {
+ inf->baudRate = DEFAULT_BAUD_RATE;
+ inf->pinRX = PIN_UNDEFINED;
+ inf->pinTX = PIN_UNDEFINED;
+ inf->bytesize = DEFAULT_BYTESIZE;
+ inf->parity = DEFAULT_PARITY; // PARITY_NONE = 0, PARITY_ODD = 1, PARITY_EVEN = 2 FIXME: enum?
+ inf->stopbits = DEFAULT_STOPBITS;
+}
+
+/** Set up a UART, if pins are -1 they will be guessed */
+void jshUSARTSetup(IOEventFlags device, JshUSARTInfo *inf);
+/** Kick a device into action (if required). For instance we may need
+ * to set up interrupts */
+void jshUSARTKick(IOEventFlags device);
+
+typedef enum {
+ SPIF_CPHA = 1,
+ SPIF_CPOL = 2,
+ SPIF_SPI_MODE_0 = 0,
+ SPIF_SPI_MODE_1 = SPIF_CPHA,
+ SPIF_SPI_MODE_2 = SPIF_CPOL,
+ SPIF_SPI_MODE_3 = SPIF_CPHA | SPIF_CPOL,
+ /* Mode CPOL CPHA
+ 0 0 0
+ 1 0 1
+ 2 1 0
+ 3 1 1
+ */
+
+} PACKED_FLAGS JshSPIFlags;
+
+typedef struct {
+ int baudRate;
+ Pin pinSCK;
+ Pin pinMISO;
+ Pin pinMOSI;
+ unsigned char spiMode;
+} PACKED_FLAGS JshSPIInfo;
+static inline void jshSPIInitInfo(JshSPIInfo *inf) {
+ inf->baudRate = 1000000;
+ inf->pinSCK = PIN_UNDEFINED;
+ inf->pinMISO = PIN_UNDEFINED;
+ inf->pinMOSI = PIN_UNDEFINED;
+ inf->spiMode = SPIF_SPI_MODE_0;
+}
+
+/** Set up SPI, if pins are -1 they will be guessed */
+void jshSPISetup(IOEventFlags device, JshSPIInfo *inf);
+/** Send data through the given SPI device (if data>=0), and return the result
+ * of the previous send (or -1). If data<0, no data is sent and the function
+ * waits for data to be returned */
+int jshSPISend(IOEventFlags device, int data);
+/** Send 16 bit data through the given SPI device. */
+void jshSPISend16(IOEventFlags device, int data);
+/** Set whether to send 16 bits or 8 over SPI */
+void jshSPISet16(IOEventFlags device, bool is16);
+
+typedef struct {
+ Pin pinSCL;
+ Pin pinSDA;
+ char slaveAddr; // or -1 if it is master!
+ // speed? 100khz std
+ // timeout?
+} PACKED_FLAGS JshI2CInfo;
+static inline void jshI2CInitInfo(JshI2CInfo *inf) {
+ inf->pinSCL = PIN_UNDEFINED;
+ inf->pinSDA = PIN_UNDEFINED;
+ inf->slaveAddr = (char)-1; // master
+}
+/** Set up I2C, if pins are -1 they will be guessed */
+void jshI2CSetup(IOEventFlags device, JshI2CInfo *inf);
+/** Addresses are 7 bit - that is, between 0 and 0x7F */
+void jshI2CWrite(IOEventFlags device, unsigned char address, int nBytes, const unsigned char *data);
+void jshI2CRead(IOEventFlags device, unsigned char address, int nBytes, unsigned char *data);
+
+
+/// Save contents of JsVars into Flash
+void jshSaveToFlash();
+/// Load contents of JsVars from Flash
+void jshLoadFromFlash();
+/// Returns true if flash contains something useful
+bool jshFlashContainsCode();
+
+/// Enter simple sleep mode (can be woken up by interrupts). Returns true on success
+bool jshSleep(JsSysTime timeUntilWake);
+
+// ---------------------------------------------- LOW LEVEL
+#ifdef ARM
+// ----------------------------------------------------------------------------
+// SYSTICK
+// On SYSTick interrupt, call this
+void jshDoSysTick();
+#ifdef USB
+// Kick the USB SysTick watchdog - we need this to see if we have disconnected or not
+void jshKickUSBWatchdog();
+#endif
+
+#endif // ARM
+
+#endif /* JSHARDWARE_H_ */
diff --git a/components/external/espruino/src/jsinteractive.c b/components/external/espruino/src/jsinteractive.c
new file mode 100644
index 0000000000..9e63fed94e
--- /dev/null
+++ b/components/external/espruino/src/jsinteractive.c
@@ -0,0 +1,1686 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Interactive Shell implementation
+ * ----------------------------------------------------------------------------
+ */
+#include "jsutils.h"
+#include "jsinteractive.h"
+#include "jshardware.h"
+#include "jswrapper.h"
+#include "jswrap_json.h"
+#include "jswrap_io.h"
+#ifdef USE_NET
+#include "httpserver.h"
+#endif
+#ifdef USE_LCD_FSMC
+#include "graphics.h"
+#include "lcd_fsmc.h"
+#endif
+
+#ifdef ARM
+#define CHAR_DELETE_SEND 0x08
+#else
+#define CHAR_DELETE_SEND '\b'
+#endif
+
+// ----------------------------------------------------------------------------
+typedef struct TimerState {
+ JsSysTime time;
+ JsSysTime interval;
+ bool recurring;
+ JsVarRef callback; // a calback, or 0
+} TimerState;
+
+typedef enum {
+ IS_NONE,
+ IS_HAD_R,
+ IS_HAD_27,
+ IS_HAD_27_79,
+ IS_HAD_27_91,
+ IS_HAD_27_91_49,
+ IS_HAD_27_91_51,
+ IS_HAD_27_91_52,
+ IS_HAD_27_91_53,
+ IS_HAD_27_91_54,
+} InputState;
+
+TODOFlags todo = TODO_NOTHING;
+JsVar *events = 0; // Array of events to execute
+JsVarRef timerArray = 0; // Linked List of timers to check and run
+JsVarRef watchArray = 0; // Linked List of input watches to check and run
+// ----------------------------------------------------------------------------
+IOEventFlags consoleDevice = DEFAULT_CONSOLE_DEVICE; ///< The console device for user interaction
+Pin pinBusyIndicator = DEFAULT_BUSY_PIN_INDICATOR;
+Pin pinSleepIndicator = DEFAULT_SLEEP_PIN_INDICATOR;
+bool echo; ///< do we provide any user feedback?
+bool allowDeepSleep;
+// ----------------------------------------------------------------------------
+JsParse p; ///< The parser we're using for interactiveness
+JsVar *inputLine = 0; ///< The current input line
+bool inputLineRemoved = false;
+int inputCursorPos = 0; ///< The position of the cursor in the input line
+InputState inputState = 0; ///< state for dealing with cursor keys
+bool hasUsedHistory = false; ///< Used to speed up - if we were cycling through history and then edit, we need to copy the string
+unsigned char loopsIdling; ///< How many times around the loop have we been entirely idle?
+bool interruptedDuringEvent; ///< Were we interrupted while executing an event? If so may want to clear timers
+// ----------------------------------------------------------------------------
+
+IOEventFlags jsiGetDeviceFromClass(JsVar *class) {
+ // Built-in classes have their object data set to the device name
+ return jshFromDeviceString(class->varData.str);
+}
+
+JsVar *jsiGetClassNameFromDevice(IOEventFlags device) {
+ const char *deviceName = jshGetDeviceString(device);
+ return jsvFindChildFromString(p.root, deviceName, false);
+}
+
+static inline bool jsiShowInputLine() {
+ return echo && !inputLineRemoved;
+}
+
+/// Change the console to a new location
+void jsiSetConsoleDevice(IOEventFlags device) {
+ if (device == consoleDevice) return;
+
+ if (!jshIsDeviceInitialised(device)) {
+ JshUSARTInfo inf;
+ jshUSARTInitInfo(&inf);
+ jshUSARTSetup(device, &inf);
+ }
+
+ jsiConsoleRemoveInputLine();
+ if (echo) { // intentionally not using jsiShowInputLine()
+ jsiConsolePrint("Console Moved to ");
+ jsiConsolePrint(jshGetDeviceString(device));
+ jsiConsolePrint("\n");
+ }
+ IOEventFlags oldDevice = consoleDevice;
+ consoleDevice = device;
+ if (echo) { // intentionally not using jsiShowInputLine()
+ jsiConsolePrint("Console Moved from ");
+ jsiConsolePrint(jshGetDeviceString(oldDevice));
+ jsiConsolePrint("\n");
+ }
+}
+
+/// Get the device that the console is currently on
+IOEventFlags jsiGetConsoleDevice() {
+ return consoleDevice;
+}
+
+void jsiConsolePrintChar(char data) {
+ jshTransmit(consoleDevice, (unsigned char)data);
+}
+
+void jsiConsolePrint(const char *str) {
+ while (*str) {
+ if (*str == '\n') jsiConsolePrintChar('\r');
+ jsiConsolePrintChar(*(str++));
+ }
+}
+
+void jsiConsolePrintf(const char *fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ vcbprintf((vcbprintf_callback)jsiConsolePrint,0, fmt, argp);
+ va_end(argp);
+}
+
+
+void jsiConsolePrintInt(JsVarInt d) {
+ char buf[32];
+ itoa(d, buf, 10);
+ jsiConsolePrint(buf);
+}
+
+
+/// Print the contents of a string var from a character position until end of line (adding an extra ' ' to delete a character if there was one)
+void jsiConsolePrintStringVarUntilEOL(JsVar *v, int fromCharacter, bool andBackup) {
+ int chars = 0;
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, fromCharacter);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ if (ch == '\n') break;
+ jsiConsolePrintChar(ch);
+ chars++;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ if (andBackup) {
+ jsiConsolePrintChar(' ');chars++;
+ while (chars--) jsiConsolePrintChar(0x08); //delete
+ }
+}
+
+/** Print the contents of a string var - directly - starting from the given character, and
+ * using newLineCh to prefix new lines (if it is not 0). */
+void jsiConsolePrintStringVarWithNewLineChar(JsVar *v, int fromCharacter, char newLineCh) {
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, fromCharacter);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ if (ch == '\n') jsiConsolePrintChar('\r');
+ jsiConsolePrintChar(ch);
+ if (ch == '\n' && newLineCh) jsiConsolePrintChar(newLineCh);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+}
+
+/// Print the contents of a string var - directly
+void jsiConsolePrintStringVar(JsVar *v) {
+ jsiConsolePrintStringVarWithNewLineChar(v,0,0);
+}
+
+/** Assuming that we are at the end of the string, this backs up
+ * and deletes it */
+void jsiConsoleEraseStringVarBackwards(JsVar *v) {
+ assert(jsvHasCharacterData(v));
+
+ int line, lines = jsvGetLinesInString(v);
+ for (line=lines;line>0;line--) {
+ int i,chars = jsvGetCharsOnLine(v, line);
+ if (line==lines) {
+ for (i=0;i<chars;i++) jsiConsolePrintChar(0x08); // move cursor back
+ }
+ for (i=0;i<chars;i++) jsiConsolePrintChar(' '); // move cursor forwards and wipe out
+ for (i=0;i<chars;i++) jsiConsolePrintChar(0x08); // move cursor back
+ if (line>1) {
+ // clear the character before - this would have had a colon
+ jsiConsolePrintChar(0x08);
+ jsiConsolePrintChar(' ');
+ // move cursor up
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(65);
+ }
+ }
+}
+
+/** Assuming that we are at fromCharacter position in the string var,
+ * erase everything that comes AFTER and return the cursor to 'fromCharacter'
+ * On newlines, if erasePrevCharacter, we remove the character before too. */
+void jsiConsoleEraseStringVarFrom(JsVar *v, int fromCharacter, bool erasePrevCharacter) {
+ assert(jsvHasCharacterData(v));
+ int cursorLine, cursorCol;
+ jsvGetLineAndCol(v, fromCharacter, &cursorLine, &cursorCol);
+ // delete contents of current line
+ int i, chars = jsvGetCharsOnLine(v, cursorLine);
+ for (i=cursorCol;i<=chars;i++) jsiConsolePrintChar(' ');
+ for (i=0;i<chars;i++) jsiConsolePrintChar(0x08); // move cursor back
+
+ int line, lines = jsvGetLinesInString(v);
+ for (line=cursorLine+1;line<=lines;line++) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(66); // move down
+ chars = jsvGetCharsOnLine(v, line);
+ for (i=0;i<chars;i++) jsiConsolePrintChar(' '); // move cursor forwards and wipe out
+ for (i=0;i<chars;i++) jsiConsolePrintChar(0x08); // move cursor back
+ if (erasePrevCharacter) {
+ jsiConsolePrintChar(0x08); // move cursor back
+ jsiConsolePrintChar(' ');
+ }
+ }
+ // move the cursor back up
+ for (line=cursorLine+1;line<=lines;line++) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(65);
+ }
+ // move the cursor forwards
+ for (i=1;i<cursorCol;i++) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(67);
+ }
+}
+
+void jsiMoveCursor(int oldX, int oldY, int newX, int newY) {
+ // see http://www.termsys.demon.co.uk/vtansi.htm - we could do this better
+ // move cursor
+ while (oldX < newX) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(67);
+ oldX++;
+ }
+ while (oldX > newX) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(68);
+ oldX--;
+ }
+ while (oldY < newY) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(66);
+ oldY++;
+ }
+ while (oldY > newY) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(65);
+ oldY--;
+ }
+}
+
+void jsiMoveCursorChar(JsVar *v, int fromCharacter, int toCharacter) {
+ if (fromCharacter==toCharacter) return;
+ int oldX, oldY;
+ jsvGetLineAndCol(v, fromCharacter, &oldY, &oldX);
+ int newX, newY;
+ jsvGetLineAndCol(v, toCharacter, &newY, &newX);
+ jsiMoveCursor(oldX, oldY, newX, newY);
+}
+
+/// If the input line was shown in the console, remove it
+void jsiConsoleRemoveInputLine() {
+ if (!inputLineRemoved) {
+ inputLineRemoved = true;
+ if (echo && inputLine) { // intentionally not using jsiShowInputLine()
+ jsiMoveCursorChar(inputLine, inputCursorPos, 0);
+ jsiConsoleEraseStringVarFrom(inputLine, 0, true);
+ jsiConsolePrintChar(0x08); // go back to start of line
+ }
+ }
+}
+
+/// If the input line has been removed, return it
+void jsiReturnInputLine() {
+ if (inputLineRemoved) {
+ inputLineRemoved = false;
+ if (echo) { // intentionally not using jsiShowInputLine()
+ jsiConsolePrintChar('\r'); // carriage return
+ jsiConsolePrintChar('>');
+ jsiConsolePrintStringVarWithNewLineChar(inputLine, 0, ':');
+ jsiMoveCursorChar(inputLine, (int)jsvGetStringLength(inputLine), inputCursorPos);
+ }
+ }
+}
+
+void jsiConsolePrintPosition(struct JsLex *lex, int tokenPos) {
+ int line,col;
+ jsvGetLineAndCol(lex->sourceVar, tokenPos, &line, &col);
+ jsiConsolePrintf("line %d col %d\n",line,col);
+}
+
+void jsiConsolePrintTokenLineMarker(struct JsLex *lex, int tokenPos) {
+ int line = 1,col = 1;
+ jsvGetLineAndCol(lex->sourceVar, tokenPos, &line, &col);
+ int startOfLine = jsvGetIndexFromLineAndCol(lex->sourceVar, line, 1);
+ jsiConsolePrintStringVarUntilEOL(lex->sourceVar, startOfLine, false);
+ jsiConsolePrint("\n");
+ while (col-- > 1) jsiConsolePrintChar(' ');
+ jsiConsolePrintChar('^');
+ jsiConsolePrint("\n");
+}
+
+
+/// Print the contents of a string var to a device - directly
+void jsiTransmitStringVar(IOEventFlags device, JsVar *v) {
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ jshTransmit(device, (unsigned char)ch);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+}
+
+void jsiSetBusy(JsiBusyDevice device, bool isBusy) {
+ static JsiBusyDevice business = 0;
+
+ if (isBusy)
+ business |= device;
+ else
+ business &= (JsiBusyDevice)~device;
+
+ if (pinBusyIndicator != PIN_UNDEFINED)
+ jshPinOutput(pinBusyIndicator, business!=0);
+}
+
+void jsiSetSleep(bool isSleep) {
+ if (pinSleepIndicator != PIN_UNDEFINED)
+ jshPinOutput(pinSleepIndicator, isSleep);
+}
+
+static JsVarRef _jsiInitNamedArray(const char *name) {
+ JsVar *arrayName = jsvFindChildFromString(p.root, name, true);
+ if (!arrayName) return 0; // out of memory
+ if (!arrayName->firstChild) {
+ JsVar *array = jsvNewWithFlags(JSV_ARRAY);
+ if (!array) { // out of memory
+ jsvUnLock(arrayName);
+ return 0;
+ }
+
+ arrayName->firstChild = jsvGetRef(jsvRef(array));
+ jsvUnLock(array);
+ }
+ JsVarRef arrayRef = jsvRefRef(arrayName->firstChild);
+ jsvUnLock(arrayName);
+ return arrayRef;
+}
+
+// Used when recovering after being flashed
+// 'claim' anything we are using
+void jsiSoftInit() {
+#ifdef USE_NET
+ httpInit();
+#endif
+#ifdef USE_LCD_FSMC
+ JsVar *parent = jspNewObject(jsiGetParser(), "LCD", "Graphics");
+ if (parent) {
+ JsVar *parentObj = jsvSkipName(parent);
+ JsGraphics gfx;
+ graphicsStructInit(&gfx);
+ gfx.data.type = JSGRAPHICSTYPE_FSMC;
+ gfx.graphicsVar = parentObj;
+ gfx.data.width = 320;
+ gfx.data.height = 240;
+ gfx.data.bpp = 16;
+ lcdInit_FSMC(&gfx);
+ lcdSetCallbacks_FSMC(&gfx);
+ graphicsSplash(&gfx);
+ graphicsSetVar(&gfx);
+ jsvUnLock(parentObj);
+ jsvUnLock(parent);
+ }
+#endif
+
+
+ events = jsvNewWithFlags(JSV_ARRAY);
+ inputLine = jsvNewFromEmptyString();
+ inputCursorPos = 0;
+ allowDeepSleep = false;
+
+ // Load timer/watch arrays
+ timerArray = _jsiInitNamedArray(JSI_TIMERS_NAME);
+ watchArray = _jsiInitNamedArray(JSI_WATCHES_NAME);
+
+ // Now run initialisation code
+ JsVar *initName = jsvFindChildFromString(p.root, JSI_INIT_CODE_NAME, false);
+ if (initName && initName->firstChild) {
+ //jsiConsolePrint("Running initialisation code...\n");
+ JsVar *initCode = jsvLock(initName->firstChild);
+ jsvUnLock(jspEvaluateVar(&p, initCode, 0));
+ jsvUnLock(initCode);
+ jsvRemoveChild(p.root, initName);
+ }
+ jsvUnLock(initName);
+
+ // Check any existing watches and set up interrupts for them
+ if (watchArray) {
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ JsVarRef watch = watchArrayPtr->firstChild;
+ while (watch) {
+ JsVar *watchNamePtr = jsvLock(watch);
+ JsVar *watchPin = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ jshPinWatch(jshGetPinFromVar(watchPin), true);
+ jsvUnLock(watchPin);
+ watch = watchNamePtr->nextSibling;
+ jsvUnLock(watchNamePtr);
+ }
+ jsvUnLock(watchArrayPtr);
+ }
+
+ // Check any existing timers and try and set time correctly
+ if (timerArray) {
+ JsSysTime currentTime = jshGetSystemTime();
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ JsVarRef timer = timerArrayPtr->firstChild;
+ while (timer) {
+ JsVar *timerNamePtr = jsvLock(timer);
+ JsVar *timerTime = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "time", false));
+ JsVarFloat interval = jsvGetFloatAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "interval", false)));
+ jsvSetInteger(timerTime, currentTime + jshGetTimeFromMilliseconds(interval));
+ jsvUnLock(timerTime);
+ timer = timerNamePtr->nextSibling;
+ jsvUnLock(timerNamePtr);
+ }
+ jsvUnLock(timerArrayPtr);
+ }
+ // And look for onInit function
+ JsVar *onInit = jsvFindChildFromString(p.root, JSI_ONINIT_NAME, false);
+ if (onInit && onInit->firstChild) {
+ if (echo) jsiConsolePrint("Running onInit()...\n");
+ JsVar *func = jsvSkipName(onInit);
+ if (jsvIsFunction(func))
+ jspExecuteFunction(&p, func, 0, 0, (JsVar**)0);
+ else if (jsvIsString(func))
+ jsvUnLock(jspEvaluateVar(&p, func, 0));
+ else
+ jsError("onInit is not a Function or a String");
+ }
+ jsvUnLock(onInit);
+}
+
+/** Append the code required to initialise a serial port to this string */
+void jsiAppendSerialInitialisation(JsVar *str, const char *serialName, bool addCallbacks) {
+ JsVar *serialVar = jsvObjectGetChild(p.root, serialName, 0);
+ if (serialVar) {
+ if (addCallbacks) {
+ JsVar *onData = jsvSkipOneNameAndUnLock(jsvFindChildFromString(serialVar, USART_CALLBACK_NAME, false));
+ if (onData) {
+ JsVar *onDataStr = jsvAsString(onData, true/*unlock*/);
+ jsvAppendString(str, serialName);
+ jsvAppendString(str, ".onData(");
+ jsvAppendStringVarComplete(str, onDataStr);
+ jsvAppendString(str, ");\n");
+ jsvUnLock(onDataStr);
+ }
+ }
+ JsVar *baud = jsvObjectGetChild(serialVar, USART_BAUDRATE_NAME, 0);
+ JsVar *options = jsvObjectGetChild(serialVar, DEVICE_OPTIONS_NAME, 0);
+ if (baud || options) {
+ JsVarInt baudrate = jsvGetInteger(baud);
+ if (baudrate <= 0) baudrate = DEFAULT_BAUD_RATE;
+ jsvAppendPrintf(str, "%s.setup(%d", serialName, baudrate);
+ if (jsvIsObject(options)) {
+ jsvAppendString(str, ", ");
+ jsfGetJSON(options, str);
+ }
+ jsvAppendString(str, ");\n");
+ }
+ jsvUnLock(baud);
+ jsvUnLock(options);
+ jsvUnLock(serialVar);
+ }
+}
+
+/** Append the code required to initialise a SPI port to this string */
+void jsiAppendDeviceInitialisation(JsVar *str, const char *deviceName) {
+ JsVar *deviceVar = jsvObjectGetChild(p.root, deviceName, 0);
+ if (deviceVar) {
+ JsVar *options = jsvObjectGetChild(deviceVar, DEVICE_OPTIONS_NAME, 0);
+ if (options) {
+ jsvAppendString(str, deviceName);
+ jsvAppendString(str, ".setup(");
+ if (jsvIsObject(options)) {
+ jsfGetJSON(options, str);
+ }
+ jsvAppendString(str, ");\n");
+ }
+ jsvUnLock(options);
+ jsvUnLock(deviceVar);
+ }
+}
+
+/** Append all the code required to initialise hardware to this string */
+void jsiAppendHardwareInitialisation(JsVar *str, bool addCallbacks) {
+ if (!echo) jsvAppendString(str, "echo(0);");
+ if (pinBusyIndicator != DEFAULT_BUSY_PIN_INDICATOR) {
+ jsvAppendPrintf(str, "setBusyIndicator(%p);\n", pinBusyIndicator);
+ }
+ if (pinSleepIndicator != DEFAULT_BUSY_PIN_INDICATOR) {
+ jsvAppendPrintf(str, "setSleepIndicator(%p);\n", pinSleepIndicator);
+ }
+ if (allowDeepSleep) {
+ jsvAppendPrintf(str, "setDeepSleep(1);\n");
+ }
+
+ jsiAppendSerialInitialisation(str, "USB", addCallbacks);
+ int i;
+ for (i=0;i<USARTS;i++)
+ jsiAppendSerialInitialisation(str, jshGetDeviceString(EV_SERIAL1+i), addCallbacks);
+ for (i=0;i<SPIS;i++)
+ jsiAppendDeviceInitialisation(str, jshGetDeviceString(EV_SPI1+i));
+ for (i=0;i<I2CS;i++)
+ jsiAppendDeviceInitialisation(str, jshGetDeviceString(EV_I2C1+i));
+ // pins
+ Pin pin;
+ for (pin=0;jshIsPinValid(pin) && pin<255;pin++) {
+ if (IS_PIN_USED_INTERNALLY(pin)) continue;
+ JshPinState state = jshPinGetState(pin);
+ JshPinState statem = state&JSHPINSTATE_MASK;
+ if (statem == JSHPINSTATE_GPIO_OUT) {
+ bool isOn = (state&JSHPINSTATE_PIN_IS_ON)!=0;
+ if (!isOn && IS_PIN_A_LED(pin)) continue;
+ jsvAppendPrintf(str, "digitalWrite(%p,%d);\n",pin,isOn?1:0);
+ } else if (/*statem == JSHPINSTATE_GPIO_IN ||*/statem == JSHPINSTATE_GPIO_IN_PULLUP || statem == JSHPINSTATE_GPIO_IN_PULLDOWN) {
+ // don't bother with normal inputs, as they come up in this state (ish) anyway
+ const char *s = "";
+ if (statem == JSHPINSTATE_GPIO_IN_PULLUP) s="_pullup";
+ if (statem == JSHPINSTATE_GPIO_IN_PULLDOWN) s="_pulldown";
+ jsvAppendPrintf(str, "pinMode(%p,input%s);\n",pin,s);
+ }
+ }
+}
+
+// Used when shutting down before flashing
+// 'release' anything we are using, but ensure that it doesn't get freed
+void jsiSoftKill() {
+ jsvUnLock(inputLine);
+ inputLine=0;
+ inputCursorPos = 0;
+
+ // Unref Watches/etc
+ if (events) {
+ jsvUnLock(events);
+ events=0;
+ }
+ if (timerArray) {
+ jsvUnRefRef(timerArray);
+ timerArray=0;
+ }
+ if (watchArray) {
+ // Check any existing watches and disable interrupts for them
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ JsVarRef watch = watchArrayPtr->firstChild;
+ while (watch) {
+ JsVar *watchNamePtr = jsvLock(watch);
+ JsVar *watchPin = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ jshPinWatch(jshGetPinFromVar(watchPin), false);
+ jsvUnLock(watchPin);
+ watch = watchNamePtr->nextSibling;
+ jsvUnLock(watchNamePtr);
+ }
+ jsvUnLock(watchArrayPtr);
+
+ jsvUnRefRef(watchArray);
+ watchArray=0;
+ }
+ // Save initialisation information
+ JsVar *initName = jsvFindChildFromString(p.root, JSI_INIT_CODE_NAME, true);
+ if (initName->firstChild) {
+ jsvUnRefRef(initName->firstChild);
+ initName->firstChild = 0;
+ }
+ JsVar *initCode = jsvNewFromEmptyString();
+ if (initCode) { // out of memory
+ initName->firstChild = jsvGetRef(jsvRef(initCode));
+ jsiAppendHardwareInitialisation(initCode, false);
+ jsvUnLock(initCode);
+ }
+ jsvUnLock(initName);
+
+#ifdef USE_NET
+ httpKill();
+#endif
+}
+
+void jsiInit(bool autoLoad) {
+ jsvInit();
+ jspInit(&p);
+
+#ifdef USE_LCD
+ lcdInit_Main(&LCD);
+#ifdef USE_LCD_ILI9325
+ lcdInit_FSMC_ILI9325(&LCD, LCD_WIDTH, LCD_HEIGHT);
+#endif
+#ifdef USE_LCD_SDL
+ lcdInit_SDL(&LCD, LCD_WIDTH, LCD_HEIGHT);
+#endif
+ lcdClear(0);
+ lcdSplash();
+#endif
+
+
+
+ /*for (i=0;i<IOPINS;i++)
+ ioPinState[i].callbacks = 0;*/
+
+ // Set state
+ interruptedDuringEvent = false;
+ // Set defaults
+ echo = true;
+ consoleDevice = DEFAULT_CONSOLE_DEVICE;
+ pinBusyIndicator = DEFAULT_BUSY_PIN_INDICATOR;
+ if (jshIsUSBSERIALConnected())
+ consoleDevice = EV_USBSERIAL;
+
+ /* If flash contains any code, then we should
+ Try and load from it... */
+ if (autoLoad && jshFlashContainsCode()) {
+ jspSoftKill(&p);
+ jsvSoftKill();
+ jshLoadFromFlash();
+ jsvSoftInit();
+ jspSoftInit(&p);
+ }
+ //jsvTrace(jsvGetRef(jsiGetParser()->root), 0)
+
+ // Softinit may run initialisation code that will overwrite defaults
+ jsiSoftInit();
+
+ if (echo) { // intentionally not using jsiShowInputLine()
+#ifndef LINUX
+ // set up terminal to avoid word wrap
+ jsiConsolePrint("\e[?7l");
+#endif
+ // rectangles @ http://www.network-science.de/ascii/
+ jsiConsolePrint("\n"
+ " _____ _ \n"
+ " | __|___ ___ ___ _ _|_|___ ___ \n"
+ " | __|_ -| . | _| | | | | . |\n"
+ " |_____|___| _|_| |___|_|_|_|___|");
+ jsiConsolePrint("\n"
+ " |_| http://www.espruino.com\n"
+ " "JS_VERSION" Copyright 2013 Gordon Williams\n"
+ "-------------------------------------------\n"
+ " KickStarter Version\n"
+ "-------------------------------------------\n");
+ jsiConsolePrint("\n>");
+ }
+}
+
+
+
+void jsiKill() {
+ jsiSoftKill();
+
+ jspKill(&p);
+ jsvKill();
+}
+
+int jsiCountBracketsInInput() {
+ int brackets = 0;
+
+ JsLex lex;
+ jslInit(&lex, inputLine);
+ while (lex.tk!=LEX_EOF && lex.tk!=LEX_UNFINISHED_COMMENT) {
+ if (lex.tk=='{' || lex.tk=='[' || lex.tk=='(') brackets++;
+ if (lex.tk=='}' || lex.tk==']' || lex.tk==')') brackets--;
+ if (brackets<0) break; // closing bracket before opening!
+ jslGetNextToken(&lex);
+ }
+ if (lex.tk==LEX_UNFINISHED_COMMENT)
+ brackets=1000; // if there's an unfinished comment, we're in the middle of something
+ jslKill(&lex);
+
+ return brackets;
+}
+
+/// Tries to get rid of some memory (by clearing command history). Returns true if it got rid of something, false if it didn't.
+bool jsiFreeMoreMemory() {
+ JsVar *history = jsvObjectGetChild(p.root, JSI_HISTORY_NAME, 0);
+ if (!history) return 0;
+ JsVar *item = jsvArrayPopFirst(history);
+ bool freed = item!=0;
+ jsvUnLock(item);
+ jsvUnLock(history);
+ // TODO: could also free the array structure?
+ return freed;
+}
+
+// Add a new line to the command history
+void jsiHistoryAddLine(JsVar *newLine) {
+ if (!newLine || jsvGetStringLength(newLine)==0) return;
+ JsVar *history = jsvFindChildFromString(p.root, JSI_HISTORY_NAME, true);
+ if (!history) return; // out of memory
+ // ensure we actually have the history array
+ if (!history->firstChild) {
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ if (!arr) {// out of memory
+ jsvUnLock(history);
+ return;
+ }
+ history->firstChild = jsvGetRef(jsvRef(arr));
+ jsvUnLock(arr);
+ }
+ history = jsvSkipNameAndUnLock(history);
+ // if it was already in history, remove it - we'll put it back in front
+ JsVar *alreadyInHistory = jsvGetArrayIndexOf(history, newLine, false/*not exact*/);
+ if (alreadyInHistory) {
+ jsvRemoveChild(history, alreadyInHistory);
+ jsvUnLock(alreadyInHistory);
+ }
+ // put it back in front
+ jsvArrayPush(history, newLine);
+ jsvUnLock(history);
+}
+
+JsVar *jsiGetHistoryLine(bool previous /* next if false */) {
+ JsVar *history = jsvObjectGetChild(p.root, JSI_HISTORY_NAME, 0);
+ JsVar *historyLine = 0;
+ if (history) {
+ JsVar *idx = jsvGetArrayIndexOf(history, inputLine, true/*exact*/); // get index of current line
+ if (idx) {
+ if (previous && idx->prevSibling) {
+ historyLine = jsvSkipNameAndUnLock(jsvLock(idx->prevSibling));
+ } else if (!previous && idx->nextSibling) {
+ historyLine = jsvSkipNameAndUnLock(jsvLock(idx->nextSibling));
+ }
+ jsvUnLock(idx);
+ } else {
+ if (previous) historyLine = jsvSkipNameAndUnLock(jsvArrayGetLast(history));
+ // if next, we weren't using history so couldn't go forwards
+ }
+
+ jsvUnLock(history);
+ }
+ return historyLine;
+}
+
+bool jsiIsInHistory(JsVar *line) {
+ JsVar *history = jsvObjectGetChild(p.root, JSI_HISTORY_NAME, 0);
+ if (!history) return false;
+ JsVar *historyFound = jsvGetArrayIndexOf(history, line, true/*exact*/);
+ bool inHistory = historyFound!=0;
+ jsvUnLock(historyFound);
+ jsvUnLock(history);
+ return inHistory;
+}
+
+void jsiReplaceInputLine(JsVar *newLine) {
+ if (jsiShowInputLine()) {
+ int oldLen = (int)jsvGetStringLength(inputLine);
+ jsiMoveCursorChar(inputLine, inputCursorPos, oldLen); // move cursor to end
+ jsiConsoleEraseStringVarBackwards(inputLine);
+ jsiConsolePrintStringVarWithNewLineChar(newLine,0,':');
+ }
+ jsvUnLock(inputLine);
+ inputLine = jsvLockAgain(newLine);
+ inputCursorPos = (int)jsvGetStringLength(inputLine);
+}
+
+void jsiChangeToHistory(bool previous) {
+ JsVar *nextHistory = jsiGetHistoryLine(previous);
+ if (nextHistory) {
+ jsiReplaceInputLine(nextHistory);
+ jsvUnLock(nextHistory);
+ hasUsedHistory = true;
+ } else if (!previous) { // if next, but we have something, just clear the line
+ if (jsiShowInputLine()) {
+ jsiConsoleEraseStringVarBackwards(inputLine);
+ }
+ jsvUnLock(inputLine);
+ inputLine = jsvNewFromEmptyString();
+ inputCursorPos = 0;
+ }
+}
+
+void jsiIsAboutToEditInputLine() {
+ // we probably plan to do something with the line now - check it wasn't in history
+ // and if it was, duplicate it
+ if (hasUsedHistory) {
+ hasUsedHistory = false;
+ if (jsiIsInHistory(inputLine)) {
+ JsVar *newLine = jsvCopy(inputLine);
+ if (newLine) { // could have been out of memory!
+ jsvUnLock(inputLine);
+ inputLine = newLine;
+ }
+ }
+ }
+}
+
+void jsiHandleDelete(bool isBackspace) {
+ int l = (int)jsvGetStringLength(inputLine);
+ if (isBackspace && inputCursorPos<=0) return; // at beginning of line
+ if (!isBackspace && inputCursorPos>=l) return; // at end of line
+ // work out if we are deleting a newline
+ bool deleteNewline = (isBackspace && jsvGetCharInString(inputLine,inputCursorPos-1)=='\n') ||
+ (!isBackspace && jsvGetCharInString(inputLine,inputCursorPos)=='\n');
+ // If we mod this to keep the string, use jsiIsAboutToEditInputLine
+ if (deleteNewline && jsiShowInputLine()) {
+ jsiConsoleEraseStringVarFrom(inputLine, inputCursorPos, true/*before newline*/); // erase all in front
+ if (isBackspace) {
+ // delete newline char
+ jsiConsolePrintChar(0x08);
+ jsiConsolePrintChar(' ');
+ jsiMoveCursorChar(inputLine, inputCursorPos, inputCursorPos-1); // move cursor back
+ }
+ }
+
+ JsVar *v = jsvNewFromEmptyString();
+ int p = inputCursorPos;
+ if (isBackspace) p--;
+ if (p>0) jsvAppendStringVar(v, inputLine, 0, p); // add before cursor (delete)
+ if (p+1<l) jsvAppendStringVar(v, inputLine, p+1, JSVAPPENDSTRINGVAR_MAXLENGTH); // add the rest
+ jsvUnLock(inputLine);
+ inputLine=v;
+ if (isBackspace)
+ inputCursorPos--; // move cursor back
+
+ // update the console
+ if (jsiShowInputLine()) {
+ if (deleteNewline) {
+ // we already removed everything, so just put it back
+ jsiConsolePrintStringVarWithNewLineChar(inputLine, inputCursorPos, ':');
+ jsiMoveCursorChar(inputLine, (int)jsvGetStringLength(inputLine), inputCursorPos); // move cursor back
+ } else {
+ // clear the character and move line back
+ if (isBackspace) jsiConsolePrintChar(0x08);
+ jsiConsolePrintStringVarUntilEOL(inputLine, inputCursorPos, true/*and backup*/);
+ }
+ }
+}
+
+void jsiHandleHome() {
+ while (inputCursorPos>0 && jsvGetCharInString(inputLine,inputCursorPos-1)!='\n') {
+ if (jsiShowInputLine()) jsiConsolePrintChar(0x08);
+ inputCursorPos--;
+ }
+}
+
+void jsiHandleEnd() {
+ int l = (int)jsvGetStringLength(inputLine);
+ while (inputCursorPos<l && jsvGetCharInString(inputLine,inputCursorPos)!='\n') {
+ if (jsiShowInputLine())
+ jsiConsolePrintChar(jsvGetCharInString(inputLine,inputCursorPos));
+ inputCursorPos++;
+ }
+}
+
+/** Page up/down move cursor to beginnint or end */
+void jsiHandlePageUpDown(bool isDown) {
+ int x,y;
+ jsvGetLineAndCol(inputLine, inputCursorPos, &y, &x);
+ if (!isDown) { // up
+ inputCursorPos = 0;
+ } else { // down
+ inputCursorPos = (int)jsvGetStringLength(inputLine);
+ }
+ int newX=x,newY=y;
+ jsvGetLineAndCol(inputLine, inputCursorPos, &newY, &newX);
+ jsiMoveCursor(x,y,newX,newY);
+}
+
+void jsiHandleMoveUpDown(int direction) {
+ int x,y, lines=jsvGetLinesInString(inputLine);
+ jsvGetLineAndCol(inputLine, inputCursorPos, &y, &x);
+ int newX=x,newY=y;
+ newY+=direction;
+ if (newY<1) newY=1;
+ if (newY>lines) newY=lines;
+ // work out cursor pos and feed back through - we might not be able to get right to the same place
+ // if we move up
+ inputCursorPos = jsvGetIndexFromLineAndCol(inputLine, newY, newX);
+ jsvGetLineAndCol(inputLine, inputCursorPos, &newY, &newX);
+ if (jsiShowInputLine()) {
+ jsiMoveCursor(x,y,newX,newY);
+ }
+}
+
+bool jsiAtEndOfInputLine() {
+ int i = inputCursorPos, l = (int)jsvGetStringLength(inputLine);
+ while (i < l) {
+ if (!isWhitespace(jsvGetCharInString(inputLine, i)))
+ return false;
+ i++;
+ }
+ return true;
+}
+
+void jsiHandleChar(char ch) {
+ //jsiConsolePrintf("[%d:%d]\n", inputState, ch);
+ //
+ // special stuff
+ // 27 then 91 then 68 - left
+ // 27 then 91 then 67 - right
+ // 27 then 91 then 65 - up
+ // 27 then 91 then 66 - down
+ // 27 then 91 then 51 then 126 - backwards delete
+ // 27 then 91 then 52 then 126 - numpad end
+ // 27 then 91 then 49 then 126 - numpad home
+ // 27 then 91 then 53 then 126 - pgup
+ // 27 then 91 then 54 then 126 - pgdn
+ // 27 then 79 then 70 - home
+ // 27 then 79 then 72 - end
+
+ if (ch == 0) {
+ inputState = IS_NONE; // ignore 0 - it's scary
+ } else if (ch == 27) {
+ inputState = IS_HAD_27;
+ } else if (inputState==IS_HAD_27) {
+ inputState = IS_NONE;
+ if (ch == 79)
+ inputState = IS_HAD_27_79;
+ else if (ch == 91)
+ inputState = IS_HAD_27_91;
+ } else if (inputState==IS_HAD_27_79) { // Numpad
+ inputState = IS_NONE;
+ if (ch == 70) jsiHandleEnd();
+ else if (ch == 72) jsiHandleHome();
+ else if (ch == 111) jsiHandleChar('/');
+ else if (ch == 106) jsiHandleChar('*');
+ else if (ch == 109) jsiHandleChar('-');
+ else if (ch == 107) jsiHandleChar('+');
+ else if (ch == 77) jsiHandleChar('\r');
+ } else if (inputState==IS_HAD_27_91) {
+ inputState = IS_NONE;
+ if (ch==68) { // left
+ if (inputCursorPos>0 && jsvGetCharInString(inputLine,inputCursorPos-1)!='\n') {
+ inputCursorPos--;
+ if (jsiShowInputLine()) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(68);
+ }
+ }
+ } else if (ch==67) { // right
+ if (inputCursorPos<(int)jsvGetStringLength(inputLine) && jsvGetCharInString(inputLine,inputCursorPos)!='\n') {
+ inputCursorPos++;
+ if (jsiShowInputLine()) {
+ jsiConsolePrintChar(27);
+ jsiConsolePrintChar(91);
+ jsiConsolePrintChar(67);
+ }
+ }
+ } else if (ch==65) { // up
+ int l = (int)jsvGetStringLength(inputLine);
+ if ((l==0 || jsiIsInHistory(inputLine)) && inputCursorPos==l)
+ jsiChangeToHistory(true); // if at end of line
+ else
+ jsiHandleMoveUpDown(-1);
+ } else if (ch==66) { // down
+ int l = (int)jsvGetStringLength(inputLine);
+ if ((l==0 || jsiIsInHistory(inputLine)) && inputCursorPos==l)
+ jsiChangeToHistory(false); // if at end of line
+ else
+ jsiHandleMoveUpDown(1);
+ } else if (ch==49) {
+ inputState=IS_HAD_27_91_49;
+ } else if (ch==51) {
+ inputState=IS_HAD_27_91_51;
+ } else if (ch==52) {
+ inputState=IS_HAD_27_91_52;
+ } else if (ch==53) {
+ inputState=IS_HAD_27_91_53;
+ } else if (ch==54) {
+ inputState=IS_HAD_27_91_54;
+ }
+ } else if (inputState==IS_HAD_27_91_49) {
+ inputState = IS_NONE;
+ if (ch==126) { // Numpad Home
+ jsiHandleHome();
+ }
+ } else if (inputState==IS_HAD_27_91_51) {
+ inputState = IS_NONE;
+ if (ch==126) { // Numpad (forwards) Delete
+ jsiHandleDelete(false/*not backspace*/);
+ }
+ } else if (inputState==IS_HAD_27_91_52) {
+ inputState = IS_NONE;
+ if (ch==126) { // Numpad End
+ jsiHandleEnd();
+ }
+ } else if (inputState==IS_HAD_27_91_53) {
+ inputState = IS_NONE;
+ if (ch==126) { // Page Up
+ jsiHandlePageUpDown(0);
+ }
+ } else if (inputState==IS_HAD_27_91_54) {
+ inputState = IS_NONE;
+ if (ch==126) { // Page Down
+ jsiHandlePageUpDown(1);
+ }
+ } else {
+ inputState = IS_NONE;
+ if (ch == 0x08 || ch == 0x7F /*delete*/) {
+ jsiHandleDelete(true /*backspace*/);
+ } else if (ch == '\n' && inputState == IS_HAD_R) {
+ inputState = IS_NONE; // ignore \ r\n - we already handled it all on \r
+ } else if (ch == '\r' || ch == '\n') {
+ if (jsiAtEndOfInputLine()) { // at EOL so we need to figure out if we can execute or not
+ if (ch == '\r') inputState = IS_HAD_R;
+ if (jsiCountBracketsInInput()<=0) { // actually execute!
+ if (jsiShowInputLine()) {
+ jsiConsolePrintChar('\r');
+ jsiConsolePrintChar('\n');
+ }
+ inputLineRemoved = true;
+
+ // Get line to execute, and reset inputLine
+ JsVar *lineToExecute = jsvStringTrimRight(inputLine);
+ jsvUnLock(inputLine);
+ inputLine = jsvNewFromEmptyString();
+ inputCursorPos = 0;
+ // execute!
+ JsVar *v = jspEvaluateVar(&p, lineToExecute, 0);
+ // add input line to history
+ jsiHistoryAddLine(lineToExecute);
+ jsvUnLock(lineToExecute);
+ // print result
+ if (echo) { // intentionally not using jsiShowInputLine()
+ jsiConsolePrintChar('=');
+ jsfPrintJSON(v);
+ jsiConsolePrint("\n");
+ }
+ jsvUnLock(v);
+ // console will be returned next time around the input loop
+ } else {
+ // Brackets aren't all closed, so we're going to append a newline
+ // without executing
+ if (jsiShowInputLine()) jsiConsolePrint("\n:");
+ jsiIsAboutToEditInputLine();
+ jsvAppendCharacter(inputLine, '\n');
+ inputCursorPos++;
+ }
+ } else { // new line - but not at end of line!
+ jsiIsAboutToEditInputLine();
+ if (jsiShowInputLine()) jsiConsoleEraseStringVarFrom(inputLine, inputCursorPos, false/*no need to erase the char before*/); // erase all in front
+ JsVar *v = jsvNewFromEmptyString();
+ if (inputCursorPos>0) jsvAppendStringVar(v, inputLine, 0, inputCursorPos);
+ jsvAppendCharacter(v, '\n');
+ jsvAppendStringVar(v, inputLine, inputCursorPos, JSVAPPENDSTRINGVAR_MAXLENGTH); // add the rest
+ jsvUnLock(inputLine);
+ inputLine=v;
+ if (jsiShowInputLine()) { // now print the rest
+ jsiConsolePrintStringVarWithNewLineChar(inputLine, inputCursorPos, ':');
+ jsiMoveCursorChar(inputLine, (int)jsvGetStringLength(inputLine), inputCursorPos+1); // move cursor back
+ }
+ inputCursorPos++;
+ }
+ } else if (ch>=32 || ch=='\t') {
+ // Add the character to our input line
+ jsiIsAboutToEditInputLine();
+ int l = (int)jsvGetStringLength(inputLine);
+ bool hasTab = ch=='\t';
+ if (inputCursorPos>=l) {
+ if (hasTab) jsvAppendString(inputLine, " ");
+ else jsvAppendCharacter(inputLine, ch);
+ } else {
+ JsVar *v = jsvNewFromEmptyString();
+ if (inputCursorPos>0) jsvAppendStringVar(v, inputLine, 0, inputCursorPos);
+ if (hasTab) jsvAppendString(v, " ");
+ else jsvAppendCharacter(v, ch);
+ jsvAppendStringVar(v, inputLine, inputCursorPos, JSVAPPENDSTRINGVAR_MAXLENGTH); // add the rest
+ jsvUnLock(inputLine);
+ inputLine=v;
+ if (jsiShowInputLine()) jsiConsolePrintStringVarUntilEOL(inputLine, inputCursorPos, true/*and backup*/);
+ }
+ inputCursorPos += hasTab ? 4 : 1;
+ if (jsiShowInputLine()) {
+ if (hasTab) jsiConsolePrint(" ");
+ else jsiConsolePrintChar(ch);
+ }
+ }
+ }
+}
+
+void jsiQueueEvents(JsVarRef callbacks, JsVar *arg0, JsVar *arg1) { // array of functions or single function
+ if (!callbacks) return;
+
+ JsVar *callbackVar = jsvLock(callbacks);
+ // if it is a single callback, just add it
+ if (jsvIsFunction(callbackVar) || jsvIsString(callbackVar)) {
+ JsVar *event = jsvNewWithFlags(JSV_OBJECT);
+ if (event) { // Could be out of memory error!
+ jsvUnLock(jsvAddNamedChild(event, callbackVar, "func"));
+ if (arg0) jsvUnLock(jsvAddNamedChild(event, arg0, "arg0"));
+ if (arg1) jsvUnLock(jsvAddNamedChild(event, arg1, "arg1"));
+ jsvArrayPushAndUnLock(events, event);
+ }
+ jsvUnLock(callbackVar);
+ } else {
+ assert(jsvIsArray(callbackVar));
+ // go through all callbacks
+ JsVarRef next = callbackVar->firstChild;
+ jsvUnLock(callbackVar);
+ while (next) {
+ //jsPrint("Queue Event\n");
+ JsVar *child = jsvLock(next);
+
+ // for each callback...
+ JsVar *event = jsvNewWithFlags(JSV_OBJECT);
+ if (event) { // Could be out of memory error!
+ jsvUnLock(jsvAddNamedChild(event, child, "func"));
+ if (arg0) jsvUnLock(jsvAddNamedChild(event, arg0, "arg0"));
+ if (arg1) jsvUnLock(jsvAddNamedChild(event, arg1, "arg1"));
+ // add event to the events list
+ jsvArrayPushAndUnLock(events, event);
+ // go to next callback
+ }
+ next = child->nextSibling;
+ jsvUnLock(child);
+ }
+ }
+}
+
+void jsiQueueObjectCallbacks(JsVar *object, const char *callbackName, JsVar *arg0, JsVar *arg1) {
+ JsVar *callback = jsvObjectGetChild(object, callbackName, 0);
+ if (!callback) return;
+ jsiQueueEvents(jsvGetRef(callback), arg0, arg1);
+ jsvUnLock(callback);
+}
+
+void jsiExecuteEvents() {
+ bool hasEvents = !jsvArrayIsEmpty(events);
+ bool wasInterrupted = jspIsInterrupted();
+ if (hasEvents) jsiSetBusy(BUSY_INTERACTIVE, true);
+ while (!jsvArrayIsEmpty(events)) {
+ JsVar *event = jsvSkipNameAndUnLock(jsvArrayPopFirst(events));
+ // Get function to execute
+ JsVar *func = jsvObjectGetChild(event, "func", 0);
+ JsVar *args[2];
+ args[0] = jsvObjectGetChild(event, "arg0", 0);
+ args[1] = jsvObjectGetChild(event, "arg1", 0);
+ // free
+ jsvUnLock(event);
+
+
+ // now run..
+ if (func) {
+ if (jsvIsFunction(func))
+ jspExecuteFunction(&p, func, 0, 2, args);
+ else if (jsvIsString(func))
+ jsvUnLock(jspEvaluateVar(&p, func, 0));
+ else
+ jsError("Unknown type of callback in Event Queue");
+ }
+ //jsPrint("Event Done\n");
+ jsvUnLock(func);
+ jsvUnLock(args[0]);
+ jsvUnLock(args[1]);
+ }
+ if (hasEvents) {
+ jsiSetBusy(BUSY_INTERACTIVE, false);
+ if (!wasInterrupted && jspIsInterrupted())
+ interruptedDuringEvent = true;
+ }
+}
+
+void jsiExecuteEventCallback(JsVar *callbackVar, JsVar *arg0, JsVar *arg1) { // array of functions or single function
+ bool wasInterrupted = jspIsInterrupted();
+ JsVar *callbackNoNames = jsvSkipName(callbackVar);
+
+ if (callbackNoNames) {
+ if (jsvIsArray(callbackNoNames)) {
+ JsVarRef next = callbackNoNames->firstChild;
+ while (next) {
+ JsVar *child = jsvLock(next);
+ jsiExecuteEventCallback(child, arg0, arg1);
+ next = child->nextSibling;
+ jsvUnLock(child);
+ }
+ } else if (jsvIsFunction(callbackNoNames)) {
+ JsVar *args[2] = { arg0, arg1 };
+ JsVar *parent = 0;
+ jspExecuteFunction(&p, callbackNoNames, parent, 2, args);
+ } else if (jsvIsString(callbackNoNames))
+ jsvUnLock(jspEvaluateVar(&p, callbackNoNames, 0));
+ else
+ jsError("Unknown type of callback in Event Queue");
+ jsvUnLock(callbackNoNames);
+ }
+ if (!wasInterrupted && jspIsInterrupted())
+ interruptedDuringEvent = true;
+}
+
+bool jsiHasTimers() {
+ if (!timerArray) return false;
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ JsVarInt c = jsvGetArrayLength(timerArrayPtr);
+ jsvUnLock(timerArrayPtr);
+ return c>0;
+}
+
+void jsiPrintUnregisteredMessage(const char *desc) {
+ jsiConsolePrint("\n You must have registered Espruino in order to ");
+ jsiConsolePrint(desc);
+ jsiConsolePrint(".\n\n Please type register() for more information.\n\n");
+}
+
+void jsiIdle() {
+ // This is how many times we have been here and not done anything.
+ // It will be zeroed if we do stuff later
+ if (loopsIdling<255) loopsIdling++;
+
+ // Handle hardware-related idle stuff (like checking for pin events)
+ bool wasBusy = false;
+ IOEvent event;
+ while (jshPopIOEvent(&event)) {
+ jsiSetBusy(BUSY_INTERACTIVE, true);
+ wasBusy = true;
+
+ IOEventFlags eventType = IOEVENTFLAGS_GETTYPE(event.flags);
+
+ loopsIdling = 0; // because we're not idling
+ if (eventType == consoleDevice) {
+ int i, c = IOEVENTFLAGS_GETCHARS(event.flags);
+ jsiSetBusy(BUSY_INTERACTIVE, true);
+ for (i=0;i<c;i++) jsiHandleChar(event.data.chars[i]);
+ jsiSetBusy(BUSY_INTERACTIVE, false);
+ }
+
+
+
+ if (DEVICE_IS_USART(eventType)) {
+ // ------------------------------------------------------------------------ SERIAL CALLBACK
+ JsVar *usartClass = jsvSkipNameAndUnLock(jsiGetClassNameFromDevice(IOEVENTFLAGS_GETTYPE(event.flags)));
+ if (usartClass) {
+ JsVar *callback = jsvFindChildFromString(usartClass, USART_CALLBACK_NAME, false);
+
+ if (callback) {
+ int i, c = IOEVENTFLAGS_GETCHARS(event.flags);
+
+// Part of hackish solution to 7 bit support on STM32
+#ifdef STM32
+ unsigned char bytesize = 8;
+ unsigned char parity = 0;
+ JsVar *options = jsvObjectGetChild(usartClass, DEVICE_OPTIONS_NAME, 0);
+
+ if(jsvIsObject(options)) {
+ bytesize = (unsigned char)jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "bytesize", 0));
+ JsVar *v = jsvObjectGetChild(options, "parity", 0);
+
+ if(jsvIsString(v)) {
+ parity = 0xFF;
+ char s[8] = "";
+
+ jsvGetString(v, s, sizeof(s) - 1);
+
+ if(!strcmp(s, "o") || !strcmp(s, "odd")) {
+ parity = 1;
+ }
+ else if(!strcmp(s, "e") || !strcmp(s, "even")) {
+ parity = 2;
+ }
+ }
+ else if(jsvIsInt(v)) {
+ parity = (unsigned char)jsvGetInteger(v);
+ }
+
+ jsvUnLock(v);
+ }
+
+ jsvUnLock(options);
+#endif
+
+ for (i=0; i<c; i++) {
+ JsVar *data = jsvNewWithFlags(JSV_OBJECT);
+
+ if (data) {
+ JsVar *dataTime = jsvNewFromString("X");
+
+#ifdef STM32
+ if(bytesize == 7 && parity > 0) {
+ dataTime->varData.str[0] = event.data.chars[i] & 0x7F;
+ }
+ else if(bytesize == 8 && parity > 0) {
+ dataTime->varData.str[0] = event.data.chars[i] & 0xFF;
+ }
+ else {
+ dataTime->varData.str[0] = event.data.chars[i];
+ }
+#else
+ dataTime->varData.str[0] = event.data.chars[i];
+#endif
+
+ if (dataTime) jsvUnLock(jsvAddNamedChild(data, dataTime, "data"));
+ jsvUnLock(dataTime);
+ }
+
+ jsiExecuteEventCallback(callback, data, 0);
+ jsvUnLock(data);
+ }
+ }
+ jsvUnLock(callback);
+ jsvUnLock(usartClass);
+ }
+ } else if (DEVICE_IS_EXTI(eventType)) { // ---------------------------------------------------------------- PIN WATCH
+ // we have an event... find out what it was for...
+ // Check everything in our Watch array
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ JsVarRef watch = watchArrayPtr->firstChild;
+ while (watch) {
+ JsVar *watchNamePtr = jsvLock(watch); // effectively the array index
+ JsVar *watchPin = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ Pin pin = jshGetPinFromVar(watchPin); // TODO: could be faster?
+ jsvUnLock(watchPin);
+
+ if (jshIsEventForPin(&event, pin)) {
+ bool pinIsHigh = (event.flags&EV_EXTI_IS_HIGH)!=0;
+ int watchEdge = (int)jsvGetIntegerAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "edge", false)));
+ if (watchEdge==0 || (pinIsHigh && watchEdge>0) || (!pinIsHigh && watchEdge<0)) { // edge triggering
+ JsVar *watchCallback = jsvFindChildFromStringRef(watchNamePtr->firstChild, "callback", false);
+ bool watchRecurring = jsvGetBoolAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "recur", false)));
+ JsVar *data = jsvNewWithFlags(JSV_OBJECT);
+ if (data) {
+ JsVar *dataTime = jsvNewFromFloat(jshGetMillisecondsFromTime(event.data.time)/1000);
+ if (dataTime) jsvUnLock(jsvAddNamedChild(data, dataTime, "time"));
+ jsvUnLock(dataTime);
+ JsVar *dataState = jsvNewFromBool(pinIsHigh);
+ if (dataState) jsvUnLock(jsvAddNamedChild(data, dataState, "state"));
+ jsvUnLock(dataState);
+ }
+ jsiExecuteEventCallback(watchCallback, data, 0);
+ jsvUnLock(data);
+ if (!watchRecurring) {
+ // free all
+ jsvRemoveChild(watchArrayPtr, watchNamePtr);
+ }
+ jsvUnLock(watchCallback);
+ }
+ }
+ watch = watchNamePtr->nextSibling;
+ jsvUnLock(watchNamePtr);
+ }
+ jsvUnLock(watchArrayPtr);
+ }
+ }
+
+ // Reset Flow control if it was set...
+ if (jshGetEventsUsed() < IOBUFFER_XON) {
+ int i;
+ for (i=0;i<USARTS;i++)
+ jshSetFlowControlXON(EV_SERIAL1+i, true);
+ }
+
+ // Check timers
+ JsSysTime minTimeUntilNext = JSSYSTIME_MAX;
+ JsSysTime time = jshGetSystemTime();
+
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ JsVarRef timer = timerArrayPtr->firstChild;
+ while (timer) {
+ JsVar *timerNamePtr = jsvLock(timer);
+ timer = timerNamePtr->nextSibling; // ptr to next
+ JsVar *timerTime = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "time", false));
+ JsSysTime timeUntilNext = jsvGetInteger(timerTime) - time;
+ if (timeUntilNext < minTimeUntilNext)
+ minTimeUntilNext = timeUntilNext;
+ if (timerTime && timeUntilNext<=0) {
+ // we're now doing work
+ jsiSetBusy(BUSY_INTERACTIVE, true);
+ wasBusy = true;
+
+ JsVar *timerCallback = jsvFindChildFromStringRef(timerNamePtr->firstChild, "callback", false);
+ JsVar *timerRecurring = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "recur", false));
+ JsVar *data = jsvNewWithFlags(JSV_OBJECT);
+ if (data) {
+ JsVar *dataTime = jsvNewFromFloat(jshGetMillisecondsFromTime(jsvGetInteger(timerTime))/1000);
+ if (dataTime) jsvUnLock(jsvAddNamedChild(data, dataTime, "time"));
+ jsvUnLock(dataTime);
+ }
+ jsiExecuteEventCallback(timerCallback, data, 0);
+ jsvUnLock(data);
+ if (jsvGetBool(timerRecurring)) {
+ JsVarFloat interval = jsvGetFloatAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "interval", false)));
+ if (interval<=0)
+ jsvSetInteger(timerTime, time); // just set to current system time
+ else
+ jsvSetInteger(timerTime, jsvGetInteger(timerTime)+jshGetTimeFromMilliseconds(interval));
+ } else {
+ // free all
+ JsVar *foundChild = jsvFindChildFromVar(timerArrayPtr, timerNamePtr, false);
+ if (foundChild) {
+ // check it exists - could have been removed during jsiExecuteEventCallback!
+ jsvRemoveChild(timerArrayPtr, timerNamePtr);
+ jsvUnLock(foundChild);
+ }
+ }
+ jsvUnLock(timerCallback);
+ jsvUnLock(timerRecurring);
+
+ }
+ jsvUnLock(timerTime);
+ jsvUnLock(timerNamePtr);
+ }
+ jsvUnLock(timerArrayPtr);
+
+
+ // Check for network events
+#ifdef USE_NET
+ httpIdle();
+#endif
+
+ // Just in case we got any events to do and didn't clear loopsIdling before
+ if (wasBusy || !jsvArrayIsEmpty(events) )
+ loopsIdling = 0;
+
+ if (wasBusy)
+ jsiSetBusy(BUSY_INTERACTIVE, false);
+
+
+ // TODO: could now sort events by time?
+ // execute any outstanding events
+ if (!jspIsInterrupted()) {
+ jsiExecuteEvents();
+ }
+ if (interruptedDuringEvent) {
+ jspSetInterrupted(false);
+ interruptedDuringEvent = false;
+ jsiConsolePrint("Execution Interrupted during event processing - clearing all timers and watches.\n");
+ jswrap_interface_clearInterval(0);
+ jswrap_interface_clearWatch(0);
+ }
+
+ // check for TODOs
+ if (todo) {
+ jsiSetBusy(BUSY_INTERACTIVE, true);
+ if (todo & TODO_RESET) {
+ todo &= (TODOFlags)~TODO_RESET;
+ // shut down everything and start up again
+ jsiKill();
+ jsiInit(false); // don't autoload
+ }
+ if (todo & TODO_FLASH_SAVE) {
+ todo &= (TODOFlags)~TODO_FLASH_SAVE;
+
+ jsvGarbageCollect(); // nice to have everything all tidy!
+ jsiSoftKill();
+ jspSoftKill(&p);
+ jsvSoftKill();
+ jshSaveToFlash();
+ jsvSoftInit();
+ jspSoftInit(&p);
+ jsiSoftInit();
+ }
+ if (todo & TODO_FLASH_LOAD) {
+ todo &= (TODOFlags)~TODO_FLASH_LOAD;
+
+ jsiSoftKill();
+ jspSoftKill(&p);
+ jsvSoftKill();
+ jshLoadFromFlash();
+ jsvSoftInit();
+ jspSoftInit(&p);
+ jsiSoftInit();
+ }
+ jsiSetBusy(BUSY_INTERACTIVE, false);
+ }
+
+ /* if we've been around this loop, there is nothing to do, and
+ * we have a spare 10ms then let's do some Garbage Collection
+ * just in case. */
+ if (loopsIdling==1 &&
+ minTimeUntilNext > jshGetTimeFromMilliseconds(10)) {
+ jsiSetBusy(BUSY_INTERACTIVE, true);
+ jsvGarbageCollect();
+ jsiSetBusy(BUSY_INTERACTIVE, false);
+ }
+ // Go to sleep!
+ if (loopsIdling>1 && // once around the idle loop without having done any work already (just in case)
+#ifdef USB
+ !jshIsUSBSERIALConnected() && // if USB is on, no point sleeping (later, sleep might be more drastic)
+#endif
+ !jshHasEvents() && //no events have arrived in the mean time
+ !jshHasTransmitData()/* && //nothing left to send over serial?
+ minTimeUntilNext > SYSTICK_RANGE*5/4*/) { // we are sure we won't miss anything - leave a little leeway (SysTick will wake us up!)
+ jshSleep(minTimeUntilNext);
+ }
+}
+
+void jsiLoop() {
+ // idle stuff for hardware
+ jshIdle();
+ // Do general idle stuff
+ jsiIdle();
+ // Idle LCD
+#ifdef USE_LCD
+ graphicsIdle();
+#endif
+
+ if (jspIsInterrupted()) {
+ jspSetInterrupted(false);
+ jsiConsoleRemoveInputLine();
+ // clear input line
+ jsvUnLock(inputLine);
+ inputLine = jsvNewFromEmptyString();
+ }
+
+ // return console (if it was gone!)
+ jsiReturnInputLine();
+}
+
+/** Output extra functions defined in an object such that they can be copied to a new device */
+void jsiDumpObjectState(JsVar *parentName, JsVar *parent) {
+ JsVarRef childRef = parent->firstChild;
+ while (childRef) {
+ JsVar *child = jsvLock(childRef);
+ JsVar *data = jsvSkipName(child);
+ if (jsvIsStringEqual(child, JSPARSE_PROTOTYPE_VAR)) {
+ JsVarRef protoRef = data->firstChild;
+ while (protoRef) {
+ JsVar *proto = jsvLock(protoRef);
+ jsiConsolePrintStringVar(parentName);
+ jsiConsolePrint(".prototype.");
+ jsiConsolePrintStringVar(proto);
+ jsiConsolePrint(" = ");
+ JsVar *protoData = jsvSkipName(proto);
+ jsfPrintJSON(protoData);
+ jsvUnLock(protoData);
+ jsiConsolePrint(";\n");
+ protoRef = proto->nextSibling;
+ jsvUnLock(proto);
+ }
+ } else {
+ jsiConsolePrintStringVar(parentName);
+ jsiConsolePrint(".");
+ jsiConsolePrintStringVar(child);
+ jsiConsolePrint(" = ");
+ jsfPrintJSON(data);
+ jsiConsolePrint(";\n");
+
+ }
+ jsvUnLock(data);
+ childRef = child->nextSibling;
+ jsvUnLock(child);
+ }
+}
+
+/** Output current interpreter state such that it can be copied to a new device */
+void jsiDumpState() {
+ JsVarRef childRef = p.root->firstChild;
+ while (childRef) {
+ JsVar *child = jsvLock(childRef);
+ char childName[JSLEX_MAX_TOKEN_LENGTH];
+ jsvGetString(child, childName, JSLEX_MAX_TOKEN_LENGTH);
+
+ JsVar *data = jsvSkipName(child);
+ if (jspIsCreatedObject(&p, data) || jswIsBuiltInObject(childName)) {
+ jsiDumpObjectState(child, data);
+ } else if (jsvIsStringEqual(child, JSI_TIMERS_NAME)) {
+ // skip - done later
+ } else if (jsvIsStringEqual(child, JSI_WATCHES_NAME)) {
+ // skip - done later
+ } else if (child->varData.str[0]==JS_HIDDEN_CHAR ||
+ jshFromDeviceString(childName)!=EV_NONE) {
+ // skip - don't care about this stuff
+ } else if (!jsvIsNative(data)) { // just a variable/function!
+ if (jsvIsFunction(data)) {
+ // function-specific output
+ jsiConsolePrint("function ");
+ jsiConsolePrintStringVar(child);
+ jsfPrintJSONForFunction(data);
+ jsiConsolePrint("\n");
+ // print any prototypes we had
+ JsVar *proto = jsvObjectGetChild(data, JSPARSE_PROTOTYPE_VAR, 0);
+ if (proto) {
+ JsVarRef protoRef = proto->firstChild;
+ jsvUnLock(proto);
+ while (protoRef) {
+ JsVar *protoName = jsvLock(protoRef);
+ JsVar *protoData = jsvSkipName(protoName);
+ jsiConsolePrintStringVar(child);
+ jsiConsolePrint(".prototype.");
+ jsiConsolePrintStringVar(protoName);
+ jsiConsolePrint(" = ");
+ jsfPrintJSON(protoData);
+ jsiConsolePrint(";\n");
+ jsvUnLock(protoData);
+ protoRef = protoName->nextSibling;
+ jsvUnLock(protoName);
+ }
+ }
+ } else {
+ // normal variable definition
+ jsiConsolePrint("var ");
+ jsiConsolePrintStringVar(child);
+ jsiConsolePrint(" = ");
+ jsfPrintJSON(data);
+ jsiConsolePrint(";\n");
+ }
+ }
+ jsvUnLock(data);
+ childRef = child->nextSibling;
+ jsvUnLock(child);
+ }
+ // Now do timers
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ JsVarRef timerRef = timerArrayPtr->firstChild;
+ jsvUnLock(timerArrayPtr);
+ while (timerRef) {
+ JsVar *timerNamePtr = jsvLock(timerRef);
+ JsVar *timerCallback = jsvSkipOneNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "callback", false));
+ bool recur = jsvGetBoolAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "recur", false)));
+ JsVar *timerInterval = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(timerNamePtr->firstChild, "interval", false));
+ jsiConsolePrint(recur ? "setInterval(" : "setTimeout(");
+ jsfPrintJSON(timerCallback);
+ jsiConsolePrint(", ");
+ jsfPrintJSON(timerInterval);
+ jsiConsolePrint(");\n");
+ jsvUnLock(timerInterval);
+ jsvUnLock(timerCallback);
+ // next
+ timerRef = timerNamePtr->nextSibling;
+ jsvUnLock(timerNamePtr);
+ }
+ // Now do watches
+ {
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ JsVarRef watchRef = watchArrayPtr->firstChild;
+ jsvUnLock(watchArrayPtr);
+ while (watchRef) {
+ JsVar *watchNamePtr = jsvLock(watchRef);
+ JsVar *watchCallback = jsvSkipOneNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "callback", false));
+ bool watchRecur = jsvGetBoolAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "recur", false)));
+ int watchEdge = (int)jsvGetIntegerAndUnLock(jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "edge", false)));
+ JsVar *watchPin = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ jsiConsolePrint("setWatch(");
+ jsfPrintJSON(watchCallback);
+ jsiConsolePrint(", ");
+ jsfPrintJSON(watchPin);
+ jsiConsolePrint(", { repeat:");
+ jsiConsolePrint(watchRecur?"true":"false");
+ jsiConsolePrint(", edge:");
+ if (watchEdge<0) jsiConsolePrint("'falling'");
+ else if (watchEdge>0) jsiConsolePrint("'rising'");
+ else jsiConsolePrint("'both'");
+ jsiConsolePrint(" });\n");
+ jsvUnLock(watchPin);
+ jsvUnLock(watchCallback);
+ // next
+ watchRef = watchNamePtr->nextSibling;
+ jsvUnLock(watchNamePtr);
+ }
+ }
+ // and now serial
+ JsVar *str = jsvNewFromEmptyString();
+ jsiAppendHardwareInitialisation(str, true);
+ jsiConsolePrintStringVar(str);
+ jsvUnLock(str);
+}
+
+void jsiSetTodo(TODOFlags newTodo) {
+ todo = newTodo;
+}
+
+JsParse *jsiGetParser() {
+ return &p;
+}
diff --git a/components/external/espruino/src/jsinteractive.h b/components/external/espruino/src/jsinteractive.h
new file mode 100644
index 0000000000..6d67bb6788
--- /dev/null
+++ b/components/external/espruino/src/jsinteractive.h
@@ -0,0 +1,105 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Interactive Shell implementation
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSINTERACTIVE_H_
+#define JSINTERACTIVE_H_
+
+#include "jsparse.h"
+#include "jshardware.h"
+
+#define JSI_WATCHES_NAME ">watches"
+#define JSI_TIMERS_NAME ">timers"
+#define JSI_HISTORY_NAME ">history"
+#define JSI_INIT_CODE_NAME ">init"
+#define JSI_ONINIT_NAME "onInit"
+
+/// autoLoad = do we load the current state if it exists?
+void jsiInit(bool autoLoad);
+void jsiKill();
+
+void jsiLoop();
+
+/// Tries to get rid of some memory (by clearing command history). Returns true if it got rid of something, false if it didn't.
+bool jsiFreeMoreMemory();
+
+bool jsiHasTimers(); // are there timers still left to run?
+JsParse *jsiGetParser();
+
+/// Queue up callbacks for other things (touchscreen? network?)
+void jsiQueueObjectCallbacks(JsVar *object, const char *callbackName, JsVar *arg0, JsVar *arg1);
+
+
+IOEventFlags jsiGetDeviceFromClass(JsVar *deviceClass);
+JsVar *jsiGetClassNameFromDevice(IOEventFlags device);
+
+/// Change the console to a new location
+void jsiSetConsoleDevice(IOEventFlags device);
+/// Get the device that the console is currently on
+IOEventFlags jsiGetConsoleDevice();
+/// Transmit a byte
+void jsiConsolePrintChar(char data);
+/// Transmit a string
+void jsiConsolePrint(const char *str);
+/// Write the formatted string to the console (see vcbprintf)
+void jsiConsolePrintf(const char *fmt, ...);
+/// Print the contents of a string var - directly
+void jsiConsolePrintStringVar(JsVar *v);
+/// Transmit an integer
+void jsiConsolePrintInt(JsVarInt d);
+/// Transmit a position in the lexer (for reporting errors)
+void jsiConsolePrintPosition(struct JsLex *lex, int tokenPos);
+/// Transmit the current line, along with a marker of where the error was (for reporting errors)
+void jsiConsolePrintTokenLineMarker(struct JsLex *lex, int tokenPos);
+/// Print the contents of a string var to a device - directly
+void jsiTransmitStringVar(IOEventFlags device, JsVar *v);
+/// If the input line was shown in the console, remove it
+void jsiConsoleRemoveInputLine();
+/// Change what is in the inputline into something else (and update the console)
+void jsiReplaceInputLine(JsVar *newLine);
+
+/// Flags for jsiSetBusy - THESE SHOULD BE 2^N
+typedef enum {
+ BUSY_INTERACTIVE = 1,
+ BUSY_TRANSMIT = 2,
+ // ??? = 4
+} JsiBusyDevice;
+/// Shows a busy indicator, if one is set up
+void jsiSetBusy(JsiBusyDevice device, bool isBusy);
+/// Shows a sleep indicator, if one is set up
+void jsiSetSleep(bool isSleep);
+
+
+// for jswrap_interactive/io.c ----------------------------------------------------
+typedef enum {
+ TODO_NOTHING = 0,
+ TODO_FLASH_SAVE = 1,
+ TODO_FLASH_LOAD = 2,
+ TODO_RESET = 4,
+} TODOFlags;
+#define USART_CALLBACK_NAME "_callback"
+#define USART_BAUDRATE_NAME "_baudrate"
+#define DEVICE_OPTIONS_NAME "_options"
+
+extern Pin pinBusyIndicator;
+extern Pin pinSleepIndicator;
+extern bool echo;
+extern bool allowDeepSleep;
+void jsiDumpState();
+void jsiSetTodo(TODOFlags newTodo);
+#define TIMER_MIN_INTERVAL 0.1 // in milliseconds
+extern JsVarRef timerArray; // Linked List of timers to check and run
+extern JsVarRef watchArray; // Linked List of input watches to check and run
+// end for jswrap_interactive/io.c ------------------------------------------------
+
+
+#endif /* JSINTERACTIVE_H_ */
diff --git a/components/external/espruino/src/jslex.c b/components/external/espruino/src/jslex.c
new file mode 100644
index 0000000000..02d9befa58
--- /dev/null
+++ b/components/external/espruino/src/jslex.c
@@ -0,0 +1,501 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Lexer (convert JsVar strings into a series of tokens)
+ * ----------------------------------------------------------------------------
+ */
+#include "jslex.h"
+
+
+void jslSeek(JsLex *lex, JslCharPos seekToChar) {
+ jsvStringIteratorFree(&lex->it);
+ jsvStringIteratorNew(&lex->it, lex->sourceVar, seekToChar);
+}
+
+void NO_INLINE jslGetNextCh(JsLex *lex) {
+ lex->currCh = lex->nextCh;
+ lex->nextCh = jsvStringIteratorGetChar(&lex->it);
+ jsvStringIteratorNextInline(&lex->it);
+}
+
+static inline void jslTokenAppendChar(JsLex *lex, char ch) {
+ /* Add character to buffer but check it isn't too big.
+ * Also Leave ONE character at the end for null termination */
+ if (lex->tokenl < JSLEX_MAX_TOKEN_LENGTH-1) {
+ lex->token[lex->tokenl++] = ch;
+ }
+#ifdef DEBUG
+ else {
+ jsWarnAt("Token name is too long! skipping character", lex, lex->tokenStart);
+ }
+#endif
+}
+
+static bool jslIsToken(JsLex *lex, const char *token, int startOffset) {
+ int i;
+ for (i=startOffset;i<lex->tokenl;i++) {
+ if (lex->token[i]!=token[i]) return false;
+ // if token is smaller than lex->token, there will be a null char
+ // which will be different from the token
+ }
+ return token[lex->tokenl] == 0; // only match if token ends now
+}
+
+void jslGetNextToken(JsLex *lex) {
+ lex->tk = LEX_EOF;
+ lex->tokenl = 0; // clear token string
+ if (lex->tokenValue) {
+ jsvUnLock(lex->tokenValue);
+ lex->tokenValue = 0;
+ }
+ while (lex->currCh && isWhitespace(lex->currCh)) jslGetNextCh(lex);
+ // newline comments
+ if (lex->currCh=='/' && lex->nextCh=='/') {
+ while (lex->currCh && lex->currCh!='\n') jslGetNextCh(lex);
+ jslGetNextCh(lex);
+ jslGetNextToken(lex);
+ return;
+ }
+ // block comments
+ if (lex->currCh=='/' && lex->nextCh=='*') {
+ while (lex->currCh && !(lex->currCh=='*' && lex->nextCh=='/'))
+ jslGetNextCh(lex);
+ 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(lex);
+ jslGetNextCh(lex);
+ jslGetNextToken(lex);
+ return;
+ }
+ // record beginning of this token
+ lex->tokenLastStart = lex->tokenStart;
+ lex->tokenStart = (JslCharPos)(lex->it.index-2);
+ // tokens
+ if (isAlpha(lex->currCh) || lex->currCh=='$') { // IDs
+ while (isAlpha(lex->currCh) || isNumeric(lex->currCh) || lex->currCh=='$') {
+ jslTokenAppendChar(lex, lex->currCh);
+ jslGetNextCh(lex);
+ }
+ 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(lex,"break", 1)) lex->tk = LEX_R_BREAK;
+ break;
+ case 'c': if (jslIsToken(lex,"case", 1)) lex->tk = LEX_R_CASE;
+ else if (jslIsToken(lex,"continue", 1)) lex->tk = LEX_R_CONTINUE;
+ break;
+ case 'd': if (jslIsToken(lex,"default", 1)) lex->tk = LEX_R_DEFAULT;
+ else if (jslIsToken(lex,"do", 1)) lex->tk = LEX_R_DO;
+ break;
+ case 'e': if (jslIsToken(lex,"else", 1)) lex->tk = LEX_R_ELSE;
+ break;
+ case 'f': if (jslIsToken(lex,"false", 1)) lex->tk = LEX_R_FALSE;
+ else if (jslIsToken(lex,"for", 1)) lex->tk = LEX_R_FOR;
+ else if (jslIsToken(lex,"function", 1)) lex->tk = LEX_R_FUNCTION;
+ break;
+ case 'i': if (jslIsToken(lex,"if", 1)) lex->tk = LEX_R_IF;
+ else if (jslIsToken(lex,"in", 1)) lex->tk = LEX_R_IN;
+ else if (jslIsToken(lex,"instanceof", 1)) lex->tk = LEX_R_INSTANCEOF;
+ break;
+ case 'n': if (jslIsToken(lex,"new", 1)) lex->tk = LEX_R_NEW;
+ else if (jslIsToken(lex,"null", 1)) lex->tk = LEX_R_NULL;
+ break;
+ case 'r': if (jslIsToken(lex,"return", 1)) lex->tk = LEX_R_RETURN;
+ break;
+ case 's': if (jslIsToken(lex,"switch", 1)) lex->tk = LEX_R_SWITCH;
+ break;
+ case 't': if (jslIsToken(lex,"this", 1)) lex->tk = LEX_R_THIS;
+ else if (jslIsToken(lex,"true", 1)) lex->tk = LEX_R_TRUE;
+ else if (jslIsToken(lex,"typeof", 1)) lex->tk = LEX_R_TYPEOF;
+ break;
+ case 'u': if (jslIsToken(lex,"undefined", 1)) lex->tk = LEX_R_UNDEFINED;
+ break;
+ case 'w': if (jslIsToken(lex,"while", 1)) lex->tk = LEX_R_WHILE;
+ break;
+ case 'v': if (jslIsToken(lex,"var", 1)) lex->tk = LEX_R_VAR;
+ else if (jslIsToken(lex,"void", 1)) lex->tk = LEX_R_VOID;
+ break;
+ default: break;
+ }
+ } else if (isNumeric(lex->currCh)) { // Numbers
+ // TODO: check numbers aren't the wrong format
+ bool canBeFloating = true;
+ if (lex->currCh=='0') {
+ jslTokenAppendChar(lex, lex->currCh);
+ jslGetNextCh(lex);
+ }
+
+ if ((lex->currCh=='x' || lex->currCh=='X') ||
+ (lex->currCh=='b' || lex->currCh=='B') ||
+ (lex->currCh=='o' || lex->currCh=='O')) {
+ canBeFloating = false;
+ jslTokenAppendChar(lex, lex->currCh); jslGetNextCh(lex);
+ }
+ lex->tk = LEX_INT;
+ while (isNumeric(lex->currCh) || (!canBeFloating && isHexadecimal(lex->currCh))) {
+ jslTokenAppendChar(lex, lex->currCh);
+ jslGetNextCh(lex);
+ }
+ if (canBeFloating && lex->currCh=='.') {
+ lex->tk = LEX_FLOAT;
+ jslTokenAppendChar(lex, '.');
+ jslGetNextCh(lex);
+ while (isNumeric(lex->currCh)) {
+ jslTokenAppendChar(lex, lex->currCh);
+ jslGetNextCh(lex);
+ }
+ }
+ // do fancy e-style floating point
+ if (canBeFloating && (lex->currCh=='e'||lex->currCh=='E')) {
+ lex->tk = LEX_FLOAT;
+ jslTokenAppendChar(lex, lex->currCh); jslGetNextCh(lex);
+ if (lex->currCh=='-' || lex->currCh=='+') { jslTokenAppendChar(lex, lex->currCh); jslGetNextCh(lex); }
+ while (isNumeric(lex->currCh)) {
+ jslTokenAppendChar(lex, lex->currCh); jslGetNextCh(lex);
+ }
+ }
+ } else if (lex->currCh=='"' || lex->currCh=='\'') {
+ char delim = lex->currCh;
+ lex->tokenValue = jsvNewFromEmptyString();
+ // strings...
+ jslGetNextCh(lex);
+ while (lex->currCh && lex->currCh!=delim) {
+ if (lex->currCh == '\\') {
+ jslGetNextCh(lex);
+ char ch = lex->currCh;
+ switch (lex->currCh) {
+ case 'n' : ch = '\n'; jslGetNextCh(lex); break;
+ case 'a' : ch = '\a'; jslGetNextCh(lex); break;
+ case 'r' : ch = '\r'; jslGetNextCh(lex); break;
+ case 't' : ch = '\t'; jslGetNextCh(lex); break;
+ case 'x' : { // hex digits
+ char buf[5] = "0x??";
+ jslGetNextCh(lex);
+ buf[2] = lex->currCh; jslGetNextCh(lex);
+ buf[3] = lex->currCh; jslGetNextCh(lex);
+ 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(lex);
+ if (lex->currCh>='0' && lex->currCh<='7') {
+ buf[n++] = lex->currCh; jslGetNextCh(lex);
+ if (lex->currCh>='0' && lex->currCh<='7') {
+ buf[n++] = lex->currCh; jslGetNextCh(lex);
+ }
+ }
+ buf[n]=0;
+ ch = (char)stringToInt(buf);
+ } else {
+ // for anything else, just push the character through
+ jslGetNextCh(lex);
+ }
+ break;
+ }
+ if (lex->tokenValue) {
+ jslTokenAppendChar(lex, ch);
+ jsvAppendCharacter(lex->tokenValue, ch);
+ }
+ } else {
+ if (lex->tokenValue) {
+ jslTokenAppendChar(lex, lex->currCh);
+ jsvAppendCharacter(lex->tokenValue, lex->currCh);
+ }
+ jslGetNextCh(lex);
+ }
+ }
+ jslGetNextCh(lex);
+ lex->tk = LEX_STR;
+ } else {
+ // single chars
+ lex->tk = lex->currCh;
+ jslGetNextCh(lex);
+ if (lex->tk=='=' && lex->currCh=='=') { // ==
+ lex->tk = LEX_EQUAL;
+ jslGetNextCh(lex);
+ if (lex->currCh=='=') { // ===
+ lex->tk = LEX_TYPEEQUAL;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='!' && lex->currCh=='=') { // !=
+ lex->tk = LEX_NEQUAL;
+ jslGetNextCh(lex);
+ if (lex->currCh=='=') { // !==
+ lex->tk = LEX_NTYPEEQUAL;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='<') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_LEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='<') {
+ lex->tk = LEX_LSHIFT;
+ jslGetNextCh(lex);
+ if (lex->currCh=='=') { // <<=
+ lex->tk = LEX_LSHIFTEQUAL;
+ jslGetNextCh(lex);
+ }
+ }
+ } else if (lex->tk=='>') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_GEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='>') {
+ lex->tk = LEX_RSHIFT;
+ jslGetNextCh(lex);
+ if (lex->currCh=='=') { // >>=
+ lex->tk = LEX_RSHIFTEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='>') { // >>>
+ jslGetNextCh(lex);
+ if (lex->currCh=='=') { // >>>=
+ lex->tk = LEX_RSHIFTUNSIGNEDEQUAL;
+ jslGetNextCh(lex);
+ } else {
+ lex->tk = LEX_RSHIFTUNSIGNED;
+ }
+ }
+ }
+ } else if (lex->tk=='+') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_PLUSEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='+') {
+ lex->tk = LEX_PLUSPLUS;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='-') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_MINUSEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='-') {
+ lex->tk = LEX_MINUSMINUS;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='&') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_ANDEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->currCh=='&') {
+ lex->tk = LEX_ANDAND;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='|') {
+ if (lex->currCh=='=') {
+ lex->tk = LEX_OREQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->tk=='|' && lex->currCh=='|') {
+ lex->tk = LEX_OROR;
+ jslGetNextCh(lex);
+ }
+ } else if (lex->tk=='^' && lex->currCh=='=') {
+ lex->tk = LEX_XOREQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->tk=='*' && lex->currCh=='=') {
+ lex->tk = LEX_MULEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->tk=='/' && lex->currCh=='=') {
+ lex->tk = LEX_DIVEQUAL;
+ jslGetNextCh(lex);
+ } else if (lex->tk=='%' && lex->currCh=='=') {
+ lex->tk = LEX_MODEQUAL;
+ jslGetNextCh(lex);
+ }
+ }
+ /* This isn't quite right yet */
+ lex->tokenLastEnd = lex->tokenEnd;
+ lex->tokenEnd = (JslCharPos)(lex->it.index-3)/*because of nextCh/currCh/etc */;
+}
+
+static inline void jslPreload(JsLex *lex) {
+ // set up..
+ jslGetNextCh(lex);
+ jslGetNextCh(lex);
+ jslGetNextToken(lex);
+}
+
+void jslInit(JsLex *lex, JsVar *var) {
+ lex->sourceVar = jsvLockAgain(var);
+ // reset stuff
+ lex->tk = 0;
+ lex->tokenStart = 0;
+ lex->tokenEnd = 0;
+ lex->tokenLastStart = 0;
+ lex->tokenLastEnd = 0;
+ lex->tokenl = 0;
+ lex->tokenValue = 0;
+ // set up iterator
+ jsvStringIteratorNew(&lex->it, lex->sourceVar, 0);
+ jslPreload(lex);
+}
+
+void jslKill(JsLex *lex) {
+ lex->tk = LEX_EOF; // safety ;)
+ jsvStringIteratorFree(&lex->it);
+ if (lex->tokenValue) {
+ jsvUnLock(lex->tokenValue);
+ lex->tokenValue = 0;
+ }
+ jsvUnLock(lex->sourceVar);
+}
+
+void jslSeekTo(JsLex *lex, JslCharPos seekToChar) {
+ jslSeek(lex, seekToChar);
+ jslPreload(lex);
+}
+
+void jslReset(JsLex *lex) {
+ jslSeekTo(lex, 0);
+}
+
+void jslTokenAsString(int token, char *str, size_t len) {
+ // see JS_ERROR_TOKEN_BUF_SIZE
+ if (token>32 && token<128) {
+ assert(len>=4);
+ str[0] = '\'';
+ str[1] = (char)token;
+ str[2] = '\'';
+ str[3] = 0;
+ return;
+ }
+
+ switch (token) {
+ case LEX_EOF : strncpy(str, "EOF", len); return;
+ case LEX_ID : strncpy(str, "ID", len); return;
+ case LEX_INT : strncpy(str, "INT", len); return;
+ case LEX_FLOAT : strncpy(str, "FLOAT", len); return;
+ case LEX_STR : strncpy(str, "STRING", len); return;
+ }
+ if (token>=LEX_EQUAL && token<LEX_R_LIST_END) {
+ const char tokenNames[] =
+ /* LEX_EQUAL : */ "==\0"
+ /* LEX_TYPEEQUAL : */ "===\0"
+ /* LEX_NEQUAL : */ "!=\0"
+ /* LEX_NTYPEEQUAL : */ "!==\0"
+ /* LEX_LEQUAL : */ "<=\0"
+ /* LEX_LSHIFT : */ "<<\0"
+ /* LEX_LSHIFTEQUAL : */ "<<=\0"
+ /* LEX_GEQUAL : */ ">=\0"
+ /* LEX_RSHIFT : */ ">>\0"
+ /* LEX_RSHIFTUNSIGNED */ ">>>\0"
+ /* LEX_RSHIFTEQUAL : */ ">>=\0"
+ /* LEX_RSHIFTUNSIGNEDEQUAL */ ">>>=\0"
+ /* LEX_PLUSEQUAL : */ "+=\0"
+ /* LEX_MINUSEQUAL : */ "-=\0"
+ /* LEX_PLUSPLUS : */ "++\0"
+ /* LEX_MINUSMINUS */ "--\0"
+ /* LEX_MULEQUAL : */ "*=\0"
+ /* LEX_DIVEQUAL : */ "/=\0"
+ /* LEX_MODEQUAL : */ "%=\0"
+ /* LEX_ANDEQUAL : */ "&=\0"
+ /* LEX_ANDAND : */ "&&\0"
+ /* LEX_OREQUAL : */ "|=\0"
+ /* LEX_OROR : */ "||\0"
+ /* LEX_XOREQUAL : */ "^=\0"
+
+ // reserved words
+ /*LEX_R_IF : */ "if\0"
+ /*LEX_R_ELSE : */ "else\0"
+ /*LEX_R_DO : */ "do\0"
+ /*LEX_R_WHILE : */ "while\0"
+ /*LEX_R_FOR : */ "for\0"
+ /*LEX_R_BREAK : */ "return\0"
+ /*LEX_R_CONTINUE */ "continue\0"
+ /*LEX_R_FUNCTION */ "function\0"
+ /*LEX_R_RETURN */ "return\0"
+ /*LEX_R_VAR : */ "var\0"
+ /*LEX_R_THIS : */ "this\0"
+ /*LEX_R_TRUE : */ "true\0"
+ /*LEX_R_FALSE : */ "false\0"
+ /*LEX_R_NULL : */ "null\0"
+ /*LEX_R_UNDEFINED */ "undefined\0"
+ /*LEX_R_NEW : */ "new\0"
+ /*LEX_R_IN : */ "in\0"
+ /*LEX_R_INSTANCEOF */ "instanceof\0"
+ /*LEX_R_SWITCH */ "switch\0"
+ /*LEX_R_CASE */ "case\0"
+ /*LEX_R_DEFAULT */ "default\0"
+ /*LEX_R_TYPEOF : */ "typeof\0"
+ /*LEX_R_VOID : */ "void\0"
+ ;
+ unsigned int p = 0;
+ int n = token-LEX_EQUAL;
+ while (n>0 && p<sizeof(tokenNames)) {
+ while (tokenNames[p] && p<sizeof(tokenNames)) p++;
+ p++; // skip the zero
+ n--; // next token
+ }
+ assert(n==0);
+ strncpy(str, &tokenNames[p], len);
+ return;
+ }
+
+ assert(len>=10);
+ strncpy(str, "?[",len);
+ itoa(token, &str[2], 10);
+ strncat(str, "]",len);
+}
+
+void jslGetTokenString(JsLex *lex, char *str, size_t len) {
+ if (lex->tk == LEX_ID) {
+ strncpy(str, "ID:", len);
+ strncat(str, jslGetTokenValueAsString(lex), len);
+ } else if (lex->tk == LEX_STR) {
+ strncpy(str, "String:'", len);
+ strncat(str, jslGetTokenValueAsString(lex), len);
+ strncat(str, "'", len);
+ } else
+ jslTokenAsString(lex->tk, str, len);
+}
+
+char *jslGetTokenValueAsString(JsLex *lex) {
+ assert(lex->tokenl < JSLEX_MAX_TOKEN_LENGTH);
+ lex->token[lex->tokenl] = 0; // add final null
+ return lex->token;
+}
+
+JsVar *jslGetTokenValueAsVar(JsLex *lex) {
+ if (lex->tokenValue) {
+ return jsvLockAgain(lex->tokenValue);
+ } else {
+ assert(lex->tokenl < JSLEX_MAX_TOKEN_LENGTH);
+ lex->token[lex->tokenl] = 0; // add final null
+ return jsvNewFromString(lex->token);
+ }
+}
+
+/// 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, lex->tokenStart);
+ // Sod it, skip this token anyway - stops us looping
+ jslGetNextToken(lex);
+ return false;
+ }
+ jslGetNextToken(lex);
+ return true;
+}
+
diff --git a/components/external/espruino/src/jslex.h b/components/external/espruino/src/jslex.h
new file mode 100644
index 0000000000..8a1aee3a9b
--- /dev/null
+++ b/components/external/espruino/src/jslex.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Lexer (convert JsVar strings into a series of tokens)
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSLEX_H_
+#define JSLEX_H_
+
+#include "jsutils.h"
+#include "jsvar.h"
+
+typedef struct JsLex
+{
+ // Actual Lexing related stuff
+ char currCh, nextCh;
+ short tk; ///< The type of the token that we have
+ JslCharPos tokenStart; ///< Position in the data at the beginning of the token we have here
+ JslCharPos tokenEnd; ///< Position in the data at the last character of the token we have here
+ JslCharPos tokenLastStart; ///< Position in the data of the first character of the last token
+ JslCharPos tokenLastEnd; ///< Position in the data of the last character of the last token
+ char token[JSLEX_MAX_TOKEN_LENGTH]; ///< Data contained in the token we have here
+ JsVar *tokenValue; ///< JsVar containing the current token - used only for strings
+ unsigned char tokenl; ///< the current length of token
+
+ /* Where we get our data from...
+ *
+ * This is a bit more tricky than normal because the data comes from JsVars,
+ * which only have fixed length strings. If we go past this, we have to go
+ * to the next jsVar...
+ */
+ JsVar *sourceVar; // the actual string var
+ JsvStringIterator it; // Iterator for the string
+} JsLex;
+
+void jslInit(JsLex *lex, JsVar *var);
+void jslKill(JsLex *lex);
+void jslReset(JsLex *lex);
+void jslSeekTo(JsLex *lex, JslCharPos seekToChar); // like jslSeek, but pre-fills characters
+
+bool jslMatch(JsLex *lex, int expected_tk); ///< Match, and return true on success, false on failure
+void jslTokenAsString(int token, char *str, size_t len); ///< output the given token as a string - for debugging
+void jslGetTokenString(JsLex *lex, char *str, size_t len);
+char *jslGetTokenValueAsString(JsLex *lex);
+JsVar *jslGetTokenValueAsVar(JsLex *lex);
+
+// Only for more 'internal' use
+void jslSeek(JsLex *lex, JslCharPos seekToChar); // like jslSeekTo, but doesn't pre-fill characters
+void jslGetNextCh(JsLex *lex);
+void jslGetNextToken(JsLex *lex); ///< Get the text token from our text string
+
+#endif /* JSLEX_H_ */
diff --git a/components/external/espruino/src/jsparse.c b/components/external/espruino/src/jsparse.c
new file mode 100644
index 0000000000..fb7d42a068
--- /dev/null
+++ b/components/external/espruino/src/jsparse.c
@@ -0,0 +1,2213 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Recursive descent parser for code execution
+ * ----------------------------------------------------------------------------
+ */
+#include "jsparse.h"
+#include "jsinteractive.h"
+#include "jswrapper.h"
+
+/* Info about execution when Parsing - this saves passing it on the stack
+ * for each call */
+JsExecInfo execInfo;
+
+// ----------------------------------------------- Forward decls
+JsVar *jspeBase();
+JsVar *jspeBaseWithComma();
+JsVar *jspeBlock();
+JsVar *jspeStatement();
+// ----------------------------------------------- Utils
+#define JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, CLEANUP_CODE, RETURN_VAL) { if (!jslMatch(execInfo.lex,(TOKEN))) { jspSetError(); CLEANUP_CODE; return RETURN_VAL; } }
+#define JSP_MATCH_WITH_RETURN(TOKEN, RETURN_VAL) JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, , RETURN_VAL)
+#define JSP_MATCH(TOKEN) JSP_MATCH_WITH_CLEANUP_AND_RETURN(TOKEN, , 0)
+#define JSP_SHOULD_EXECUTE (((execInfo.execute)&EXEC_RUN_MASK)==EXEC_YES)
+#define JSP_SAVE_EXECUTE() JsExecFlags oldExecute = execInfo.execute
+#define JSP_RESTORE_EXECUTE() execInfo.execute = (execInfo.execute&(JsExecFlags)(~EXEC_SAVE_RESTORE_MASK)) | (oldExecute&EXEC_SAVE_RESTORE_MASK);
+#define JSP_HAS_ERROR (((execInfo.execute)&EXEC_ERROR_MASK)!=0)
+
+/// if interrupting execution, this is set
+bool jspIsInterrupted() {
+ return (execInfo.execute & EXEC_INTERRUPTED)!=0;
+}
+
+/// if interrupting execution, this is set
+void jspSetInterrupted(bool interrupt) {
+ if (interrupt)
+ execInfo.execute = execInfo.execute | EXEC_INTERRUPTED;
+ else
+ execInfo.execute = execInfo.execute & (JsExecFlags)~EXEC_INTERRUPTED;
+}
+
+static inline void jspSetError() {
+ execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_YES) | EXEC_ERROR;
+}
+
+bool jspHasError() {
+ return JSP_HAS_ERROR;
+}
+
+///< Same as jsvSetValueOfName, but nice error message
+void jspReplaceWith(JsVar *dst, JsVar *src) {
+ // If this is an index in an array buffer, write directly into the array buffer
+ if (jsvIsArrayBufferName(dst)) {
+ JsVarInt idx = jsvGetInteger(dst);
+ JsVar *arrayBuffer = jsvLock(dst->firstChild);
+ 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)) {
+ jsErrorAt("Unable to assign value to non-reference", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ return;
+ }
+ jsvSetValueOfName(dst, src);
+}
+
+void jspeiInit(JsParse *parse, JsLex *lex) {
+ execInfo.parse = parse;
+ execInfo.lex = lex;
+ execInfo.scopeCount = 0;
+ execInfo.execute = EXEC_YES;
+}
+
+void jspeiKill() {
+ execInfo.parse = 0;
+ execInfo.lex = 0;
+ assert(execInfo.scopeCount==0);
+}
+
+bool jspeiAddScope(JsVarRef scope) {
+ if (execInfo.scopeCount >= JSPARSE_MAX_SCOPES) {
+ jsError("Maximum number of scopes exceeded");
+ jspSetError();
+ return false;
+ }
+ execInfo.scopes[execInfo.scopeCount++] = jsvRefRef(scope);
+ return true;
+}
+
+void jspeiRemoveScope() {
+ if (execInfo.scopeCount <= 0) {
+ jsErrorInternal("Too many scopes removed");
+ jspSetError();
+ return;
+ }
+ jsvUnRefRef(execInfo.scopes[--execInfo.scopeCount]);
+}
+
+JsVar *jspeiFindInScopes(const char *name) {
+ int i;
+ for (i=execInfo.scopeCount-1;i>=0;i--) {
+ JsVar *ref = jsvFindChildFromStringRef(execInfo.scopes[i], name, false);
+ if (ref) return ref;
+ }
+ return jsvFindChildFromString(execInfo.parse->root, name, false);
+}
+
+JsVar *jspeiFindOnTop(const char *name, bool createIfNotFound) {
+ if (execInfo.scopeCount>0)
+ return jsvFindChildFromStringRef(execInfo.scopes[execInfo.scopeCount-1], name, createIfNotFound);
+ return jsvFindChildFromString(execInfo.parse->root, name, createIfNotFound);
+}
+JsVar *jspeiFindNameOnTop(JsVar *childName, bool createIfNotFound) {
+ if (execInfo.scopeCount>0)
+ return jsvFindChildFromVarRef(execInfo.scopes[execInfo.scopeCount-1], childName, createIfNotFound);
+ return jsvFindChildFromVar(execInfo.parse->root, childName, createIfNotFound);
+}
+
+/** Here we assume that we have already looked in the parent itself -
+ * and are now going down looking at the stuff it inherited */
+JsVar *jspeiFindChildFromStringInParents(JsVar *parent, const char *name) {
+ if (jsvIsObject(parent)) {
+ // If an object, look for an 'inherits' var
+ JsVar *inheritsFrom = jsvObjectGetChild(parent, JSPARSE_INHERITS_VAR, 0);
+
+ // if there's no inheritsFrom, just default to 'Object.prototype'
+ if (!inheritsFrom) {
+ JsVar *obj = jsvObjectGetChild(execInfo.parse->root, "Object", 0);
+ if (obj) {
+ inheritsFrom = jsvObjectGetChild(obj, JSPARSE_PROTOTYPE_VAR, 0);
+ jsvUnLock(obj);
+ }
+ }
+
+ if (inheritsFrom && inheritsFrom!=parent) {
+ // we have what it inherits from (this is ACTUALLY the prototype var)
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/proto
+ JsVar *child = jsvFindChildFromString(inheritsFrom, name, false);
+ if (!child)
+ child = jspeiFindChildFromStringInParents(inheritsFrom, name);
+ jsvUnLock(inheritsFrom);
+ if (child) return child;
+ }
+ } else { // Not actually an object - but might be an array/string/etc
+ const char *objectName = jswGetBasicObjectName(parent);
+ while (objectName) {
+ JsVar *objName = jsvFindChildFromString(execInfo.parse->root, objectName, false);
+ if (objName) {
+ JsVar *result = 0;
+ JsVar *obj = jsvSkipNameAndUnLock(objName);
+ if (obj) {
+ // We have found an object with this name - search for the prototype var
+ JsVar *proto = jsvObjectGetChild(obj, JSPARSE_PROTOTYPE_VAR, 0);
+ if (proto) {
+ result = jsvFindChildFromString(proto, name, false);
+ jsvUnLock(proto);
+ }
+ jsvUnLock(obj);
+ }
+ if (result) return result;
+ }
+ /* We haven't found anything in the actual object, we should check the 'Object' itself
+ eg, we tried 'String', so now we should try 'Object'. Built-in types don't have room for
+ a prototype field, so we hard-code it */
+ objectName = jswGetBasicObjectPrototypeName(objectName);
+ }
+ }
+
+ // no luck!
+ return 0;
+}
+
+JsVar *jspeiGetScopesAsVar() {
+ if (execInfo.scopeCount==0) return 0;
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ int i;
+ for (i=0;i<execInfo.scopeCount;i++) {
+ //printf("%d %d\n",i,execInfo.scopes[i]);
+ JsVar *scope = jsvLock(execInfo.scopes[i]);
+ JsVar *idx = jsvMakeIntoVariableName(jsvNewFromInteger(i), scope);
+ jsvUnLock(scope);
+ if (!idx) { // out of memort
+ jspSetError();
+ return arr;
+ }
+ jsvAddName(arr, idx);
+ jsvUnLock(idx);
+ }
+ //printf("%d\n",arr->firstChild);
+ return arr;
+}
+
+void jspeiLoadScopesFromVar(JsVar *arr) {
+ execInfo.scopeCount = 0;
+ //printf("%d\n",arr->firstChild);
+ JsVarRef childref = arr->firstChild;
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ //printf("%d %d %d %d\n",execInfo.scopeCount,childref,child, child->firstChild);
+ execInfo.scopes[execInfo.scopeCount] = jsvRefRef(child->firstChild);
+ execInfo.scopeCount++;
+ childref = child->nextSibling;
+ jsvUnLock(child);
+ }
+}
+// -----------------------------------------------
+#ifdef ARM
+extern int _end;
+#endif
+bool jspCheckStackPosition() {
+#ifdef ARM
+ void *frame = __builtin_frame_address(0);
+ if ((char*)frame < ((char*)&_end)+1024/*so many bytes leeway*/) {
+// jsiConsolePrintf("frame: %d,end:%d\n",(int)frame,(int)&_end);
+ jsErrorAt("Too much recursion - the stack is about to overflow", execInfo.lex, execInfo.lex->tokenStart );
+ jspSetInterrupted(true);
+ return false;
+ }
+#endif
+ return true;
+}
+
+
+// Set execFlags such that we are not executing
+void jspSetNoExecute() {
+ execInfo.execute = (execInfo.execute & (JsExecFlags)(int)~EXEC_RUN_MASK) | EXEC_NO;
+}
+
+// parse single variable name
+bool jspParseVariableName() {
+ JSP_MATCH(LEX_ID);
+ return true;
+}
+
+// parse function with no arguments
+bool jspParseEmptyFunction() {
+ JSP_MATCH(LEX_ID);
+ JSP_MATCH('(');
+ if (execInfo.lex->tk != ')')
+ jsvUnLock(jspeBase());
+ // throw away extra params
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ jsvUnLock(jspeBase());
+ }
+ JSP_MATCH(')');
+ return true;
+}
+
+// parse function with a single argument, return its value (no names!)
+JsVar *jspParseSingleFunction() {
+ JsVar *v = 0;
+ JSP_MATCH(LEX_ID);
+ JSP_MATCH('(');
+ if (execInfo.lex->tk != ')')
+ v = jsvSkipNameAndUnLock(jspeBase());
+ // throw away extra params
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') {
+ JSP_MATCH_WITH_RETURN(',', v);
+ jsvUnLock(jspeBase());
+ }
+ JSP_MATCH_WITH_RETURN(')', v);
+ return v;
+}
+
+/// parse function with max 4 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say
+bool jspParseFunction(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d) {
+ if (a) *a = 0;
+ if (b) *b = 0;
+ if (c) *c = 0;
+ if (d) *d = 0;
+ JSP_MATCH(LEX_ID);
+ JSP_MATCH('(');
+ if (a && execInfo.lex->tk != ')') {
+ *a = jspeBase();
+ if (!(skipName&JSP_NOSKIP_A)) *a = jsvSkipNameAndUnLock(*a);
+ }
+ if (b && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *b = jspeBase();
+ if (!(skipName&JSP_NOSKIP_B)) *b = jsvSkipNameAndUnLock(*b);
+ }
+ if (c && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *c = jspeBase();
+ if (!(skipName&JSP_NOSKIP_C)) *c = jsvSkipNameAndUnLock(*c);
+ }
+ if (d && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *d = jspeBase();
+ if (!(skipName&JSP_NOSKIP_D)) *d = jsvSkipNameAndUnLock(*d);
+ }
+ // throw away extra params
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ jsvUnLock(jspeBase());
+ }
+ JSP_MATCH(')');
+ return true;
+}
+
+/// parse function with max 8 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say
+bool jspParseFunction8(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d, JsVar **e, JsVar **f, JsVar **g, JsVar **h) {
+ if (a) *a = 0;
+ if (b) *b = 0;
+ if (c) *c = 0;
+ if (d) *d = 0;
+ if (e) *e = 0;
+ if (f) *f = 0;
+ if (g) *g = 0;
+ if (h) *h = 0;
+ JSP_MATCH(LEX_ID);
+ JSP_MATCH('(');
+ if (a && execInfo.lex->tk != ')') {
+ *a = jspeBase();
+ if (!(skipName&JSP_NOSKIP_A)) *a = jsvSkipNameAndUnLock(*a);
+ }
+ if (b && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *b = jspeBase();
+ if (!(skipName&JSP_NOSKIP_B)) *b = jsvSkipNameAndUnLock(*b);
+ }
+ if (c && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *c = jspeBase();
+ if (!(skipName&JSP_NOSKIP_C)) *c = jsvSkipNameAndUnLock(*c);
+ }
+ if (d && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *d = jspeBase();
+ if (!(skipName&JSP_NOSKIP_D)) *d = jsvSkipNameAndUnLock(*d);
+ }
+ if (e && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *e = jspeBase();
+ if (!(skipName&JSP_NOSKIP_E)) *e = jsvSkipNameAndUnLock(*e);
+ }
+ if (f && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *f = jspeBase();
+ if (!(skipName&JSP_NOSKIP_F)) *f = jsvSkipNameAndUnLock(*f);
+ }
+ if (g && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *g = jspeBase();
+ if (!(skipName&JSP_NOSKIP_G)) *g = jsvSkipNameAndUnLock(*g);
+ }
+ if (h && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ *h = jspeBase();
+ if (!(skipName&JSP_NOSKIP_H)) *h = jsvSkipNameAndUnLock(*h);
+ }
+ // throw away extra params
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') {
+ JSP_MATCH(',');
+ jsvUnLock(jspeBase());
+ }
+ JSP_MATCH(')');
+ return true;
+}
+
+/// parse a function with any number of argument, and return an array of de-named aruments
+JsVar *jspParseFunctionAsArray() {
+ JSP_MATCH(LEX_ID);
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ if (!arr) return 0; // out of memory
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN('(', jsvUnLock(arr), 0);
+ while (!JSP_HAS_ERROR && execInfo.lex->tk!=')' && execInfo.lex->tk!=LEX_EOF) {
+ JsVar *arg = jsvSkipNameAndUnLock(jspeBase());
+ jsvArrayPushAndUnLock(arr, arg); // even if undefined
+ if (execInfo.lex->tk!=')') JSP_MATCH_WITH_CLEANUP_AND_RETURN(',', jsvUnLock(arr), 0);
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(arr), 0);
+ return arr;
+}
+
+// ----------------------------------------------
+
+// we return a value so that JSP_MATCH can return 0 if it fails (if we pass 0, we just parse all args)
+bool jspeFunctionArguments(JsVar *funcVar) {
+ JSP_MATCH('(');
+ while (execInfo.lex->tk!=')') {
+ if (funcVar) {
+ JsVar *param = jsvAddNamedChild(funcVar, 0, jslGetTokenValueAsString(execInfo.lex));
+ if (!param) { // out of memory
+ jspSetError();
+ return false;
+ }
+ param->flags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter
+ jsvUnLock(param);
+ }
+ JSP_MATCH(LEX_ID);
+ if (execInfo.lex->tk!=')') JSP_MATCH(',');
+ }
+ JSP_MATCH(')');
+ return true;
+}
+
+bool jspeParseNativeFunction(JsCallback callbackPtr) {
+ char funcName[JSLEX_MAX_TOKEN_LENGTH];
+ JsVar *funcVar;
+ JsVar *base = jsvLockAgain(execInfo.parse->root);
+ JSP_MATCH(LEX_R_FUNCTION);
+ // not too bothered about speed/memory here as only called on init :)
+ strncpy(funcName, jslGetTokenValueAsString(execInfo.lex), JSLEX_MAX_TOKEN_LENGTH);
+ JSP_MATCH(LEX_ID);
+ /* Check for dots, we might want to do something like function 'String.substring' ... */
+ while (execInfo.lex->tk == '.') {
+ JsVar *link;
+ JSP_MATCH('.');
+ link = jsvFindChildFromString(base, funcName, false);
+ // if it doesn't exist, make a new object class
+ if (!link) {
+ JsVar *obj = jsvNewWithFlags(JSV_OBJECT);
+ link = jsvAddNamedChild(base, obj, funcName);
+ jsvUnLock(obj);
+ }
+ // set base to the object (not the name)
+ jsvUnLock(base);
+ base = jsvSkipNameAndUnLock(link);
+ // Look for another name
+ strncpy(funcName, jslGetTokenValueAsString(execInfo.lex), JSLEX_MAX_TOKEN_LENGTH);
+ JSP_MATCH(LEX_ID);
+ }
+ // So now, base points to an object where we want our function
+ funcVar = jsvNewWithFlags(JSV_FUNCTION | JSV_NATIVE);
+ if (!funcVar) {
+ jsvUnLock(base);
+ jspSetError();
+ return false; // Out of memory
+ }
+ funcVar->varData.callback = callbackPtr;
+ jspeFunctionArguments(funcVar);
+
+ if (JSP_HAS_ERROR) { // probably out of memory while parsing
+ jsvUnLock(base);
+ jsvUnLock(funcVar);
+ return false;
+ }
+ // Add the function with its name
+ JsVar *funcNameVar = jsvFindChildFromString(base, funcName, true);
+ if (funcNameVar) // could be out of memory
+ jsvUnLock(jsvSetValueOfName(funcNameVar, funcVar)); // unlocks funcNameVar
+ jsvUnLock(base);
+ jsvUnLock(funcVar);
+ return true;
+}
+
+bool jspAddNativeFunction(JsParse *parse, const char *funcDesc, JsCallback callbackPtr) {
+ JsVar *fncode = jsvNewFromString(funcDesc);
+ if (!fncode) return false; // out of memory!
+
+ JSP_SAVE_EXECUTE();
+ JsExecInfo oldExecInfo = execInfo;
+
+ // Set up Lexer
+
+ JsLex lex;
+ jslInit(&lex, fncode);
+ jsvUnLock(fncode);
+
+
+ jspeiInit(parse, &lex);
+
+ // Parse
+ bool success = jspeParseNativeFunction(callbackPtr);
+ if (!success) {
+ jsError("Parsing Native Function failed!");
+ jspSetError();
+ }
+
+
+ // cleanup
+ jspeiKill();
+ jslKill(&lex);
+ JSP_RESTORE_EXECUTE();
+ oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok.
+ execInfo = oldExecInfo;
+
+ return success;
+}
+
+JsVar *jspeFunctionDefinition() {
+ // actually parse a function... We assume that the LEX_FUNCTION and name
+ // have already been parsed
+ JsVar *funcVar = 0;
+ if (JSP_SHOULD_EXECUTE)
+ funcVar = jsvNewWithFlags(JSV_FUNCTION);
+ // Get arguments save them to the structure
+ if (!jspeFunctionArguments(funcVar)) {
+ jsvUnLock(funcVar);
+ // parse failed
+ return 0;
+ }
+ // Get the code - first parse it so we know where it stops
+ JslCharPos funcBegin = execInfo.lex->tokenStart;
+ JSP_SAVE_EXECUTE();
+ jspSetNoExecute();
+ jsvUnLock(jspeBlock());
+ JSP_RESTORE_EXECUTE();
+ // Then create var and set
+ if (JSP_SHOULD_EXECUTE) {
+ // code var
+ JsVar *funcCodeVar = jsvNewFromLexer(execInfo.lex, funcBegin, (JslCharPos)(execInfo.lex->tokenLastEnd+1));
+ jsvUnLock(jsvAddNamedChild(funcVar, funcCodeVar, JSPARSE_FUNCTION_CODE_NAME));
+ jsvUnLock(funcCodeVar);
+ // scope var
+ JsVar *funcScopeVar = jspeiGetScopesAsVar();
+ if (funcScopeVar) {
+ jsvUnLock(jsvAddNamedChild(funcVar, funcScopeVar, JSPARSE_FUNCTION_SCOPE_NAME));
+ jsvUnLock(funcScopeVar);
+ }
+ }
+ return funcVar;
+}
+
+/* Parse just the brackets of a function - and throw
+ * everything away */
+bool jspeParseFunctionCallBrackets() {
+ JSP_MATCH('(');
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ')') {
+ jsvUnLock(jspeBase());
+ if (execInfo.lex->tk!=')') JSP_MATCH(',');
+ }
+ if (!JSP_HAS_ERROR) JSP_MATCH(')');
+ return 0;
+}
+
+/** Handle a function call (assumes we've parsed the function name and we're
+ * on the start bracket). 'thisArg' is the value of the 'this' variable when the
+ * function is executed (it's usually the parent object)
+ *
+ * If !isParsing and arg0!=0, argument 0 is set to what is supplied (same with arg1)
+ *
+ * functionName is used only for error reporting - and can be 0
+ */
+JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *thisArg, bool isParsing, int argCount, JsVar **argPtr) {
+ if (JSP_SHOULD_EXECUTE && !function) {
+ jsErrorAt("Function not found! Skipping.", execInfo.lex, execInfo.lex->tokenLastStart );
+ jspSetError();
+ }
+
+ if (JSP_SHOULD_EXECUTE) jspCheckStackPosition(); // try and ensure that we won't overflow our stack
+
+ if (JSP_SHOULD_EXECUTE && function) {
+ JsVar *functionRoot;
+ JsVar *functionCode = 0;
+ JsVar *returnVarName;
+ JsVar *returnVar;
+ JsVarRef v;
+ if (!jsvIsFunction(function)) {
+ char buf[JS_ERROR_BUF_SIZE];
+ strncpy(buf, "Expecting a function to call", JS_ERROR_BUF_SIZE);
+ const char *name = jswGetBasicObjectName(function);
+ if (name) {
+ strncat(buf, ", got a ", JS_ERROR_BUF_SIZE);
+ strncat(buf, name, JS_ERROR_BUF_SIZE);
+ }
+ jsErrorAt(buf, execInfo.lex, execInfo.lex->tokenLastStart );
+ jspSetError();
+ return 0;
+ }
+
+ /** Special case - we're parsing and we hit an already-defined function
+ * that has no 'code'. This means that we should use jswHandleFunctionCall
+ * to try and parse it */
+ if (!jsvIsNative(function)) {
+ functionCode = jsvFindChildFromString(function, JSPARSE_FUNCTION_CODE_NAME, false);
+ if (isParsing && !functionCode) {
+ char buf[32];
+ jsvGetString(functionName, buf, sizeof(buf));
+ JslCharPos pos = execInfo.lex->tokenStart;
+ jslSeekTo(execInfo.lex, execInfo.lex->tokenLastStart); // NASTY! because jswHandleFunctionCall expects to parse IDs
+ JsVar *res = jswHandleFunctionCall(0, 0, buf);
+ // but we didn't find anything - so just carry on...
+ if (res!=JSW_HANDLEFUNCTIONCALL_UNHANDLED)
+ return res;
+ jslSeekTo(execInfo.lex, pos); // NASTY!
+ }
+ }
+
+ if (isParsing) JSP_MATCH('(');
+ // create a new symbol table entry for execution of this function
+ // OPT: can we cache this function execution environment + param variables?
+ // OPT: Probably when calling a function ONCE, use it, otherwise when recursing, make new?
+ functionRoot = jsvNewWithFlags(JSV_FUNCTION);
+ if (!functionRoot) { // out of memory
+ jspSetError();
+ return 0;
+ }
+ JsVar *oldThisVar = execInfo.thisVar;
+ execInfo.thisVar = thisArg;
+ if (isParsing) {
+ int hadParams = 0;
+ // grab in all parameters. We go around this loop until we've run out
+ // of named parameters AND we've parsed all the supplied arguments
+ v = function->firstChild;
+ while (!JSP_HAS_ERROR && (v || execInfo.lex->tk!=')')) {
+ JsVar *param = 0;
+ if (v) param = jsvLock(v);
+ bool paramDefined = jsvIsFunctionParameter(param);
+ if (execInfo.lex->tk!=')' || paramDefined) {
+ hadParams++;
+ JsVar *value = 0;
+ // ONLY parse this if it was supplied, otherwise leave 0 (undefined)
+ if (execInfo.lex->tk!=')')
+ value = jspeBase();
+ // and if execute, copy it over
+ if (JSP_SHOULD_EXECUTE) {
+ value = jsvSkipNameAndUnLock(value);
+ JsVar *paramName = paramDefined ? jsvCopy(param) : jsvNewFromEmptyString();
+ paramName->flags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter
+ JsVar *newValueName = jsvMakeIntoVariableName(paramName, value);
+ if (newValueName) { // could be out of memory
+ jsvAddName(functionRoot, newValueName);
+ } else
+ jspSetError();
+ jsvUnLock(newValueName);
+ }
+ jsvUnLock(value);
+ if (execInfo.lex->tk!=')') JSP_MATCH(',');
+ }
+ if (param) {
+ v = param->nextSibling;
+ jsvUnLock(param);
+ }
+ }
+ JSP_MATCH(')');
+ } else if (JSP_SHOULD_EXECUTE && argCount>0) { // and NOT isParsing
+ int args = 0;
+ v = function->firstChild;
+ while (args<argCount) {
+ JsVar *param = v ? jsvLock(v) : 0;
+ bool paramDefined = jsvIsFunctionParameter(param);
+ JsVar *paramName = paramDefined ? jsvCopy(param) : jsvNewFromEmptyString();
+ paramName->flags |= JSV_FUNCTION_PARAMETER; // force this to be called a function parameter
+ JsVar *newValueName = jsvMakeIntoVariableName(paramName, argPtr[args]);
+ if (newValueName) // could be out of memory - or maybe just not supplied!
+ jsvAddName(functionRoot, newValueName);
+ jsvUnLock(newValueName);
+ args++;
+ if (param) {
+ v = param->nextSibling;
+ jsvUnLock(param);
+ }
+ }
+ }
+ // setup a return variable
+ returnVarName = jsvAddNamedChild(functionRoot, 0, JSPARSE_RETURN_VAR);
+ if (!returnVarName) // out of memory
+ jspSetError();
+ //jsvTrace(jsvGetRef(functionRoot), 5); // debugging
+
+ if (!JSP_HAS_ERROR) {
+ if (jsvIsNative(function)) {
+ assert(function->varData.callback);
+ if (function->varData.callback)
+ function->varData.callback(jsvGetRef(functionRoot));
+ } else {
+ // save old scopes
+ JsVarRef oldScopes[JSPARSE_MAX_SCOPES];
+ int oldScopeCount;
+ int i;
+ oldScopeCount = execInfo.scopeCount;
+ for (i=0;i<execInfo.scopeCount;i++)
+ oldScopes[i] = execInfo.scopes[i];
+ // if we have a scope var, load it up. We may not have one if there were no scopes apart from root
+ JsVar *functionScope = jsvFindChildFromString(function, JSPARSE_FUNCTION_SCOPE_NAME, false);
+ if (functionScope) {
+ JsVar *functionScopeVar = jsvLock(functionScope->firstChild);
+ //jsvTrace(jsvGetRef(functionScopeVar),5);
+ jspeiLoadScopesFromVar(functionScopeVar);
+ jsvUnLock(functionScopeVar);
+ jsvUnLock(functionScope);
+ } else {
+ // no scope var defined? We have no scopes at all!
+ execInfo.scopeCount = 0;
+ }
+ // add the function's execute space to the symbol table so we can recurse
+ if (jspeiAddScope(jsvGetRef(functionRoot))) {
+ /* Adding scope may have failed - we may have descended too deep - so be sure
+ * not to pull somebody else's scope off
+ */
+
+ /* we just want to execute the block, but something could
+ * have messed up and left us with the wrong ScriptLex, so
+ * we want to be careful here... */
+ if (functionCode) {
+ JsLex *oldLex;
+ JsVar* functionCodeVar = jsvSkipNameAndUnLock(functionCode);
+ JsLex newLex;
+ jslInit(&newLex, functionCodeVar);
+ jsvUnLock(functionCodeVar);
+
+ oldLex = execInfo.lex;
+ execInfo.lex = &newLex;
+ JSP_SAVE_EXECUTE();
+ jspeBlock();
+ bool hasError = JSP_HAS_ERROR;
+ JSP_RESTORE_EXECUTE(); // because return will probably have set execute to false
+ jslKill(&newLex);
+ execInfo.lex = oldLex;
+ if (hasError) {
+ jsiConsolePrint("in function ");
+ if (jsvIsString(functionName)) {
+ jsiConsolePrint("\"");
+ jsiConsolePrintStringVar(functionName);
+ jsiConsolePrint("\" ");
+ }
+ jsiConsolePrint("called from ");
+ if (execInfo.lex)
+ jsiConsolePrintPosition(execInfo.lex, execInfo.lex->tokenLastEnd);
+ else
+ jsiConsolePrint("system\n");
+ jspSetError();
+ }
+ }
+
+ jspeiRemoveScope();
+ }
+
+ // Unref old scopes
+ for (i=0;i<execInfo.scopeCount;i++)
+ jsvUnRefRef(execInfo.scopes[i]);
+ // restore function scopes
+ for (i=0;i<oldScopeCount;i++)
+ execInfo.scopes[i] = oldScopes[i];
+ execInfo.scopeCount = oldScopeCount;
+ }
+ }
+
+ /* Return to old 'this' var. No need to unlock as we never locked before */
+ execInfo.thisVar = oldThisVar;
+
+ /* get the real return var before we remove it from our function */
+ returnVar = jsvSkipNameAndUnLock(returnVarName);
+ if (returnVarName) // could have failed with out of memory
+ jsvSetValueOfName(returnVarName, 0); // remove return value (which helps stops circular references)
+ jsvUnLock(functionRoot);
+ if (returnVar)
+ return returnVar;
+ else
+ return 0;
+ } else if (isParsing) { // ---------------------------------- function, but not executing - just parse args and be done
+ jspeParseFunctionCallBrackets();
+ /* Do not return function, as it will be unlocked! */
+ return 0;
+ } else return 0;
+}
+
+JsVar *jspeFactorSingleId() {
+ JsVar *a = JSP_SHOULD_EXECUTE ? jspeiFindInScopes(jslGetTokenValueAsString(execInfo.lex)) : 0;
+ if (JSP_SHOULD_EXECUTE && !a) {
+ const char *tokenName = jslGetTokenValueAsString(execInfo.lex); // BEWARE - this won't hang around forever!
+ /* Special case! We haven't found the variable, so check out
+ * and see if it's one of our builtins... */
+ if (jswIsBuiltInObject(tokenName)) {
+ JsVar *obj = jspNewBuiltin(tokenName);
+ if (obj) { // not out of memory
+ a = jsvAddNamedChild(execInfo.parse->root, obj, tokenName);
+ jsvUnLock(obj);
+ }
+ } else {
+ a = jswHandleFunctionCall(0, 0, tokenName);
+ if (a != JSW_HANDLEFUNCTIONCALL_UNHANDLED)
+ return a;
+ /* Variable doesn't exist! JavaScript says we should create it
+ * (we won't add it here. This is done in the assignment operator)*/
+ a = jsvMakeIntoVariableName(jslGetTokenValueAsVar(execInfo.lex), 0);
+ }
+ }
+ JSP_MATCH_WITH_RETURN(LEX_ID, a);
+
+ return a;
+}
+
+JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) {
+ /* The parent if we're executing a method call */
+ JsVar *parent = 0;
+
+ while (execInfo.lex->tk=='.' || execInfo.lex->tk=='[') {
+ if (execInfo.lex->tk == '.') { // ------------------------------------- Record Access
+ JSP_MATCH('.');
+ if (JSP_SHOULD_EXECUTE) {
+ // Note: name will go away when we oarse something else!
+ const char *name = jslGetTokenValueAsString(execInfo.lex);
+
+ JsVar *aVar = jsvSkipName(a);
+ JsVar *child = 0;
+ if (aVar && jswGetBasicObjectName(aVar)) {
+ // if we're an object (or pretending to be one)
+ if (jsvHasChildren(aVar))
+ child = jsvFindChildFromString(aVar, name, false);
+
+ if (!child)
+ child = jspeiFindChildFromStringInParents(aVar, name);
+
+
+ if (child) {
+ // it was found - no need for name ptr now, so match!
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child);
+ } else { // NOT FOUND...
+ /* Check for builtins via separate function
+ * This way we save on RAM for built-ins because all comes out of program code.
+ *
+ * We don't check for prototype vars, so people can overload the built
+ * in functions (eg. Person.prototype.toString). HOWEVER if we did
+ * this for 'this' then we couldn't say 'this.toString()'
+ * */
+ if (!jsvIsString(a) || (!jsvIsStringEqual(a, JSPARSE_PROTOTYPE_VAR))) // don't try and use builtins on the prototype var!
+ child = jswHandleFunctionCall(aVar, a/*name*/, name);
+ else
+ child = JSW_HANDLEFUNCTIONCALL_UNHANDLED;
+ if (child == JSW_HANDLEFUNCTIONCALL_UNHANDLED) {
+ child = 0;
+ // It wasn't handled... We already know this is an object so just add a new child
+ if (jsvIsObject(aVar) || jsvIsFunction(aVar) || jsvIsArray(aVar)) {
+ JsVar *value = 0;
+ if (jsvIsFunction(aVar) && strcmp(name, JSPARSE_PROTOTYPE_VAR)==0)
+ value = jsvNewWithFlags(JSV_OBJECT); // prototype is supposed to be an object
+ child = jsvAddNamedChild(aVar, value, name);
+ jsvUnLock(value);
+ } else {
+ // could have been a string...
+ jsErrorAt("Field or method does not already exist, and can't create it on a non-object", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child);
+ }
+ }
+ } else {
+ jsErrorAt("Using '.' operator on non-object", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(parent);jsvUnLock(a);, child);
+ }
+ jsvUnLock(parent);
+ parent = aVar;
+ jsvUnLock(a);
+ a = child;
+ } else {
+ // Not executing, just match
+ JSP_MATCH_WITH_RETURN(LEX_ID, a);
+ }
+ } else if (execInfo.lex->tk == '[') { // ------------------------------------- Array Access
+ JsVar *index;
+ JSP_MATCH('[');
+ index = jspeBase();
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(']', jsvUnLock(parent);jsvUnLock(index);, a);
+ if (JSP_SHOULD_EXECUTE) {
+ /* Index filtering (bug #19) - if we have an array index A that is:
+ is_string(A) && int_to_string(string_to_int(A)) = =A
+ then convert it to an integer. Should be too nasty for performance
+ as we only do this when accessing an array with a string */
+ if (jsvIsString(index) && jsvIsStringNumericStrict(index)) {
+ JsVar *v = jsvNewFromInteger(jsvGetInteger(index));
+ jsvUnLock(index);
+ index = v;
+ }
+
+ JsVar *aVar = jsvSkipName(a);
+ if (aVar && (jsvIsArrayBuffer(aVar))) {
+ // for array buffers, we actually create a NAME, and hand that back - then when we assign (or use SkipName) we pull out the correct data
+ JsVar *indexValue = jsvSkipName(index);
+ jsvUnLock(a);
+ a = jsvMakeIntoVariableName(jsvNewFromInteger(jsvGetInteger(indexValue)), aVar);
+ jsvUnLock(indexValue);
+ if (a) // turn into an 'array buffer name'
+ a->flags = (a->flags & ~(JSV_NAME|JSV_VARTYPEMASK)) | JSV_ARRAYBUFFERNAME;
+ } else if (aVar && (jsvIsArray(aVar) || jsvIsObject(aVar) || jsvIsFunction(aVar))) {
+ // TODO: If we set to undefined, maybe we should remove the name?
+ JsVar *indexValue = jsvSkipName(index);
+ if (!jsvIsString(indexValue) && !jsvIsNumeric(indexValue))
+ indexValue = jsvAsString(indexValue, true);
+ JsVar *child = jsvFindChildFromVar(aVar, indexValue, true);
+ jsvUnLock(indexValue);
+
+ jsvUnLock(parent);
+ parent = jsvLockAgain(aVar);
+ jsvUnLock(a);
+ a = child;
+ } else if (aVar && (jsvIsString(aVar))) {
+ JsVarInt idx = jsvGetIntegerAndUnLock(jsvSkipName(index));
+ JsVar *child = 0;
+ if (idx>=0 && idx<(JsVarInt)jsvGetStringLength(aVar)) {
+ char ch = jsvGetCharInString(aVar, (int)idx);
+ child = jsvNewFromEmptyString();
+ if (child) jsvAppendStringBuf(child, &ch, 1);
+ }
+ jsvUnLock(parent);
+ parent = jsvLockAgain(aVar);
+ jsvUnLock(a);
+ a = child;
+ } else {
+ jsWarnAt("Variable is not an Array or Object", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jsvUnLock(parent);
+ parent = 0;
+ jsvUnLock(a);
+ a = 0;
+ }
+ jsvUnLock(aVar);
+ }
+ jsvUnLock(index);
+ } else {
+ assert(0);
+ }
+ }
+
+ if (parentResult) *parentResult = parent;
+ else jsvUnLock(parent);
+ return a;
+}
+
+JsVar *jspeFactor();
+void jspEnsureIsPrototype(JsVar *prototypeName);
+
+JsVar *jspeConstruct(JsVar *func, JsVar *funcName, bool hasArgs) {
+ assert(JSP_SHOULD_EXECUTE);
+ if (!jsvIsFunction(func)) {
+ jsErrorAt("Constructor should be a function", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ return 0;
+ }
+
+ JsVar *thisObj = jsvNewWithFlags(JSV_OBJECT);
+ // Make sure the function has a 'prototype' var
+ JsVar *prototypeName = jsvFindChildFromString(func, JSPARSE_PROTOTYPE_VAR, true);
+ jspEnsureIsPrototype(prototypeName); // make sure it's an object
+ jsvUnLock(jsvAddNamedChild(thisObj, prototypeName, JSPARSE_INHERITS_VAR));
+ jsvUnLock(prototypeName);
+
+ JsVar *a = jspeFunctionCall(func, funcName, thisObj, hasArgs, 0, 0);
+
+ if (a) {
+ jsvUnLock(thisObj);
+ thisObj = a;
+ } else {
+ jsvUnLock(a);
+ JsVar *constructor = jsvFindChildFromString(thisObj, JSPARSE_CONSTRUCTOR_VAR, true);
+ if (constructor) {
+ jsvSetValueOfName(constructor, funcName);
+ jsvUnLock(constructor);
+ }
+ }
+ return thisObj;
+}
+
+JsVar *jspeFactorFunctionCall() {
+ /* The parent if we're executing a method call */
+ bool isConstructor = false;
+ if (execInfo.lex->tk==LEX_R_NEW) {
+ JSP_MATCH(LEX_R_NEW);
+ isConstructor = true;
+
+ if (execInfo.lex->tk==LEX_R_NEW) {
+ jsError("Nesting 'new' operators is unsupported");
+ jspSetError();
+ return 0;
+ }
+ }
+
+ JsVar *parent = 0;
+ JsVar *a = jspeFactorMember(jspeFactor(), &parent);
+
+ while (execInfo.lex->tk=='(' || (isConstructor && JSP_SHOULD_EXECUTE)) {
+ JsVar *funcName = a;
+ JsVar *func = jsvSkipName(funcName);
+
+ /* The constructor function doesn't change parsing, so if we're
+ * not executing, just short-cut it. */
+ if (isConstructor && JSP_SHOULD_EXECUTE) {
+ // If we have '(' parse an argument list, otherwise don't look for any args
+ bool parseArgs = execInfo.lex->tk=='(';
+ a = jspeConstruct(func, funcName, parseArgs);
+ isConstructor = false; // don't treat subsequent brackets as constructors
+ } else
+ a = jspeFunctionCall(func, funcName, parent, true, 0, 0);
+
+ jsvUnLock(funcName);
+ jsvUnLock(func);
+
+ jsvUnLock(parent); parent=0;
+ a = jspeFactorMember(a, &parent);
+ }
+
+ jsvUnLock(parent);
+ return a;
+}
+
+JsVar *jspeFactorId() {
+ return jspeFactorSingleId();
+}
+
+
+JsVar *jspeFactorObject() {
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *contents = jsvNewWithFlags(JSV_OBJECT);
+ if (!contents) { // out of memory
+ jspSetError();
+ return 0;
+ }
+ /* JSON-style object definition */
+ JSP_MATCH_WITH_RETURN('{', contents);
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != '}') {
+ JsVar *varName = 0;
+ if (JSP_SHOULD_EXECUTE) {
+ varName = jslGetTokenValueAsVar(execInfo.lex);
+ if (!varName) { // out of memory
+ return contents;
+ }
+ }
+ // we only allow strings or IDs on the left hand side of an initialisation
+ if (execInfo.lex->tk==LEX_STR) {
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_STR, jsvUnLock(varName), contents);
+ } else {
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(varName), contents);
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(varName), contents);
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *valueVar;
+ JsVar *value = jspeBase(); // value can be 0 (could be undefined!)
+ valueVar = jsvSkipNameAndUnLock(value);
+ varName = jsvMakeIntoVariableName(varName, valueVar);
+ jsvAddName(contents, varName);
+ jsvUnLock(valueVar);
+ }
+ jsvUnLock(varName);
+ // no need to clean here, as it will definitely be used
+ if (execInfo.lex->tk != '}') JSP_MATCH_WITH_RETURN(',', contents);
+ }
+ JSP_MATCH_WITH_RETURN('}', contents);
+ return contents;
+ } else {
+ // Not executing so do fast skip
+ return jspeBlock();
+ }
+}
+
+JsVar *jspeFactorArray() {
+ int idx = 0;
+ JsVar *contents = 0;
+ if (JSP_SHOULD_EXECUTE) {
+ contents = jsvNewWithFlags(JSV_ARRAY);
+ if (!contents) { // out of memory
+ jspSetError();
+ return 0;
+ }
+ }
+ /* JSON-style array */
+ JSP_MATCH_WITH_RETURN('[', contents);
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != ']') {
+ if (JSP_SHOULD_EXECUTE) {
+ // OPT: Store array indices as actual ints
+ JsVar *a;
+ JsVar *aVar;
+ JsVar *indexName;
+ a = jspeBase();
+ aVar = jsvSkipNameAndUnLock(a);
+ indexName = jsvMakeIntoVariableName(jsvNewFromInteger(idx), aVar);
+ if (indexName) { // could be out of memory
+ jsvAddName(contents, indexName);
+ jsvUnLock(indexName);
+ }
+ jsvUnLock(aVar);
+ } else {
+ jsvUnLock(jspeBase());
+ }
+ // no need to clean here, as it will definitely be used
+ if (execInfo.lex->tk != ']') JSP_MATCH_WITH_RETURN(',', contents);
+ idx++;
+ }
+ JSP_MATCH_WITH_RETURN(']', contents);
+ return contents;
+}
+
+void jspEnsureIsPrototype(JsVar *prototypeName) {
+ if (!prototypeName) return;
+ JsVar *prototypeVar = jsvSkipName(prototypeName);
+ if (!jsvIsObject(prototypeVar)) {
+ if (!jsvIsUndefined(prototypeVar))
+ jsWarn("Prototype is not an Object, so setting it to {}");
+ jsvUnLock(prototypeVar);
+ prototypeVar = jsvNewWithFlags(JSV_OBJECT); // prototype is supposed to be an object
+ JsVar *lastName = jsvSkipToLastName(prototypeName);
+ jsvSetValueOfName(lastName, prototypeVar);
+ jsvUnLock(lastName);
+ }
+ jsvUnLock(prototypeVar);
+}
+
+JsVar *jspeFactorTypeOf() {
+ JSP_MATCH(LEX_R_TYPEOF);
+ JsVar *a = jspeBase();
+ JsVar *result = 0;
+ if (JSP_SHOULD_EXECUTE) {
+ a = jsvSkipNameAndUnLock(a);
+ result=jsvNewFromString(jsvGetTypeOf(a));
+ }
+ jsvUnLock(a);
+ return result;
+}
+
+JsVar *jspeFactor() {
+ if (execInfo.lex->tk=='(') {
+ JsVar *a = 0;
+ JSP_MATCH('(');
+ if (jspCheckStackPosition())
+ a = jspeBaseWithComma();
+ if (!JSP_HAS_ERROR) JSP_MATCH_WITH_RETURN(')',a);
+ return a;
+ } else if (execInfo.lex->tk==LEX_R_TRUE) {
+ JSP_MATCH(LEX_R_TRUE);
+ return JSP_SHOULD_EXECUTE ? jsvNewFromBool(true) : 0;
+ } else if (execInfo.lex->tk==LEX_R_FALSE) {
+ JSP_MATCH(LEX_R_FALSE);
+ return JSP_SHOULD_EXECUTE ? jsvNewFromBool(false) : 0;
+ } else if (execInfo.lex->tk==LEX_R_NULL) {
+ JSP_MATCH(LEX_R_NULL);
+ return JSP_SHOULD_EXECUTE ? jsvNewWithFlags(JSV_NULL) : 0;
+ } else if (execInfo.lex->tk==LEX_R_UNDEFINED) {
+ JSP_MATCH(LEX_R_UNDEFINED);
+ return 0;
+ } else if (execInfo.lex->tk==LEX_ID) {
+ return jspeFactorId();
+ } else if (execInfo.lex->tk==LEX_INT) {
+ // atol works only on decimals
+ // strtol handles 0x12345 as well
+ //JsVarInt v = (JsVarInt)atol(jslGetTokenValueAsString(execInfo.lex));
+ //JsVarInt v = (JsVarInt)strtol(jslGetTokenValueAsString(execInfo.lex),0,0); // broken on PIC
+ if (JSP_SHOULD_EXECUTE) {
+ JsVarInt v = stringToInt(jslGetTokenValueAsString(execInfo.lex));
+ JSP_MATCH(LEX_INT);
+ return jsvNewFromInteger(v);
+ } else {
+ JSP_MATCH(LEX_INT);
+ return 0;
+ }
+ } else if (execInfo.lex->tk==LEX_FLOAT) {
+ if (JSP_SHOULD_EXECUTE) {
+ JsVarFloat v = stringToFloat(jslGetTokenValueAsString(execInfo.lex));
+ JSP_MATCH(LEX_FLOAT);
+ return jsvNewFromFloat(v);
+ } else {
+ JSP_MATCH(LEX_FLOAT);
+ return 0;
+ }
+ } else if (execInfo.lex->tk==LEX_STR) {
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *a = jslGetTokenValueAsVar(execInfo.lex);
+ JSP_MATCH_WITH_RETURN(LEX_STR, a);
+ return a;
+ } else {
+ JSP_MATCH(LEX_STR);
+ return 0;
+ }
+ } else if (execInfo.lex->tk=='{') {
+ return jspeFactorObject();
+ } else if (execInfo.lex->tk=='[') {
+ return jspeFactorArray();
+ } else if (execInfo.lex->tk==LEX_R_FUNCTION) {
+ JSP_MATCH(LEX_R_FUNCTION);
+ return jspeFunctionDefinition();
+ } else if (execInfo.lex->tk==LEX_R_THIS) {
+ JSP_MATCH(LEX_R_THIS);
+ return jsvLockAgain( execInfo.thisVar ? execInfo.thisVar : execInfo.parse->root );
+ } else if (execInfo.lex->tk==LEX_R_TYPEOF) {
+ return jspeFactorTypeOf();
+ } else if (execInfo.lex->tk==LEX_R_VOID) {
+ JSP_MATCH(LEX_R_VOID);
+ jsvUnLock(jspeFactor());
+ return 0;
+ }
+ // Nothing we can do here... just hope it's the end...
+ JSP_MATCH(LEX_EOF);
+ return 0;
+}
+
+__attribute((noinline)) JsVar *__jspePostfix(JsVar *a) {
+ while (execInfo.lex->tk==LEX_PLUSPLUS || execInfo.lex->tk==LEX_MINUSMINUS) {
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *one = jsvNewFromInteger(1);
+ JsVar *res = jsvMathsOpSkipNames(a, one, op==LEX_PLUSPLUS ? '+' : '-');
+ JsVar *oldValue;
+ jsvUnLock(one);
+ oldValue = jsvSkipName(a); // keep the old value
+ // in-place add/subtract
+ jspReplaceWith(a, res);
+ jsvUnLock(res);
+ // but then use the old value
+ jsvUnLock(a);
+ a = oldValue;
+ }
+ }
+ return a;
+}
+
+JsVar *jspePostfix() {
+ JsVar *a;
+ if (execInfo.lex->tk==LEX_PLUSPLUS || execInfo.lex->tk==LEX_MINUSMINUS) {
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ a = jspePostfix();
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *one = jsvNewFromInteger(1);
+ JsVar *res = jsvMathsOpSkipNames(a, one, op==LEX_PLUSPLUS ? '+' : '-');
+ jsvUnLock(one);
+ // in-place add/subtract
+ jspReplaceWith(a, res);
+ jsvUnLock(res);
+ }
+ } else
+ a = jspeFactorFunctionCall();
+ return __jspePostfix(a);
+}
+
+JsVar *jspeUnary() {
+ if (execInfo.lex->tk=='!' || execInfo.lex->tk=='~' || execInfo.lex->tk=='-' || execInfo.lex->tk=='+') {
+ short tk = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ if (!JSP_SHOULD_EXECUTE) {
+ return jspePostfix();
+ }
+ if (tk=='!') { // logical not
+ return jsvNewFromBool(!jsvGetBoolAndUnLock(jsvSkipNameAndUnLock(jspeUnary())));
+ } else if (tk=='~') { // bitwise not
+ return jsvNewFromInteger(~jsvGetIntegerAndUnLock(jsvSkipNameAndUnLock(jspeUnary())));
+ } else if (tk=='-') { // unary minus
+ return jsvNegateAndUnLock(jspeUnary()); // names already skipped
+ } else if (tk=='+') { // unary plus (convert to number)
+ return jsvAsNumber(jspeUnary()); // names already skipped
+ }
+ assert(0);
+ return 0;
+ } else
+ return jspePostfix();
+}
+
+__attribute((noinline)) JsVar *__jspeTerm(JsVar *a) {
+ while (execInfo.lex->tk=='*' || execInfo.lex->tk=='/' || execInfo.lex->tk=='%') {
+ JsVar *b;
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ b = jspeUnary();
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *res = jsvMathsOpSkipNames(a, b, op);
+ jsvUnLock(a); a = res;
+ }
+ jsvUnLock(b);
+ }
+ return a;
+}
+
+JsVar *jspeTerm() {
+ return __jspeTerm(jspeUnary());
+}
+
+__attribute((noinline)) JsVar *__jspeExpression(JsVar *a) {
+ while (execInfo.lex->tk=='+' || execInfo.lex->tk=='-') {
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ JsVar *b = jspeTerm();
+ if (JSP_SHOULD_EXECUTE) {
+ // not in-place, so just replace
+ JsVar *res = jsvMathsOpSkipNames(a, b, op);
+ jsvUnLock(a); a = res;
+ }
+ jsvUnLock(b);
+ }
+ return a;
+}
+
+
+JsVar *jspeExpression() {
+ return __jspeExpression(jspeTerm());
+}
+
+__attribute((noinline)) JsVar *__jspeShift(JsVar *a) {
+ if (execInfo.lex->tk==LEX_LSHIFT || execInfo.lex->tk==LEX_RSHIFT || execInfo.lex->tk==LEX_RSHIFTUNSIGNED) {
+ JsVar *b;
+ int op = execInfo.lex->tk;
+ JSP_MATCH(op);
+ b = jspeExpression();
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *res = jsvMathsOpSkipNames(a, b, op);
+ jsvUnLock(a); a = res;
+ }
+ jsvUnLock(b);
+ }
+ return a;
+}
+
+JsVar *jspeShift() {
+ return __jspeShift(jspeExpression());
+}
+
+__attribute((noinline)) JsVar *__jspeCondition(JsVar *a) {
+ JsVar *b;
+ while (execInfo.lex->tk==LEX_EQUAL || execInfo.lex->tk==LEX_NEQUAL ||
+ execInfo.lex->tk==LEX_TYPEEQUAL || execInfo.lex->tk==LEX_NTYPEEQUAL ||
+ execInfo.lex->tk==LEX_LEQUAL || execInfo.lex->tk==LEX_GEQUAL ||
+ execInfo.lex->tk=='<' || execInfo.lex->tk=='>' ||
+ execInfo.lex->tk==LEX_R_INSTANCEOF ||
+ (execInfo.lex->tk==LEX_R_IN && !(execInfo.execute&EXEC_FOR_INIT))) {
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ b = jspeShift();
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *res = 0;
+ if (op==LEX_R_IN) {
+ JsVar *av = jsvSkipName(a);
+ JsVar *bv = jsvSkipName(b);
+ if (jsvIsArray(bv) || jsvIsObject(bv)) {
+ JsVar *varFound = jsvGetArrayIndexOf(bv, av, false/*not exact*/); // ArrayIndexOf will return 0 if not found
+ res = jsvNewFromBool(varFound!=0);
+ jsvUnLock(varFound);
+ } // else it will be undefined
+ jsvUnLock(av);
+ jsvUnLock(bv);
+ } else if (op==LEX_R_INSTANCEOF) {
+ bool inst = false;
+ JsVar *av = jsvSkipName(a);
+ JsVar *bv = jsvSkipName(b);
+ if (!jsvIsFunction(bv)) {
+ jsErrorAt("Expecting a function on RHS in instanceof check", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ } else {
+ if (jsvIsObject(av)) {
+ JsVar *constructor = jsvObjectGetChild(av, JSPARSE_CONSTRUCTOR_VAR, 0);
+ if (constructor==bv) inst=true;
+ else inst = jspIsConstructor(bv,"Object");
+ jsvUnLock(constructor);
+ } else {
+ const char *name = jswGetBasicObjectName(av);
+ if (name) {
+ inst = jspIsConstructor(bv, name);
+ }
+ }
+ }
+ jsvUnLock(av);
+ jsvUnLock(bv);
+ res = jsvNewFromBool(inst);
+ } else {
+ res = jsvMathsOpSkipNames(a, b, op);
+
+ }
+ jsvUnLock(a); a = res;
+ }
+ jsvUnLock(b);
+ }
+ return a;
+}
+
+JsVar *jspeCondition() {
+ return __jspeCondition(jspeShift());
+}
+
+__attribute((noinline)) JsVar *__jspeLogic(JsVar *a) {
+ JsVar *b = 0;
+ while (execInfo.lex->tk=='&' || execInfo.lex->tk=='|' || execInfo.lex->tk=='^' || execInfo.lex->tk==LEX_ANDAND || execInfo.lex->tk==LEX_OROR) {
+ bool shortCircuit = false;
+ bool boolean = false;
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+
+ // if we have short-circuit ops, then if we know the outcome
+ // we don't bother to execute the other op. Even if not
+ // we need to tell mathsOp it's an & or |
+ if (op==LEX_ANDAND) {
+ op = '&';
+ shortCircuit = !jsvGetBoolAndUnLock(jsvSkipName(a));
+ boolean = true;
+ } else if (op==LEX_OROR) {
+ op = '|';
+ shortCircuit = jsvGetBoolAndUnLock(jsvSkipName(a));
+ boolean = true;
+ }
+
+ JSP_SAVE_EXECUTE();
+ if (shortCircuit) jspSetNoExecute();
+ b = jspeCondition();
+ if (shortCircuit) JSP_RESTORE_EXECUTE();
+ if (JSP_SHOULD_EXECUTE && !shortCircuit) {
+ JsVar *res;
+ if (boolean) {
+ JsVar *newa = jsvNewFromBool(jsvGetBoolAndUnLock(jsvSkipName(a)));
+ JsVar *newb = jsvNewFromBool(jsvGetBoolAndUnLock(jsvSkipName(b)));
+ jsvUnLock(a); a = newa;
+ jsvUnLock(b); b = newb;
+ }
+ res = jsvMathsOpSkipNames(a, b, op);
+ jsvUnLock(a); a = res;
+ }
+ jsvUnLock(b);
+ }
+ return a;
+}
+
+JsVar *jspeLogic() {
+ return __jspeLogic(jspeCondition());
+}
+
+__attribute((noinline)) JsVar *__jspeTernary(JsVar *lhs) {
+ if (execInfo.lex->tk=='?') {
+ JSP_MATCH('?');
+ if (!JSP_SHOULD_EXECUTE) {
+ // just let lhs pass through
+ jsvUnLock(jspeBase());
+ JSP_MATCH(':');
+ jsvUnLock(jspeBase());
+ } else {
+ bool first = jsvGetBoolAndUnLock(jsvSkipName(lhs));
+ jsvUnLock(lhs);
+ if (first) {
+ lhs = jspeBase();
+ JSP_MATCH(':');
+ JSP_SAVE_EXECUTE();
+ jspSetNoExecute();
+ jsvUnLock(jspeBase());
+ JSP_RESTORE_EXECUTE();
+ } else {
+ JSP_SAVE_EXECUTE();
+ jspSetNoExecute();
+ jsvUnLock(jspeBase());
+ JSP_RESTORE_EXECUTE();
+ JSP_MATCH(':');
+ lhs = jspeBase();
+ }
+ }
+ }
+
+ return lhs;
+}
+
+JsVar *jspeTernary() {
+ return __jspeTernary(jspeLogic());
+}
+
+__attribute((noinline)) JsVar *__jspeBase(JsVar *lhs) {
+ if (execInfo.lex->tk=='=' || execInfo.lex->tk==LEX_PLUSEQUAL || execInfo.lex->tk==LEX_MINUSEQUAL ||
+ execInfo.lex->tk==LEX_MULEQUAL || execInfo.lex->tk==LEX_DIVEQUAL || execInfo.lex->tk==LEX_MODEQUAL ||
+ execInfo.lex->tk==LEX_ANDEQUAL || execInfo.lex->tk==LEX_OREQUAL ||
+ execInfo.lex->tk==LEX_XOREQUAL || execInfo.lex->tk==LEX_RSHIFTEQUAL ||
+ execInfo.lex->tk==LEX_LSHIFTEQUAL || execInfo.lex->tk==LEX_RSHIFTUNSIGNEDEQUAL) {
+ JsVar *rhs;
+ /* If we're assigning to this and we don't have a parent,
+ * add it to the symbol table root as per JavaScript. */
+ if (JSP_SHOULD_EXECUTE && lhs && !lhs->refs) {
+ if (jsvIsName(lhs)/* && jsvGetStringLength(lhs)>0*/) {
+ if (!jsvIsArrayBufferName(lhs))
+ jsvAddName(execInfo.parse->root, lhs);
+ } else // TODO: Why was this here? can it happen?
+ jsWarnAt("Trying to assign to an un-named type\n", execInfo.lex, execInfo.lex->tokenLastEnd);
+ }
+
+ int op = execInfo.lex->tk;
+ JSP_MATCH(execInfo.lex->tk);
+ rhs = jspeBase();
+ rhs = jsvSkipNameAndUnLock(rhs); // ensure we get rid of any references on the RHS
+ if (JSP_SHOULD_EXECUTE && lhs) {
+ if (op=='=') {
+ jspReplaceWith(lhs, rhs);
+ } else {
+ if (op==LEX_PLUSEQUAL) op='+';
+ else if (op==LEX_MINUSEQUAL) op='-';
+ else if (op==LEX_MULEQUAL) op='*';
+ else if (op==LEX_DIVEQUAL) op='/';
+ else if (op==LEX_MODEQUAL) op='%';
+ else if (op==LEX_ANDEQUAL) op='&';
+ else if (op==LEX_OREQUAL) op='|';
+ else if (op==LEX_XOREQUAL) op='^';
+ else if (op==LEX_RSHIFTEQUAL) op=LEX_RSHIFT;
+ else if (op==LEX_LSHIFTEQUAL) op=LEX_LSHIFT;
+ else if (op==LEX_RSHIFTUNSIGNEDEQUAL) op=LEX_RSHIFTUNSIGNED;
+ if (op=='+' && jsvIsName(lhs)) {
+ JsVar *currentValue = jsvSkipName(lhs);
+ if (jsvIsString(currentValue) && currentValue->refs==1) {
+ /* A special case for string += where this is the only use of the string,
+ * as we may be able to do a simple append (rather than clone + append)*/
+ JsVar *str = jsvAsString(rhs, false);
+ jsvAppendStringVarComplete(currentValue, str);
+ jsvUnLock(str);
+ op = 0;
+ }
+ jsvUnLock(currentValue);
+ }
+ if (op) {
+ /* Fallback which does a proper add */
+ JsVar *res = jsvMathsOpSkipNames(lhs,rhs,op);
+ jspReplaceWith(lhs, res);
+ jsvUnLock(res);
+ }
+ }
+ }
+ jsvUnLock(rhs);
+ }
+ return lhs;
+}
+
+
+JsVar *jspeBase() {
+ return __jspeBase(jspeTernary());
+}
+
+// jspeBase where ',' is allowed to add multiple expressions
+JsVar *jspeBaseWithComma() {
+ while (!JSP_HAS_ERROR) {
+ JsVar *a = jspeBase();
+ if (execInfo.lex->tk!=',') return a;
+ // if we get a comma, we just forget this data and parse the next bit...
+ jsvUnLock(a);
+ JSP_MATCH(',');
+ }
+ return 0;
+}
+
+JsVar *jspeBlock() {
+ JSP_MATCH('{');
+ if (JSP_SHOULD_EXECUTE) {
+ while (execInfo.lex->tk && execInfo.lex->tk!='}') {
+ jsvUnLock(jspeStatement());
+ if (JSP_HAS_ERROR) {
+ if (execInfo.lex && !(execInfo.execute&EXEC_ERROR_LINE_REPORTED)) {
+ execInfo.execute = (JsExecFlags)(execInfo.execute | EXEC_ERROR_LINE_REPORTED);
+ jsiConsolePrint("at ");
+ jsiConsolePrintPosition(execInfo.lex, execInfo.lex->tokenLastEnd);
+ jsiConsolePrintTokenLineMarker(execInfo.lex, execInfo.lex->tokenLastEnd);
+ }
+ return 0;
+ }
+ }
+ JSP_MATCH('}');
+ } else {
+ // fast skip of blocks
+ int brackets = 1;
+ while (execInfo.lex->tk && brackets) {
+ if (execInfo.lex->tk == '{') brackets++;
+ if (execInfo.lex->tk == '}') brackets--;
+ JSP_MATCH(execInfo.lex->tk);
+ }
+ }
+ return 0;
+}
+
+JsVar *jspeBlockOrStatement() {
+ if (execInfo.lex->tk=='{')
+ return jspeBlock();
+ else {
+ JsVar *v = jspeStatement();
+ if (execInfo.lex->tk==';') JSP_MATCH(';');
+ return v;
+ }
+}
+
+JsVar *jspeStatementVar() {
+ JsVar *lastDefined = 0;
+ /* variable creation. TODO - we need a better way of parsing the left
+ * hand side. Maybe just have a flag called can_create_var that we
+ * set and then we parse as if we're doing a normal equals.*/
+ JSP_MATCH(LEX_R_VAR);
+ bool hasComma = true; // for first time in loop
+ while (hasComma && execInfo.lex->tk == LEX_ID) {
+ JsVar *a = 0;
+ if (JSP_SHOULD_EXECUTE) {
+ a = jspeiFindOnTop(jslGetTokenValueAsString(execInfo.lex), true);
+ if (!a) { // out of memory
+ jspSetError();
+ return lastDefined;
+ }
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(a), lastDefined);
+ // now do stuff defined with dots
+ while (execInfo.lex->tk == '.') {
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN('.', jsvUnLock(a), lastDefined);
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *lastA = a;
+ a = jsvFindChildFromString(lastA, jslGetTokenValueAsString(execInfo.lex), true);
+ jsvUnLock(lastA);
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_ID, jsvUnLock(a), lastDefined);
+ }
+ // sort out initialiser
+ if (execInfo.lex->tk == '=') {
+ JsVar *var;
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN('=', jsvUnLock(a), lastDefined);
+ var = jsvSkipNameAndUnLock(jspeBase());
+ if (JSP_SHOULD_EXECUTE)
+ jspReplaceWith(a, var);
+ jsvUnLock(var);
+ }
+ jsvUnLock(lastDefined);
+ lastDefined = a;
+ hasComma = execInfo.lex->tk == ',';
+ if (hasComma) JSP_MATCH_WITH_RETURN(',', lastDefined);
+ }
+ return lastDefined;
+}
+
+JsVar *jspeStatementIf() {
+ bool cond;
+ JsVar *var;
+ JSP_MATCH(LEX_R_IF);
+ JSP_MATCH('(');
+ var = jspeBase();
+ JSP_MATCH(')');
+ cond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(var));
+ jsvUnLock(var);
+
+ JSP_SAVE_EXECUTE();
+ if (!cond) jspSetNoExecute();
+ jsvUnLock(jspeBlockOrStatement());
+ if (!cond) JSP_RESTORE_EXECUTE();
+ if (execInfo.lex->tk==LEX_R_ELSE) {
+ //JSP_MATCH(';'); ???
+ JSP_MATCH(LEX_R_ELSE);
+ JSP_SAVE_EXECUTE();
+ if (cond) jspSetNoExecute();
+ jsvUnLock(jspeBlockOrStatement());
+ if (cond) JSP_RESTORE_EXECUTE();
+ }
+ return 0;
+}
+
+JsVar *jspeStatementSwitch() {
+ JSP_MATCH(LEX_R_SWITCH);
+ JSP_MATCH('(');
+ JsVar *switchOn = jspeBase();
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(switchOn), 0);
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN('{', jsvUnLock(switchOn), 0);
+ JSP_SAVE_EXECUTE();
+ bool execute = JSP_SHOULD_EXECUTE;
+ bool hasExecuted = false;
+ if (execute) execInfo.execute=EXEC_NO|EXEC_IN_SWITCH;
+ while (execInfo.lex->tk==LEX_R_CASE) {
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_R_CASE, jsvUnLock(switchOn), 0);
+ JsExecFlags oldFlags = execInfo.execute;
+ if (execute) execInfo.execute=EXEC_YES|EXEC_IN_SWITCH;
+ JsVar *test = jspeBase();
+ execInfo.execute = oldFlags|EXEC_IN_SWITCH;;
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(':', jsvUnLock(switchOn);jsvUnLock(test), 0);
+ bool cond = false;
+ if (execute)
+ cond = jsvGetBoolAndUnLock(jsvMathsOpSkipNames(switchOn, test, LEX_EQUAL));
+ if (cond) hasExecuted = true;
+ jsvUnLock(test);
+ if (cond && (execInfo.execute&EXEC_RUN_MASK)==EXEC_NO)
+ execInfo.execute=EXEC_YES|EXEC_IN_SWITCH;
+ while (!JSP_HAS_ERROR && execInfo.lex->tk!=LEX_EOF && execInfo.lex->tk!=LEX_R_CASE && execInfo.lex->tk!=LEX_R_DEFAULT && execInfo.lex->tk!='}')
+ jsvUnLock(jspeBlockOrStatement());
+ }
+ jsvUnLock(switchOn);
+ if (execute && (execInfo.execute&EXEC_RUN_MASK)==EXEC_BREAK)
+ execInfo.execute=EXEC_YES|EXEC_IN_SWITCH;
+ JSP_RESTORE_EXECUTE();
+
+ if (execInfo.lex->tk==LEX_R_DEFAULT) {
+ JSP_MATCH(LEX_R_DEFAULT);
+ JSP_MATCH(':');
+ JSP_SAVE_EXECUTE();
+ if (hasExecuted) jspSetNoExecute();
+ while (!JSP_HAS_ERROR && execInfo.lex->tk!=LEX_EOF && execInfo.lex->tk!='}')
+ jsvUnLock(jspeBlockOrStatement());
+ JSP_RESTORE_EXECUTE();
+ }
+ JSP_MATCH('}');
+ return 0;
+}
+
+JsVar *jspeStatementWhile() {
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ int loopCount = JSPARSE_MAX_LOOP_ITERATIONS;
+#endif
+ JsVar *cond;
+ bool loopCond;
+ bool hasHadBreak = false;
+ // We do repetition by pulling out the string representing our statement
+ // there's definitely some opportunity for optimisation here
+ JSP_MATCH(LEX_R_WHILE);
+ JSP_MATCH('(');
+ JslCharPos whileCondStart = execInfo.lex->tokenStart;
+ cond = jspeBase();
+ loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond));
+ jsvUnLock(cond);
+ JSP_MATCH(')');
+ JslCharPos whileBodyStart = execInfo.lex->tokenStart;
+ JSP_SAVE_EXECUTE();
+ // actually try and execute first bit of while loop (we'll do the rest in the actual loop later)
+ if (!loopCond) jspSetNoExecute();
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ JslCharPos whileBodyEnd = execInfo.lex->tokenStart;
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+ if (execInfo.execute == EXEC_CONTINUE)
+ execInfo.execute = EXEC_YES;
+ if (execInfo.execute == EXEC_BREAK) {
+ execInfo.execute = EXEC_YES;
+ hasHadBreak = true; // fail loop condition, so we exit
+ }
+ if (!loopCond) JSP_RESTORE_EXECUTE();
+
+ while (!hasHadBreak && loopCond
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ && loopCount-->0
+#endif
+ ) {
+ jslSeekTo(execInfo.lex, whileCondStart);
+ cond = jspeBase();
+ loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond));
+ jsvUnLock(cond);
+ if (loopCond) {
+ jslSeekTo(execInfo.lex, whileBodyStart);
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+ if (execInfo.execute == EXEC_CONTINUE)
+ execInfo.execute = EXEC_YES;
+ if (execInfo.execute == EXEC_BREAK) {
+ execInfo.execute = EXEC_YES;
+ hasHadBreak = true;
+ }
+ }
+ }
+ jslSeekTo(execInfo.lex, whileBodyEnd);
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ if (loopCount<=0) {
+ jsErrorAt("WHILE Loop exceeded the maximum number of iterations (" STRINGIFY(JSPARSE_MAX_LOOP_ITERATIONS) ")", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ }
+#endif
+ return 0;
+}
+
+JsVar *jspeStatementFor() {
+ JSP_MATCH(LEX_R_FOR);
+ JSP_MATCH('(');
+ execInfo.execute |= EXEC_FOR_INIT;
+ // initialisation
+ JsVar *forStatement = 0;
+ // we could have 'for (;;)' - so don't munch up our semicolon if that's all we have
+ if (execInfo.lex->tk != ';')
+ forStatement = jspeStatement();
+ execInfo.execute &= (JsExecFlags)~EXEC_FOR_INIT;
+ if (execInfo.lex->tk == LEX_R_IN) {
+ // for (i in array)
+ // where i = jsvUnLock(forStatement);
+ if (!jsvIsName(forStatement)) {
+ jsvUnLock(forStatement);
+ jsErrorAt("FOR a IN b - 'a' must be a variable name", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ return 0;
+ }
+ bool addedIteratorToScope = false;
+ if (JSP_SHOULD_EXECUTE && !forStatement->refs) {
+ // if the variable did not exist, add it to the scope
+ addedIteratorToScope = true;
+ jsvAddName(execInfo.parse->root, forStatement);
+ }
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(LEX_R_IN, jsvUnLock(forStatement), 0);
+ JsVar *array = jsvSkipNameAndUnLock(jspeExpression());
+ JSP_MATCH_WITH_CLEANUP_AND_RETURN(')', jsvUnLock(forStatement);jsvUnLock(array), 0);
+ JslCharPos forBodyStart = execInfo.lex->tokenStart;
+ JSP_SAVE_EXECUTE();
+ jspSetNoExecute();
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ JslCharPos forBodyEnd = execInfo.lex->tokenStart;
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+ JSP_RESTORE_EXECUTE();
+
+ if (jsvIsIterable(array)) {
+ bool (*checkerFunction)(JsVar*) = 0;
+ if (jsvIsFunction(array)) checkerFunction = jsvIsInternalFunctionKey;
+ else if (jsvIsObject(array)) checkerFunction = jsvIsInternalObjectKey;
+ JsvIterator it;
+ jsvIteratorNew(&it, array);
+ bool hasHadBreak = false;
+ while (JSP_SHOULD_EXECUTE && jsvIteratorHasElement(&it) && !hasHadBreak) {
+ JsVar *loopIndexVar = jsvIteratorGetKey(&it);
+ bool ignore = false;
+ if (checkerFunction && checkerFunction(loopIndexVar))
+ ignore = true;
+ if (!ignore) {
+ JsVar *indexValue = jsvIsName(loopIndexVar) ?
+ jsvCopyNameOnly(loopIndexVar, false/*no copy children*/, false/*not a name*/) :
+ loopIndexVar;
+ if (indexValue) { // could be out of memory
+ assert(!jsvIsName(indexValue) && indexValue->refs==0);
+ jsvSetValueOfName(forStatement, indexValue);
+ if (indexValue!=loopIndexVar) jsvUnLock(indexValue);
+
+ jsvIteratorNext(&it);
+
+ jslSeekTo(execInfo.lex, forBodyStart);
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+
+ if (execInfo.execute == EXEC_CONTINUE)
+ execInfo.execute = EXEC_YES;
+ if (execInfo.execute == EXEC_BREAK) {
+ execInfo.execute = EXEC_YES;
+ hasHadBreak = true;
+ }
+ }
+ } else
+ jsvIteratorNext(&it);
+ jsvUnLock(loopIndexVar);
+ }
+ jsvIteratorFree(&it);
+ } else {
+ jsErrorAt("FOR loop can only iterate over Arrays, Strings or Objects", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ }
+
+ jslSeekTo(execInfo.lex, forBodyEnd);
+
+ if (addedIteratorToScope) {
+ jsvRemoveChild(execInfo.parse->root, forStatement);
+ }
+ jsvUnLock(forStatement);
+ jsvUnLock(array);
+ } else { // NORMAL FOR LOOP
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ int loopCount = JSPARSE_MAX_LOOP_ITERATIONS;
+#endif
+ bool loopCond = true;
+ bool hasHadBreak = false;
+
+ jsvUnLock(forStatement);
+ JSP_MATCH(';');
+ JslCharPos forCondStart = execInfo.lex->tokenStart;
+ if (execInfo.lex->tk != ';') {
+ JsVar *cond = jspeBase(); // condition
+ loopCond = JSP_SHOULD_EXECUTE && jsvGetBoolAndUnLock(jsvSkipName(cond));
+ jsvUnLock(cond);
+ }
+ JSP_MATCH(';');
+ JslCharPos forIterStart = execInfo.lex->tokenStart;
+ if (execInfo.lex->tk != ')') { // we could have 'for (;;)'
+ JSP_SAVE_EXECUTE();
+ jspSetNoExecute();
+ jsvUnLock(jspeBase()); // iterator
+ JSP_RESTORE_EXECUTE();
+ }
+ JSP_MATCH(')');
+
+ JslCharPos forBodyStart = execInfo.lex->tokenStart; // actual for body
+ JSP_SAVE_EXECUTE();
+ if (!loopCond) jspSetNoExecute();
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ JslCharPos forBodyEnd = execInfo.lex->tokenStart;
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+ if (execInfo.execute == EXEC_CONTINUE)
+ execInfo.execute = EXEC_YES;
+ if (execInfo.execute == EXEC_BREAK) {
+ execInfo.execute = EXEC_YES;
+ hasHadBreak = true;
+ }
+ if (!loopCond) JSP_RESTORE_EXECUTE();
+ if (loopCond) {
+ jslSeekTo(execInfo.lex, forIterStart);
+ if (execInfo.lex->tk != ')') jsvUnLock(jspeBase());
+ }
+ while (!hasHadBreak && JSP_SHOULD_EXECUTE && loopCond
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ && loopCount-->0
+#endif
+ ) {
+ jslSeekTo(execInfo.lex, forCondStart);
+ ;
+ if (execInfo.lex->tk == ';') {
+ loopCond = true;
+ } else {
+ JsVar *cond = jspeBase();
+ loopCond = jsvGetBoolAndUnLock(jsvSkipName(cond));
+ jsvUnLock(cond);
+ }
+ if (JSP_SHOULD_EXECUTE && loopCond) {
+ jslSeekTo(execInfo.lex, forBodyStart);
+ execInfo.execute |= EXEC_IN_LOOP;
+ jsvUnLock(jspeBlockOrStatement());
+ execInfo.execute &= (JsExecFlags)~EXEC_IN_LOOP;
+ if (execInfo.execute == EXEC_CONTINUE)
+ execInfo.execute = EXEC_YES;
+ if (execInfo.execute == EXEC_BREAK) {
+ execInfo.execute = EXEC_YES;
+ hasHadBreak = true;
+ }
+ }
+ if (JSP_SHOULD_EXECUTE && loopCond) {
+ jslSeekTo(execInfo.lex, forIterStart);
+ if (execInfo.lex->tk != ')') jsvUnLock(jspeBase());
+ }
+ }
+ jslSeekTo(execInfo.lex, forBodyEnd);
+#ifdef JSPARSE_MAX_LOOP_ITERATIONS
+ if (loopCount<=0) {
+ jsErrorAt("FOR Loop exceeded the maximum number of iterations ("STRINGIFY(JSPARSE_MAX_LOOP_ITERATIONS)")", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ }
+#endif
+ }
+ return 0;
+}
+
+JsVar *jspeStatementReturn() {
+ JsVar *result = 0;
+ JSP_MATCH(LEX_R_RETURN);
+ if (execInfo.lex->tk != ';') {
+ // we only want the value, so skip the name if there was one
+ result = jsvSkipNameAndUnLock(jspeBaseWithComma());
+ }
+ if (JSP_SHOULD_EXECUTE) {
+ JsVar *resultVar = jspeiFindOnTop(JSPARSE_RETURN_VAR, false);
+ if (resultVar) {
+ jspReplaceWith(resultVar, result);
+ jsvUnLock(resultVar);
+ } else {
+ jsErrorAt("RETURN statement, but not in a function.\n", execInfo.lex, execInfo.lex->tokenLastEnd);
+ jspSetError();
+ }
+ jspSetNoExecute(); // Stop anything else in this function executing
+ }
+ jsvUnLock(result);
+ return 0;
+}
+
+JsVar *jspeStatementFunctionDecl() {
+ JsVar *funcName = 0;
+ JsVar *funcVar;
+ JSP_MATCH(LEX_R_FUNCTION);
+ if (JSP_SHOULD_EXECUTE)
+ funcName = jsvMakeIntoVariableName(jsvNewFromString(jslGetTokenValueAsString(execInfo.lex)), 0);
+ if (!funcName) { // out of memory
+ jspSetError();
+ return 0;
+ }
+ JSP_MATCH(LEX_ID);
+ funcVar = jspeFunctionDefinition();
+ if (JSP_SHOULD_EXECUTE) {
+ // find a function with the same name (or make one)
+ // OPT: can Find* use just a JsVar that is a 'name'?
+ JsVar *existingFunc = jspeiFindNameOnTop(funcName, true);
+ // replace it
+ jspReplaceWith(existingFunc, funcVar);
+ jsvUnLock(funcName);
+ funcName = existingFunc;
+ }
+ jsvUnLock(funcVar);
+ return funcName;
+}
+
+JsVar *jspeStatement() {
+ if (execInfo.lex->tk==LEX_ID ||
+ execInfo.lex->tk==LEX_INT ||
+ execInfo.lex->tk==LEX_FLOAT ||
+ execInfo.lex->tk==LEX_STR ||
+ execInfo.lex->tk==LEX_R_NEW ||
+ execInfo.lex->tk==LEX_R_NULL ||
+ execInfo.lex->tk==LEX_R_UNDEFINED ||
+ execInfo.lex->tk==LEX_R_TRUE ||
+ execInfo.lex->tk==LEX_R_FALSE ||
+ execInfo.lex->tk==LEX_R_THIS ||
+ execInfo.lex->tk==LEX_R_TYPEOF ||
+ execInfo.lex->tk==LEX_R_VOID ||
+ execInfo.lex->tk==LEX_PLUSPLUS ||
+ execInfo.lex->tk==LEX_MINUSMINUS ||
+ execInfo.lex->tk=='!' ||
+ execInfo.lex->tk=='-' ||
+ execInfo.lex->tk=='+' ||
+ execInfo.lex->tk=='~' ||
+ execInfo.lex->tk=='[' ||
+ execInfo.lex->tk=='(') {
+ /* Execute a simple statement that only contains basic arithmetic... */
+ return jspeBaseWithComma();
+ } else if (execInfo.lex->tk=='{') {
+ /* A block of code */
+ return jspeBlock();
+ } else if (execInfo.lex->tk==';') {
+ /* Empty statement - to allow things like ;;; */
+ JSP_MATCH(';');
+ return 0;
+ } else if (execInfo.lex->tk==LEX_R_VAR) {
+ return jspeStatementVar();
+ } else if (execInfo.lex->tk==LEX_R_IF) {
+ return jspeStatementIf();
+ } else if (execInfo.lex->tk==LEX_R_WHILE) {
+ return jspeStatementWhile();
+ } else if (execInfo.lex->tk==LEX_R_FOR) {
+ return jspeStatementFor();
+ } else if (execInfo.lex->tk==LEX_R_RETURN) {
+ return jspeStatementReturn();
+ } else if (execInfo.lex->tk==LEX_R_FUNCTION) {
+ return jspeStatementFunctionDecl();
+ } else if (execInfo.lex->tk==LEX_R_CONTINUE) {
+ JSP_MATCH(LEX_R_CONTINUE);
+ if (JSP_SHOULD_EXECUTE) {
+ if (!(execInfo.execute & EXEC_IN_LOOP))
+ jsErrorAt("CONTINUE statement outside of FOR or WHILE loop", execInfo.lex, execInfo.lex->tokenLastEnd);
+ else
+ execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_RUN_MASK) | EXEC_CONTINUE;
+ }
+ } else if (execInfo.lex->tk==LEX_R_BREAK) {
+ JSP_MATCH(LEX_R_BREAK);
+ if (JSP_SHOULD_EXECUTE) {
+ if (!(execInfo.execute & (EXEC_IN_LOOP|EXEC_IN_SWITCH)))
+ jsErrorAt("BREAK statement outside of SWITCH, FOR or WHILE loop", execInfo.lex, execInfo.lex->tokenLastEnd);
+ else
+ execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_RUN_MASK) | EXEC_BREAK;
+ }
+ } else if (execInfo.lex->tk==LEX_R_SWITCH) {
+ return jspeStatementSwitch();
+ } else JSP_MATCH(LEX_EOF);
+ return 0;
+}
+
+// -----------------------------------------------------------------------------
+/// Create a new built-in object that jswrapper can use to check for built-in functions
+JsVar *jspNewBuiltin(const char *instanceOf) {
+ JsVar *objFunc = jsvNewWithFlags(JSV_FUNCTION);
+ if (!objFunc) return 0; // out of memory
+ // set object data to be object name
+ if (strlen(instanceOf)==sizeof(objFunc->varData))
+ memcpy(objFunc->varData.str, instanceOf, sizeof(objFunc->varData)); // no trailing zero!
+ else
+ strncpy(objFunc->varData.str, instanceOf, sizeof(objFunc->varData));
+ return objFunc;
+}
+
+
+JsVar *jspNewObject(JsParse *parse, const char *name, const char *instanceOf) {
+ JsVar *objFuncName = jsvFindChildFromString(parse->root, instanceOf, true);
+ if (!objFuncName) // out of memory
+ return 0;
+
+ JsVar *objFunc = jsvSkipName(objFuncName);
+ if (!objFunc) {
+ objFunc = jspNewBuiltin(instanceOf);
+ if (!objFunc) { // out of memory
+ jsvUnLock(objFuncName);
+ return 0;
+ }
+
+ // set up name
+ jsvSetValueOfName(objFuncName, objFunc);
+ }
+
+ JsVar *prototypeName = jsvFindChildFromString(objFunc, JSPARSE_PROTOTYPE_VAR, true);
+ jspEnsureIsPrototype(prototypeName); // make sure it's an object
+ jsvUnLock(objFunc);
+ if (!prototypeName) { // out of memory
+ jsvUnLock(objFuncName);
+ return 0;
+ }
+
+ JsVar *obj = jsvNewWithFlags(JSV_OBJECT);
+ if (!obj) { // out of memory
+ jsvUnLock(objFuncName);
+ jsvUnLock(prototypeName);
+ return 0;
+ }
+ if (name) {
+ // set object data to be object name
+ strncpy(obj->varData.str, name, sizeof(obj->varData));
+ }
+ // add inherits/constructor/etc
+ jsvUnLock(jsvAddNamedChild(obj, prototypeName, JSPARSE_INHERITS_VAR));
+ jsvUnLock(prototypeName);prototypeName=0;
+ jsvUnLock(jsvAddNamedChild(obj, objFuncName, JSPARSE_CONSTRUCTOR_VAR));
+ jsvUnLock(objFuncName);
+ if (name) {
+ JsVar *objName = jsvAddNamedChild(parse->root, obj, name);
+ jsvUnLock(obj);
+ if (!objName) { // out of memory
+ return 0;
+ }
+ return objName;
+ } else
+ return obj;
+}
+
+/** Returns true if the constructor function given is the same as that
+ * of the object with the given name. */
+bool jspIsConstructor(JsVar *constructor, const char *constructorName) {
+ JsVar *objFunc = jsvObjectGetChild(execInfo.parse->root, constructorName, 0);
+ if (!objFunc) return false;
+ bool isConstructor = objFunc == constructor;
+ jsvUnLock(objFunc);
+ return isConstructor;
+}
+
+// -----------------------------------------------------------------------------
+
+void jspSoftInit(JsParse *parse) {
+ parse->root = jsvFindOrCreateRoot();
+ // Root now has a lock and a ref
+}
+
+/** Is v likely to have been created by this parser? */
+bool jspIsCreatedObject(JsParse *parse, JsVar *v) {
+ return
+ v==parse->root;
+}
+
+void jspSoftKill(JsParse *parse) {
+ jsvUnLock(parse->root);
+ // Root now has just a ref
+}
+
+void jspInit(JsParse *parse) {
+ jspSoftInit(parse);
+}
+
+void jspKill(JsParse *parse) {
+ jspSoftKill(parse);
+ // Unreffing this should completely kill everything attached to root
+ JsVar *r = jsvFindOrCreateRoot();
+ jsvUnRef(r);
+ jsvUnLock(r);
+}
+
+
+
+JsVar *jspEvaluateVar(JsParse *parse, JsVar *str, JsVar *scope) {
+ JsLex lex;
+ JsVar *v = 0;
+ JSP_SAVE_EXECUTE();
+ JsExecInfo oldExecInfo = execInfo;
+
+ assert(jsvIsString(str));
+ jslInit(&lex, str);
+
+ jspeiInit(parse, &lex);
+ bool scopeAdded = false;
+ if (scope)
+ scopeAdded = jspeiAddScope(jsvGetRef(scope));
+ while (!JSP_HAS_ERROR && execInfo.lex->tk != LEX_EOF) {
+ jsvUnLock(v);
+ v = jspeBlockOrStatement();
+ }
+ // clean up
+ if (scopeAdded) jspeiRemoveScope();
+ jspeiKill();
+ jslKill(&lex);
+
+ // restore state
+ JSP_RESTORE_EXECUTE();
+ oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok.
+ execInfo = oldExecInfo;
+
+ // It may have returned a reference, but we just want the value...
+ if (v) {
+ return jsvSkipNameAndUnLock(v);
+ }
+ // nothing returned
+ return 0;
+}
+
+JsVar *jspEvaluate(JsParse *parse, const char *str) {
+ JsVar *v = 0;
+
+ JsVar *evCode = jsvNewFromString(str);
+ if (!jsvIsMemoryFull())
+ v = jspEvaluateVar(parse, evCode, 0);
+ jsvUnLock(evCode);
+
+ return v;
+}
+
+bool jspExecuteFunction(JsParse *parse, JsVar *func, JsVar *parent, int argCount, JsVar **argPtr) {
+ JSP_SAVE_EXECUTE();
+ JsExecInfo oldExecInfo = execInfo;
+
+ jspeiInit(parse, 0);
+ JsVar *resultVar = jspeFunctionCall(func, 0, parent, false, argCount, argPtr);
+ bool result = jsvGetBool(resultVar);
+ jsvUnLock(resultVar);
+ // clean up
+ jspeiKill();
+ // restore state
+ JSP_RESTORE_EXECUTE();
+ oldExecInfo.execute = execInfo.execute; // JSP_RESTORE_EXECUTE has made this ok.
+ execInfo = oldExecInfo;
+
+
+ return result;
+}
+
+
+/// Evaluate a JavaScript module and return its exports
+JsVar *jspEvaluateModule(JsParse *parse, JsVar *moduleContents) {
+ assert(jsvIsString(moduleContents));
+ JsVar *scope = jsvNewWithFlags(JSV_OBJECT);
+ if (!scope) return 0; // out of mem
+ JsVar *scopeExports = jsvNewWithFlags(JSV_OBJECT);
+ if (!scopeExports) { jsvUnLock(scope); return 0; } // out of mem
+ jsvUnLock(jsvAddNamedChild(scope, scopeExports, "exports"));
+
+ jsvUnLock(jspEvaluateVar(parse, moduleContents, scope));
+
+ jsvUnLock(scope);
+ return scopeExports;
+}
diff --git a/components/external/espruino/src/jsparse.h b/components/external/espruino/src/jsparse.h
new file mode 100644
index 0000000000..7abaee4b6e
--- /dev/null
+++ b/components/external/espruino/src/jsparse.h
@@ -0,0 +1,126 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Recursive descent parser for code execution
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSPARSE_H_
+#define JSPARSE_H_
+
+#include "jsvar.h"
+#include "jslex.h"
+
+
+typedef struct {
+ JsVar *root; ///< root of symbol table
+} JsParse;
+
+void jspInit(JsParse *parse);
+void jspKill(JsParse *parse);
+
+// jspSoft* - 'release' or 'claim' anything we are using, but ensure that it doesn't get freed
+void jspSoftInit(JsParse *parse); ///< used when recovering from or saving to flash
+void jspSoftKill(JsParse *parse); ///< used when recovering from or saving to flash
+bool jspIsCreatedObject(JsParse *parse, JsVar *v); ///< Is v likely to have been created by this parser?
+/** Returns true if the constructor function given is the same as that
+ * of the object with the given name. */
+bool jspIsConstructor(JsVar *constructor, const char *constructorName);
+
+/// Create a new built-in object that jswrapper can use to check for built-in functions
+JsVar *jspNewBuiltin(const char *name);
+/** Create a new object of the given instance and add it to root with name 'name'.
+ * If name!=0, added to root with name, and the name is returned
+ * If name==0, not added to root and Object itself returned */
+JsVar *jspNewObject(JsParse *parse, const char *name, const char *instanceOf);
+
+/// if interrupting execution, this is set
+bool jspIsInterrupted();
+/// if interrupting execution, this is set
+void jspSetInterrupted(bool interrupt);
+/// Has there been an error during parsing
+bool jspHasError();
+
+bool jspAddNativeFunction(JsParse *parse, const char *funcDesc, JsCallback callbackPtr);
+JsVar *jspEvaluateVar(JsParse *parse, JsVar *str, JsVar *scope);
+JsVar *jspEvaluate(JsParse *parse, const char *str);
+bool jspExecuteFunction(JsParse *parse, JsVar *func, JsVar *parent, int argCount, JsVar **argPtr);
+
+/// Evaluate a JavaScript module and return its exports
+JsVar *jspEvaluateModule(JsParse *parse, JsVar *moduleContents);
+
+/** When parsing, this enum defines whether
+ we are executing or not */
+typedef enum {
+ EXEC_NO = 0,
+ EXEC_YES = 1,
+ EXEC_BREAK = 2,
+ EXEC_CONTINUE = 4,
+
+ EXEC_INTERRUPTED = 8, // true if execution has been interrupted
+ EXEC_ERROR = 16,
+ EXEC_ERROR_LINE_REPORTED = 32, // if an error has been reported, set this so we don't do it too much
+
+ EXEC_FOR_INIT = 64, // when in for initialiser parsing - hack to avoid getting confused about multiple use for IN
+ EXEC_IN_LOOP = 128, // when in a loop, set this - we can then block break/continue outside it
+ EXEC_IN_SWITCH = 256, // when in a switch, set this - we can then block break outside it/loops
+
+ EXEC_RUN_MASK = EXEC_YES|EXEC_BREAK|EXEC_CONTINUE|EXEC_INTERRUPTED,
+ EXEC_ERROR_MASK = EXEC_INTERRUPTED|EXEC_ERROR,
+ EXEC_SAVE_RESTORE_MASK = EXEC_YES|EXEC_IN_LOOP|EXEC_IN_SWITCH, // the things JSP_SAVE/RESTORE_EXECUTE should keep track of
+} JsExecFlags;
+
+/** This structure is used when parsing the JavaScript. It contains
+ * everything that should be needed. */
+typedef struct {
+ JsParse *parse;
+ JsLex *lex;
+
+ // TODO: could store scopes as JsVar array for speed
+ JsVarRef scopes[JSPARSE_MAX_SCOPES];
+ int scopeCount;
+ /// Value of 'this' reserved word
+ JsVar *thisVar;
+
+ JsExecFlags execute;
+} JsExecInfo;
+
+/// flags for jspParseFunction
+typedef enum {
+ JSP_NOSKIP_A = 1,
+ JSP_NOSKIP_B = 2,
+ JSP_NOSKIP_C = 4,
+ JSP_NOSKIP_D = 8,
+ JSP_NOSKIP_E = 16,
+ JSP_NOSKIP_F = 32,
+ JSP_NOSKIP_G = 64,
+ JSP_NOSKIP_H = 128,
+} JspSkipFlags;
+
+/// parse function with max 4 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say
+bool jspParseFunction(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d);
+/// parse function with max 8 arguments (can set arg to 0 to avoid parse). Usually first arg will be 0, but if we DON'T want to skip names on an arg stuff, we can say
+bool jspParseFunction8(JspSkipFlags skipName, JsVar **a, JsVar **b, JsVar **c, JsVar **d, JsVar **e, JsVar **f, JsVar **g, JsVar **h);
+
+bool jspParseVariableName(); ///< parse single variable name
+bool jspParseEmptyFunction(); ///< parse function with no arguments
+JsVar *jspParseSingleFunction(); ///< parse function with a single argument, return its value (no names!)
+JsVar *jspParseFunctionAsArray(); ///< parse a function with any number of argument, and return an array of de-named aruments
+
+/** Handle a function call (assumes we've parsed the function name and we're
+ * on the start bracket). 'thisArg' is the value of the 'this' variable when the
+ * function is executed (it's usually the parent object)
+ *
+ * If !isParsing and arg0!=0, argument 0 is set to what is supplied (same with arg1)
+ *
+ * functionName is used only for error reporting - and can be 0
+ */
+JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *thisArg, bool isParsing, int argCount, JsVar **argPtr);
+
+#endif /* JSPARSE_H_ */
diff --git a/components/external/espruino/src/jspin.c b/components/external/espruino/src/jspin.c
new file mode 100644
index 0000000000..c345e615e8
--- /dev/null
+++ b/components/external/espruino/src/jspin.c
@@ -0,0 +1,150 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Utilities and definitions for handling Pins
+ * ----------------------------------------------------------------------------
+ */
+
+#include "jspin.h"
+#include "jspininfo.h" // auto-generated
+
+bool jshIsPinValid(Pin pin) {
+ // Note, PIN_UNDEFINED is always > JSH_PIN_COUNT
+ return pin < JSH_PIN_COUNT && pinInfo[pin].port!=JSH_PORT_NONE;
+}
+
+
+Pin jshGetPinFromString(const char *s) {
+ // built in constants
+
+ if (s[0]=='B' && s[1]=='T' && s[2]=='N') {
+#ifdef BTN1_PININDEX
+ if (!s[3]) return BTN1_PININDEX;
+ if (s[3]=='1' && !s[4]) return BTN1_PININDEX;
+#endif
+#ifdef BTN2_PININDEX
+ if (s[3]=='2' && !s[4]) return BTN2_PININDEX;
+#endif
+#ifdef BTN3_PININDEX
+ if (s[3]=='3' && !s[4]) return BTN3_PININDEX;
+#endif
+#ifdef BTN4_PININDEX
+ if (s[3]=='4' && !s[4]) return BTN4_PININDEX;
+#endif
+ }
+ if (s[0]=='L' && s[1]=='E' && s[2]=='D') {
+#ifdef LED1_PININDEX
+ if (!s[3]) return LED1_PININDEX;
+ if (s[3]=='1' && !s[4]) return LED1_PININDEX;
+#endif
+#ifdef LED2_PININDEX
+ if (s[3]=='2' && !s[4]) return LED2_PININDEX;
+#endif
+#ifdef LED3_PININDEX
+ if (s[3]=='3' && !s[4]) return LED3_PININDEX;
+#endif
+#ifdef LED4_PININDEX
+ if (s[3]=='4' && !s[4]) return LED4_PININDEX;
+#endif
+#ifdef LED5_PININDEX
+ if (s[3]=='5' && !s[4]) return LED5_PININDEX;
+#endif
+#ifdef LED6_PININDEX
+ if (s[3]=='6' && !s[4]) return LED6_PININDEX;
+#endif
+#ifdef LED7_PININDEX
+ if (s[3]=='7' && !s[4]) return LED7_PININDEX;
+#endif
+#ifdef LED8_PININDEX
+ if (s[3]=='8' && !s[4]) return LED8_PININDEX;
+#endif
+ }
+
+ if ((s[0]>='A' && s[0]<='H') && s[1]) { // first 6 are analogs
+ int port = JSH_PORTA+s[0]-'A';
+ Pin pin = 127;
+ if (!s[2] && (s[1]>='0' && s[1]<='9')) { // D0-D9
+ pin = (Pin)(s[1]-'0');
+ } else if (!s[3] && (s[1]>='1' && s[1]<='3' && s[2]>='0' && s[2]<='9')) { // D1X-D3X
+ pin = (Pin)((s[1]-'0')*10 + (s[2]-'0'));
+ }
+ if (port == JSH_PORTA) {
+ if (pin<JSH_PORTA_COUNT) return (Pin)(JSH_PORTA_OFFSET + pin);
+ } else if (port == JSH_PORTB) {
+ if (pin<JSH_PORTB_COUNT) return (Pin)(JSH_PORTB_OFFSET + pin);
+ } else if (port == JSH_PORTC) {
+ if (pin<JSH_PORTC_COUNT) return (Pin)(JSH_PORTC_OFFSET + pin);
+ } else if (port == JSH_PORTD) {
+ if (pin<JSH_PORTD_COUNT) return (Pin)(JSH_PORTD_OFFSET + pin);
+#if JSH_PORTE_OFFSET!=-1
+ } else if (port == JSH_PORTE) {
+ if (pin<JSH_PORTE_COUNT) return (Pin)(JSH_PORTE_OFFSET + pin);
+#endif
+#if JSH_PORTF_OFFSET!=-1
+ } else if (port == JSH_PORTF) {
+ if (pin<JSH_PORTF_COUNT) return (Pin)(JSH_PORTF_OFFSET + pin);
+#endif
+#if JSH_PORTG_OFFSET!=-1
+ } else if (port == JSH_PORTG) {
+ if (pin<JSH_PORTG_COUNT) return (Pin)(JSH_PORTG_OFFSET + pin);
+#endif
+#if JSH_PORTH_OFFSET!=-1
+ } else if (port == JSH_PORTH) {
+ if (pin<JSH_PORTH_COUNT) return (Pin)(JSH_PORTH_OFFSET + pin);
+#endif
+ }
+ }
+
+ return PIN_UNDEFINED;
+}
+
+/** Write the pin name to a string. String must have at least 8 characters (to be safe) */
+void jshGetPinString(char *result, Pin pin) {
+ result[0] = 0; // just in case
+ if (
+#if JSH_PORTA_OFFSET!=0
+ pin>=JSH_PORTA_OFFSET &&
+#endif
+ pin<JSH_PORTA_OFFSET+JSH_PORTA_COUNT) {
+ result[0]='A';
+ itoa(pin-JSH_PORTA_OFFSET,&result[1],10);
+ } else if (pin>=JSH_PORTB_OFFSET && pin<JSH_PORTB_OFFSET+JSH_PORTB_COUNT) {
+ result[0]='B';
+ itoa(pin-JSH_PORTB_OFFSET,&result[1],10);
+ } else if (pin>=JSH_PORTC_OFFSET && pin<JSH_PORTC_OFFSET+JSH_PORTC_COUNT) {
+ result[0]='C';
+ itoa(pin-JSH_PORTC_OFFSET,&result[1],10);
+ } else if (pin>=JSH_PORTD_OFFSET && pin<JSH_PORTD_OFFSET+JSH_PORTD_COUNT) {
+ result[0]='D';
+ itoa(pin-JSH_PORTD_OFFSET,&result[1],10);
+#if JSH_PORTE_OFFSET!=-1
+ } else if (pin>=JSH_PORTE_OFFSET && pin<JSH_PORTE_OFFSET+JSH_PORTE_COUNT) {
+ result[0]='E';
+ itoa(pin-JSH_PORTE_OFFSET,&result[1],10);
+#endif
+#if JSH_PORTF_OFFSET!=-1
+ } else if (pin>=JSH_PORTF_OFFSET && pin<JSH_PORTF_OFFSET+JSH_PORTF_COUNT) {
+ result[0]='F';
+ itoa(pin-JSH_PORTF_OFFSET,&result[1],10);
+#endif
+#if JSH_PORTG_OFFSET!=-1
+ } else if (pin>=JSH_PORTG_OFFSET && pin<JSH_PORTG_OFFSET+JSH_PORTG_COUNT) {
+ result[0]='G';
+ itoa(pin-JSH_PORTG_OFFSET,&result[1],10);
+#endif
+#if JSH_PORTH_OFFSET!=-1
+ } else if (pin>=JSH_PORTH_OFFSET && pin<JSH_PORTH_OFFSET+JSH_PORTH_COUNT) {
+ result[0]='H';
+ itoa(pin-JSH_PORTH_OFFSET,&result[1],10);
+#endif
+ } else {
+ strncpy(result, "UNKNOWN", 8);
+ }
+}
diff --git a/components/external/espruino/src/jspin.h b/components/external/espruino/src/jspin.h
new file mode 100644
index 0000000000..ce2e63d830
--- /dev/null
+++ b/components/external/espruino/src/jspin.h
@@ -0,0 +1,228 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Utilities and definitions for handling Pins
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef JSPIN_H
+#define JSPIN_H
+
+#include "jsutils.h"
+#include "jsvar.h"
+
+typedef unsigned char Pin; ///< for specifying pins for hardware
+#define PIN_UNDEFINED ((Pin)0xFF)
+
+typedef enum {
+ JSH_PORT_NONE,
+ JSH_PORTA=1,
+ JSH_PORTB,
+ JSH_PORTC,
+ JSH_PORTD,
+ JSH_PORTE,
+ JSH_PORTF,
+ JSH_PORTG,
+ JSH_PORTH,
+} PACKED_FLAGS JsvPinInfoPort;
+
+typedef enum {
+ JSH_PIN0 = 0,
+ JSH_PIN1,
+ JSH_PIN2,
+ JSH_PIN3,
+ JSH_PIN4,
+ JSH_PIN5,
+ JSH_PIN6,
+ JSH_PIN7,
+ JSH_PIN8,
+ JSH_PIN9,
+ JSH_PIN10,
+ JSH_PIN11,
+ JSH_PIN12,
+ JSH_PIN13,
+ JSH_PIN14,
+ JSH_PIN15,
+#ifndef ARM
+ JSH_PIN16,
+ JSH_PIN17,
+ JSH_PIN18,
+ JSH_PIN19,
+ JSH_PIN20,
+ JSH_PIN21,
+ JSH_PIN22,
+ JSH_PIN23,
+ JSH_PIN24,
+ JSH_PIN25,
+ JSH_PIN26,
+ JSH_PIN27,
+ JSH_PIN28,
+ JSH_PIN29,
+ JSH_PIN30,
+ JSH_PIN31,
+#endif
+} PACKED_FLAGS JsvPinInfoPin;
+
+typedef enum {
+ JSH_ANALOG_NONE = 0,
+ JSH_ANALOG1 = 32,
+ JSH_ANALOG2 = 64,
+ JSH_ANALOG3 = 128,
+ JSH_ANALOG4 = 256,
+ JSH_ANALOG12 = JSH_ANALOG1|JSH_ANALOG2,
+ JSH_ANALOG123 = JSH_ANALOG1|JSH_ANALOG2|JSH_ANALOG3,
+ JSH_ANALOG34 = JSH_ANALOG3|JSH_ANALOG4,
+
+ JSH_ANALOG_CH0 = 0,
+ JSH_ANALOG_CH1,
+ JSH_ANALOG_CH2,
+ JSH_ANALOG_CH3,
+ JSH_ANALOG_CH4,
+ JSH_ANALOG_CH5,
+ JSH_ANALOG_CH6,
+ JSH_ANALOG_CH7,
+ JSH_ANALOG_CH8,
+ JSH_ANALOG_CH9,
+ JSH_ANALOG_CH10,
+ JSH_ANALOG_CH11,
+ JSH_ANALOG_CH12,
+ JSH_ANALOG_CH13,
+ JSH_ANALOG_CH14,
+ JSH_ANALOG_CH15,
+ JSH_ANALOG_CH16,
+
+ JSH_MASK_ANALOG_CH = 31,
+ JSH_MASK_ANALOG_ADC = JSH_ANALOG1|JSH_ANALOG2|JSH_ANALOG3|JSH_ANALOG4,
+
+} PACKED_FLAGS JsvPinInfoAnalog;
+
+typedef enum {
+ // ---------------------------- JSH_MASK_AF
+ JSH_AF0 = 0,
+ JSH_AF1,
+ JSH_AF2,
+ JSH_AF3,
+ JSH_AF4,
+ JSH_AF5,
+ JSH_AF6,
+ JSH_AF7,
+ JSH_AF8,
+ JSH_AF9,
+ JSH_AF10,
+ JSH_AF11,
+ JSH_AF12,
+ JSH_AF13,
+ JSH_AF14,
+ JSH_AF15,
+
+ // ---------------------------- JSH_MASK_TYPE
+ JSH_TIMER1 = 0x0010,
+ JSH_TIMER2 = 0x0020,
+ JSH_TIMER3 = 0x0030,
+ JSH_TIMER4 = 0x0040,
+ JSH_TIMER5 = 0x0050,
+ JSH_TIMER6 = 0x0060,
+ JSH_TIMER7 = 0x0070,
+ JSH_TIMER8 = 0x0080,
+ JSH_TIMER9 = 0x0090,
+ JSH_TIMER10 = 0x00A0,
+ JSH_TIMER11 = 0x00B0,
+ JSH_TIMER12 = 0x00C0,
+ JSH_TIMER13 = 0x00D0,
+ JSH_TIMER14 = 0x00E0,
+ JSH_TIMER15 = 0x00F0,
+ JSH_TIMER16 = 0x0100,
+ JSH_TIMER17 = 0x0110,
+ JSH_TIMER18 = 0x0120,
+ JSH_TIMERMAX = JSH_TIMER18,
+ JSH_DAC = 0x0180,
+ JSH_SPI1 = 0x0200,
+ JSH_SPI2 = 0x0210,
+ JSH_SPI3 = 0x0220,
+ JSH_SPIMAX = JSH_SPI3,
+ JSH_I2C1 = 0x0280,
+ JSH_I2C2 = 0x0290,
+ JSH_I2C3 = 0x02A0,
+ JSH_I2CMAX = JSH_I2C3,
+ JSH_USART1 = 0x0300,
+ JSH_USART2 = 0x0310,
+ JSH_USART3 = 0x0320,
+ JSH_USART4 = 0x0330,
+ JSH_USART5 = 0x0340,
+ JSH_USART6 = 0x0350,
+ JSH_USARTMAX = JSH_USART6,
+
+ // ---------------------------- JSH_MASK_INFO
+
+ JSH_TIMER_CH1 = 0x0000,
+ JSH_TIMER_CH2 = 0x1000,
+ JSH_TIMER_CH3 = 0x2000,
+ JSH_TIMER_CH4 = 0x3000,
+ JSH_MASK_TIMER_CH = 0x7000,
+ JSH_TIMER_NEGATED = 0x8000,
+
+ JSH_USART_RX = 0x0000,
+ JSH_USART_TX = 0x1000,
+
+ JSH_SPI_MISO = 0x0000,
+ JSH_SPI_MOSI = 0x1000,
+ JSH_SPI_SCK = 0x2000,
+
+ JSH_I2C_SCL = 0x0000,
+ JSH_I2C_SDA = 0x1000,
+
+ JSH_DAC_CH1 = 0x0000,
+ JSH_DAC_CH2 = 0x1000,
+
+ // ---------------------------- Masks
+ JSH_MASK_AF = 0x000F,
+ JSH_MASK_TYPE = 0x0FF0,
+ JSH_MASK_INFO = 0xF000,
+} PACKED_FLAGS JshPinFunction;
+
+#define JSH_PINFUNCTION_IS_TIMER(F) ( \
+ (((F)&JSH_MASK_TYPE)>=JSH_TIMER1) && \
+ (((F)&JSH_MASK_TYPE)<=JSH_TIMER18))
+#define JSH_PINFUNCTION_IS_DAC(F) ( \
+ (((F)&JSH_MASK_TYPE)==JSH_DAC) || \
+0 )
+#define JSH_PINFUNCTION_IS_USART(F) ( \
+ (((F)&JSH_MASK_TYPE)>=JSH_USART1) && \
+ (((F)&JSH_MASK_TYPE)<=JSH_USART6))
+#define JSH_PINFUNCTION_IS_I2C(F) ( \
+ (((F)&JSH_MASK_TYPE)>=JSH_I2C1) && \
+ (((F)&JSH_MASK_TYPE)<=JSH_I2CMAX))
+#define JSH_PINFUNCTION_IS_SPI(F) ( \
+ (((F)&JSH_MASK_TYPE)>=JSH_SPI1) && \
+ (((F)&JSH_MASK_TYPE)<=JSH_SPIMAX))
+
+bool jshIsPinValid(Pin pin); ///< is the specific pin actually valid?
+
+/// Given a string, convert it to a pin ID (or -1 if it doesn't exist)
+Pin jshGetPinFromString(const char *s);
+/** Write the pin name to a string. String must have at least 8 characters (to be safe) */
+void jshGetPinString(char *result, Pin pin);
+
+/// Given a var, convert it to a pin ID (or -1 if it doesn't exist). safe for undefined!
+static inline Pin jshGetPinFromVar(JsVar *pinv) {
+ if (jsvIsString(pinv) && pinv->varData.str[5]==0/*should never be more than 4 chars!*/) {
+ return jshGetPinFromString(&pinv->varData.str[0]);
+ } else if (jsvIsInt(pinv) /* This also tests for the Pin datatype */) {
+ return (Pin)jsvGetInteger(pinv);
+ } else return PIN_UNDEFINED;
+}
+
+static inline Pin jshGetPinFromVarAndUnLock(JsVar *pinv) {
+ Pin pin = jshGetPinFromVar(pinv);
+ jsvUnLock(pinv);
+ return pin;
+}
+
+#endif //JSPIN_H
diff --git a/components/external/espruino/src/jsutils.c b/components/external/espruino/src/jsutils.c
new file mode 100644
index 0000000000..669956989e
--- /dev/null
+++ b/components/external/espruino/src/jsutils.c
@@ -0,0 +1,439 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Misc utils and cheapskate stdlib implementation
+ * ----------------------------------------------------------------------------
+ */
+#include "jsutils.h"
+#include "jslex.h"
+#include "jshardware.h"
+#include "jsinteractive.h"
+
+// needed for isnan / isfinite
+#ifdef ARM
+#include "mconf.h"
+#include "protos.h"
+#else
+#include <math.h>
+#endif
+
+extern double jswrap_math_pow(double x, double y); // for pow
+
+bool isIDString(const char *s) {
+ if (!isAlpha(*s))
+ return false;
+ while (*s) {
+ if (!(isAlpha(*s) || isNumeric(*s)))
+ return false;
+ s++;
+ }
+ return true;
+}
+
+/** escape a character - if it is required. This may return a reference to a static array,
+so you can't store the value it returns in a variable and call it again. */
+const char *escapeCharacter(char ch) {
+ if (ch=='\b') return "\\b";
+ if (ch=='\f') return "\\f";
+ if (ch=='\n') return "\\n";
+ if (ch=='\a') return "\\a";
+ if (ch=='\r') return "\\r";
+ if (ch=='\t') return "\\t";
+ if (ch=='\\') return "\\\\";
+ if (ch=='"') return "\\\"";
+ static char buf[5];
+ if (ch<32) {
+ /** just encode as hex - it's more understandable
+ * and doesn't have the issue of "\16"+"1" != "\161" */
+ buf[0]='\\';
+ buf[1]='x';
+ int n = (ch>>4)&15;
+ buf[2] = (char)((n<10)?('0'+n):('A'+n-10));
+ n=ch&15;
+ buf[3] = (char)((n<10)?('0'+n):('A'+n-10));
+ buf[4] = 0;
+ return buf;
+ }
+ buf[1] = 0;
+ buf[0] = ch;
+ return buf;
+}
+
+/* convert a number in the given radix to an int. if radix=0, autodetect */
+JsVarInt stringToIntWithRadix(const char *s, int forceRadix, bool *hasError) {
+ bool isNegated = false;
+ JsVarInt v = 0;
+ JsVarInt radix = 10;
+ if (*s == '-') {
+ isNegated = true;
+ s++;
+ }
+ if (*s == '0') {
+ radix = 8;
+ s++;
+
+ // OctalIntegerLiteral: 0o01, 0O01
+ if (*s == 'o' || *s == 'O') {
+ radix = 8;
+ s++;
+
+ // HexIntegerLiteral: 0x01, 0X01
+ } else if (*s == 'x' || *s == 'X') {
+ radix = 16;
+ s++;
+
+ // BinaryIntegerLiteral: 0b01, 0B01
+ } else if (*s == 'b' || *s == 'B') {
+ radix = 2;
+ s++;
+ }
+ }
+ if (forceRadix>0 && forceRadix<=36)
+ radix = forceRadix;
+
+ while (*s) {
+ int digit = 0;
+ if (*s >= '0' && *s <= '9')
+ digit = (*s - '0');
+ else if (*s >= 'a' && *s <= 'f')
+ digit = (10 + *s - 'a');
+ else if (*s >= 'A' && *s <= 'F')
+ digit = (10 + *s - 'A');
+ else break;
+ if (digit>=radix)
+ break;
+ v = v*radix + digit;
+ s++;
+ }
+
+ if (hasError) *hasError = *s!=0; // we're ok if we reached the end of the string
+
+ if (isNegated) return -v;
+ return v;
+}
+
+/* convert hex, binary, octal or decimal string into an int */
+JsVarInt stringToInt(const char *s) {
+ return stringToIntWithRadix(s,0,0);
+}
+
+void jsError(const char *fmt, ...) {
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrint("ERROR: ");
+ va_list argp;
+ va_start(argp, fmt);
+ vcbprintf((vcbprintf_callback)jsiConsolePrint,0, fmt, argp);
+ va_end(argp);
+ jsiConsolePrint("\n");
+}
+
+void jsErrorInternal(const char *fmt, ...) {
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrint("INTERNAL ERROR: ");
+ va_list argp;
+ va_start(argp, fmt);
+ vcbprintf((vcbprintf_callback)jsiConsolePrint,0, fmt, argp);
+ va_end(argp);
+ jsiConsolePrint("\n");
+}
+
+void jsErrorAt(const char *message, struct JsLex *lex, int tokenPos) {
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrint("ERROR: ");
+ jsiConsolePrint(message);
+ jsiConsolePrint(" at ");
+ jsiConsolePrintPosition(lex, tokenPos);
+ jsiConsolePrintTokenLineMarker(lex, tokenPos);
+}
+
+void jsWarn(const char *fmt, ...) {
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrint("WARNING: ");
+ va_list argp;
+ va_start(argp, fmt);
+ vcbprintf((vcbprintf_callback)jsiConsolePrint,0, fmt, argp);
+ va_end(argp);
+ jsiConsolePrint("\n");
+}
+
+void jsWarnAt(const char *message, struct JsLex *lex, int tokenPos) {
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrint("WARNING: ");
+ jsiConsolePrint(message);
+ jsiConsolePrint(" at ");
+ jsiConsolePrintPosition(lex, tokenPos);
+}
+
+void jsAssertFail(const char *file, int line, const char *expr) {
+ jsiConsoleRemoveInputLine();
+ if (expr) {
+ jsiConsolePrintf("ASSERT(%s) FAILED AT ", expr);
+ } else
+ jsiConsolePrint("ASSERT FAILED AT ");
+ jsiConsolePrintf("%s:%d\n",file,line);
+
+ jsvTrace(jsvGetRef(jsvFindOrCreateRoot()), 2);
+ exit(1);
+}
+
+#ifdef SDCC
+void exit(int errcode) {dst;
+ jsiConsolePrint("EXIT CALLED.\n");
+}
+#endif
+
+#ifdef FAKE_STDLIB
+int __errno;
+
+void exit(int errcode) {
+ NOT_USED(errcode);
+ jsiConsolePrint("EXIT CALLED.\n");
+ while (1);
+}
+
+char * strncat(char *dst, const char *src, size_t c) {
+ char *dstx = dst;
+ while (*(++dstx)) c--;
+ while (*src && c>1) {
+ *(dstx++) = *(src++);
+ c--;
+ }
+ if (c>0) *dstx = 0;
+ return dst;
+}
+char *strncpy(char *dst, const char *src, size_t c) {
+ char *dstx = dst;
+ while (*src && c>1) {
+ *(dstx++) = *(src++);
+ c--;
+ }
+ if (c>0) *dstx = 0;
+ return dst;
+}
+size_t strlen(const char *s) {
+ size_t l=0;
+ while (*(s++)) l++;
+ return l;
+}
+int strcmp(const char *a, const char *b) {
+ while (*a && *b) {
+ if (*a != *b)
+ return *a - *b; // correct?
+ a++;b++;
+ }
+ return *a - *b;
+}
+void *memcpy(void *dst, const void *src, size_t size) {
+ size_t i;
+ for (i=0;i<size;i++)
+ ((char*)dst)[i] = ((char*)src)[i];
+ return dst;
+}
+
+void *memset(void *dst, int val, size_t size) {
+ unsigned char *d = (unsigned char*)dst;
+ unsigned int i;
+ for (i=0;i<size;i++)
+ d[i]=(unsigned char)val;
+ return dst;
+}
+
+unsigned int rand() {
+ static unsigned int m_w = 0xDEADBEEF; /* must not be zero */
+ static unsigned int m_z = 0xCAFEBABE; /* must not be zero */
+
+ m_z = 36969 * (m_z & 65535) + (m_z >> 16);
+ m_w = 18000 * (m_w & 65535) + (m_w >> 16);
+ return (m_z << 16) + m_w; /* 32-bit result */
+}
+#endif
+
+JsVarFloat stringToFloat(const char *s) {
+ bool isNegated = false;
+ JsVarFloat v = 0;
+ JsVarFloat mul = 0.1;
+ if (*s == '-') {
+ isNegated = true;
+ s++;
+ }
+ // handle integer part
+ while (*s) {
+ if (*s >= '0' && *s <= '9')
+ v = (v*10) + (*s - '0');
+ else break;
+ s++;
+ }
+ // handle decimal point
+ if (*s == '.') {
+ s++; // skip .
+
+ while (*s) {
+ if (*s >= '0' && *s <= '9')
+ v += mul*(*s - '0');
+ else break;
+ mul /= 10;
+ s++;
+ }
+ }
+
+ // handle exponentials
+ if (*s == 'e' || *s == 'E') {
+ s++; // skip E
+ bool isENegated = false;
+ if (*s == '-' || *s == '+') {
+ isENegated = *s=='-';
+ s++;
+ }
+ int e = 0;
+ while (*s) {
+ if (*s >= '0' && *s <= '9')
+ e = (e*10) + (*s - '0');
+ else break;
+ s++;
+ }
+ if (isENegated) e=-e;
+ v = v * jswrap_math_pow(10, e);
+ }
+ // check we have parsed everything
+ if (*s!=0) return NAN;
+
+ if (isNegated) return -v;
+ return v;
+}
+
+
+char itoch(int val) {
+ if (val<10) return (char)('0'+val);
+ return (char)('A'+val-10);
+}
+
+#ifndef HAS_STDLIB
+void itoa(JsVarInt vals,char *str,unsigned int base) {
+ JsVarIntUnsigned val;
+ if (vals<0) {
+ *(str++)='-';
+ val = (JsVarIntUnsigned)(-vals);
+ } else {
+ val = (JsVarIntUnsigned)vals;
+ }
+ JsVarIntUnsigned d = 1;
+ while (d*base <= val) d*=base;
+ while (d > 1) {
+ unsigned int v = (unsigned int)(val / d);
+ val -= v*d;
+ *(str++) = itoch((int)v);
+ d /= base;
+ }
+ *(str++)=itoch((int)val);
+ *(str++)=0;
+}
+#endif
+
+void ftoa(JsVarFloat val,char *str) {
+ if (isnan(val)) strncpy(str,"NaN",4);
+ else if (!isfinite(val)) {
+ if (val<0) strncpy(str,"-Infinity",10);
+ else strncpy(str,"Infinity",10);
+ } else {
+ const JsVarFloat base = 10;
+ if (val<0) {
+ *(str++)='-';
+ val = -val;
+ }
+ JsVarFloat d = 1;
+ while (d*base <= val) d*=base;
+ while (d >= 1) {
+ int v = (int)(val / d);
+ val -= v*d;
+ *(str++)=itoch(v);
+ d /= base;
+ }
+ #ifndef USE_NO_FLOATS
+ if (val>0) {
+ *(str++)='.';
+ while (val>0.000001) {
+ int v = (int)((val / d) + 0.0000005);
+ val -= v*d;
+ *(str++)=itoch(v);
+ d /= base;
+ }
+ }
+ #endif
+
+ *(str++)=0;
+ }
+}
+
+
+/// Wrap a value so it is always between 0 and size (eg. wrapAround(angle, 360))
+JsVarFloat wrapAround(JsVarFloat val, JsVarFloat size) {
+ val = val / size;
+ val = val - (int)val;
+ return val * size;
+}
+
+/** Espruino-special printf with a callback
+ * Supported are:
+ * %d = int
+ * %x = int as hex
+ * %L = JsVarInt
+ * %Lx = JsVarInt as hex
+ * %f = JsVarFloat
+ * %s = string (char *)
+ * %c = char
+ * %v = JsVar * (prints var as string)
+ * %t = JsVar * (prints type of var)
+ * %p = Pin
+ *
+ * Anything else will assert
+ */
+void vcbprintf(vcbprintf_callback user_callback, void *user_data, const char *fmt, va_list argp) {
+ char buf[32];
+ while (*fmt) {
+ if (*fmt == '%') {
+ fmt++;
+ switch (*fmt++) {
+ case 'd': itoa(va_arg(argp, int), buf, 10); user_callback(buf,user_data); break;
+ case 'x': itoa(va_arg(argp, int), buf, 16); user_callback(buf,user_data); break;
+ case 'L': {
+ unsigned int rad = 10;
+ if (*fmt=='x') { rad=16; fmt++; }
+ itoa(va_arg(argp, JsVarInt), buf, rad); user_callback(buf,user_data);
+ } break;
+ case 'f': ftoa(va_arg(argp, JsVarFloat), buf); user_callback(buf,user_data); break;
+ case 's': user_callback(va_arg(argp, char *), user_data); break;
+ case 'c': buf[0]=(char)va_arg(argp, int/*char*/);buf[1]=0; user_callback(buf, user_data); break;
+ case 'v': {
+ JsVar *v = jsvAsString(va_arg(argp, JsVar*), false/*no unlock*/);
+ buf[1] = 0;
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ // OPT: this could be faster than it is (sending whole blocks at once)
+ while (jsvStringIteratorHasChar(&it)) {
+ buf[0] = jsvStringIteratorGetChar(&it);
+ user_callback(buf,user_data);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ jsvUnLock(v);
+ } break;
+ case 't': user_callback(jsvGetTypeOf(va_arg(argp, JsVar*)), user_data); break;
+ case 'p': jshGetPinString(buf, (Pin)va_arg(argp, int/*Pin*/)); user_callback(buf, user_data); break;
+ default: assert(0); return; // eep
+ }
+ } else {
+ buf[0] = *(fmt++);
+ buf[1] = 0;
+ user_callback(&buf[0], user_data);
+ }
+ }
+}
+
+
diff --git a/components/external/espruino/src/jsutils.h b/components/external/espruino/src/jsutils.h
new file mode 100644
index 0000000000..4097830ed0
--- /dev/null
+++ b/components/external/espruino/src/jsutils.h
@@ -0,0 +1,374 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Misc utils and cheapskate stdlib implementation
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSUTILS_H_
+#define JSUTILS_H_
+
+#include "platform_config.h"
+
+#ifndef FAKE_STDLIB
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+#include <stdarg.h> // for va_args
+
+#define JS_VERSION "1v46"
+/*
+ In code:
+ TODO - should be fixed
+ FIXME - will probably break if used
+ OPT - potential for speed optimisation
+*/
+
+// surely bool is defined??
+#ifdef RT_USING_JS
+typedef unsigned int size_t;
+#else
+#ifdef ARM
+typedef unsigned int size_t;
+#define alloca(x) __builtin_alloca(x)
+#endif
+#endif
+
+#if !defined(__USB_TYPE_H) && !defined(CPLUSPLUS) // it is defined in this file too!
+typedef enum {FALSE = 0, TRUE = !FALSE} bool;
+//typedef unsigned char bool;
+//#define TRUE (1)
+//#define FALSE (0)
+#endif
+
+#define true (1)
+#define false (0)
+#define NAN (((JsVarFloat)0)/(JsVarFloat)0)
+
+/* Number of Js Variables allowed and Js Reference format.
+
+ JsVarRef = char -> 15 bytes/JsVar so JSVAR_CACHE_SIZE = (RAM - 3000) / 15
+ JsVarRef = short -> 20 bytes/JsVar so JSVAR_CACHE_SIZE = (RAM - 3000) / 20
+ JsVarRef = int -> 26 bytes/JsVar so JSVAR_CACHE_SIZE = (RAM - 3000) / 26
+
+ NOTE: JSVAR_CACHE_SIZE must be at least 2 less than the number we can fit in JsVarRef
+ See jshardware.c FLASH constants - all this must be able to fit in flash
+
+
+*/
+
+#ifdef RESIZABLE_JSVARS
+ // probably linux - allow us to allocate more blocks of variables
+ typedef unsigned int JsVarRef;
+ #define JSVAR_SIZE 30
+ #define JSVAR_DATA_STRING_LEN 8 // Actually 9 seems like a good number as 'prototype'==9
+ #define JSVAR_DATA_STRING_MAX_LEN 24 // (JSVAR_DATA_STRING_LEN + sizeof(JsVarRef)*3 + sizeof(JsVarRefCounter)) - but see JSV_STRING_LEN_MAX - WE HAVE TO CLIP!
+#else
+ /** JsVerRef stores References for variables - We treat 0 as null
+ * NOTE: we store JSVAR_DATA_STRING_* as actual values so we can do #if on them below
+ *
+ */
+ #if JSVAR_CACHE_SIZE <= 254
+ typedef unsigned char JsVarRef;
+ #define JSVAR_SIZE 15
+ #define JSVAR_DATA_STRING_LEN 8 // Actually 9 seems like a good number as 'prototype'==9
+ #define JSVAR_DATA_STRING_MAX_LEN 12 // (JSVAR_DATA_STRING_LEN + sizeof(JsVarRef)*3 + sizeof(JsVarRefCounter)) - but see JSV_STRING_LEN_MAX too
+ #else
+ typedef unsigned short JsVarRef;
+ #define JSVAR_SIZE 20
+ #define JSVAR_DATA_STRING_LEN 8 // Actually 9 seems like a good number as 'prototype'==9
+ #define JSVAR_DATA_STRING_MAX_LEN 16 // (JSVAR_DATA_STRING_LEN + sizeof(JsVarRef)*3 + sizeof(JsVarRefCounter)) - but see JSV_STRING_LEN_MAX too - WE HAVE TO CLIP!
+ #endif
+#endif
+
+typedef long long JsVarInt;
+typedef unsigned long long JsVarIntUnsigned;
+#ifdef USE_FLOATS
+typedef float JsVarFloat;
+#else
+typedef double JsVarFloat;
+#endif
+
+typedef short JslCharPos;
+#define JSSYSTIME_MAX 0x7FFFFFFFFFFFFFFFLL
+typedef long long JsSysTime;
+
+#define JSLEX_MAX_TOKEN_LENGTH 64
+#define JS_ERROR_BUF_SIZE 64 // size of buffer error messages are written into
+#define JS_ERROR_TOKEN_BUF_SIZE 16 // see jslTokenAsString
+
+#define JS_NUMBER_BUFFER_SIZE 24
+
+#define JSPARSE_MAX_SCOPES 8
+// Don't restrict number of iterations now
+//#define JSPARSE_MAX_LOOP_ITERATIONS 8192
+
+#define STRINGIFY_HELPER(x) #x
+#define STRINGIFY(x) STRINGIFY_HELPER(x)
+#define NOT_USED(x) ( (void)(x) )
+
+// javascript specific names
+#define JSPARSE_RETURN_VAR "return" // variable name used for returning function results
+#define JSPARSE_PROTOTYPE_VAR "prototype"
+#define JSPARSE_CONSTRUCTOR_VAR "constructor"
+#define JSPARSE_INHERITS_VAR "__proto__"
+// internal names that hopefully nobody will be able to access
+#define JS_HIDDEN_CHAR '>' // initial character of var name determines that we shouldn't see this stuff
+#define JS_HIDDEN_CHAR_STR ">"
+#define JSPARSE_FUNCTION_CODE_NAME JS_HIDDEN_CHAR_STR"code"
+#define JSPARSE_FUNCTION_SCOPE_NAME JS_HIDDEN_CHAR_STR"scope"
+#define JSPARSE_MODULE_CACHE_NAME JS_HIDDEN_CHAR_STR"modules"
+
+#if !defined(NO_ASSERT)
+ #ifdef __STRING
+ #define assert(X) if (!(X)) jsAssertFail(__FILE__,__LINE__,__STRING(X));
+ #else
+ #define assert(X) if (!(X)) jsAssertFail(__FILE__,__LINE__,"");
+ #endif
+#else
+ #define assert(X)
+#endif
+
+/// Used when we have enums we want to squash down
+#define PACKED_FLAGS __attribute__ ((__packed__))
+
+/// Used before functions that we want to ensure are not inlined (eg. "void NO_INLINE foo() {}")
+#define NO_INLINE __attribute__ ((noinline))
+
+/// Maximum amount of locks we ever expect to have on a variable (this could limit recursion) must be 2^n-1
+#define JSV_LOCK_MAX 15
+
+/// preprocessor power of 2 - suitable up to 16 bits
+#define NEXT_POWER_2(X) \
+ (((X) | (X)>>1 | (X)>>2 | (X)>>3 | \
+ (X)>>4 | (X)>>5 | (X)>>6 | (X)>>7 | \
+ (X)>>8 | (X)>>9 | (X)>>10 | (X)>>11 | \
+ (X)>>12 | (X)>>13 | (X)>>14 | (X)>>15)+1)
+/// Proprocessor get bit number
+#define GET_BIT_NUMBER(X) \
+ (((X)== 1)? 0: \
+ ((X)== 2)? 1: \
+ ((X)== 4)? 2: \
+ ((X)== 8)? 3: \
+ ((X)== 16)? 4: \
+ ((X)== 32)? 5: \
+ ((X)== 64)? 6: \
+ ((X)== 128)? 7: \
+ ((X)== 256)? 8: \
+ ((X)== 512)? 9: \
+ ((X)== 1024)?10: \
+ ((X)== 2048)?11: \
+ ((X)== 4096)?12: \
+ ((X)== 8192)?13: \
+ ((X)==16384)?14: \
+ ((X)==32768)?15:10000/*error*/)
+
+
+
+/** These flags are at the top of each JsVar and provide information about what it is, as
+ * well as how many Locks it has. Everything is packed in as much as possible to allow us to
+ * get down to within 2 bytes. */
+typedef enum {
+ JSV_UNUSED = 0, ///< Variable not used for anything
+ JSV_ROOT = JSV_UNUSED+1, ///< The root of everything - there is only one of these
+ // 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_STRING = JSV_NULL+1, ///< string
+ JSV_STRING_0 = JSV_STRING, // string of length 0
+ JSV_STRING_MAX = JSV_STRING_0+JSVAR_DATA_STRING_LEN,
+ JSV_STRING_EXT = JSV_STRING_MAX+1, ///< extra character data for string (if it didn't fit in first JsVar). These use unused pointer fields for extra characters
+ JSV_STRING_EXT_0 = JSV_STRING_EXT,
+ JSV_STRING_EXT_MAX = JSV_STRING_EXT_0+JSVAR_DATA_STRING_MAX_LEN,
+ JSV_ARRAY = JSV_STRING_EXT_MAX+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_NUMERICSTART = JSV_FUNCTION+1, ///< --------- Start of numeric variable types
+ JSV_INTEGER = JSV_NUMERICSTART, ///< integer number (note JSV_NUMERICMASK)
+ JSV_FLOAT = JSV_INTEGER+1, ///< floating point double (note JSV_NUMERICMASK)
+ JSV_BOOLEAN = JSV_FLOAT+1, ///< boolean (note JSV_NUMERICMASK)
+ JSV_PIN = JSV_BOOLEAN+1, ///< pin (note JSV_NUMERICMASK)
+ JSV_NUMERICEND = JSV_PIN, ///< --------- End of numeric variable types
+ JSV_VAR_END = JSV_NUMERICEND, ///< End of numeric variable types
+
+ JSV_VARTYPEMASK = NEXT_POWER_2(JSV_VAR_END)-1,
+
+ // names can be STRING,
+ JSV_NAME = JSV_VARTYPEMASK+1, ///< a NAME of a variable - this isn't a variable itself (and can be an int/string/etc.)
+ JSV_NATIVE = JSV_NAME<<1, ///< to specify this is a native function, root, function parameter, OR that it should not be freed
+ JSV_GARBAGE_COLLECT = JSV_NATIVE<<1, ///< When garbage collecting, this flag is true IF we should GC!
+ JSV_IS_RECURSING = JSV_GARBAGE_COLLECT<<1, ///< used to stop recursive loops in jsvTrace
+ JSV_LOCK_ONE = JSV_IS_RECURSING<<1,
+ JSV_LOCK_MASK = JSV_LOCK_MAX * JSV_LOCK_ONE,
+
+
+
+ JSV_ARRAYBUFFERNAME = JSV_NAME|JSV_ARRAYBUFFER, ///< used for indexing into an ArrayBuffer. varData is an INT in this case
+ JSV_FUNCTION_PARAMETER = JSV_NATIVE | JSV_NAME, ///< this is inside a function, so it should be quite obvious
+
+} PACKED_FLAGS JsVarFlags; // aiming to get this in 2 bytes!
+
+/// The amount of bits we must shift to get the number of locks - forced to be a constant
+static const int JSV_LOCK_SHIFT = GET_BIT_NUMBER(JSV_LOCK_ONE);
+
+typedef enum LEX_TYPES {
+ LEX_EOF = 0,
+ LEX_ID = 256,
+ LEX_INT,
+ LEX_FLOAT,
+ LEX_STR,
+ LEX_UNFINISHED_COMMENT,
+
+ LEX_EQUAL,
+ LEX_TYPEEQUAL,
+ LEX_NEQUAL,
+ LEX_NTYPEEQUAL,
+ LEX_LEQUAL,
+ LEX_LSHIFT,
+ LEX_LSHIFTEQUAL,
+ LEX_GEQUAL,
+ LEX_RSHIFT,
+ LEX_RSHIFTUNSIGNED,
+ LEX_RSHIFTEQUAL,
+ LEX_RSHIFTUNSIGNEDEQUAL,
+ LEX_PLUSEQUAL,
+ LEX_MINUSEQUAL,
+ LEX_PLUSPLUS,
+ LEX_MINUSMINUS,
+ LEX_MULEQUAL,
+ LEX_DIVEQUAL,
+ LEX_MODEQUAL,
+ LEX_ANDEQUAL,
+ LEX_ANDAND,
+ LEX_OREQUAL,
+ LEX_OROR,
+ LEX_XOREQUAL,
+ // reserved words
+#define LEX_R_LIST_START LEX_R_IF
+ LEX_R_IF,
+ LEX_R_ELSE,
+ LEX_R_DO,
+ LEX_R_WHILE,
+ LEX_R_FOR,
+ LEX_R_BREAK,
+ LEX_R_CONTINUE,
+ LEX_R_FUNCTION,
+ LEX_R_RETURN,
+ LEX_R_VAR,
+ LEX_R_THIS,
+ LEX_R_TRUE,
+ LEX_R_FALSE,
+ LEX_R_NULL,
+ LEX_R_UNDEFINED,
+ LEX_R_NEW,
+ LEX_R_IN,
+ LEX_R_INSTANCEOF,
+ LEX_R_SWITCH,
+ LEX_R_CASE,
+ LEX_R_DEFAULT,
+ LEX_R_TYPEOF,
+ LEX_R_VOID,
+
+ LEX_R_LIST_END /* always the last entry */
+} LEX_TYPES;
+
+// To handle variable size bit fields
+#define BITFIELD_DECL(BITFIELD, N) unsigned int BITFIELD[(N+31)/32]
+#define BITFIELD_GET(BITFIELD, N) ((BITFIELD[(N)>>5] >> ((N)&31))&1)
+#define BITFIELD_SET(BITFIELD, N, VALUE) (BITFIELD[(N)>>5] = (BITFIELD[(N)>>5]& (unsigned int)~(1 << ((N)&31))) | (unsigned int)((VALUE)?(1 << ((N)&31)):0) )
+
+
+static inline bool isWhitespace(char ch) {
+ return (ch==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r');
+}
+
+static inline bool isNumeric(char ch) {
+ return (ch>='0') && (ch<='9');
+}
+
+static inline bool isHexadecimal(char ch) {
+ return ((ch>='0') && (ch<='9')) ||
+ ((ch>='a') && (ch<='f')) ||
+ ((ch>='A') && (ch<='F'));
+}
+static inline bool isAlpha(char ch) {
+ return ((ch>='a') && (ch<='z')) || ((ch>='A') && (ch<='Z')) || ch=='_';
+}
+
+
+bool isIDString(const char *s);
+
+/** escape a character - if it is required. This may return a reference to a static array,
+so you can't store the value it returns in a variable and call it again. */
+const char *escapeCharacter(char ch);
+/* convert a number in the given radix to an int. if radix=0, autodetect */
+JsVarInt stringToIntWithRadix(const char *s, int radix, bool *hasError);
+/* convert hex, binary, octal or decimal string into an int */
+JsVarInt stringToInt(const char *s);
+
+// forward decl
+struct JsLex;
+// ------------
+
+void jsError(const char *fmt, ...);
+void jsErrorInternal(const char *fmt, ...);
+void jsErrorAt(const char *message, struct JsLex *lex, int tokenPos);
+void jsWarn(const char *fmt, ...);
+void jsWarnAt(const char *message, struct JsLex *lex, int tokenPos);
+void jsAssertFail(const char *file, int line, const char *expr);
+
+#ifdef FAKE_STDLIB
+void exit(int errcode);
+char *strncat(char *dst, const char *src, size_t c);
+char *strncpy(char *dst, const char *src, size_t c);
+size_t strlen(const char *s);
+int strcmp(const char *a, const char *b);
+void *memcpy(void *dst, const void *src, size_t size);
+void *memset(void *dst, int val, size_t size);
+#define RAND_MAX (0xFFFFFFFFU)
+unsigned int rand();
+#else
+// FIXME: use itoa/ftoa direct - sprintf is huge
+//#define itoa(val,str,base) sprintf(str,"%d",(int)val)
+//#define ftoa(val,str) sprintf(str,"%f",val)
+
+#endif
+
+JsVarFloat stringToFloat(const char *str);
+
+#ifndef HAS_STDLIB
+void itoa(JsVarInt val,char *str,unsigned int base);
+#endif
+char itoch(int val);
+void ftoa(JsVarFloat val,char *str);
+
+/// Wrap a value so it is always between 0 and size (eg. wrapAround(angle, 360))
+JsVarFloat wrapAround(JsVarFloat val, JsVarFloat size);
+
+
+typedef void (*vcbprintf_callback)(const char *str, void *user_data);
+/** Espruino-special printf with a callback
+ * Supported are:
+ * %d = int
+ * %x = int as hex
+ * %L = JsVarInt
+ * %Lx = JsVarInt as hex
+ * %f = JsVarFloat
+ * %s = string (char *)
+ * %c = char
+ * %v = JsVar *
+ * %p = Pin
+ *
+ * Anything else will assert
+ */
+void vcbprintf(vcbprintf_callback user_callback, void *user_data, const char *fmt, va_list argp);
+
+
+#endif /* JSUTILS_H_ */
diff --git a/components/external/espruino/src/jsvar.c b/components/external/espruino/src/jsvar.c
new file mode 100644
index 0000000000..242a8f049b
--- /dev/null
+++ b/components/external/espruino/src/jsvar.c
@@ -0,0 +1,2542 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Variables
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jslex.h"
+#include "jsparse.h"
+#include "jswrap_json.h"
+#include "jsinteractive.h"
+#include "jswrapper.h"
+
+
+/** Basically, JsVars are stored in one big array, so save the need for
+ * lots of memory allocation. On Linux, the arrays are in blocks, so that
+ * more blocks can be allocated. We can't use realloc on one big block as
+ * this may change the address of vars that are already locked!
+ *
+ */
+
+#ifdef RESIZABLE_JSVARS
+JsVar **jsVarBlocks = 0;
+unsigned int jsVarsSize = 0;
+#define JSVAR_BLOCK_SIZE 1024
+#define JSVAR_BLOCK_SHIFT 10
+#else
+JsVar jsVars[JSVAR_CACHE_SIZE];
+unsigned int jsVarsSize = JSVAR_CACHE_SIZE;
+#endif
+
+JsVarRef jsVarFirstEmpty; ///< reference of first unused variable (variables are in a linked list)
+
+/** Return a pointer - UNSAFE for null refs.
+ * This is effectively a Lock without locking! */
+static inline JsVar *jsvGetAddressOf(JsVarRef ref) {
+ assert(ref);
+#ifdef RESIZABLE_JSVARS
+ JsVarRef t = ref-1;
+ return &jsVarBlocks[t>>JSVAR_BLOCK_SHIFT][t&(JSVAR_BLOCK_SIZE-1)];
+#else
+ return &jsVars[ref-1];
+#endif
+}
+
+JsVar *_jsvGetAddressOf(JsVarRef ref) {
+ return jsvGetAddressOf(ref);
+}
+
+
+// For debugging/testing ONLY - maximum # of vars we are allowed to use
+void jsvSetMaxVarsUsed(unsigned int size) {
+#ifdef RESIZABLE_JSVARS
+ assert(size < JSVAR_BLOCK_SIZE); // remember - this is only for DEBUGGING - as such it doesn't use multiple blocks
+#else
+ assert(size < JSVAR_CACHE_SIZE);
+#endif
+ jsVarsSize = size;
+}
+
+
+// maps the empty variables in...
+void jsvSoftInit() {
+ jsVarFirstEmpty = 0;
+ JsVar *lastEmpty = 0;
+ JsVarRef i;
+ for (i=1;i<=jsVarsSize;i++) {
+ if ((jsvGetAddressOf(i)->flags&JSV_VARTYPEMASK) == JSV_UNUSED) {
+ jsvGetAddressOf(i)->nextSibling = 0;
+ if (lastEmpty)
+ lastEmpty->nextSibling = i;
+ else
+ jsVarFirstEmpty = i;
+ lastEmpty = jsvGetAddressOf(i);
+ }
+ }
+}
+
+void jsvSoftKill() {
+}
+
+/** This links all JsVars together, so we can have our nice
+ * linked list of free JsVars. It returns the ref of the first
+ * item - that we should set jsVarFirstEmpty to (if it is 0) */
+static JsVarRef jsvInitJsVars(JsVarRef start, unsigned int count) {
+ JsVarRef i;
+ for (i=start;i<start+count;i++) {
+ JsVar *v = jsvGetAddressOf(i);
+ v->flags = JSV_UNUSED;
+#ifdef LARGE_MEM
+ v->this = i;
+#endif
+ // v->locks = 0; // locks is 0 anyway because it is stored in flags
+ v->nextSibling = (JsVarRef)(i+1); // link to next
+ }
+ jsvGetAddressOf((JsVarRef)(start+count-1))->nextSibling = (JsVarRef)0; // set the final one to 0
+ return start;
+}
+
+void jsvInit() {
+#ifdef RESIZABLE_JSVARS
+ jsVarsSize = JSVAR_BLOCK_SIZE;
+ jsVarBlocks = malloc(sizeof(JsVar*)); // just 1
+ jsVarBlocks[0] = malloc(sizeof(JsVar) * JSVAR_BLOCK_SIZE);
+#endif
+
+ jsVarFirstEmpty = jsvInitJsVars(1/*first*/, jsVarsSize);
+ jsvSoftInit();
+}
+
+void jsvKill() {
+#ifdef RESIZABLE_JSVARS
+
+ unsigned int i;
+ for (i=0;i<jsVarsSize>>JSVAR_BLOCK_SHIFT;i++)
+ free(jsVarBlocks[i]);
+ free(jsVarBlocks);
+ jsVarBlocks = 0;
+ jsVarsSize = 0;
+#endif
+}
+
+/** Find or create the ROOT variable item - used mainly
+ * if recovering from a saved state. */
+JsVar *jsvFindOrCreateRoot() {
+ JsVarRef i;
+ for (i=1;i<=jsVarsSize;i++)
+ if (jsvIsRoot(jsvGetAddressOf(i)))
+ return jsvLock(i);
+
+ return jsvRef(jsvNewWithFlags(JSV_ROOT));
+}
+
+/// Get number of memory records (JsVars) used
+unsigned int jsvGetMemoryUsage() {
+ unsigned int usage = 0;
+ unsigned int i;
+ for (i=1;i<=jsVarsSize;i++) {
+ JsVar *v = jsvGetAddressOf((JsVarRef)i);
+ if ((v->flags&JSV_VARTYPEMASK) != JSV_UNUSED)
+ usage++;
+ }
+ return usage;
+}
+
+/// Get total amount of memory records
+unsigned int jsvGetMemoryTotal() {
+ return jsVarsSize;
+}
+
+/// Try and allocate more memory - only works if RESIZABLE_JSVARS is defined
+void jsvSetMemoryTotal(unsigned int jsNewVarCount) {
+#ifdef RESIZABLE_JSVARS
+ if (jsNewVarCount <= jsVarsSize) return; // never allow us to have less!
+ // When resizing, we just allocate a bunch more
+ unsigned int oldSize = jsVarsSize;
+ unsigned int oldBlockCount = jsVarsSize >> JSVAR_BLOCK_SHIFT;
+ unsigned int newBlockCount = (jsNewVarCount+JSVAR_BLOCK_SIZE-1) >> JSVAR_BLOCK_SHIFT;
+ jsVarsSize = newBlockCount << JSVAR_BLOCK_SHIFT;
+ // resize block table
+ jsVarBlocks = realloc(jsVarBlocks, sizeof(JsVar*)*newBlockCount);
+ // allocate more blocks
+ unsigned int i;
+ for (i=oldBlockCount;i<newBlockCount;i++)
+ jsVarBlocks[i] = malloc(sizeof(JsVar) * JSVAR_BLOCK_SIZE);
+ /** and now reset all the newly allocated vars. We know jsVarFirstEmpty
+ * is 0 (because jsiFreeMoreMemory returned 0) so we can just assign it. */
+ assert(!jsVarFirstEmpty);
+ jsVarFirstEmpty = jsvInitJsVars(oldSize+1, jsVarsSize-oldSize);
+ // jsiConsolePrintf("Resized memory from %d blocks to %d\n", oldBlockCount, newBlockCount);
+#else
+ NOT_USED(jsNewVarCount);
+ assert(0);
+#endif
+}
+
+/// Get whether memory is full or not
+bool jsvIsMemoryFull() {
+ return !jsVarFirstEmpty;
+}
+
+// Show what is still allocated, for debugging memory problems
+void jsvShowAllocated() {
+ JsVarRef i;
+ for (i=1;i<=jsVarsSize;i++) {
+ if ((jsvGetAddressOf(i)->flags&JSV_VARTYPEMASK) != JSV_UNUSED) {
+ jsiConsolePrintf("USED VAR #%d:",i);
+ jsvTrace(i, 2);
+ }
+ }
+}
+
+bool jsvHasCharacterData(const JsVar *v) {
+ return jsvIsString(v) || jsvIsStringExt(v);
+}
+
+bool jsvHasStringExt(const JsVar *v) {
+ return jsvIsString(v) || jsvIsStringExt(v);
+}
+
+bool jsvHasChildren(const JsVar *v) {
+ return jsvIsFunction(v) || jsvIsObject(v) || jsvIsArray(v) || jsvIsRoot(v);
+}
+
+/// Is this variable a type that uses firstChild to point to a single Variable (ie. it doesn't have multiple children)
+bool jsvHasSingleChild(const JsVar *v) {
+ return jsvIsName(v) || jsvIsArrayBuffer(v) || jsvIsArrayBufferName(v);
+}
+
+
+
+JsVar *jsvNew() {
+ if (jsVarFirstEmpty!=0) {
+ JsVar *v = jsvLock(jsVarFirstEmpty);
+ jsVarFirstEmpty = v->nextSibling; // move our reference to the next in the free list
+ assert((v->flags&JSV_VARTYPEMASK) == JSV_UNUSED);
+ // reset it
+ v->refs = 0;
+ //v->locks = 1;
+ v->flags = JSV_LOCK_ONE;
+ v->varData.callback = 0;
+ v->firstChild = 0;
+ v->lastChild = 0;
+ v->prevSibling = 0;
+ v->nextSibling = 0;
+ // return pointer
+ return v;
+ }
+ /* we don't have memort - second last hope - run garbage collector */
+ if (jsvGarbageCollect())
+ return jsvNew(); // if it freed something, continue
+ /* we don't have memory - last hope - ask jsInteractive to try and free some it
+ may have kicking around */
+ if (jsiFreeMoreMemory())
+ return jsvNew();
+ /* We couldn't claim any more memory by Garbage collecting... */
+#ifdef RESIZABLE_JSVARS
+ jsvSetMemoryTotal(jsVarsSize*2);
+ return jsvNew();
+#else
+ // On a micro, we're screwed.
+ jsError("Out of Memory!");
+ jspSetInterrupted(true);
+ return 0;
+#endif
+}
+
+void jsvFreePtr(JsVar *var) {
+ /* To be here, we're not supposed to be part of anything else. If
+ * we were, we'd have been freed by jsvGarbageCollect */
+ assert(jsvIsStringExt(var) || (!var->nextSibling && !var->prevSibling));
+
+ // Names that Link to other things
+ if (jsvHasSingleChild(var)) {
+ if (var->firstChild) {
+ JsVar *child = jsvLock(var->firstChild);
+ jsvUnRef(child); var->firstChild = 0; // unlink the child
+ jsvUnLock(child); // unlock should trigger a free
+ }
+ }
+ /* No else, because a String Name may have a single child, but
+ * also StringExts */
+
+ /* Now, free children - see jsvar.h comments for how! */
+ if (jsvHasStringExt(var)) {
+ // TODO: make string free this non-recursive
+ JsVarRef stringDataRef = var->lastChild;
+ var->lastChild = 0;
+ if (stringDataRef) {
+ JsVar *child = jsvLock(stringDataRef);
+ assert(jsvIsStringExt(child));
+ jsvFreePtr(child);
+ jsvUnLock(child);
+ }
+ } else if (jsvHasChildren(var)) {
+ JsVarRef childref = var->firstChild;
+ var->firstChild = 0;
+ var->lastChild = 0;
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ assert(jsvIsName(child));
+ childref = child->nextSibling;
+ child->prevSibling = 0;
+ child->nextSibling = 0;
+ jsvUnRef(child);
+ jsvUnLock(child);
+ }
+ } else {
+ assert(!var->firstChild);
+ assert(!var->lastChild);
+ }
+
+ // free!
+ var->flags = (var->flags & ~JSV_VARTYPEMASK) | JSV_UNUSED;
+ // add this to our free list
+ var->nextSibling = jsVarFirstEmpty;
+ jsVarFirstEmpty = jsvGetRef(var);
+}
+
+/// Get a reference from a var - SAFE for null vars
+JsVarRef jsvGetRef(JsVar *var) {
+ if (!var) return 0;
+#ifdef LARGE_MEM
+ return var->this;
+#else
+ #ifdef RESIZABLE_JSVARS
+ unsigned int i, c = jsVarsSize>>JSVAR_BLOCK_SHIFT;
+ for (i=0;i<c;i++) {
+ if (var>=jsVarBlocks[i] && var<&jsVarBlocks[i][JSVAR_BLOCK_SIZE]) {
+ JsVarRef r = (JsVarRef)(1 + (i<<JSVAR_BLOCK_SHIFT) + (var - jsVarBlocks[i]));
+ return r;
+ }
+ }
+ return 0;
+ #else
+ return (JsVarRef)(1 + (var - jsVars));
+ #endif
+#endif
+}
+
+/// Lock this reference and return a pointer - UNSAFE for null refs
+JsVar *jsvLock(JsVarRef ref) {
+ JsVar *var = jsvGetAddressOf(ref);
+ //var->locks++;
+ assert(jsvGetLocks(var) < JSV_LOCK_MAX);
+ var->flags += JSV_LOCK_ONE;
+#ifdef DEBUG
+ if (jsvGetLocks(var)==0) {
+ jsError("Too many locks to Variable!");
+ //jsPrint("Var #");jsPrintInt(ref);jsPrint("\n");
+ }
+#endif
+ return var;
+}
+
+/// Lock this pointer and return a pointer - UNSAFE for null pointer
+JsVar *jsvLockAgain(JsVar *var) {
+ assert(var);
+ assert(jsvGetLocks(var) < JSV_LOCK_MAX);
+ var->flags += JSV_LOCK_ONE;
+#ifdef DEBUG
+ if (var->locks==0) {
+ jsError("Too many locks to Variable!");
+ //jsPrint("Var #");jsPrintInt(ref);jsPrint("\n");
+ }
+#endif
+ return var;
+}
+
+/// Unlock this variable - this is SAFE for null variables
+void jsvUnLock(JsVar *var) {
+ if (!var) return;
+ assert(jsvGetLocks(var)>0);
+ var->flags -= JSV_LOCK_ONE;
+ /* if we know we're free, then we can just free
+ * this variable right now. Loops of variables
+ * are handled by the Garbage Collector.
+ * Note: we check var->refs first as it is fastest and most likely to be false */
+ if (var->refs == 0 && jsvHasRef(var) && jsvGetLocks(var) == 0 && (var->flags&JSV_VARTYPEMASK)!=JSV_UNUSED) {
+ jsvFreePtr(var);
+ }
+}
+
+/// Reference - set this variable as used by something
+JsVar *jsvRef(JsVar *v) {
+ assert(v && jsvHasRef(v));
+ v->refs++;
+ return v;
+}
+
+/// Unreference - set this variable as not used by anything
+void jsvUnRef(JsVar *var) {
+ assert(var && var->refs>0 && jsvHasRef(var));
+ var->refs--;
+ // locks are never 0 here, so why bother checking!
+ assert(jsvGetLocks(var)>0);
+}
+
+/// Helper fn, Reference - set this variable as used by something
+JsVarRef jsvRefRef(JsVarRef ref) {
+ JsVar *v;
+ assert(ref);
+ v = jsvLock(ref);
+ assert(!jsvIsStringExt(v));
+ jsvRef(v);
+ jsvUnLock(v);
+ return ref;
+}
+
+/// Helper fn, Unreference - set this variable as not used by anything
+JsVarRef jsvUnRefRef(JsVarRef ref) {
+ JsVar *v;
+ assert(ref);
+ v = jsvLock(ref);
+ assert(!jsvIsStringExt(v));
+ jsvUnRef(v);
+ jsvUnLock(v);
+ return 0;
+}
+
+JsVar *jsvNewFromString(const char *str) {
+ // Create a var
+ JsVar *first = jsvNewWithFlags(JSV_STRING);
+ if (!first) {
+ jsWarn("Unable to create string as not enough memory");
+ return 0;
+ }
+ // Now we copy the string, but keep creating new jsVars if we go
+ // over the end
+ JsVar *var = jsvLockAgain(first);
+ while (*str) {
+ // copy data in
+ size_t i, l = jsvGetMaxCharactersInVar(var);
+ for (i=0;i<l && *str;i++)
+ var->varData.str[i] = *(str++);
+ // might as well shove a zero terminator on it if we can
+ if (i<l) var->varData.str[i]=0;
+ // we've stopped if the string was empty
+ jsvSetCharactersInVar(var, i);
+
+ // if there is still some left, it's because we filled up our var...
+ // make a new one, link it in, and unlock the old one.
+ if (*str) {
+ JsVar *next = jsvNewWithFlags(JSV_STRING_EXT);
+ if (!next) {
+ jsWarn("Truncating string as not enough memory");
+ jsvUnLock(var);
+ return first;
+ }
+ // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner)
+ var->lastChild = jsvGetRef(next);
+ jsvUnLock(var);
+ var = next;
+ }
+ }
+ jsvUnLock(var);
+ // return
+ return first;
+}
+
+JsVar *jsvNewStringOfLength(unsigned int byteLength) {
+ // Create a var
+ JsVar *first = jsvNewWithFlags(JSV_STRING);
+ if (!first) {
+ jsWarn("Unable to create string as not enough memory");
+ return 0;
+ }
+ // Now zero the string, but keep creating new jsVars if we go
+ // over the end
+ JsVar *var = jsvLockAgain(first);
+ while (byteLength>0) {
+ // copy data in
+ size_t i, l = jsvGetMaxCharactersInVar(var);
+ for (i=0;i<l && byteLength>0;i++,byteLength--)
+ var->varData.str[i] = 0;
+ // might as well shove a zero terminator on it if we can
+ if (i<l) var->varData.str[i]=0;
+ // we've stopped if the string was empty
+ jsvSetCharactersInVar(var, i);
+
+ // if there is still some left, it's because we filled up our var...
+ // make a new one, link it in, and unlock the old one.
+ if (byteLength>0) {
+ JsVar *next = jsvNewWithFlags(JSV_STRING_EXT);
+ if (!next) {
+ jsWarn("Truncating string as not enough memory");
+ jsvUnLock(var);
+ return first;
+ }
+ // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner)
+ var->lastChild = jsvGetRef(next);
+ jsvUnLock(var);
+ var = next;
+ }
+ }
+ jsvUnLock(var);
+ // return
+ return first;
+}
+
+JsVar *jsvNewFromLexer(struct JsLex *lex, JslCharPos charFrom, JslCharPos charTo) {
+ // Create a var
+ JsVar *var = jsvNewFromEmptyString();
+ if (!var) { // out of memory
+ return 0;
+ }
+
+ jsvAppendStringVar(var, lex->sourceVar, charFrom, (JslCharPos)charTo-charFrom);
+ return var;
+}
+
+JsVar *jsvNewWithFlags(JsVarFlags flags) {
+ JsVar *var = jsvNew();
+ if (!var) return 0; // no memory
+ var->flags = (var->flags&(JsVarFlags)(~JSV_VARTYPEMASK)) | (flags&(JsVarFlags)(~JSV_LOCK_MASK));
+ return var;
+}
+JsVar *jsvNewFromInteger(JsVarInt value) {
+ JsVar *var = jsvNewWithFlags(JSV_INTEGER);
+ if (!var) return 0; // no memory
+ var->varData.integer = value;
+ return var;
+}
+JsVar *jsvNewFromBool(bool value) {
+ JsVar *var = jsvNewWithFlags(JSV_BOOLEAN);
+ if (!var) return 0; // no memory
+ var->varData.integer = value ? 1 : 0;
+ return var;
+}
+JsVar *jsvNewFromFloat(JsVarFloat value) {
+ JsVar *var = jsvNewWithFlags(JSV_FLOAT);
+ if (!var) return 0; // no memory
+ var->varData.floating = value;
+ return var;
+}
+JsVar *jsvMakeIntoVariableName(JsVar *var, JsVar *valueOrZero) {
+ if (!var) return 0;
+ assert(var->refs==0); // make sure it's unused
+ var->flags |= JSV_NAME;
+ if (valueOrZero)
+ var->firstChild = jsvGetRef(jsvRef(valueOrZero));
+ return var;
+}
+
+JsVar *jsvNewFromPin(int pin) {
+ JsVar *v = jsvNewFromInteger((JsVarInt)pin);
+ if (v) {
+ v->flags = (JsVarFlags)((v->flags & ~JSV_VARTYPEMASK) | JSV_PIN);
+ }
+ return v;
+}
+
+bool jsvIsBasicVarEqual(JsVar *a, JsVar *b) {
+ // quick checks
+ if (a==b) return true;
+ if (!a || !b) return false; // one of them is undefined
+ // OPT: would this be useful as compare instead?
+ assert(jsvIsBasic(a) && jsvIsBasic(b));
+ if (jsvIsNumeric(a) && jsvIsNumeric(b)) {
+ if (jsvIsInt(a)) {
+ if (jsvIsInt(b)) {
+ return a->varData.integer == b->varData.integer;
+ } else {
+ assert(jsvIsFloat(b));
+ return a->varData.integer == b->varData.floating;
+ }
+ } else {
+ assert(jsvIsFloat(a));
+ if (jsvIsInt(b)) {
+ return a->varData.floating == b->varData.integer;
+ } else {
+ assert(jsvIsFloat(b));
+ return a->varData.floating == b->varData.floating;
+ }
+ }
+ } else if (jsvIsString(a) && jsvIsString(b)) {
+ JsvStringIterator ita, itb;
+ jsvStringIteratorNew(&ita, a, 0);
+ jsvStringIteratorNew(&itb, b, 0);
+ while (true) {
+ char a = jsvStringIteratorGetChar(&ita);
+ char b = jsvStringIteratorGetChar(&itb);
+ if (a != b) {
+ jsvStringIteratorFree(&ita);
+ jsvStringIteratorFree(&itb);
+ return false;
+ }
+ if (!a) { // equal, but end of string
+ jsvStringIteratorFree(&ita);
+ jsvStringIteratorFree(&itb);
+ return true;
+ }
+ jsvStringIteratorNext(&ita);
+ jsvStringIteratorNext(&itb);
+ }
+ // we never get here
+ return false; // make compiler happy
+ } else {
+ //TODO: are there any other combinations we should check here?? String v int?
+ return false;
+ }
+}
+
+bool jsvIsEqual(JsVar *a, JsVar *b) {
+ if (jsvIsBasic(a) && jsvIsBasic(b))
+ return jsvIsBasicVarEqual(a,b);
+ return jsvGetRef(a)==jsvGetRef(b);
+}
+
+/// Get a const string representing this variable - if we can. Otherwise return 0
+const char *jsvGetConstString(const JsVar *v) {
+ if (jsvIsUndefined(v)) {
+ return "undefined";
+ } else if (jsvIsNull(v)) {
+ return "null";
+ } else if (jsvIsBoolean(v)) {
+ return jsvGetBool(v) ? "true" : "false";
+ } else if (jsvIsRoot(v)) {
+ return "[object Hardware]";
+ } else if (jsvIsObject(v)) {
+ return "[object Object]";
+ }
+ return 0;
+}
+
+/// Return the 'type' of the JS variable (eg. JS's typeof operator)
+const char *jsvGetTypeOf(const JsVar *v) {
+ if (jsvIsNull(v)) return "object";
+ if (jsvIsUndefined(v)) return "undefined";
+ if (jsvIsFunction(v)) return "function";
+ if (jsvIsObject(v) || jsvIsArray(v)) return "object";
+ if (jsvIsString(v)) return "string";
+ if (jsvIsBoolean(v)) return "boolean";
+ if (jsvIsNumeric(v)) return "number";
+ return "?";
+}
+
+/// Save this var as a string to the given buffer, and return how long it was (return val doesn't include terminating 0)
+size_t jsvGetString(const JsVar *v, char *str, size_t len) {
+ const char *s = jsvGetConstString(v);
+ if (s) {
+ strncpy(str, s, len);
+ return strlen(s);
+ } else if (jsvIsInt(v)) {
+ itoa(v->varData.integer, str, 10);
+ return strlen(str);
+ } else if (jsvIsFloat(v)) {
+ ftoa(v->varData.floating, str);
+ return strlen(str);
+ } else if (jsvHasCharacterData(v)) {
+ if (jsvIsStringExt(v))
+ jsErrorInternal("Calling jsvGetString on a JSV_STRING_EXT");
+ size_t l = len;
+ JsvStringIterator it;
+ jsvStringIteratorNewConst(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ if (l--<=1) {
+ *str = 0;
+ jsWarn("jsvGetString overflowed\n");
+ jsvStringIteratorFree(&it);
+ return len;
+ }
+ *(str++) = jsvStringIteratorGetChar(&it);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ *str = 0;
+ return len-l;
+ } else {
+ // Try and get as a JsVar string, and try again
+ JsVar *stringVar = jsvAsString((JsVar*)v, false); // we know we're casting to non-const here
+ if (stringVar) {
+ size_t l = jsvGetString(stringVar, str, len); // call again - but this time with converted var
+ jsvUnLock(stringVar);
+ return l;
+ } else {
+ strncpy(str, "", len);
+ jsErrorInternal("Variable type cannot be converted to string");
+ return 0;
+ }
+ }
+}
+
+/// Set the Data in this string. This must JUST overwrite - not extend or shrink
+void jsvSetString(JsVar *v, char *str, size_t len) {
+ assert(jsvHasCharacterData(v));
+ assert(len == jsvGetStringLength(v));
+
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ size_t i;
+ for (i=0;i<len;i++) {
+ jsvStringIteratorSetChar(&it, str[i]);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+}
+
+/** If var is a string, lock and return it, else
+ * create a new string. unlockVar means this will auto-unlock 'var' */
+JsVar *jsvAsString(JsVar *v, bool unlockVar) {
+ JsVar *str = 0;
+ // If it is string-ish, but not quite a string, copy it
+ if (jsvHasCharacterData(v) && jsvIsName(v)) {
+ str = jsvNewFromEmptyString();
+ if (str) jsvAppendStringVarComplete(str,v);
+ } else if (jsvIsString(v)) { // If it is a string - just return a reference
+ str = jsvLockAgain(v);
+ } else {
+ const char *constChar = jsvGetConstString(v);
+ if (constChar) {
+ // if we could get this as a simple const char, do that..
+ str = jsvNewFromString(constChar);
+ } else if (jsvIsPin(v)) {
+ char buf[8];
+ jshGetPinString(buf, (Pin)v->varData.integer);
+ str = jsvNewFromString(buf);
+ } else if (jsvIsInt(v)) {
+ char buf[JS_NUMBER_BUFFER_SIZE];
+ itoa(v->varData.integer, buf, 10);
+ str = jsvNewFromString(buf);
+ } else if (jsvIsFloat(v)) {
+ char buf[JS_NUMBER_BUFFER_SIZE];
+ ftoa(v->varData.floating, buf);
+ str = jsvNewFromString(buf);
+ } else if (jsvIsArray(v) || jsvIsArrayBuffer(v)) {
+ JsVar *filler = jsvNewFromString(",");
+ str = jsvArrayJoin(v, filler);
+ jsvUnLock(filler);
+ } else if (jsvIsFunction(v)) {
+ str = jsvNewFromEmptyString();
+ if (str) jsfGetJSON(v, str);
+ } else {
+ jsErrorInternal("Variable type cannot be converted to string");
+ str = 0;
+ }
+ }
+
+ if (unlockVar) jsvUnLock(v);
+ return str;
+}
+
+size_t jsvGetStringLength(JsVar *v) {
+ size_t strLength = 0;
+ JsVar *var = v;
+ JsVarRef ref = 0;
+
+ if (!jsvHasCharacterData(v)) return 0;
+
+ while (var) {
+ JsVarRef refNext = var->lastChild;
+ strLength += jsvGetCharactersInVar(var);
+
+ // Go to next
+ if (ref) jsvUnLock(var); // note use of if (ref), not var
+ ref = refNext;
+ var = ref ? jsvLock(ref) : 0;
+ }
+ if (ref) jsvUnLock(var); // note use of if (ref), not var
+ return strLength;
+}
+
+// IN A STRING get the number of lines in the string (min=1)
+int jsvGetLinesInString(JsVar *v) {
+ int lines = 1;
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ if (jsvStringIteratorGetChar(&it)=='\n') lines++;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return lines;
+}
+
+// IN A STRING Get the number of characters on a line - lines start at 1
+int jsvGetCharsOnLine(JsVar *v, int line) {
+ int currentLine = 1;
+ int chars = 0;
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ if (jsvStringIteratorGetChar(&it)=='\n') {
+ currentLine++;
+ if (currentLine > line) break;
+ } else if (currentLine==line) chars++;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return chars;
+}
+
+// IN A STRING, get the line and column of the given character. Both values must be non-null
+void jsvGetLineAndCol(JsVar *v, int charIdx, int* line, int *col) {
+ int x = 1;
+ int y = 1;
+ int n = 0;
+ assert(line && col);
+
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ if (n==charIdx) {
+ jsvStringIteratorFree(&it);
+ *line = y;
+ *col = x;
+ return;
+ }
+ x++;
+ if (ch=='\n') {
+ x=1; y++;
+ }
+ n++;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ // uh-oh - not found
+ *line = y;
+ *col = x;
+}
+
+// IN A STRING, get a character index from a line and column
+int jsvGetIndexFromLineAndCol(JsVar *v, int line, int col) {
+ int x = 1;
+ int y = 1;
+ int n = 0;
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ if ((y==line && x>=col) || y>line) {
+ jsvStringIteratorFree(&it);
+ return (y>line) ? (n-1) : n;
+ }
+ x++;
+ if (ch=='\n') {
+ x=1; y++;
+ }
+ n++;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return n;
+}
+
+void jsvAppendString(JsVar *var, const char *str) {
+ assert(jsvIsString(var));
+ JsVar *block = jsvLockAgain(var);
+ // Find the block at end of the string...
+ while (block->lastChild) {
+ JsVarRef next = block->lastChild;
+ jsvUnLock(block);
+ block = jsvLock(next);
+ }
+ // find how full the block is
+ size_t blockChars = jsvGetCharactersInVar(block);
+ // now start appending
+ while (*str) {
+ // copy data in
+ size_t i, l=jsvGetMaxCharactersInVar(block);
+ for (i=blockChars;i<l && *str;i++) {
+ block->varData.str[i] = *(str++);
+ }
+ jsvSetCharactersInVar(block, i);
+ // if there is still some left, it's because we filled up our var...
+ // make a new one, link it in, and unlock the old one.
+ if (*str) {
+ JsVar *next = jsvNewWithFlags(JSV_STRING_EXT);
+ if (!next) break;
+ // 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
+ }
+ }
+ jsvUnLock(block);
+}
+
+void jsvAppendStringBuf(JsVar *var, const char *str, int length) {
+ assert(jsvIsString(var));
+ JsVar *block = jsvLockAgain(var);
+ // Find the block at end of the string...
+ while (block->lastChild) {
+ JsVarRef next = block->lastChild;
+ jsvUnLock(block);
+ block = jsvLock(next);
+ }
+ // find how full the block is
+ size_t blockChars = jsvGetCharactersInVar(block);
+ // now start appending
+ while (length) {
+ // copy data in
+ size_t i, l=jsvGetMaxCharactersInVar(block);
+ for (i=blockChars;i<l && length;i++) {
+ block->varData.str[i] = *(str++);
+ length--;
+ }
+ jsvSetCharactersInVar(block, i);
+ // if there is still some left, it's because we filled up our var...
+ // make a new one, link it in, and unlock the old one.
+ if (length) {
+ JsVar *next = jsvNewWithFlags(JSV_STRING_EXT);
+ if (!next) break;
+ // 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
+ }
+ }
+ jsvUnLock(block);
+}
+
+static void _jsvAppendPrintf(const char *str, void *user_data) {
+ while (*str)
+ jsvStringIteratorAppend((JsvStringIterator *)user_data, *(str++));
+}
+
+void jsvAppendPrintf(JsVar *var, const char *fmt, ...) {
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, var, 0);
+ jsvStringIteratorGotoEnd(&it);
+
+ va_list argp;
+ va_start(argp, fmt);
+ vcbprintf((vcbprintf_callback)&_jsvAppendPrintf,&it, fmt, argp);
+ va_end(argp);
+
+ jsvStringIteratorFree(&it);
+}
+
+/** Append str to var. Both must be strings. stridx = start char or str, maxLength = max number of characters (can be JSVAPPENDSTRINGVAR_MAXLENGTH).
+ * stridx can be negative to go from end of string */
+void jsvAppendStringVar(JsVar *var, const JsVar *str, int stridx, int maxLength) {
+ JsVar *block = jsvLockAgain(var);
+ assert(jsvIsString(var));
+ // Find the block at end of the string...
+ while (block->lastChild) {
+ JsVarRef next = block->lastChild;
+ jsvUnLock(block);
+ block = jsvLock(next);
+ }
+ // find how full the block is
+ size_t blockChars = jsvGetCharactersInVar(block);
+ // now start appending
+ JsvStringIterator it;
+ jsvStringIteratorNewConst(&it, str, stridx);
+ 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);
+}
+
+/** Append all of str to var. Both must be strings. */
+void jsvAppendStringVarComplete(JsVar *var, const JsVar *str) {
+ jsvAppendStringVar(var, str, 0, JSVAPPENDSTRINGVAR_MAXLENGTH);
+}
+
+char jsvGetCharInString(JsVar *v, int idx) {
+ if (!jsvIsString(v)) return 0;
+ if (idx<0) idx += (int)jsvGetStringLength(v); // <0 goes from end of string
+ if (idx<0) return 0;
+
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, v, idx);
+ char ch = jsvStringIteratorGetChar(&it);
+ jsvStringIteratorFree(&it);
+ return ch;
+}
+
+/** Does this string contain only Numeric characters? */
+bool jsvIsStringNumeric(const JsVar *var) {
+ assert(jsvIsString(var));
+ JsvStringIterator it;
+ jsvStringIteratorNewConst(&it, var, 0); // we know it's non const
+ int chars = 0;
+ while (jsvStringIteratorHasChar(&it)) {
+ chars++;
+ char ch = jsvStringIteratorGetChar(&it);
+ if (!isNumeric(ch)) { // FIXME: should check for non-integer values (floating point?)
+ jsvStringIteratorFree(&it);
+ return false;
+ }
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return chars>0;
+}
+
+/** Does this string contain only Numeric characters? This is for arrays
+ * and makes the assertion that int_to_string(string_to_int(var))==var */
+bool jsvIsStringNumericStrict(const JsVar *var) {
+ assert(jsvIsString(var));
+ JsvStringIterator it;
+ jsvStringIteratorNewConst(&it, var, 0); // we know it's non const
+ bool hadNonZero = false;
+ bool hasLeadingZero = false;
+ int chars = 0;
+ while (jsvStringIteratorHasChar(&it)) {
+ chars++;
+ char ch = jsvStringIteratorGetChar(&it);
+ if (!isNumeric(ch)) {
+ // test for leading zero ensures int_to_string(string_to_int(var))==var
+ jsvStringIteratorFree(&it);
+ return false;
+ }
+ if (!hadNonZero && ch=='0') hasLeadingZero=true;
+ if (ch!='0') hadNonZero=true;
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return chars>0 && (!hasLeadingZero || chars==1);
+}
+
+JsVarInt jsvGetInteger(const JsVar *v) {
+ if (!v) return 0; // undefined
+ /* strtol understands about hex and octal */
+ if (jsvIsInt(v) || jsvIsBoolean(v) || jsvIsPin(v) || jsvIsArrayBufferName(v)) return v->varData.integer;
+ if (jsvIsNull(v)) return 0;
+ if (jsvIsUndefined(v)) return 0;
+ if (jsvIsFloat(v)) return (JsVarInt)v->varData.floating;
+ if (jsvIsString(v) && jsvIsStringNumeric(v)) {
+ char buf[32];
+ jsvGetString(v, buf, sizeof(buf));
+ return stringToInt(buf);
+ }
+ return 0;
+}
+
+void jsvSetInteger(JsVar *v, JsVarInt value) {
+ assert(jsvIsInt(v));
+ v->varData.integer = value;
+}
+
+bool jsvGetBool(const JsVar *v) {
+ return jsvGetInteger(v)!=0;
+}
+
+JsVarFloat jsvGetFloat(const JsVar *v) {
+ if (!v) return NAN; // undefined
+ if (jsvIsFloat(v)) return v->varData.floating;
+ if (jsvIsInt(v)) return (JsVarFloat)v->varData.integer;
+ if (jsvIsNull(v)) return 0;
+ if (jsvIsString(v) && jsvIsStringNumeric(v)) {
+ char buf[32];
+ jsvGetString(v, buf, sizeof(buf));
+ return stringToFloat(buf);
+ }
+ return NAN;
+}
+
+/// Convert the given variable to a number
+JsVar *jsvAsNumber(JsVar *var) {
+ if (jsvIsInt(var) || jsvIsFloat(var)) return jsvLockAgain(var);
+ if (jsvIsBoolean(var) || jsvIsPin(var)) return jsvNewFromInteger(var->varData.integer);
+ return jsvNewFromFloat(jsvGetFloat(var));
+}
+
+#ifdef SAVE_ON_FLASH
+JsVarInt jsvGetIntegerAndUnLock(JsVar *v) { return _jsvGetIntegerAndUnLock(v); }
+JsVarFloat jsvGetFloatAndUnLock(JsVar *v) { return _jsvGetFloatAndUnLock(v); }
+bool jsvGetBoolAndUnLock(JsVar *v) { return _jsvGetBoolAndUnLock(v); }
+#endif
+
+/** Get the item at the given location in the array buffer and return the result */
+size_t jsvGetArrayBufferLength(JsVar *arrayBuffer) {
+ assert(jsvIsArrayBuffer(arrayBuffer));
+ return arrayBuffer->varData.arraybuffer.length;
+}
+
+/** Get the item at the given location in the array buffer and return the result */
+JsVar *jsvArrayBufferGet(JsVar *arrayBuffer, JsVarInt idx) {
+ JsvArrayBufferIterator it;
+ jsvArrayBufferIteratorNew(&it, arrayBuffer, idx);
+ JsVar *v = jsvArrayBufferIteratorGetValue(&it);
+ jsvArrayBufferIteratorFree(&it);
+ return v;
+}
+
+/** Set the item at the given location in the array buffer */
+void jsvArrayBufferSet(JsVar *arrayBuffer, JsVarInt idx, JsVar *value) {
+ JsvArrayBufferIterator it;
+ jsvArrayBufferIteratorNew(&it, arrayBuffer, idx);
+ jsvArrayBufferIteratorSetValue(&it, value);
+ jsvArrayBufferIteratorFree(&it);
+}
+
+
+/** Given an integer name that points to an arraybuffer or an arraybufferview, evaluate it and return the result */
+JsVar *jsvArrayBufferGetFromName(JsVar *name) {
+ assert(jsvIsArrayBufferName(name));
+ JsVarInt idx = jsvGetInteger(name);
+ JsVar *arrayBuffer = jsvLock(name->firstChild);
+ JsVar *value = jsvArrayBufferGet(arrayBuffer, idx);
+ jsvUnLock(arrayBuffer);
+ return value;
+}
+
+/** If a is a name skip it and go to what it points to - and so on.
+ * ALWAYS locks - so must unlock what it returns. It MAY
+ * return 0. */
+JsVar *jsvSkipName(JsVar *a) {
+ JsVar *pa = a;
+ if (!a) return 0;
+ if (jsvIsArrayBufferName(pa)) return jsvArrayBufferGetFromName(pa);
+ while (jsvIsName(pa)) {
+ JsVarRef n = pa->firstChild;
+ if (pa!=a) jsvUnLock(pa);
+ if (!n) return 0;
+ pa = jsvLock(n);
+ }
+ if (pa==a) jsvLockAgain(pa);
+ return pa;
+}
+
+/** If a is a name skip it and go to what it points to.
+ * ALWAYS locks - so must unlock what it returns. It MAY
+ * return 0. */
+JsVar *jsvSkipOneName(JsVar *a) {
+ JsVar *pa = a;
+ if (!a) return 0;
+ if (jsvIsArrayBufferName(pa)) return jsvArrayBufferGetFromName(pa);
+ if (jsvIsName(pa)) {
+ JsVarRef n = pa->firstChild;
+ if (pa!=a) jsvUnLock(pa);
+ if (!n) return 0;
+ pa = jsvLock(n);
+ }
+ if (pa==a) jsvLockAgain(pa);
+ return pa;
+}
+
+/** If a is a's child is a name skip it and go to what it points to.
+ * ALWAYS locks - so must unlock what it returns. */
+JsVar *jsvSkipToLastName(JsVar *a) {
+ assert(jsvIsName(a));
+ a = jsvLockAgain(a);
+ while (true) {
+ if (!a->firstChild) return a;
+ JsVar *child = jsvLock(a->firstChild);
+ if (jsvIsName(child)) {
+ jsvUnLock(a);
+ a = child;
+ } else {
+ jsvUnLock(child);
+ return a;
+ }
+ }
+ return 0; // not called
+}
+
+
+// Also see jsvIsBasicVarEqual
+bool jsvIsStringEqual(JsVar *var, const char *str) {
+ if (!jsvHasCharacterData(var)) {
+ assert(jsvIsBasic(var));
+ return 0; // not a string so not equal!
+ }
+
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, var, 0);
+ while (jsvStringIteratorHasChar(&it) && *str) {
+ if (jsvStringIteratorGetChar(&it) != *str) {
+ jsvStringIteratorFree(&it);
+ return false;
+ }
+ str++;
+ jsvStringIteratorNext(&it);
+ }
+ bool eq = jsvStringIteratorGetChar(&it)==*str; // should both be 0 if equal
+ jsvStringIteratorFree(&it);
+ return eq;
+}
+
+
+/** Compare 2 strings, starting from the given character positions. equalAtEndOfString means that
+ * if one of the strings ends, we treat them as equal.
+ * For a basic strcmp, do: jsvCompareString(a,b,0,0,false)
+ * */
+int jsvCompareString(JsVar *va, JsVar *vb, int starta, int startb, bool equalAtEndOfString) {
+ JsvStringIterator ita, itb;
+ jsvStringIteratorNew(&ita, va, starta);
+ jsvStringIteratorNew(&itb, vb, startb);
+ // step to first positions
+ while (true) {
+ int ca = jsvStringIteratorGetCharOrMinusOne(&ita);
+ int cb = jsvStringIteratorGetCharOrMinusOne(&itb);
+
+ if (ca != cb) {
+ jsvStringIteratorFree(&ita);
+ jsvStringIteratorFree(&itb);
+ if ((ca<0 || cb<0) && equalAtEndOfString) return 0;
+ return ca - cb;
+ }
+ if (ca < 0) { // both equal, but end of string
+ jsvStringIteratorFree(&ita);
+ jsvStringIteratorFree(&itb);
+ return 0;
+ }
+ jsvStringIteratorNext(&ita);
+ jsvStringIteratorNext(&itb);
+ }
+ // never get here, but the compiler warns...
+ return true;
+}
+
+/** Compare 2 integers, >0 if va>vb, <0 if va<vb. If compared with a non-integer, that gets put later */
+int jsvCompareInteger(JsVar *va, JsVar *vb) {
+ if (jsvIsInt(va) && jsvIsInt(vb))
+ return (int)(jsvGetInteger(va) - jsvGetInteger(vb));
+ else if (jsvIsInt(va))
+ return -1;
+ else if (jsvIsInt(vb))
+ return 1;
+ else
+ return 0;
+}
+
+/** Copy only a name, not what it points to. ALTHOUGH the link to what it points to is maintained unless linkChildren=false
+ If keepAsName==false, this will be converted into a normal variable */
+JsVar *jsvCopyNameOnly(JsVar *src, bool linkChildren, bool keepAsName) {
+ assert(jsvIsName(src));
+ JsVarFlags flags = src->flags;
+ if (!keepAsName) flags &= (JsVarFlags)~JSV_NAME; // make sure this is NOT a name
+ JsVar *dst = jsvNewWithFlags(flags);
+ if (!dst) return 0; // out of memory
+
+ memcpy(&dst->varData, &src->varData, sizeof(JsVarData));
+
+ dst->lastChild = 0;
+ dst->firstChild = 0;
+ dst->prevSibling = 0;
+ dst->nextSibling = 0;
+ // Copy LINK of what it points to
+ if (linkChildren && src->firstChild) {
+ dst->firstChild = jsvRefRef(src->firstChild);
+ }
+ // Copy extra string data if there was any
+ if (jsvHasStringExt(src)) {
+ // copy extra bits of string if there were any
+ if (src->lastChild) {
+ JsVar *child = jsvLock(src->lastChild);
+ JsVar *childCopy = jsvCopy(child);
+ if (childCopy) { // could be out of memory
+ dst->lastChild = jsvGetRef(childCopy); // no ref for stringext
+ jsvUnLock(childCopy);
+ }
+ jsvUnLock(child);
+ }
+ } else {
+ assert(jsvIsBasic(src)); // in case we missed something!
+ }
+ return dst;
+}
+
+JsVar *jsvCopy(JsVar *src) {
+ JsVar *dst = jsvNewWithFlags(src->flags);
+ if (!dst) return 0; // out of memory
+ if (!jsvIsStringExt(src)) {
+ memcpy(&dst->varData, &src->varData, sizeof(JsVarData));
+ dst->lastChild = 0;
+ dst->firstChild = 0;
+ dst->prevSibling = 0;
+ dst->nextSibling = 0;
+ } else {
+ // stringexts use the extra pointers after varData to store characters
+ // see jsvGetMaxCharactersInVar
+ memcpy(&dst->varData, &src->varData, JSVAR_DATA_STRING_MAX_LEN);
+ dst->lastChild = 0;
+ }
+
+ // Copy what names point to
+ if (jsvIsName(src)) {
+ if (src->firstChild) {
+ JsVar *child = jsvLock(src->firstChild);
+ JsVar *childCopy = jsvRef(jsvCopy(child));
+ jsvUnLock(child);
+ if (childCopy) { // could have been out of memory
+ dst->firstChild = jsvGetRef(childCopy);
+ jsvUnLock(childCopy);
+ }
+ }
+ }
+
+ if (jsvHasStringExt(src)) {
+ // copy extra bits of string if there were any
+ if (src->lastChild) {
+ JsVar *child = jsvLock(src->lastChild);
+ JsVar *childCopy = jsvCopy(child);
+ if (childCopy) {// could be out of memory
+ dst->lastChild = jsvGetRef(childCopy); // no ref for stringext
+ jsvUnLock(childCopy);
+ }
+ jsvUnLock(child);
+ }
+ } else if (jsvHasChildren(src)) {
+ // Copy children..
+ JsVarRef vr;
+ vr = src->firstChild;
+ while (vr) {
+ JsVar *name = jsvLock(vr);
+ JsVar *child = jsvCopyNameOnly(name, true/*link children*/, true/*keep as name*/); // NO DEEP COPY!
+ if (child) { // could have been out of memory
+ jsvAddName(dst, child);
+ jsvUnLock(child);
+ }
+ vr = name->nextSibling;
+ jsvUnLock(name);
+ }
+ } else {
+ assert(jsvIsBasic(src)); // in case we missed something!
+ }
+
+ return dst;
+}
+
+void jsvAddName(JsVar *parent, JsVar *namedChild) {
+ namedChild = jsvRef(namedChild); // ref here VERY important as adding to structure!
+ assert(jsvIsName(namedChild));
+
+ if (parent->lastChild) { // we have children already
+ JsVar *insertAfter = jsvLock(parent->lastChild);
+ if (jsvIsArray(parent)) {
+ // we must insert in order - so step back until we get the right place
+ while (insertAfter && jsvCompareInteger(namedChild, insertAfter)<0) {
+ JsVarRef prev = insertAfter->prevSibling;
+ jsvUnLock(insertAfter);
+ insertAfter = prev ? jsvLock(prev) : 0;
+ }
+ }
+
+ if (insertAfter) {
+ if (insertAfter->nextSibling) {
+ // great, we're in the middle...
+ JsVar *insertBefore = jsvLock(insertAfter->nextSibling);
+ insertBefore->prevSibling = jsvGetRef(namedChild);
+ namedChild->nextSibling = jsvGetRef(insertBefore);
+ jsvUnLock(insertBefore);
+ } else {
+ // We're at the end - just set up the parent
+ parent->lastChild = jsvGetRef(namedChild);
+ }
+ insertAfter->nextSibling = jsvGetRef(namedChild);
+ namedChild->prevSibling = jsvGetRef(insertAfter);
+ jsvUnLock(insertAfter);
+ } else { // Insert right at the beginning of the array
+ // Link 2 children together
+ JsVar *firstChild = jsvLock(parent->firstChild);
+ firstChild->prevSibling = jsvGetRef(namedChild);
+ jsvUnLock(firstChild);
+
+ namedChild->nextSibling = parent->firstChild;
+ // finally set the new child as the first one
+ parent->firstChild = jsvGetRef(namedChild);
+ }
+ } else { // we have no children - just add it
+ parent->firstChild = parent->lastChild = jsvGetRef(namedChild);
+
+ }
+}
+
+JsVar *jsvAddNamedChild(JsVar *parent, JsVar *child, const char *name) {
+ JsVar *namedChild = jsvMakeIntoVariableName(jsvNewFromString(name), child);
+ if (!namedChild) return 0; // Out of memory
+ jsvAddName(parent, namedChild);
+ return namedChild;
+}
+
+JsVar *jsvSetNamedChild(JsVar *parent, JsVar *child, const char *name) {
+ JsVar *namedChild = jsvFindChildFromString(parent, name, true);
+ if (namedChild) // could be out of memory
+ return jsvSetValueOfName(namedChild, child);
+ return 0;
+}
+
+JsVar *jsvSetValueOfName(JsVar *name, JsVar *src) {
+ assert(name && jsvIsName(name));
+ assert(name!=src); // no infinite loops!
+ // all is fine, so replace the existing child...
+ /* Existing child may be null in the case of Z = 0 where
+ * we create 'Z' and pass it down to '=' to have the value
+ * filled in (or it may be undefined). */
+ if (name->firstChild) jsvUnRefRef(name->firstChild); // free existing
+ if (src) {
+ // we can link to a name if we want (so can remove the assert!)
+ name->firstChild = jsvGetRef(jsvRef(src));
+ } else
+ name->firstChild = 0;
+ return name;
+}
+
+JsVar *jsvFindChildFromString(JsVar *parent, const char *name, bool addIfNotFound) {
+ /* Pull out first 4 bytes, and ensure that everything
+ * is 0 padded so that we can do a nice speedy check. */
+ char fastCheck[4];
+ fastCheck[0] = name[0];
+ if (name[0]) {
+ fastCheck[1] = name[1];
+ if (name[1]) {
+ fastCheck[2] = name[2];
+ if (name[2]) {
+ fastCheck[3] = name[3];
+ } else {
+ fastCheck[3] = 0;
+ }
+ } else {
+ fastCheck[2] = 0;
+ fastCheck[3] = 0;
+ }
+ } else {
+ fastCheck[1] = 0;
+ fastCheck[2] = 0;
+ fastCheck[3] = 0;
+ }
+
+ assert(jsvHasChildren(parent));
+ JsVarRef childref = parent->firstChild;
+ while (childref) {
+ // Don't Lock here, just use GetAddressOf - to try and speed up the finding
+ // TODO: We can do this now, but when/if we move to cacheing vars, it'll break
+ JsVar *child = jsvGetAddressOf(childref);
+ if (*(int*)fastCheck==*(int*)child->varData.str && // speedy check of first 4 bytes
+ jsvIsStringEqual(child, name)) {
+ // found it! unlock parent but leave child locked
+ return jsvLockAgain(child);
+ }
+ childref = child->nextSibling;
+ }
+
+ JsVar *child = 0;
+ if (addIfNotFound) {
+ child = jsvMakeIntoVariableName(jsvNewFromString(name), 0);
+ if (child) // could be out of memory
+ jsvAddName(parent, child);
+ }
+ return child;
+}
+
+/** Non-recursive finding */
+JsVar *jsvFindChildFromVar(JsVar *parent, JsVar *childName, bool addIfNotFound) {
+ JsVar *child;
+ JsVarRef childref = parent->firstChild;
+
+ while (childref) {
+ child = jsvLock(childref);
+ if (jsvIsBasicVarEqual(child, childName)) {
+ // found it! unlock parent but leave child locked
+ return child;
+ }
+ childref = child->nextSibling;
+ jsvUnLock(child);
+ }
+
+ child = 0;
+ if (addIfNotFound && childName) {
+ if (childName->refs == 0) {
+ // Not reffed - great! let's just use it
+ if (!jsvIsName(childName))
+ childName = jsvMakeIntoVariableName(childName, 0);
+ child = jsvLockAgain(childName);
+ } else { // it was reffed, we must add a new one
+ child = jsvMakeIntoVariableName(jsvCopy(childName), 0);
+ }
+ jsvAddName(parent, child);
+ }
+ return child;
+}
+
+void jsvRemoveChild(JsVar *parent, JsVar *child) {
+ assert(jsvHasChildren(parent));
+ JsVarRef childref = jsvGetRef(child);
+ // unlink from parent
+ if (parent->firstChild == childref)
+ parent->firstChild = child->nextSibling;
+ if (parent->lastChild == childref)
+ parent->lastChild = child->prevSibling;
+ // unlink from child list
+ if (child->prevSibling) {
+ JsVar *v = jsvLock(child->prevSibling);
+ v->nextSibling = child->nextSibling;
+ jsvUnLock(v);
+ }
+ if (child->nextSibling) {
+ JsVar *v = jsvLock(child->nextSibling);
+ v->prevSibling = child->prevSibling;
+ jsvUnLock(v);
+ }
+ child->prevSibling = 0;
+ child->nextSibling = 0;
+
+ jsvUnRef(child);
+}
+
+void jsvRemoveAllChildren(JsVar *parent) {
+ assert(jsvHasChildren(parent));
+ while (parent->firstChild) {
+ JsVar *v = jsvLock(parent->firstChild);
+ jsvRemoveChild(parent, v);
+ jsvUnLock(v);
+ }
+}
+
+/// Get the named child of an object. If createChild!=0 then create the child
+JsVar *jsvObjectGetChild(JsVar *obj, const char *name, JsVarFlags createChild) {
+ if (!obj) return 0;
+ assert(jsvHasChildren(obj));
+ JsVar *childName = jsvFindChildFromString(obj, name, createChild);
+ if (!childName && createChild) {
+ JsVar *child = jsvNewWithFlags(createChild);
+ jsvSetValueOfName(childName, child);
+ jsvUnLock(childName);
+ return child;
+ }
+ if (childName)
+ return jsvSkipNameAndUnLock(childName);
+ return 0;
+}
+
+/// Set the named child of an object, and return the child (so you can choose to unlock it if you want)
+JsVar *jsvObjectSetChild(JsVar *obj, const char *name, JsVar *child) {
+ assert(jsvHasChildren(obj));
+ // child can actually be a name (for instance if it is a named function)
+ JsVar *childName = jsvFindChildFromString(obj, name, true);
+ if (!childName) return 0; // out of memory
+ jsvSetValueOfName(childName, child);
+ jsvUnLock(childName);
+ return child;
+}
+
+int jsvGetChildren(JsVar *v) {
+ //OPT: could length be stored as the value of the array?
+ int children = 0;
+ JsVarRef childref = v->firstChild;
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ children++;
+ childref = child->nextSibling;
+ jsvUnLock(child);
+ }
+ return children;
+}
+
+
+JsVarInt jsvGetArrayLength(JsVar *arr) {
+ JsVarRef childref = arr->lastChild;
+ // Just look at last non-string element!
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ if (jsvIsInt(child)) {
+ JsVarInt lastIdx = jsvGetInteger(child);
+ jsvUnLock(child);
+ return lastIdx+1;
+ }
+ // if not an int, keep going
+ childref = child->prevSibling;
+ jsvUnLock(child);
+ }
+ return 0;
+}
+
+JsVarInt jsvGetLength(JsVar *src) {
+ if (jsvIsArray(src)) {
+ return jsvGetArrayLength(src);
+ } else if (jsvIsArrayBuffer(src)) {
+ return (JsVarInt)jsvGetArrayBufferLength(src);
+ } else if (jsvIsString(src)) {
+ return (JsVarInt)jsvGetStringLength(src);
+ } else if (jsvIsObject(src) || jsvIsFunction(src)) {
+ return jsvGetChildren(src);
+ } else {
+ return 1;
+ }
+}
+
+/** Count the amount of JsVars used. Mostly useful for debugging */
+size_t jsvCountJsVarsUsed(JsVar *v) {
+ size_t count = 1;
+ if (jsvHasChildren(v)) {
+ JsVarRef childref = v->firstChild;
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ count += jsvCountJsVarsUsed(child);
+ childref = child->nextSibling;
+ jsvUnLock(child);
+ }
+ }
+ if (jsvHasCharacterData(v)) {
+ size_t count = 0;
+ JsVarRef childref = v->lastChild;
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ count++;
+ childref = child->lastChild;
+ jsvUnLock(child);
+ }
+ }
+ if (jsvIsName(v) && v->firstChild) {
+ JsVar *child = jsvLock(v->firstChild);
+ count += jsvCountJsVarsUsed(child);
+ jsvUnLock(child);
+ }
+ return count;
+}
+
+
+JsVar *jsvGetArrayItem(JsVar *arr, int index) {
+ JsVarRef childref = arr->firstChild;
+ while (childref) {
+ JsVarInt childIndex;
+ JsVar *child = jsvLock(childref);
+
+ assert(jsvIsInt(child));
+ childIndex = jsvGetInteger(child);
+ if (childIndex == index) {
+ JsVar *item = child->firstChild ? jsvLock(child->firstChild) : 0;
+ jsvUnLock(child);
+ return item;
+ }
+ childref = child->nextSibling;
+ jsvUnLock(child);
+ }
+ return 0; // undefined
+}
+
+/// Get the index of the value in the array (matchExact==use pointer, not equality check)
+JsVar *jsvGetArrayIndexOf(JsVar *arr, JsVar *value, bool matchExact) {
+ JsVarRef indexref;
+ assert(jsvIsArray(arr) || jsvIsObject(arr));
+ indexref = arr->firstChild;
+ while (indexref) {
+ JsVar *childIndex = jsvLock(indexref);
+ assert(jsvIsName(childIndex))
+ if (childIndex->firstChild) {
+ JsVar *childValue = jsvLock(childIndex->firstChild);
+ if ((matchExact && childValue==value) ||
+ (!matchExact && jsvIsBasicVarEqual(childValue, value))) {
+ jsvUnLock(childValue);
+ return childIndex;
+ }
+ jsvUnLock(childValue);
+ } else if (jsvIsUndefined(value))
+ return childIndex; // both are undefined, so we return the index
+ indexref = childIndex->nextSibling;
+ jsvUnLock(childIndex);
+ }
+ return 0; // undefined
+}
+
+/// Adds new elements to the end of an array, and returns the new length. initialValue is the item index when no items are currently in the array.
+JsVarInt jsvArrayPushWithInitialSize(JsVar *arr, JsVar *value, JsVarInt initialValue) {
+ assert(jsvIsArray(arr));
+ JsVarInt index = jsvGetArrayLength(arr);
+ if (index==0) index=initialValue;
+ JsVar *idx = jsvMakeIntoVariableName(jsvNewFromInteger(index), value);
+ if (!idx) {
+ jsWarn("Out of memory while appending to array");
+ return 0;
+ }
+ jsvAddName(arr, idx);
+ jsvUnLock(idx);
+ return index+1; // new size
+}
+
+/// Adds new elements to the end of an array, and returns the new length
+JsVarInt jsvArrayPush(JsVar *arr, JsVar *value) {
+ return jsvArrayPushWithInitialSize(arr, value, 0);
+}
+
+/// Adds a new element to the end of an array, unlocks it, and returns the new length
+JsVarInt jsvArrayPushAndUnLock(JsVar *arr, JsVar *value) {
+ JsVarInt l = jsvArrayPushWithInitialSize(arr, value, 0);
+ jsvUnLock(value);
+ return l;
+}
+
+/// Removes the last element of an array, and returns that element (or 0 if empty). includes the NAME
+JsVar *jsvArrayPop(JsVar *arr) {
+ assert(jsvIsArray(arr));
+ if (arr->lastChild) {
+ JsVar *child = jsvLock(arr->lastChild);
+ if (arr->firstChild == arr->lastChild)
+ arr->firstChild = 0; // if 1 item in array
+ arr->lastChild = child->prevSibling; // unlink from end of array
+ jsvUnRef(child); // as no longer in array
+ if (child->prevSibling) {
+ JsVar *v = jsvLock(child->prevSibling);
+ v->nextSibling = 0;
+ jsvUnLock(v);
+ }
+ child->prevSibling = 0;
+ return child; // and return it
+ } else {
+ // no children!
+ return 0;
+ }
+}
+
+/// Removes the first element of an array, and returns that element (or 0 if empty).
+JsVar *jsvArrayPopFirst(JsVar *arr) {
+ assert(jsvIsArray(arr));
+ if (arr->firstChild) {
+ JsVar *child = jsvLock(arr->firstChild);
+ if (arr->firstChild == arr->lastChild)
+ arr->lastChild = 0; // if 1 item in array
+ arr->firstChild = child->nextSibling; // unlink from end of array
+ jsvUnRef(child); // as no longer in array
+ if (child->nextSibling) {
+ JsVar *v = jsvLock(child->nextSibling);
+ v->prevSibling = 0;
+ jsvUnLock(v);
+ }
+ child->nextSibling = 0;
+ return child; // and return it
+ } else {
+ // no children!
+ return 0;
+ }
+}
+
+/// Get the last element of an array (does not remove, unlike jsvArrayPop), and returns that element (or 0 if empty) includes the NAME
+JsVar *jsvArrayGetLast(JsVar *arr) {
+ assert(jsvIsArray(arr));
+ if (arr->lastChild) {
+ return jsvLock(arr->lastChild);
+ } else { // no children!
+ return 0;
+ }
+}
+
+/// Join all elements of an array together into a string
+JsVar *jsvArrayJoin(JsVar *arr, JsVar *filler) {
+ JsVar *str = jsvNewFromEmptyString();
+ if (!str) return 0; // out of memory
+
+ JsVarInt index = 0;
+ JsvIterator it;
+ jsvIteratorNew(&it, arr);
+ while (jsvIteratorHasElement(&it)) {
+ JsVar *key = jsvIteratorGetKey(&it);
+ if (jsvIsInt(key)) {
+ JsVarInt thisIndex = jsvGetInteger(key);
+ // add the filler
+ if (filler) {
+ while (index<thisIndex) {
+ index++;
+ jsvAppendStringVarComplete(str, filler);
+ }
+ }
+ // add the value
+ JsVar *value = jsvIteratorGetValue(&it);
+ if (value) {
+ JsVar *valueStr = jsvAsString(value, true /* UNLOCK */);
+ if (valueStr) { // could be out of memory
+ jsvAppendStringVarComplete(str, valueStr);
+ jsvUnLock(valueStr);
+ }
+ }
+ }
+ jsvUnLock(key);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ return str;
+}
+
+/// Insert a new element before beforeIndex, DOES NOT UPDATE INDICES
+void jsvArrayInsertBefore(JsVar *arr, JsVar *beforeIndex, JsVar *element) {
+ if (beforeIndex) {
+ JsVar *idxVar = jsvMakeIntoVariableName(jsvNewFromInteger(0), element);
+ if (!idxVar) return; // out of memory
+
+ JsVarRef idxRef = jsvGetRef(jsvRef(idxVar));
+ JsVarRef prev = beforeIndex->prevSibling;
+ if (prev) {
+ JsVar *prevVar = jsvRef(jsvLock(prev));
+ jsvSetInteger(idxVar, jsvGetInteger(prevVar)+1); // update index number
+ prevVar->nextSibling = idxRef;
+ jsvUnLock(prevVar);
+ idxVar->prevSibling = prev;
+ } else {
+ idxVar->prevSibling = 0;
+ arr->firstChild = idxRef;
+ }
+ beforeIndex->prevSibling = idxRef;
+ idxVar->nextSibling = jsvGetRef(jsvRef(beforeIndex));
+ jsvUnLock(idxVar);
+ } else
+ jsvArrayPush(arr, element);
+}
+
+/** Same as jsvMathsOpPtr, but if a or b are a name, skip them
+ * and go to what they point to. */
+JsVar *jsvMathsOpSkipNames(JsVar *a, JsVar *b, int op) {
+ JsVar *pa = jsvSkipName(a);
+ JsVar *pb = jsvSkipName(b);
+ JsVar *res = jsvMathsOp(pa,pb,op);
+ jsvUnLock(pa);
+ jsvUnLock(pb);
+ return res;
+}
+
+
+JsVar *jsvMathsOpError(int op, const char *datatype) {
+ char buf[JS_ERROR_BUF_SIZE];
+ size_t bufpos = 0;
+ strncpy(&buf[bufpos], "Operation ", JS_ERROR_BUF_SIZE-bufpos);
+ bufpos=strlen(buf);
+ jslTokenAsString(op, &buf[bufpos], JS_ERROR_TOKEN_BUF_SIZE-bufpos);
+ bufpos=strlen(buf);
+ strncat(&buf[bufpos], " not supported on the ", JS_ERROR_BUF_SIZE-bufpos);
+ bufpos=strlen(buf);
+ strncat(&buf[bufpos], datatype, JS_ERROR_BUF_SIZE-bufpos);
+ bufpos=strlen(buf);
+ strncat(&buf[bufpos], " datatype", JS_ERROR_BUF_SIZE-bufpos);
+ jsError(buf);
+ return 0;
+}
+
+JsVar *jsvMathsOp(JsVar *a, JsVar *b, int op) {
+ // Type equality check
+ if (op == LEX_TYPEEQUAL || op == LEX_NTYPEEQUAL) {
+ // check type first, then call again to check data
+ bool eql = (a==0) == (b==0);
+ if (a && b) eql = ((a->flags & JSV_VARTYPEMASK) ==
+ (b->flags & JSV_VARTYPEMASK));
+ if (eql) {
+ JsVar *contents = jsvMathsOp(a,b, LEX_EQUAL);
+ if (!jsvGetBool(contents)) eql = false;
+ jsvUnLock(contents);
+ }
+ if (op == LEX_TYPEEQUAL)
+ return jsvNewFromBool(eql);
+ else
+ return jsvNewFromBool(!eql);
+ }
+
+ bool needsInt = op=='&' || op=='|' || op=='^' || op=='%' || op==LEX_LSHIFT || op==LEX_RSHIFT || op==LEX_RSHIFTUNSIGNED;
+ bool needsNumeric = needsInt || op=='*' || op=='/' || op=='%' || op=='-';
+
+ // do maths...
+ if (jsvIsUndefined(a) && jsvIsUndefined(b)) {
+ if (op == LEX_EQUAL)
+ return jsvNewFromBool(true);
+ else if (op == LEX_NEQUAL)
+ return jsvNewFromBool(false);
+ else
+ return 0; // undefined
+ } else if (needsNumeric ||
+ ((jsvIsNumeric(a) || jsvIsUndefined(a) || jsvIsNull(a)) &&
+ (jsvIsNumeric(b) || jsvIsUndefined(b) || jsvIsNull(b)))) {
+ if (needsInt || !(jsvIsFloat(a) || jsvIsFloat(b))) {
+ // use ints
+ JsVarInt da = jsvGetInteger(a);
+ JsVarInt db = jsvGetInteger(b);
+ switch (op) {
+ case '+': return jsvNewFromInteger(da+db);
+ case '-': return jsvNewFromInteger(da-db);
+ case '*': return jsvNewFromInteger(da*db);
+ case '/': return jsvNewFromFloat((JsVarFloat)da/(JsVarFloat)db);
+ case '&': return jsvNewFromInteger(da&db);
+ case '|': return jsvNewFromInteger(da|db);
+ case '^': return jsvNewFromInteger(da^db);
+ case '%': return jsvNewFromInteger(da%db);
+ case LEX_LSHIFT: return jsvNewFromInteger(da << db);
+ case LEX_RSHIFT: return jsvNewFromInteger(da >> db);
+ case LEX_RSHIFTUNSIGNED: return jsvNewFromInteger((JsVarInt)(((JsVarIntUnsigned)da) >> db));
+ case LEX_EQUAL: return jsvNewFromBool(da==db);
+ case LEX_NEQUAL: return jsvNewFromBool(da!=db);
+ case '<': return jsvNewFromBool(da<db);
+ case LEX_LEQUAL: return jsvNewFromBool(da<=db);
+ case '>': return jsvNewFromBool(da>db);
+ case LEX_GEQUAL: return jsvNewFromBool(da>=db);
+ default: return jsvMathsOpError(op, "Integer");
+ }
+ } else {
+ // use doubles
+ JsVarFloat da = jsvGetFloat(a);
+ JsVarFloat db = jsvGetFloat(b);
+ switch (op) {
+ case '+': return jsvNewFromFloat(da+db);
+ case '-': return jsvNewFromFloat(da-db);
+ case '*': return jsvNewFromFloat(da*db);
+ case '/': return jsvNewFromFloat(da/db);
+ case LEX_EQUAL: return jsvNewFromBool(da==db);
+ case LEX_NEQUAL: return jsvNewFromBool(da!=db);
+ case '<': return jsvNewFromBool(da<db);
+ case LEX_LEQUAL: return jsvNewFromBool(da<=db);
+ case '>': return jsvNewFromBool(da>db);
+ case LEX_GEQUAL: return jsvNewFromBool(da>=db);
+ default: return jsvMathsOpError(op, "Double");
+ }
+ }
+ } else if ((jsvIsArray(a) || jsvIsObject(a) ||
+ jsvIsArray(b) || jsvIsObject(b)) &&
+ (op == LEX_EQUAL || op==LEX_NEQUAL)) {
+ bool isArray = jsvIsArray(a);
+ /* Just check pointers */
+ switch (op) {
+ case LEX_EQUAL: return jsvNewFromBool(a==b);
+ case LEX_NEQUAL: return jsvNewFromBool(a!=b);
+ default: return jsvMathsOpError(op, isArray?"Array":"Object");
+ }
+ } else {
+ JsVar *da = jsvAsString(a, false);
+ JsVar *db = jsvAsString(b, false);
+ if (!da || !db) { // out of memory
+ jsvUnLock(da);
+ jsvUnLock(db);
+ return 0;
+ }
+ if (op=='+') {
+ JsVar *v = jsvCopy(da);
+ // TODO: can we be fancy and not copy da if we know it isn't reffed? what about locks?
+ if (v) // could be out of memory
+ jsvAppendStringVarComplete(v, db);
+ jsvUnLock(da);
+ jsvUnLock(db);
+ return v;
+ }
+
+ int cmp = jsvCompareString(da,db,0,0,false);
+ jsvUnLock(da);
+ jsvUnLock(db);
+ // use strings
+ switch (op) {
+ case LEX_EQUAL: return jsvNewFromBool(cmp==0);
+ case LEX_NEQUAL: return jsvNewFromBool(cmp!=0);
+ case '<': return jsvNewFromBool(cmp<0);
+ case LEX_LEQUAL: return jsvNewFromBool(cmp<=0);
+ case '>': return jsvNewFromBool(cmp>0);
+ case LEX_GEQUAL: return jsvNewFromBool(cmp>=0);
+ default: return jsvMathsOpError(op, "String");
+ }
+ }
+}
+
+JsVar *jsvNegateAndUnLock(JsVar *v) {
+ JsVar *zero = jsvNewFromInteger(0);
+ JsVar *res = jsvMathsOpSkipNames(zero, v, '-');
+ jsvUnLock(zero);
+ jsvUnLock(v);
+ return res;
+}
+
+void jsvTraceLockInfo(JsVar *v) {
+ jsiConsolePrintf("#%d[r%d,l%d] ",jsvGetRef(v),v->refs,jsvGetLocks(v)-1);
+}
+
+/** Get the lowest level at which searchRef appears */
+int _jsvTraceGetLowestLevel(JsVarRef ref, JsVarRef searchRef) {
+ if (ref == searchRef) return 0;
+ int found = -1;
+ JsVar *var = jsvLock(ref);
+
+ // Use IS_RECURSING flag to stop recursion
+ if (var->flags & JSV_IS_RECURSING) {
+ jsvUnLock(var);
+ return -1;
+ }
+ var->flags |= JSV_IS_RECURSING;
+
+ if (jsvHasSingleChild(var) && var->firstChild) {
+ int f = _jsvTraceGetLowestLevel(var->firstChild, searchRef);
+ if (f>=0 && (found<0 || f<found)) found=f+1;
+ }
+ if (jsvHasChildren(var)) {
+ JsVarRef childRef = var->firstChild;
+ while (childRef) {
+ int f = _jsvTraceGetLowestLevel(childRef, searchRef);
+ if (f>=0 && (found<0 || f<found)) found=f+1;
+
+ JsVar *child = jsvLock(childRef);
+ childRef = child->nextSibling;
+ jsvUnLock(child);
+ }
+ }
+
+ var->flags &= ~JSV_IS_RECURSING;
+ jsvUnLock(var);
+
+ return found; // searchRef not found
+}
+
+void _jsvTrace(JsVarRef ref, int indent, JsVarRef baseRef, int level) {
+#ifdef SAVE_ON_FLASH
+ jsiConsolePrint("Trace unimplemented in this version.\n");
+#else
+ int i;
+ for (i=0;i<indent;i++) jsiConsolePrint(" ");
+
+ if (!ref) {
+ jsiConsolePrint("undefined\n");
+ return;
+ }
+ /*jsiConsolePrint("<");
+ jsiConsolePrintInt(level);
+ jsiConsolePrint(":");
+ jsiConsolePrintInt(_jsvTraceGetLowestLevel(baseRef, ref));
+ jsiConsolePrint("> ");*/
+
+
+ JsVar *var = jsvLock(ref);
+ jsvTraceLockInfo(var);
+
+
+ if (jsvIsName(var)) {
+ if (jsvIsFunctionParameter(var))
+ jsiConsolePrint("Param ");
+ JsVar *str = jsvAsString(var, false);
+ if (jsvIsInt(var)) {
+ jsiConsolePrintf("Name: int %v ", str);
+ } else if (jsvIsFloat(var)) {
+ jsiConsolePrintf("Name: flt %v ", str);
+ } else if (jsvIsString(var) || jsvIsFunctionParameter(var)) {
+ jsiConsolePrintf("Name: '%v' ", str);
+ } else if (jsvIsArrayBufferName(var)) {
+ jsiConsolePrintf("ArrayBufferName[%d] ", jsvGetInteger(var));
+ } else {
+ assert(0);
+ }
+ jsvUnLock(str);
+ // go to what the name points to
+ ref = var->firstChild;
+ jsvUnLock(var);
+ if (ref) {
+ level++;
+ int lowestLevel = _jsvTraceGetLowestLevel(baseRef, ref);
+ /*jsiConsolePrint("<");
+ jsiConsolePrintInt(level);
+ jsiConsolePrint(":");
+ jsiConsolePrintInt(lowestLevel);
+ jsiConsolePrint("> ");*/
+
+ var = jsvLock(ref);
+ jsvTraceLockInfo(var);
+ if (lowestLevel < level) {
+ // If this data is available elsewhere in the tree (but nearer the root)
+ // then don't print it. This makes the dump significantly more readable!
+ // It also stops us getting in recursive loops ...
+ jsiConsolePrint("...\n");
+ jsvUnLock(var);
+ return;
+ }
+
+ } else {
+ jsiConsolePrint("undefined\n");
+ return;
+ }
+ }
+
+ if (jsvIsName(var)) {
+ jsiConsolePrint("\n");
+ _jsvTrace(jsvGetRef(var), indent+2, baseRef, level+1);
+ jsvUnLock(var);
+ return;
+ }
+ if (jsvIsObject(var)) jsiConsolePrint("Object {");
+ else if (jsvIsArray(var)) jsiConsolePrint("Array [");
+ else if (jsvIsPin(var)) jsiConsolePrint("Pin ");
+ else if (jsvIsInt(var)) jsiConsolePrint("Integer ");
+ else if (jsvIsBoolean(var)) jsiConsolePrint("Bool ");
+ else if (jsvIsFloat(var)) jsiConsolePrint("Double ");
+ else if (jsvIsString(var)) jsiConsolePrint("String ");
+ else if (jsvIsArrayBuffer(var)) {
+ jsiConsolePrintf("%s ", jswGetBasicObjectName(var)); // way to get nice name
+ _jsvTrace(var->firstChild, indent+1, baseRef, level+1);
+ jsvUnLock(var);
+ return;
+ } else if (jsvIsFunction(var)) jsiConsolePrint("Function {");
+ else {
+ jsiConsolePrintf("Flags %d\n", var->flags & (JsVarFlags)~(JSV_LOCK_MASK));
+ }
+
+ if (!jsvIsObject(var) && !jsvIsArray(var) && !jsvIsFunction(var)) {
+ JsVar *str = jsvAsString(var, false);
+ if (str) {
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, str, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ jsiConsolePrint(escapeCharacter(ch));
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ jsvUnLock(str);
+ }
+ }
+
+ if (jsvHasStringExt(var)) {
+ if (!jsvIsStringExt(var) && var->firstChild) { // stringext don't have children (the use them for chars)
+ jsiConsolePrint("( Multi-block string ");
+ JsVarRef child = var->firstChild;
+ while (child) {
+ JsVar *childVar = jsvLock(child);
+ jsvTraceLockInfo(childVar);
+ child = childVar->firstChild;
+ jsvUnLock(childVar);
+ }
+ jsiConsolePrint(")\n");
+ } else
+ jsiConsolePrint("\n");
+ } else {
+ JsVarRef child = var->firstChild;
+ jsiConsolePrint("\n");
+ // dump children
+ while (child) {
+ JsVar *childVar;
+ _jsvTrace(child, indent+2, baseRef, level+1);
+ childVar = jsvLock(child);
+ child = childVar->nextSibling;
+ jsvUnLock(childVar);
+ }
+ }
+
+ if (jsvIsObject(var) || jsvIsFunction(var) || jsvIsArray(var)) {
+ int i;
+ for (i=0;i<indent;i++) jsiConsolePrint(" ");
+ jsiConsolePrint(jsvIsArray(var) ? "]\n" : "}\n");
+ }
+
+ jsvUnLock(var);
+#endif
+}
+
+/** Write debug info for this Var out to the console */
+void jsvTrace(JsVarRef ref, int indent) {
+ _jsvTrace(ref,indent,ref,0);
+}
+
+
+/** Recursively mark the variable */
+static void jsvGarbageCollectMarkUsed(JsVar *var) {
+ var->flags &= (JsVarFlags)~JSV_GARBAGE_COLLECT;
+
+ if (jsvHasCharacterData(var)) {
+ if (var->lastChild) {
+ JsVar *childVar = jsvGetAddressOf(var->lastChild);
+ if (childVar->flags & JSV_GARBAGE_COLLECT)
+ jsvGarbageCollectMarkUsed(childVar);
+ }
+ }
+ // intentionally no else
+ if (jsvHasSingleChild(var)) {
+ if (var->firstChild) {
+ JsVar *childVar = jsvGetAddressOf(var->firstChild);
+ if (childVar->flags & JSV_GARBAGE_COLLECT)
+ jsvGarbageCollectMarkUsed(childVar);
+ }
+ } else if (jsvHasChildren(var)) {
+ JsVarRef child = var->firstChild;
+ while (child) {
+ JsVar *childVar;
+ childVar = jsvGetAddressOf(child);
+ if (childVar->flags & JSV_GARBAGE_COLLECT)
+ jsvGarbageCollectMarkUsed(childVar);
+ child = childVar->nextSibling;
+ }
+ }
+}
+
+/** Run a garbage collection sweep - return true if things have been freed */
+bool jsvGarbageCollect() {
+ JsVarRef i;
+ // clear garbage collect flags
+ for (i=1;i<=jsVarsSize;i++) {
+ JsVar *var = jsvGetAddressOf(i);
+ if ((var->flags&JSV_VARTYPEMASK) != JSV_UNUSED) // if it is not unused
+ var->flags |= (JsVarFlags)JSV_GARBAGE_COLLECT;
+ }
+ // recursively add 'native' vars
+ for (i=1;i<=jsVarsSize;i++) {
+ JsVar *var = jsvGetAddressOf(i);
+ if ((var->flags & JSV_GARBAGE_COLLECT) && // not already GC'd
+ jsvGetLocks(var)>0) // or it is locked
+ jsvGarbageCollectMarkUsed(var);
+ }
+ // now sweep for things that we can GC!
+ bool freedSomething = false;
+ for (i=1;i<=jsVarsSize;i++) {
+ JsVar *var = jsvGetAddressOf(i);
+ if (var->flags & JSV_GARBAGE_COLLECT) {
+ freedSomething = true;
+ // free!
+ var->flags = JSV_UNUSED;
+ // add this to our free list
+ var->nextSibling = jsVarFirstEmpty;
+ jsVarFirstEmpty = jsvGetRef(var);
+ }
+ }
+ return freedSomething;
+}
+
+/** Remove whitespace to the right of a string - on MULTIPLE LINES */
+JsVar *jsvStringTrimRight(JsVar *srcString) {
+ JsvStringIterator src, dst;
+ JsVar *dstString = jsvNewFromEmptyString();
+ jsvStringIteratorNew(&src, srcString, 0);
+ jsvStringIteratorNew(&dst, dstString, 0);
+ int spaces = 0;
+ while (jsvStringIteratorHasChar(&src)) {
+ char ch = jsvStringIteratorGetChar(&src);
+ jsvStringIteratorNext(&src);
+
+ if (ch==' ') spaces++;
+ else if (ch=='\n') {
+ spaces = 0;
+ jsvStringIteratorAppend(&dst, ch);
+ } else {
+ for (;spaces>0;spaces--)
+ jsvStringIteratorAppend(&dst, ' ');
+ jsvStringIteratorAppend(&dst, ch);
+ }
+ }
+ jsvStringIteratorFree(&src);
+ jsvStringIteratorFree(&dst);
+ return dstString;
+}
+
+/// If v is the key of a function, return true if it is internal and shouldn't be visible to the user
+bool jsvIsInternalFunctionKey(JsVar *v) {
+ return (jsvIsString(v) && (
+ v->varData.str[0]==JS_HIDDEN_CHAR)
+ ) ||
+ jsvIsFunctionParameter(v);
+}
+
+/// If v is the key of an object, return true if it is internal and shouldn't be visible to the user
+bool jsvIsInternalObjectKey(JsVar *v) {
+ return (jsvIsString(v) && (
+ v->varData.str[0]==JS_HIDDEN_CHAR ||
+ jsvIsStringEqual(v, JSPARSE_INHERITS_VAR) ||
+ jsvIsStringEqual(v, JSPARSE_CONSTRUCTOR_VAR)
+ ));
+}
+
+// --------------------------------------------------------------------------------------------
+
+void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, int startIdx) {
+ assert(jsvHasCharacterData(str));
+ it->var = jsvLockAgain(str);
+ it->charsInVar = jsvGetCharactersInVar(str);
+ it->charIdx = (size_t)startIdx;
+ it->index = (size_t)startIdx;
+ while (it->charIdx>0 && it->charIdx >= it->charsInVar) {
+ it->charIdx -= it->charsInVar;
+ if (it->var) {
+ if (it->var->lastChild) {
+ JsVar *next = jsvLock(it->var->lastChild);
+ jsvUnLock(it->var);
+ it->var = next;
+ it->charsInVar = jsvGetCharactersInVar(it->var);
+ } else {
+ jsvUnLock(it->var);
+ it->var = 0;
+ it->charsInVar = 0;
+ return; // get out of loop
+ }
+ }
+ }
+}
+
+void jsvStringIteratorNext(JsvStringIterator *it) {
+ jsvStringIteratorNextInline(it);
+}
+
+void jsvStringIteratorGotoEnd(JsvStringIterator *it) {
+ assert(it->var);
+ while (it->var->lastChild) {
+ it->index += it->charsInVar;
+ JsVar *next = jsvLock(it->var->lastChild);
+ jsvUnLock(it->var);
+ it->var = next;
+ it->charsInVar = jsvGetCharactersInVar(it->var);
+ }
+ if (it->charsInVar) it->charIdx = it->charsInVar-1;
+ else it->charIdx = 0;
+}
+
+void jsvStringIteratorAppend(JsvStringIterator *it, char ch) {
+ if (!it->var) return;
+ if (it->charsInVar>0) {
+ assert(it->charIdx+1 == it->charsInVar /* check at end */);
+ it->charIdx++;
+ it->index++;
+ } else
+ assert(it->charIdx == 0);
+ if (it->charIdx >= jsvGetMaxCharactersInVar(it->var)) {
+ assert(!it->var->lastChild);
+ JsVar *next = jsvNewWithFlags(JSV_STRING_EXT);
+ if (!next) return; // out of memory
+ // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner)
+ it->var->lastChild = jsvGetRef(next);
+ jsvUnLock(it->var);
+ it->var = next;
+
+ it->charIdx = 0; // it's new, so empty
+ }
+
+ it->var->varData.str[it->charIdx] = ch;
+ it->charsInVar = it->charIdx+1;
+ jsvSetCharactersInVar(it->var, it->charsInVar);
+}
+
+
+// --------------------------------------------------------------------------------------------
+void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, JsVarInt index) {
+ assert(jsvIsArrayBuffer(arrayBuffer));
+ it->index = index;
+ it->type = arrayBuffer->varData.arraybuffer.type;
+ it->byteLength = arrayBuffer->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ it->byteOffset = arrayBuffer->varData.arraybuffer.byteOffset;
+ JsVar *arrayBufferData = jsvLock(arrayBuffer->firstChild);
+ while (jsvIsArrayBuffer(arrayBufferData)) {
+ JsVar *s = jsvLock(arrayBufferData->firstChild);
+ jsvUnLock(arrayBufferData);
+ arrayBufferData = s;
+ }
+ assert(jsvIsString(arrayBufferData));
+
+ it->byteLength += it->byteOffset; // because we'll check if we have more bytes using this
+ it->byteOffset = it->byteOffset + index*JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ if (it->byteOffset<0 || (it->byteLength>=0 && it->byteOffset>=(it->byteLength+1-JSV_ARRAYBUFFER_GET_SIZE(it->type)))) {
+ jsvUnLock(arrayBufferData);
+ it->type = ARRAYBUFFERVIEW_UNDEFINED;
+ return;
+ }
+ jsvStringIteratorNew(&it->it, arrayBufferData, (int)it->byteOffset);
+ jsvUnLock(arrayBufferData);
+ it->hasAccessedElement = false;
+}
+
+static void jsvArrayBufferIteratorGetValueData(JsvArrayBufferIterator *it, char *data) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return;
+ assert(!it->hasAccessedElement); // we just haven't implemented this case yet
+ unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ for (i=0;i<dataLen;i++) {
+ data[i] = jsvStringIteratorGetChar(&it->it);
+ if (dataLen!=1) jsvStringIteratorNext(&it->it);
+ }
+ if (dataLen!=1) it->hasAccessedElement = true;
+}
+
+static JsVarInt jsvArrayBufferIteratorDataToInt(JsvArrayBufferIterator *it, char *data) {
+ unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ JsVarInt v = 0;
+ if (dataLen==1) v = *(char*)data;
+ else if (dataLen==2) v = *(short*)data;
+ else if (dataLen==4) v = *(int*)data;
+ else if (dataLen==8) v = *(long long*)data;
+ else assert(0);
+ if ((!JSV_ARRAYBUFFER_IS_SIGNED(it->type)) && v<0)
+ v += 1 << (8*dataLen);
+ return v;
+}
+
+static JsVarFloat jsvArrayBufferIteratorDataToFloat(JsvArrayBufferIterator *it, char *data) {
+ unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ JsVarFloat v = 0;
+ if (dataLen==4) v = *(float*)data;
+ else if (dataLen==8) v = *(double*)data;
+ else assert(0);
+ return v;
+}
+
+JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0;
+ char data[8];
+ jsvArrayBufferIteratorGetValueData(it, data);
+ if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) {
+ return jsvNewFromFloat(jsvArrayBufferIteratorDataToFloat(it, data));
+ } else {
+ return jsvNewFromInteger(jsvArrayBufferIteratorDataToInt(it, data));
+ }
+}
+
+JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0;
+ char data[8];
+ jsvArrayBufferIteratorGetValueData(it, data);
+ if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) {
+ return (JsVarInt)jsvArrayBufferIteratorDataToFloat(it, data);
+ } else {
+ return jsvArrayBufferIteratorDataToInt(it, data);
+ }
+}
+
+JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0;
+ char data[8];
+ jsvArrayBufferIteratorGetValueData(it, data);
+ if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) {
+ return jsvArrayBufferIteratorDataToFloat(it, data);
+ } else {
+ return (JsVarFloat)jsvArrayBufferIteratorDataToInt(it, data);
+ }
+}
+
+void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return;
+ assert(!it->hasAccessedElement); // we just haven't implemented this case yet
+ char data[8];
+ unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type);
+
+ if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) {
+ JsVarFloat v = jsvGetFloat(value); ;
+ if (dataLen==4) { float f = (float)v; memcpy(data,&f,dataLen); }
+ else if (dataLen==8) { double f = (double)v; memcpy(data,&f,dataLen); }
+ else assert(0);
+ } else {
+ JsVarInt v = jsvGetInteger(value);
+ // we don't care about sign when writing - as it gets truncated
+ if (dataLen==1) { char c = (char)v; memcpy(data,&c,dataLen); }
+ else if (dataLen==2) { short c = (short)v; memcpy(data,&c,dataLen); }
+ else if (dataLen==4) { int c = (int)v; memcpy(data,&c,dataLen); }
+ else if (dataLen==8) { long long c = (long long)v; memcpy(data,&c,dataLen); }
+ else assert(0);
+ }
+
+ for (i=0;i<dataLen;i++) {
+ jsvStringIteratorSetChar(&it->it, data[i]);
+ if (dataLen!=1) jsvStringIteratorNext(&it->it);
+ }
+ if (dataLen!=1) it->hasAccessedElement = true;
+}
+
+void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt value) {
+ // FIXME: Do this without the allocation!
+ JsVar *val = jsvNewFromInteger(value);
+ jsvArrayBufferIteratorSetValue(it, val);
+ jsvUnLock(val);
+}
+
+JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it) {
+ return jsvNewFromInteger(it->index);
+}
+
+bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return false;
+ if (it->hasAccessedElement) return true;
+ if (it->byteLength>=0)
+ return it->byteOffset <= (it->byteLength-JSV_ARRAYBUFFER_GET_SIZE(it->type));
+ else
+ return jsvStringIteratorHasChar(&it->it);
+}
+
+void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it) {
+ it->index++;
+ it->byteOffset += JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ if (!it->hasAccessedElement) {
+ unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type);
+ for (i=0;i<dataLen;i++)
+ jsvStringIteratorNext(&it->it);
+ } else
+ it->hasAccessedElement = false;
+}
+
+void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) {
+ if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return;
+ jsvStringIteratorFree(&it->it);
+}
+// --------------------------------------------------------------------------------------------
+/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */
+
+void jsvIteratorNew(JsvIterator *it, JsVar *obj) {
+ if (jsvIsArray(obj)) {
+ it->type = JSVI_ARRAY;
+ jsvArrayIteratorNew(&it->it.arr, obj);
+ } else if (jsvIsObject(obj) || jsvIsFunction(obj)) {
+ it->type = JSVI_OBJECT;
+ jsvObjectIteratorNew(&it->it.obj, obj);
+ } else if (jsvIsArrayBuffer(obj)) {
+ it->type = JSVI_ARRAYBUFFER;
+ jsvArrayBufferIteratorNew(&it->it.buf, obj, 0);
+ } else if (jsvHasCharacterData(obj)) {
+ it->type = JSVI_STRING;
+ jsvStringIteratorNew(&it->it.str, obj, 0);
+ } else assert(0);
+}
+
+JsVar *jsvIteratorGetKey(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : return jsvArrayIteratorGetIndex(&it->it.arr);
+ case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj);
+ case JSVI_STRING : return jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str));
+ case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIndex(&it->it.buf);
+ default: assert(0); return 0;
+ }
+}
+
+JsVar *jsvIteratorGetValue(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : return jsvArrayIteratorGetElement(&it->it.arr);
+ case JSVI_OBJECT : return jsvObjectIteratorGetValue(&it->it.obj);
+ case JSVI_STRING : { char buf[2] = {jsvStringIteratorGetChar(&it->it.str),0}; return jsvNewFromString(buf); }
+ case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetValue(&it->it.buf);
+ default: assert(0); return 0;
+ }
+}
+
+JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : return jsvGetIntegerAndUnLock(jsvArrayIteratorGetElement(&it->it.arr));
+ case JSVI_OBJECT : return jsvGetIntegerAndUnLock(jsvObjectIteratorGetValue(&it->it.obj));
+ case JSVI_STRING : return (JsVarInt)jsvStringIteratorGetChar(&it->it.str);
+ case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIntegerValue(&it->it.buf);
+ default: assert(0); return 0;
+ }
+}
+
+bool jsvIteratorHasElement(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : return jsvArrayIteratorHasElement(&it->it.arr);
+ case JSVI_OBJECT : return jsvObjectIteratorHasElement(&it->it.obj);
+ case JSVI_STRING : return jsvStringIteratorHasChar(&it->it.str);
+ case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorHasElement(&it->it.buf);
+ default: assert(0); return 0;
+ }
+}
+
+void jsvIteratorNext(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : jsvArrayIteratorNext(&it->it.arr); break;
+ case JSVI_OBJECT : jsvObjectIteratorNext(&it->it.obj); break;
+ case JSVI_STRING : jsvStringIteratorNext(&it->it.str); break;
+ case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorNext(&it->it.buf); break;
+ default: assert(0); break;
+ }
+}
+void jsvIteratorFree(JsvIterator *it) {
+ switch (it->type) {
+ case JSVI_ARRAY : jsvArrayIteratorFree(&it->it.arr); break;
+ case JSVI_OBJECT : jsvObjectIteratorFree(&it->it.obj); break;
+ case JSVI_STRING : jsvStringIteratorFree(&it->it.str); break;
+ case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorFree(&it->it.buf); break;
+ default: assert(0); break;
+ }
+}
+
+
diff --git a/components/external/espruino/src/jsvar.h b/components/external/espruino/src/jsvar.h
new file mode 100644
index 0000000000..74d85a6ca3
--- /dev/null
+++ b/components/external/espruino/src/jsvar.h
@@ -0,0 +1,612 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Variables
+ * ----------------------------------------------------------------------------
+ */
+#ifndef JSVAR_H_
+#define JSVAR_H_
+
+#include "jsutils.h"
+
+
+typedef void (*JsCallback)(JsVarRef var)
+#ifdef SDCC
+__reentrant
+#endif
+;
+
+/** To avoid confusion - JsVarRefCounter should be big enough
+ * to store as many refs as can possibly be created - so it's
+ * safe just to set it to the same size as JsVarRef. However
+ * it is NOT a reference itself.
+ */
+typedef JsVarRef JsVarRefCounter;
+
+typedef enum {
+ ARRAYBUFFERVIEW_UNDEFINED = 0,
+ ARRAYBUFFERVIEW_ARRAYBUFFER = 1 | 64, ///< Basic ArrayBuffer type
+
+ ARRAYBUFFERVIEW_MASK_SIZE = 15,
+ ARRAYBUFFERVIEW_SIGNED = 16,
+ ARRAYBUFFERVIEW_FLOAT = 32,
+
+ ARRAYBUFFERVIEW_UINT8 = 1,
+ ARRAYBUFFERVIEW_INT8 = 1 | ARRAYBUFFERVIEW_SIGNED,
+ ARRAYBUFFERVIEW_UINT16 = 2,
+ ARRAYBUFFERVIEW_INT16 = 2 | ARRAYBUFFERVIEW_SIGNED,
+ ARRAYBUFFERVIEW_UINT32 = 4,
+ ARRAYBUFFERVIEW_INT32 = 4 | ARRAYBUFFERVIEW_SIGNED,
+ ARRAYBUFFERVIEW_FLOAT32 = 4 | ARRAYBUFFERVIEW_FLOAT,
+ ARRAYBUFFERVIEW_FLOAT64 = 8 | ARRAYBUFFERVIEW_FLOAT,
+} PACKED_FLAGS JsVarDataArrayBufferViewType;
+#define JSV_ARRAYBUFFER_GET_SIZE(T) ((T)&ARRAYBUFFERVIEW_MASK_SIZE)
+#define JSV_ARRAYBUFFER_IS_SIGNED(T) (((T)&ARRAYBUFFERVIEW_SIGNED)!=0)
+#define JSV_ARRAYBUFFER_IS_FLOAT(T) (((T)&ARRAYBUFFERVIEW_FLOAT)!=0)
+
+#define JSV_ARRAYBUFFER_MAX_LENGTH 65535
+
+typedef struct {
+ unsigned short byteOffset;
+ unsigned short length;
+ JsVarDataArrayBufferViewType type;
+} PACKED_FLAGS JsVarDataArrayBufferView;
+
+typedef union {
+ char str[JSVAR_DATA_STRING_LEN]; ///< The contents of this variable if it is a string
+ /* NOTE: For str above, we INTENTIONALLY OVERFLOW str (and hence data) in the case of STRING_EXTS
+ * to overwrite 3 references in order to grab another 6 bytes worth of string data */
+ // TODO do some magic with union/structs in order to make sure we don't intentionally write off the end of arrays
+ JsVarInt integer; ///< The contents of this variable if it is an int
+ JsVarFloat floating; ///< The contents of this variable if it is a double
+ JsCallback callback; ///< Callback for native functions, or 0
+ JsVarDataArrayBufferView arraybuffer; ///< information for array buffer views.
+} PACKED_FLAGS JsVarData;
+
+typedef struct {
+#ifdef LARGE_MEM
+ JsVarRef this; ///< The reference of this variable itself (so we can get back)
+#endif
+ JsVarFlags flags; ///< the flags determine the type of the variable - int/double/string/etc.
+
+ JsVarData varData;
+ /* NOTE: WE INTENTIONALLY OVERFLOW data in the case of STRING_EXTS
+ * to overwrite the following 3 references in order to grab another
+ * 6 bytes worth of string data */
+
+ /* For Variable NAMES (e.g. Object/Array keys) these store actual next/previous pointers for a linked list
+ * For STRING_EXT - extra characters
+ * Not used for other stuff
+ */
+ JsVarRef nextSibling;
+ JsVarRef prevSibling;
+
+ JsVarRefCounter refs; ///< The number of references held to this - used for automatic garbage collection. NOT USED for STRINGEXT though (it is just extra characters)
+
+ /**
+ * For OBJECT/ARRAY/FUNCTION - this is the first child
+ * For NAMES and REF - this is a link to the variable it points to
+ * For STRING_EXT - extra character data (NOT a link)
+ * For ARRAYBUFFER - a link to a string containing the data for the array buffer *
+ */
+ JsVarRef firstChild;
+
+ /**
+ * For OBJECT/ARRAY/FUNCTION - this is the last child
+ * For STRINGS/STRING_EXT/NAME+STRING - this is a link to more string data if it is needed
+ * For REF - this is the 'parent' that the firstChild is a member of
+ */
+ JsVarRef lastChild;
+} PACKED_FLAGS JsVar;
+
+/* We have a few different types:
+ *
+ * OBJECT/ARRAY - uses firstChild/lastChild to link to NAMEs.
+ * BUILT-IN OBJECT - as above, but we use varData to store the name as well. This means built in object names must be LESS THAN 8 CHARACTERS
+ * FUNCTION - uses firstChild/lastChild to link to NAMEs, and callback is used
+ * NAME - use nextSibling/prevSibling linking to other NAMEs, and firstChild to link to a Variable of some kind
+ * STRING - use firstChild to link to other STRINGs if String value is too long
+ * INT/DOUBLE - firstChild never used
+ */
+
+static inline unsigned char jsvGetLocks(JsVar *v) { return (unsigned char)((v->flags>>JSV_LOCK_SHIFT) & JSV_LOCK_MAX); }
+
+// For debugging/testing ONLY - maximum # of vars we are allowed to use
+void jsvSetMaxVarsUsed(unsigned int size);
+
+// Init/kill vars as a whole
+void jsvInit();
+void jsvKill();
+void jsvSoftInit(); ///< called when loading from flash
+void jsvSoftKill(); ///< called when saving to flash
+JsVar *jsvFindOrCreateRoot(); ///< Find or create the ROOT variable item - used mainly if recovering from a saved state.
+unsigned int jsvGetMemoryUsage(); ///< Get number of memory records (JsVars) used
+unsigned int jsvGetMemoryTotal(); ///< Get total amount of memory records
+bool jsvIsMemoryFull(); ///< Get whether memory is full or not
+void jsvShowAllocated(); ///< Show what is still allocated, for debugging memory problems
+/// Try and allocate more memory - only works if RESIZABLE_JSVARS is defined
+void jsvSetMemoryTotal(unsigned int jsNewVarCount);
+
+
+// Note that jsvNew* don't REF a variable for you, but the do LOCK it
+JsVar *jsvNew(); ///< Create a new variable
+JsVar *jsvNewWithFlags(JsVarFlags flags);
+JsVar *jsvNewFromString(const char *str); ///< Create a new string
+JsVar *jsvNewStringOfLength(unsigned int byteLength); ///< Create a new string of the given length - full of 0s
+static inline JsVar *jsvNewFromEmptyString() { return jsvNewWithFlags(JSV_STRING); } ;///< Create a new empty string
+JsVar *jsvNewFromLexer(struct JsLex *lex, JslCharPos charFrom, JslCharPos charTo); // Create a new STRING from part of the lexer
+JsVar *jsvNewFromInteger(JsVarInt value);
+JsVar *jsvNewFromBool(bool value);
+JsVar *jsvNewFromFloat(JsVarFloat value);
+// Turns var into a Variable name that links to the given value... No locking so no need to unlock var
+JsVar *jsvMakeIntoVariableName(JsVar *var, JsVar *valueOrZero);
+JsVar *jsvNewFromPin(int pin);
+
+/// DO NOT CALL THIS DIRECTLY - this frees an unreffed/locked var
+void jsvFreePtr(JsVar *var);
+
+/// Get a reference from a var - SAFE for null vars
+JsVarRef jsvGetRef(JsVar *var);
+
+/// SCARY - only to be used for vital stuff like load/save
+JsVar *_jsvGetAddressOf(JsVarRef ref);
+
+/// Lock this reference and return a pointer - UNSAFE for null refs
+JsVar *jsvLock(JsVarRef ref);
+
+/// Lock this pointer and return a pointer - UNSAFE for null pointer
+JsVar *jsvLockAgain(JsVar *var);
+
+/// Unlock this variable - this is SAFE for null variables
+void jsvUnLock(JsVar *var);
+
+
+/// Reference - set this variable as used by something
+JsVar *jsvRef(JsVar *v);
+
+/// Unreference - set this variable as not used by anything
+void jsvUnRef(JsVar *var);
+
+/// Helper fn, Reference - set this variable as used by something
+JsVarRef jsvRefRef(JsVarRef ref);
+
+/// Helper fn, Unreference - set this variable as not used by anything
+JsVarRef jsvUnRefRef(JsVarRef ref);
+
+static inline bool jsvIsRoot(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_ROOT; }
+static inline bool jsvIsPin(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_PIN; }
+static inline bool jsvIsInt(const JsVar *v) { return v && ((v->flags&JSV_VARTYPEMASK)==JSV_INTEGER || (v->flags&JSV_VARTYPEMASK)==JSV_PIN); }
+static inline bool jsvIsFloat(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_FLOAT; }
+static inline bool jsvIsBoolean(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_BOOLEAN; }
+static inline bool jsvIsString(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)>=JSV_STRING_0 && (v->flags&JSV_VARTYPEMASK)<=JSV_STRING_MAX; }
+static inline bool jsvIsStringExt(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)>=JSV_STRING_EXT_0 && (v->flags&JSV_VARTYPEMASK)<=JSV_STRING_EXT_MAX; } ///< The extra bits dumped onto the end of a string to store more data
+static inline bool jsvIsNumeric(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)>=JSV_NUMERICSTART && (v->flags&JSV_VARTYPEMASK)<=JSV_NUMERICEND; }
+static inline bool jsvIsFunction(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_FUNCTION; }
+static inline bool jsvIsFunctionParameter(const JsVar *v) { return v && (v->flags&JSV_FUNCTION_PARAMETER) == JSV_FUNCTION_PARAMETER; }
+static inline bool jsvIsObject(const JsVar *v) { return v && (((v->flags&JSV_VARTYPEMASK)==JSV_OBJECT) || ((v->flags&JSV_VARTYPEMASK)==JSV_ROOT)); }
+static inline bool jsvIsArray(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_ARRAY; }
+static inline bool jsvIsArrayBuffer(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_ARRAYBUFFER; }
+static inline bool jsvIsArrayBufferName(const JsVar *v) { return v && (v->flags&(JSV_VARTYPEMASK|JSV_NAME))==JSV_ARRAYBUFFERNAME; }
+static inline bool jsvIsNative(const JsVar *v) { return v && (v->flags&JSV_NATIVE)!=0; }
+static inline bool jsvIsUndefined(const JsVar *v) { return v==0; }
+static inline bool jsvIsNull(const JsVar *v) { return v && (v->flags&JSV_VARTYPEMASK)==JSV_NULL; }
+static inline bool jsvIsBasic(const JsVar *v) { return jsvIsNumeric(v) || jsvIsString(v);} ///< Is this *not* an array/object/etc
+static inline bool jsvIsName(const JsVar *v) { return v && (v->flags & JSV_NAME)!=0; } ///< NAMEs are what's used to name a variable (it is not the data itself)
+
+static inline bool jsvIsIterable(const JsVar *v) {
+ return jsvIsArray(v) || jsvIsObject(v) || jsvIsFunction(v) ||
+ jsvIsString(v) || jsvIsArrayBuffer(v);
+}
+
+/** Does this string contain only Numeric characters? */
+bool jsvIsStringNumeric(const JsVar *var);
+/** Does this string contain only Numeric characters? This is for arrays
+ * and makes the assertion that int_to_string(string_to_int(var))==var */
+bool jsvIsStringNumericStrict(const JsVar *var);
+
+// TODO: maybe isName shouldn't include ArrayBufferName?
+bool jsvHasCharacterData(const JsVar *v); ///< does the v->data union contain character data?
+bool jsvHasStringExt(const JsVar *v);
+/// Does this variable use firstChild/lastChild to point to multiple childrem
+bool jsvHasChildren(const JsVar *v);
+/// Is this variable a type that uses firstChild to point to a single Variable (ie. it doesn't have multiple children)
+bool jsvHasSingleChild(const JsVar *v);
+/// Does this variable have a 'ref' argument? Stringexts use it for extra character data
+static inline bool jsvHasRef(const JsVar *v) { return !jsvIsStringExt(v); }
+
+/// This is the number of characters a JsVar can contain, NOT string length
+static inline size_t jsvGetMaxCharactersInVar(const JsVar *v) {
+ // see jsvCopy - we need to know about this in there too
+ if (jsvIsStringExt(v)) return JSVAR_DATA_STRING_MAX_LEN;
+ assert(jsvHasCharacterData(v));
+ return JSVAR_DATA_STRING_LEN;
+}
+
+/// This is the number of characters a JsVar can contain, NOT string length
+static inline size_t jsvGetCharactersInVar(const JsVar *v) {
+ assert(jsvIsString(v) || jsvIsStringExt(v));
+ int f = v->flags&JSV_VARTYPEMASK;
+ return (size_t)(f - ((f < JSV_STRING_EXT_0) ? JSV_STRING_0 : JSV_STRING_EXT_0));
+}
+
+/// This is the number of characters a JsVar can contain, NOT string length
+static inline void jsvSetCharactersInVar(JsVar *v, size_t chars) {
+ assert(jsvIsString(v) || jsvIsStringExt(v));
+ if (jsvIsString(v)) { assert(chars <= JSVAR_DATA_STRING_LEN); }
+ if (jsvIsStringExt(v)) { assert(chars <= JSVAR_DATA_STRING_MAX_LEN); }
+
+ int f = v->flags&JSV_VARTYPEMASK;
+ v->flags = (JsVarFlags)((v->flags&(~JSV_VARTYPEMASK)) | (((f < JSV_STRING_EXT_0) ? JSV_STRING_0 : JSV_STRING_EXT_0) + (int)chars));
+}
+
+
+/** Check if two Basic Variables are equal (this IGNORES the value that is pointed to,
+ * so 'a=5'=='a=7' but 'a=5'!='b=5')
+ */
+bool jsvIsBasicVarEqual(JsVar *a, JsVar *b);
+
+/** Check if two things are equal. Basic vars are done by value,
+ * for anything else the reference/pointer must be equal */
+bool jsvIsEqual(JsVar *a, JsVar *b);
+
+
+const char *jsvGetConstString(const JsVar *v); ///< Get a const string representing this variable - if we can. Otherwise return 0
+const char *jsvGetTypeOf(const JsVar *v); ///< Return the 'type' of the JS variable (eg. JS's typeof operator)
+size_t jsvGetString(const JsVar *v, char *str, size_t len); ///< Save this var as a string to the given buffer, and return how long it was (return val doesn't include terminating 0)
+void jsvSetString(JsVar *v, char *str, size_t len); ///< Set the Data in this string. This must JUST overwrite - not extend or shrink
+JsVar *jsvAsString(JsVar *var, bool unlockVar); ///< If var is a string, lock and return it, else create a new string
+size_t jsvGetStringLength(JsVar *v); ///< Get the length of this string, IF it is a string
+int jsvGetLinesInString(JsVar *v); ///< IN A STRING get the number of lines in the string (min=1)
+int jsvGetCharsOnLine(JsVar *v, int line); ///< IN A STRING Get the number of characters on a line - lines start at 1
+void jsvGetLineAndCol(JsVar *v, int charIdx, int* line, int *col); ///< IN A STRING, get the line and column of the given character. Both values must be non-null
+int jsvGetIndexFromLineAndCol(JsVar *v, int line, int col); ///< IN A STRING, get a character index from a line and column
+bool jsvIsStringEqual(JsVar *var, const char *str);
+int jsvCompareString(JsVar *va, JsVar *vb, int starta, int startb, bool equalAtEndOfString); ///< Compare 2 strings, starting from the given character positions
+int jsvCompareInteger(JsVar *va, JsVar *vb); ///< Compare 2 integers, >0 if va>vb, <0 if va<vb. If compared with a non-integer, that gets put later
+void jsvAppendString(JsVar *var, const char *str); ///< Append the given string to this one
+void jsvAppendStringBuf(JsVar *var, const char *str, int length); ///< Append the given string to this one - but does not use null-terminated strings
+void jsvAppendPrintf(JsVar *var, const char *fmt, ...); ///< Append the formatted string to a variable (see vcbprintf)
+static inline void jsvAppendCharacter(JsVar *var, char ch) { jsvAppendStringBuf(var, &ch, 1); }; ///< Append the given character to this string
+#define JSVAPPENDSTRINGVAR_MAXLENGTH (0x7FFFFFFF)
+void jsvAppendStringVar(JsVar *var, const JsVar *str, int stridx, int maxLength); ///< Append str to var. Both must be strings. stridx = start char or str, maxLength = max number of characters (can be JSVAPPENDSTRINGVAR_MAXLENGTH). stridx can be negative to go from end of string
+void jsvAppendStringVarComplete(JsVar *var, const JsVar *str); ///< Append all of str to var. Both must be strings.
+char jsvGetCharInString(JsVar *v, int idx);
+
+JsVarInt jsvGetInteger(const JsVar *v);
+void jsvSetInteger(JsVar *v, JsVarInt value); ///< Set an integer value (use carefully!)
+JsVarFloat jsvGetFloat(const JsVar *v); ///< Get the floating point representation of this var
+bool jsvGetBool(const JsVar *v);
+JsVar *jsvAsNumber(JsVar *var); ///< Convert the given variable to a number
+static inline JsVarInt _jsvGetIntegerAndUnLock(JsVar *v) { JsVarInt i = jsvGetInteger(v); jsvUnLock(v); return i; }
+static inline JsVarFloat _jsvGetFloatAndUnLock(JsVar *v) { JsVarFloat f = jsvGetFloat(v); jsvUnLock(v); return f; }
+static inline bool _jsvGetBoolAndUnLock(JsVar *v) { bool b = jsvGetBool(v); jsvUnLock(v); return b; }
+#ifdef SAVE_ON_FLASH
+JsVarInt jsvGetIntegerAndUnLock(JsVar *v);
+JsVarFloat jsvGetFloatAndUnLock(JsVar *v);
+bool jsvGetBoolAndUnLock(JsVar *v);
+#else
+#define jsvGetIntegerAndUnLock _jsvGetIntegerAndUnLock
+#define jsvGetFloatAndUnLock _jsvGetFloatAndUnLock
+#define jsvGetBoolAndUnLock _jsvGetBoolAndUnLock
+#endif
+
+
+/** Get the item at the given location in the array buffer and return the result */
+size_t jsvGetArrayBufferLength(JsVar *arrayBuffer);
+/** Get the item at the given location in the array buffer and return the result */
+JsVar *jsvArrayBufferGet(JsVar *arrayBuffer, JsVarInt index);
+/** Set the item at the given location in the array buffer */
+void jsvArrayBufferSet(JsVar *arrayBuffer, JsVarInt index, JsVar *value);
+/** Given an integer name that points to an arraybuffer or an arraybufferview, evaluate it and return the result */
+JsVar *jsvArrayBufferGetFromName(JsVar *name);
+
+/** If a is a name skip it and go to what it points to - and so on.
+ * ALWAYS locks - so must unlock what it returns. It MAY
+ * return 0. */
+JsVar *jsvSkipName(JsVar *a);
+
+/** If a is a name skip it and go to what it points to.
+ * ALWAYS locks - so must unlock what it returns. It MAY
+ * return 0. */
+JsVar *jsvSkipOneName(JsVar *a);
+
+/** If a is a's child is a name skip it and go to what it points to.
+ * ALWAYS locks - so must unlock what it returns. */
+JsVar *jsvSkipToLastName(JsVar *a);
+
+/** Same as jsvSkipName, but ensures that 'a' is unlocked if it was
+ * a name, so it can be used INLINE_FUNC */
+static inline JsVar *jsvSkipNameAndUnLock(JsVar *a) {
+ JsVar *b = jsvSkipName(a);
+ jsvUnLock(a);
+ return b;
+}
+
+/** Same as jsvSkipOneName, but ensures that 'a' is unlocked if it was
+ * a name, so it can be used INLINE_FUNC */
+static inline JsVar *jsvSkipOneNameAndUnLock(JsVar *a) {
+ JsVar *b = jsvSkipOneName(a);
+ jsvUnLock(a);
+ return b;
+}
+
+/// MATHS!
+JsVar *jsvMathsOpSkipNames(JsVar *a, JsVar *b, int op);
+JsVar *jsvMathsOp(JsVar *a, JsVar *b, int op);
+/// Negates an integer/double value
+JsVar *jsvNegateAndUnLock(JsVar *v);
+
+/// Copy this variable and return the locked copy
+JsVar *jsvCopy(JsVar *src);
+/** Copy only a name, not what it points to. ALTHOUGH the link to what it points to is maintained unless linkChildren=false.
+ If keepAsName==false, this will be converted into a normal variable */
+JsVar *jsvCopyNameOnly(JsVar *src, bool linkChildren, bool keepAsName);
+/// Tree related stuff
+void jsvAddName(JsVar *parent, JsVar *nameChild); // Add a child, which is itself a name
+JsVar *jsvAddNamedChild(JsVar *parent, JsVar *child, const char *name); // Add a child, and create a name for it. Returns a LOCKED var. DOES NOT CHECK FOR DUPLICATES
+JsVar *jsvSetNamedChild(JsVar *parent, JsVar *child, const char *name); // Add a child, and create a name for it. Returns a LOCKED name var. CHECKS FOR DUPLICATES
+JsVar *jsvSetValueOfName(JsVar *name, JsVar *src); // Set the value of a child created with jsvAddName,jsvAddNamedChild. Returns the UNLOCKED name argument
+JsVar *jsvFindChildFromString(JsVar *parent, const char *name, bool createIfNotFound); // Non-recursive finding of child with name. Returns a LOCKED var
+JsVar *jsvFindChildFromVar(JsVar *parent, JsVar *childName, bool addIfNotFound); // Non-recursive finding of child with name. Returns a LOCKED var
+static inline JsVar *jsvFindChildFromStringRef(JsVarRef parentref, const char *name, bool addIfNotFound) { // Non-recursive finding of child with name. Returns a LOCKED var
+ JsVar *p = jsvLock(parentref);
+ JsVar *v = jsvFindChildFromString(p, name, addIfNotFound);
+ jsvUnLock(p);
+ return v;
+}
+static inline JsVar *jsvFindChildFromVarRef(JsVarRef parentref, JsVar *childName, bool addIfNotFound) { // Non-recursive finding of child with name. Returns a LOCKED var
+ JsVar *p = jsvLock(parentref);
+ JsVar *v = jsvFindChildFromVar(p, childName, addIfNotFound);
+ jsvUnLock(p);
+ return v;
+}
+/// Remove a child - note that the child MUST ACTUALLY BE A CHILD!
+void jsvRemoveChild(JsVar *parent, JsVar *child);
+void jsvRemoveAllChildren(JsVar *parent);
+static inline void jsvRemoveNamedChild(JsVar *parent, const char *name) {
+ JsVar *child = jsvFindChildFromString(parent, name, false);
+ if (child) {
+ jsvRemoveChild(parent, child);
+ jsvUnLock(child);
+ }
+}
+
+/// Get the named child of an object. If createChild!=0 then create the child
+JsVar *jsvObjectGetChild(JsVar *obj, const char *name, JsVarFlags createChild);
+/// Set the named child of an object, and return the child (so you can choose to unlock it if you want)
+JsVar *jsvObjectSetChild(JsVar *obj, const char *name, JsVar *child);
+
+int jsvGetChildren(JsVar *v); ///< number of children of a variable. also see jsvGetArrayLength and jsvGetLength
+JsVarInt jsvGetArrayLength(JsVar *arr); ///< Not the same as GetChildren, as it can be a sparse array
+JsVarInt jsvGetLength(JsVar *src); ///< General purpose length function. Does the 'right' thing
+size_t jsvCountJsVarsUsed(JsVar *v); ///< Count the amount of JsVars used. Mostly useful for debugging
+JsVar *jsvGetArrayItem(JsVar *arr, int index); ///< Get an item at the specified index in the array (and lock it)
+JsVar *jsvGetArrayIndexOf(JsVar *arr, JsVar *value, bool matchExact); ///< Get the index of the value in the array (matchExact==use pointer, not equality check)
+JsVarInt jsvArrayPushWithInitialSize(JsVar *arr, JsVar *value, JsVarInt initialValue); ///< Adds new elements to the end of an array, and returns the new length. initialValue is the item index when no items are currently in the array.
+JsVarInt jsvArrayPush(JsVar *arr, JsVar *value); ///< Adds a new element to the end of an array, and returns the new length
+JsVarInt jsvArrayPushAndUnLock(JsVar *arr, JsVar *value); ///< Adds a new element to the end of an array, unlocks it, and returns the new length
+JsVar *jsvArrayPop(JsVar *arr); ///< Removes the last element of an array, and returns that element (or 0 if empty). includes the NAME
+JsVar *jsvArrayPopFirst(JsVar *arr); ///< Removes the first element of an array, and returns that element (or 0 if empty) includes the NAME
+JsVar *jsvArrayGetLast(JsVar *arr); ///< Get the last element of an array (does not remove, unlike jsvArrayPop), and returns that element (or 0 if empty) includes the NAME
+JsVar *jsvArrayJoin(JsVar *arr, JsVar *filler); ///< Join all elements of an array together into a string
+void jsvArrayInsertBefore(JsVar *arr, JsVar *beforeIndex, JsVar *element); ///< Insert a new element before beforeIndex, DOES NOT UPDATE INDICES
+static inline bool jsvArrayIsEmpty(JsVar *arr) { assert(jsvIsArray(arr)); return !arr->firstChild; } ///< Return true is array is empty
+
+/** Write debug info for this Var out to the console */
+void jsvTrace(JsVarRef ref, int indent);
+
+/** Run a garbage collection sweep - return true if things have been freed */
+bool jsvGarbageCollect();
+
+/** Remove whitespace to the right of a string - on MULTIPLE LINES */
+JsVar *jsvStringTrimRight(JsVar *srcString);
+
+/** If v is the key of a function, return true if it is internal and shouldn't be visible to the user */
+bool jsvIsInternalFunctionKey(JsVar *v);
+
+/// If v is the key of an object, return true if it is internal and shouldn't be visible to the user
+bool jsvIsInternalObjectKey(JsVar *v);
+// --------------------------------------------------------------------------------------------
+typedef struct JsvStringIterator {
+ size_t charIdx; ///< index of character in var
+ size_t charsInVar; ///< total characters in var
+ size_t index; ///< index in string
+ JsVar *var; ///< current StringExt we're looking at
+} JsvStringIterator;
+
+// slight hack to enure we can use string iterator with const JsVars
+#define jsvStringIteratorNewConst(it,str,startIdx) jsvStringIteratorNew(it,(JsVar*)str,startIdx)
+
+void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, int startIdx);
+
+/// Gets the current character (or 0)
+static inline char jsvStringIteratorGetChar(JsvStringIterator *it) {
+ if (!it->var) return 0;
+ return it->var->varData.str[it->charIdx];
+}
+
+/// Gets the current (>=0) character (or -1)
+static inline int jsvStringIteratorGetCharOrMinusOne(JsvStringIterator *it) {
+ if (!it->var) return -1;
+ return (int)(unsigned char)it->var->varData.str[it->charIdx];
+}
+
+/// Do we have a character, or are we at the end?
+static inline bool jsvStringIteratorHasChar(JsvStringIterator *it) {
+ return it->charIdx < it->charsInVar;
+}
+
+/// Sets a character (will not extend the string - just overwrites)
+static inline void jsvStringIteratorSetChar(JsvStringIterator *it, char c) {
+ if (jsvStringIteratorHasChar(it))
+ it->var->varData.str[it->charIdx] = c;
+}
+
+/// Gets the current index in the string
+static inline size_t jsvStringIteratorGetIndex(JsvStringIterator *it) {
+ return it->index;
+}
+
+/// Move to next character
+void jsvStringIteratorNext(JsvStringIterator *it);
+
+/// Move to next character (this one is inlined where speed is needed)
+static inline void jsvStringIteratorNextInline(JsvStringIterator *it) {
+ it->charIdx++;
+ it->index++;
+ if (it->charIdx >= it->charsInVar) {
+ it->charIdx -= it->charsInVar;
+ if (it->var && it->var->lastChild) {
+ JsVar *next = jsvLock(it->var->lastChild);
+ jsvUnLock(it->var);
+ it->var = next;
+ it->charsInVar = jsvGetCharactersInVar(it->var);
+ } else {
+ jsvUnLock(it->var);
+ it->var = 0;
+ it->charsInVar = 0;
+ }
+ }
+}
+
+
+/// Go to the end of the string iterator - for use with jsvStringIteratorAppend
+void jsvStringIteratorGotoEnd(JsvStringIterator *it);
+
+/// Append a character TO THE END of a string iterator
+void jsvStringIteratorAppend(JsvStringIterator *it, char ch);
+
+static inline void jsvStringIteratorFree(JsvStringIterator *it) {
+ jsvUnLock(it->var);
+}
+
+// --------------------------------------------------------------------------------------------
+typedef struct JsArrayIterator {
+ JsVar *var;
+} JsArrayIterator;
+
+static inline void jsvArrayIteratorNew(JsArrayIterator *it, JsVar *arr) {
+ assert(jsvIsArray(arr));
+ it->var = arr->firstChild ? jsvLock(arr->firstChild) : 0;
+}
+
+/// Gets the current array element (or 0)
+static inline JsVar *jsvArrayIteratorGetElement(JsArrayIterator *it) {
+ if (!it->var) return 0; // end of array
+ return it->var->firstChild ? jsvLock(it->var->firstChild) : 0; // might even be undefined
+}
+
+/// Gets the current array element index (or 0)
+static inline JsVar *jsvArrayIteratorGetIndex(JsArrayIterator *it) {
+ if (!it->var) return 0;
+ return jsvLockAgain(it->var);
+}
+
+/// Do we have a character, or are we at the end?
+static inline bool jsvArrayIteratorHasElement(JsArrayIterator *it) {
+ return it->var != 0;
+}
+
+/// Move to next character
+static inline void jsvArrayIteratorNext(JsArrayIterator *it) {
+ if (it->var) {
+ JsVarRef next = it->var->nextSibling;
+ jsvUnLock(it->var);
+ it->var = next ? jsvLock(next) : 0;
+ }
+}
+
+static inline void jsvArrayIteratorFree(JsArrayIterator *it) {
+ jsvUnLock(it->var);
+}
+// --------------------------------------------------------------------------------------------
+typedef struct JsObjectIterator {
+ JsVar *var;
+} JsObjectIterator;
+
+static inline void jsvObjectIteratorNew(JsObjectIterator *it, JsVar *obj) {
+ assert(jsvIsObject(obj) || jsvIsFunction(obj));
+ it->var = obj->firstChild ? jsvLock(obj->firstChild) : 0;
+}
+
+/// Gets the current object element key (or 0)
+static inline JsVar *jsvObjectIteratorGetKey(JsObjectIterator *it) {
+ if (!it->var) return 0; // end of object
+ return jsvLockAgain(it->var);
+}
+
+/// Gets the current object element value (or 0)
+static inline JsVar *jsvObjectIteratorGetValue(JsObjectIterator *it) {
+ if (!it->var) return 0; // end of object
+ return it->var->firstChild ? jsvLock(it->var->firstChild) : 0; // might even be undefined
+}
+
+/// Do we have a key, or are we at the end?
+static inline bool jsvObjectIteratorHasElement(JsObjectIterator *it) {
+ return it->var != 0;
+}
+
+/// Move to next character
+static inline void jsvObjectIteratorNext(JsObjectIterator *it) {
+ if (it->var) {
+ JsVarRef next = it->var->nextSibling;
+ jsvUnLock(it->var);
+ it->var = next ? jsvLock(next) : 0;
+ }
+}
+
+static inline void jsvObjectIteratorFree(JsObjectIterator *it) {
+ jsvUnLock(it->var);
+}
+// --------------------------------------------------------------------------------------------
+typedef struct JsvArrayBufferIterator {
+ JsvStringIterator it;
+ JsVarDataArrayBufferViewType type;
+ JsVarInt byteLength;
+ JsVarInt byteOffset;
+ JsVarInt index;
+ bool hasAccessedElement;
+} JsvArrayBufferIterator;
+
+void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, JsVarInt index);
+JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it);
+JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it);
+JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it);
+void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value);
+void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt value);
+JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it);
+bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it);
+void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it);
+void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it);
+// --------------------------------------------------------------------------------------------
+union JsvIteratorUnion {
+ JsvStringIterator str;
+ JsObjectIterator obj;
+ JsArrayIterator arr;
+ JsvArrayBufferIterator buf;
+};
+
+/** General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */
+typedef struct JsvIterator {
+ enum {JSVI_STRING, JSVI_ARRAY, JSVI_OBJECT, JSVI_ARRAYBUFFER } type;
+ union JsvIteratorUnion it;
+} JsvIterator;
+
+void jsvIteratorNew(JsvIterator *it, JsVar *obj);
+JsVar *jsvIteratorGetKey(JsvIterator *it);
+JsVar *jsvIteratorGetValue(JsvIterator *it);
+JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it);
+bool jsvIteratorHasElement(JsvIterator *it);
+void jsvIteratorNext(JsvIterator *it);
+void jsvIteratorFree(JsvIterator *it);
+
+
+
+#endif /* JSVAR_H_ */
diff --git a/components/external/espruino/src/jswrap_array.c b/components/external/espruino/src/jswrap_array.c
new file mode 100644
index 0000000000..ad780a8dc9
--- /dev/null
+++ b/components/external/espruino/src/jswrap_array.c
@@ -0,0 +1,328 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript methods for Arrays
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_array.h"
+#include "jsparse.h"
+
+#define min(a,b) (((a)<(b))?(a):(b))
+#define max(a,b) (((a)>(b))?(a):(b))
+
+
+/*JSON{ "type":"class",
+ "class" : "Array",
+ "check" : "jsvIsArray(var)",
+ "description" : ["This is the built-in JavaScript class for arrays.",
+ "Arrays can be defined with ```[]```, ```new Array()```, or ```new Array(length)```" ]
+}*/
+
+/*JSON{ "type":"constructor", "class": "Array", "name": "Array",
+ "description" : "Create an Array. Either give it one integer argument (>=0) which is the length of the array, or any number of arguments ",
+ "generate" : "jswrap_array_constructor",
+ "params" : [ [ "args", "JsVarArray", "The length of the array OR any number of items to add to the array" ] ],
+ "return" : [ "JsVar", "An Array" ]
+
+}*/
+JsVar *jswrap_array_constructor(JsVar *args) {
+ assert(args);
+ if (jsvGetArrayLength(args)==1) {
+ JsVar *firstArg = jsvSkipNameAndUnLock(jsvArrayGetLast(args)); // also the first!
+ if (jsvIsInt(firstArg) && jsvGetInteger(firstArg)>=0) {
+ JsVarInt count = jsvGetInteger(firstArg);
+ // we cheat - no need to fill the array - just the last element
+ if (count>0) {
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ if (!arr) return 0; // out of memory
+ JsVar *idx = jsvMakeIntoVariableName(jsvNewFromInteger(count-1), 0);
+ if (idx) { // could be out of memory
+ jsvAddName(arr, idx);
+ jsvUnLock(idx);
+ }
+ jsvUnLock(firstArg);
+ return arr;
+ }
+ }
+ jsvUnLock(firstArg);
+ }
+ // Otherwise, we just return the array!
+ return jsvLockAgain(args);
+}
+
+/*JSON{ "type":"method", "class": "Array", "name" : "contains",
+ "description" : "Return true if this array contains the given value",
+ "generate" : "jswrap_array_contains",
+ "params" : [ [ "value", "JsVar", "The value to check for"] ],
+ "return" : ["bool", "Whether value is in the array or not"]
+}*/
+bool jswrap_array_contains(JsVar *parent, JsVar *value) {
+ // ArrayIndexOf will return 0 if not found
+ JsVar *arrElement = jsvGetArrayIndexOf(parent, value, false/*not exact*/);
+ bool contains = arrElement!=0;
+ jsvUnLock(arrElement);
+ return contains;
+}
+
+/*JSON{ "type":"method", "class": "Array", "name" : "indexOf",
+ "description" : "Return the index of the value in the array, or -1",
+ "generate" : "jswrap_array_indexOf",
+ "params" : [ [ "value", "JsVar", "The value to check for"] ],
+ "return" : ["JsVar", "the index of the value in the array, or -1"]
+}*/
+JsVar *jswrap_array_indexOf(JsVar *parent, JsVar *value) {
+ JsVar *idxName = jsvGetArrayIndexOf(parent, value, false/*not exact*/);
+ // but this is the name - we must turn it into a var
+ if (idxName == 0) return jsvNewFromInteger(-1); // not found!
+ JsVar *idx = jsvCopyNameOnly(idxName, false/* no children */, false/* Make sure this is not a name*/);
+ jsvUnLock(idxName);
+ return idx;
+}
+
+/*JSON{ "type":"method", "class": "Array", "name" : "join",
+ "description" : "Join all elements of this array together into one string, using 'separator' between them. eg. ```[1,2,3].join(' ')=='1 2 3'```",
+ "generate" : "jswrap_array_join",
+ "params" : [ [ "separator", "JsVar", "The separator"] ],
+ "return" : ["JsVar", "A String representing the Joined array"]
+}*/
+JsVar *jswrap_array_join(JsVar *parent, JsVar *filler) {
+ if (jsvIsUndefined(filler))
+ filler = jsvNewFromString(","); // the default it seems
+ else
+ filler = jsvAsString(filler, false);
+ if (!filler) return 0; // out of memory
+ JsVar *str = jsvArrayJoin(parent, filler);
+ jsvUnLock(filler);
+ return str;
+}
+
+/*JSON{ "type":"method", "class": "Array", "name" : "push",
+ "description" : "Push a new value onto the end of this array'",
+ "generate_full" : "jsvArrayPush(parent, value)",
+ "params" : [ [ "value", "JsVar", "The value to add"] ],
+ "return" : ["int", "The new size of the array"]
+}*/
+
+/*JSON{ "type":"method", "class": "Array", "name" : "pop",
+ "description" : "Pop a new value off of the end of this array",
+ "generate_full" : "jsvArrayPop(parent)",
+ "return" : ["JsVar", "The value that is popped off"]
+}*/
+
+JsVar *_jswrap_array_map_or_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar, bool isMap) {
+ if (!jsvIsFunction(funcVar)) {
+ jsError("Array.map's first argument should be a function");
+ return 0;
+ }
+ if (!jsvIsUndefined(thisVar) && !jsvIsObject(thisVar)) {
+ jsError("Arraymap's second argument should be undefined, or an object");
+ return 0;
+ }
+ JsVar *array = 0;
+ if (isMap)
+ array = jsvNewWithFlags(JSV_ARRAY);
+ if (array || !isMap) {
+ JsVarRef childRef = parent->firstChild;
+ while (childRef) {
+ JsVar *child = jsvLock(childRef);
+ if (jsvIsInt(child)) {
+ JsVar *args[3], *mapped;
+ args[0] = jsvLock(child->firstChild);
+ // child is a variable name, create a new variable for the index
+ args[1] = jsvNewFromInteger(jsvGetInteger(child));
+ args[2] = parent;
+ mapped = jspeFunctionCall(funcVar, 0, thisVar, false, 3, args);
+ jsvUnLock(args[0]);
+ jsvUnLock(args[1]);
+ if (mapped) {
+ if (isMap) {
+ JsVar *name = jsvCopyNameOnly(child, false/*linkChildren*/, true/*keepAsName*/);
+ if (name) { // out of memory?
+ name->firstChild = jsvGetRef(jsvRef(mapped));
+ jsvAddName(array, name);
+ jsvUnLock(name);
+ }
+ }
+ jsvUnLock(mapped);
+ }
+ }
+ childRef = child->nextSibling;
+ jsvUnLock(child);
+ }
+ }
+ return array;
+}
+
+/*JSON{ "type":"method", "class": "Array", "name" : "map",
+ "description" : "Return an array which is made from the following: ```A.map(function) = [function(A[0]), function(A[1]), ...]```",
+ "generate" : "jswrap_array_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", "The value that is popped off"]
+}*/
+JsVar *jswrap_array_map(JsVar *parent, JsVar *funcVar, JsVar *thisVar) {
+ return _jswrap_array_map_or_forEach(parent, funcVar, thisVar, true);
+}
+
+
+/*JSON{ "type":"method", "class": "Array", "name" : "splice",
+ "description" : "Both remove and add items to an array",
+ "generate" : "jswrap_array_splice",
+ "params" : [ [ "index", "int", "Index at which to start changing the array. If negative, will begin that many elements from the end"],
+ [ "howMany", "JsVar", "An integer indicating the number of old array elements to remove. If howMany is 0, no elements are removed."],
+ [ "element1", "JsVar", "A new item to add (optional)" ],
+ [ "element2", "JsVar", "A new item to add (optional)" ],
+ [ "element3", "JsVar", "A new item to add (optional)" ],
+ [ "element4", "JsVar", "A new item to add (optional)" ],
+ [ "element5", "JsVar", "A new item to add (optional)" ],
+ [ "element6", "JsVar", "A new item to add (optional)" ] ],
+ "return" : ["JsVar", "An array containing the removed elements. If only one element is removed, an array of one element is returned."]
+}*/
+JsVar *jswrap_array_splice(JsVar *parent, JsVarInt index, JsVar *howManyVar, JsVar *element1, JsVar *element2, JsVar *element3, JsVar *element4, JsVar *element5, JsVar *element6) {
+ JsVarInt len = jsvGetArrayLength(parent);
+ if (index<0) index+=len;
+ if (index<0) index=0;
+ if (index>len) index=len;
+ JsVarInt howMany = len; // how many to delete!
+ if (jsvIsInt(howManyVar)) howMany = jsvGetInteger(howManyVar);
+ if (howMany > len-index) howMany = len-index;
+ JsVarInt newItems = 0;
+ if (element1) newItems++;
+ if (element2) newItems++;
+ if (element3) newItems++;
+ if (element4) newItems++;
+ if (element5) newItems++;
+ if (element6) newItems++;
+ JsVarInt shift = newItems-howMany;
+
+ bool needToAdd = false;
+ JsVar *result = jsvNewWithFlags(JSV_ARRAY);
+
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, parent);
+ while (jsvArrayIteratorHasElement(&it) && !needToAdd) {
+ bool goToNext = true;
+ JsVar *idxVar = jsvArrayIteratorGetIndex(&it);
+ if (idxVar && jsvIsInt(idxVar)) {
+ JsVarInt idx = jsvGetInteger(idxVar);
+ if (idx<index) {
+ // do nothing...
+ } else if (idx<index+howMany) { // must delete
+ if (result) { // append to result array
+ JsVar *el = jsvArrayIteratorGetElement(&it);
+ jsvArrayPushAndUnLock(result, el);
+ }
+ // delete
+ goToNext = false;
+ JsVar *toRemove = jsvArrayIteratorGetIndex(&it);
+ jsvArrayIteratorNext(&it);
+ jsvRemoveChild(parent, toRemove);
+ jsvUnLock(toRemove);
+ } else { // we're greater than the amount we need to remove now
+ needToAdd = true;
+ goToNext = false;
+ }
+ }
+ jsvUnLock(idxVar);
+ if (goToNext) jsvArrayIteratorNext(&it);
+ }
+ // now we add everything
+ JsVar *beforeIndex = jsvArrayIteratorGetIndex(&it);
+ if (element1) jsvArrayInsertBefore(parent, beforeIndex, element1);
+ if (element2) jsvArrayInsertBefore(parent, beforeIndex, element2);
+ if (element3) jsvArrayInsertBefore(parent, beforeIndex, element3);
+ if (element4) jsvArrayInsertBefore(parent, beforeIndex, element4);
+ if (element5) jsvArrayInsertBefore(parent, beforeIndex, element5);
+ if (element6) jsvArrayInsertBefore(parent, beforeIndex, element6);
+ jsvUnLock(beforeIndex);
+ // And finally renumber
+ while (jsvArrayIteratorHasElement(&it)) {
+ JsVar *idxVar = jsvArrayIteratorGetIndex(&it);
+ if (idxVar && jsvIsInt(idxVar)) {
+ jsvSetInteger(idxVar, jsvGetInteger(idxVar)+shift);
+ }
+ jsvUnLock(idxVar);
+ jsvArrayIteratorNext(&it);
+ }
+ // free
+ jsvArrayIteratorFree(&it);
+
+ return result;
+}
+
+
+/*JSON{ "type":"method", "class": "Array", "name" : "slice",
+ "description" : "Return a copy of a portion of the calling array",
+ "generate" : "jswrap_array_slice",
+ "params" : [ [ "start", "JsVar", "Start index"],
+ [ "end", "JsVar", "End index (optional)"] ],
+ "return" : ["JsVar", "A new array"]
+}*/
+JsVar *jswrap_array_slice(JsVar *parent, JsVar *startVar, JsVar *endVar) {
+ JsVarInt len = jsvGetArrayLength(parent);
+ JsVarInt start = 0;
+ JsVarInt end = len;
+
+ if (!jsvIsUndefined(startVar))
+ start = jsvGetInteger(startVar);
+
+ if (!jsvIsUndefined(endVar))
+ end = jsvGetInteger(endVar);
+
+ JsVarInt k = 0;
+ JsVarInt final = len;
+ JsVar *array = jsvNewWithFlags(JSV_ARRAY);
+
+ if (!array) return 0;
+
+ if (start<0) k = max((len + start), 0);
+ else k = min(start, len);
+
+ if (end<0) final = max((len + end), 0);
+ else final = min(end, len);
+
+ bool isDone = false;
+
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, parent);
+
+ while (jsvArrayIteratorHasElement(&it) && !isDone) {
+ JsVarInt idx = jsvGetInteger(jsvArrayIteratorGetIndex(&it));
+
+ if (idx < k) {
+ jsvArrayIteratorNext(&it);
+ } else {
+ if (k < final) {
+ jsvArrayPushAndUnLock(array, jsvArrayIteratorGetElement(&it));
+ jsvArrayIteratorNext(&it);
+ k++;
+ } else {
+ isDone = true;
+ }
+ }
+ }
+
+ jsvArrayIteratorFree(&it);
+
+ return array;
+}
+
+
+/*JSON{ "type":"method", "class": "Array", "name" : "forEach",
+ "description" : "Executes a provided function once per array element.",
+ "generate" : "jswrap_array_forEach",
+ "params" : [ [ "function", "JsVar", "Function to be executed"] ,
+ [ "thisArg", "JsVar", "if specified, the function is called with 'this' set to thisArg (optional)"] ]
+}*/
+void jswrap_array_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar) {
+ _jswrap_array_map_or_forEach(parent, funcVar, thisVar, false);
+}
diff --git a/components/external/espruino/src/jswrap_array.h b/components/external/espruino/src/jswrap_array.h
new file mode 100644
index 0000000000..69dbeed53c
--- /dev/null
+++ b/components/external/espruino/src/jswrap_array.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript methods for Arrays
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_array_constructor(JsVar *args);
+bool jswrap_array_contains(JsVar *parent, JsVar *value);
+JsVar *jswrap_array_indexOf(JsVar *parent, JsVar *value);
+JsVar *jswrap_array_join(JsVar *parent, JsVar *filler);
+JsVar *jswrap_array_map(JsVar *parent, JsVar *funcVar, JsVar *thisVar);
+JsVar *jswrap_array_slice(JsVar *parent, JsVar *startVar, JsVar *endVar);
+JsVar *jswrap_array_splice(JsVar *parent, JsVarInt index, JsVar *howManyVar, JsVar *element1, JsVar *element2, JsVar *element3, JsVar *element4, JsVar *element5, JsVar *element6);
+void jswrap_array_forEach(JsVar *parent, JsVar *funcVar, JsVar *thisVar);
diff --git a/components/external/espruino/src/jswrap_arraybuffer.c b/components/external/espruino/src/jswrap_arraybuffer.c
new file mode 100644
index 0000000000..49be45c1a5
--- /dev/null
+++ b/components/external/espruino/src/jswrap_arraybuffer.c
@@ -0,0 +1,315 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript methods for ArrayBuffers
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_arraybuffer.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"class",
+ "class" : "ArrayBuffer",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_ARRAYBUFFER",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for array buffers." ]
+}*/
+
+/*JSON{ "type":"class",
+ "class" : "ArrayBufferView",
+ "description" : ["This is the built-in JavaScript class that is the prototype for Uint8Array / Float32Array / etc" ]
+}*/
+
+/*JSON{ "type":"class",
+ "class" : "Uint8Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_UINT8",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Int8Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_INT8",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Uint16Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_UINT16",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Int16Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_INT16",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Uint32Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_UINT32",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Int32Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_INT32",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Float32Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_FLOAT32",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Float64Array", "prototype" : "ArrayBufferView",
+ "check" : "jsvIsArrayBuffer(var) && var->varData.arraybuffer.type==ARRAYBUFFERVIEW_FLOAT64",
+ "not_real_object" : "Don't treat this as a real object - it's handled differently internally",
+ "description" : ["This is the built-in JavaScript class for a typed array.",
+ "Instantiate this in order to efficiently store arrays of data (Espruino's normal arrays store data in a map, which is inefficient for non-sparse arrays). " ]
+}*/
+
+
+/*JSON{ "type":"constructor", "class": "ArrayBuffer", "name": "ArrayBuffer",
+ "description" : "Create an Array Buffer object",
+ "generate" : "jswrap_arraybuffer_constructor",
+ "params" : [ [ "byteLength", "int", "The length in Bytes" ] ],
+ "return" : [ "JsVar", "An ArrayBuffer object" ]
+
+}*/
+JsVar *jswrap_arraybuffer_constructor(JsVarInt byteLength) {
+ if (byteLength <= 0 || byteLength>65535) {
+ jsError("Invalid length for ArrayBuffer\n");
+ return 0;
+ }
+ if (byteLength > JSV_ARRAYBUFFER_MAX_LENGTH) {
+ jsError("ArrayBuffer too long\n");
+ return 0;
+ }
+ JsVar *arrData = jsvNewStringOfLength((unsigned int)byteLength);
+ if (!arrData) return 0;
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAYBUFFER);
+ if (!arr) {
+ jsvUnLock(arrData);
+ return 0;
+ }
+ arr->firstChild = jsvGetRef(jsvRef(arrData));
+ jsvUnLock(arrData);
+ arr->varData.arraybuffer.type = ARRAYBUFFERVIEW_ARRAYBUFFER;
+ arr->varData.arraybuffer.byteOffset = 0;
+ arr->varData.arraybuffer.length = (unsigned short)byteLength;
+ return arr;
+}
+
+
+/*
+ * Potential invocations:
+ * Uint8Array Uint8Array(unsigned long length);
+ * Uint8Array Uint8Array(TypedArray array);
+ * Uint8Array Uint8Array(sequence<type> array);
+ * Uint8Array Uint8Array(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length);
+ */
+
+/*JSON{ "type":"constructor", "class": "Uint8Array", "name": "Uint8Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_UINT8, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Int8Array", "name": "Int8Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_INT8, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Uint16Array", "name": "Uint16Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_UINT16, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Int16Array", "name": "Int16Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_INT16, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Uint32Array", "name": "Uint32Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_UINT32, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Int32Array", "name": "Int32Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_INT32, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Float32Array", "name": "Float32Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_FLOAT32, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+/*JSON{ "type":"constructor", "class": "Float64Array", "name": "Float64Array",
+ "description" : "Create a typed array based on the given input. Either an existing Array Buffer, an Integer as a Length, or a simple array",
+ "generate_full" : "jswrap_typedarray_constructor(ARRAYBUFFERVIEW_FLOAT64, arr, jsvGetInteger(byteOffset), jsvGetInteger(length))",
+ "params" : [ [ "arr", "JsVar", "The array or typed array to base this off, or an integer which is the array length" ],
+ [ "byteOffset", "int", "The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer)" ],
+ [ "length", "int", "The length (ONLY IF the first argument was an ArrayBuffer)" ] ],
+ "return" : [ "JsVar", "A typed array" ]
+}*/
+
+
+
+JsVar *jswrap_typedarray_constructor(JsVarDataArrayBufferViewType type, JsVar *arr, JsVarInt byteOffset, JsVarInt length) {
+ JsVar *arrayBuffer = 0;
+ if (jsvIsArrayBuffer(arr)) {
+ arrayBuffer = jsvLockAgain(arr);
+ } else if (jsvIsInt(arr)) {
+ length = jsvGetInteger(arr);
+ byteOffset = 0;
+ arrayBuffer = jswrap_arraybuffer_constructor(JSV_ARRAYBUFFER_GET_SIZE(type)*length);
+ } else if (jsvIsArray(arr)) {
+ length = jsvGetArrayLength(arr);
+ byteOffset = 0;
+ arrayBuffer = jswrap_arraybuffer_constructor(JSV_ARRAYBUFFER_GET_SIZE(type)*length);
+ // later on we'll populate this
+ }
+ if (!arrayBuffer) {
+ jsError("Unsupported first argument\n");
+ return 0;
+ }
+ if (length<=0) length = (JsVarInt)jsvGetArrayBufferLength(arrayBuffer) / JSV_ARRAYBUFFER_GET_SIZE(type);
+ 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;
+ typedArr->firstChild = jsvGetRef(jsvRef(arrayBuffer));
+
+ if (jsvIsArray(arr)) {
+ // if we were given an array, populate this ArrayBuffer
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, arr);
+ while (jsvArrayIteratorHasElement(&it)) {
+ JsVar *idx = jsvArrayIteratorGetIndex(&it);
+ if (jsvIsInt(idx)) {
+ JsVar *val = jsvArrayIteratorGetElement(&it);
+ jsvArrayBufferSet(typedArr, jsvGetInteger(idx), val);
+ jsvUnLock(val);
+ }
+ jsvUnLock(idx);
+ jsvArrayIteratorNext(&it);
+ }
+ jsvArrayIteratorFree(&it);
+ }
+ }
+ jsvUnLock(arrayBuffer);
+ return typedArr;
+}
+
+
+/*JSON{ "type":"property", "class": "ArrayBufferView", "name": "buffer",
+ "description" : "The buffer this view references",
+ "generate_full" : "jsvLock(parent->firstChild)",
+ "return" : [ "JsVar", "An ArrayBuffer object" ]
+}*/
+/*JSON{ "type":"property", "class": "ArrayBufferView", "name": "byteLength",
+ "description" : "The length, in bytes, of the view",
+ "generate_full" : "parent->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(parent->varData.arraybuffer.type)",
+ "return" : [ "int", "The Length" ]
+}*/
+/*JSON{ "type":"property", "class": "ArrayBufferView", "name": "byteOffset",
+ "description" : "The offset, in bytes, to the first byte of the view within the ArrayBuffer",
+ "generate_full" : "parent->varData.arraybuffer.byteOffset",
+ "return" : [ "int", "The byte Offset" ]
+}*/
+
+/*JSON{ "type":"method", "class": "ArrayBufferView", "name": "interpolate",
+ "description" : "Interpolate between two adjacent values in the Typed Array",
+ "generate" : "jswrap_arraybufferview_interpolate",
+ "params" : [ [ "index", "float", "Floating point index to access" ] ],
+ "return" : [ "float", "The result of interpolating between (int)index and (int)(index+1)" ]
+}*/
+JsVarFloat jswrap_arraybufferview_interpolate(JsVar *parent, JsVarFloat findex) {
+ int idx = (int)findex;
+ JsVarFloat a = findex-idx;
+ JsvArrayBufferIterator it;
+ jsvArrayBufferIteratorNew(&it, parent, idx);
+ JsVarFloat fa = jsvArrayBufferIteratorGetFloatValue(&it);
+ jsvArrayBufferIteratorNext(&it);
+ JsVarFloat fb = jsvArrayBufferIteratorGetFloatValue(&it);
+ jsvArrayBufferIteratorFree(&it);
+ return fa*(1-a) + fb*a;
+}
+
+/*JSON{ "type":"method", "class": "ArrayBufferView", "name": "interpolate2d",
+ "description" : "Interpolate between two adjacent values in the Typed Array",
+ "generate" : "jswrap_arraybufferview_interpolate2d",
+ "params" : [ [ "width", "int", "Integer 'width' of 2d array" ],
+ [ "x", "float", "Floating point X index to access" ],
+ [ "y", "float", "Floating point Y index to access" ] ],
+ "return" : [ "float", "The result of interpolating in 2d between the 4 surrounding cells" ]
+}*/
+JsVarFloat jswrap_arraybufferview_interpolate2d(JsVar *parent, JsVarInt width, JsVarFloat x, JsVarFloat y) {
+ int yidx = (int)y;
+ JsVarFloat ay = y-yidx;
+
+ JsVarFloat findex = x + (JsVarFloat)(yidx*width);
+ int idx = (int)findex;
+ JsVarFloat ax = findex-idx;
+
+ JsvArrayBufferIterator it;
+ jsvArrayBufferIteratorNew(&it, parent, idx);
+
+ JsVarFloat xa,xb;
+ int i;
+
+ xa = jsvArrayBufferIteratorGetFloatValue(&it);
+ jsvArrayBufferIteratorNext(&it);
+ xb = jsvArrayBufferIteratorGetFloatValue(&it);
+ JsVarFloat ya = xa*(1-ax) + xb*ax;
+
+ for (i=1;i<width;i++) jsvArrayBufferIteratorNext(&it);
+
+ xa = jsvArrayBufferIteratorGetFloatValue(&it);
+ jsvArrayBufferIteratorNext(&it);
+ xb = jsvArrayBufferIteratorGetFloatValue(&it);
+ jsvArrayBufferIteratorFree(&it);
+ JsVarFloat yb = xa*(1-ax) + xb*ax;
+
+ return ya*(1-ay) + yb*ay;
+}
diff --git a/components/external/espruino/src/jswrap_arraybuffer.h b/components/external/espruino/src/jswrap_arraybuffer.h
new file mode 100644
index 0000000000..e318aa992c
--- /dev/null
+++ b/components/external/espruino/src/jswrap_arraybuffer.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript methods and functions in the global namespace
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_arraybuffer_constructor(JsVarInt byteLength);
+JsVar *jswrap_typedarray_constructor(JsVarDataArrayBufferViewType type, JsVar *arr, JsVarInt byteOffset, JsVarInt length);
+JsVarFloat jswrap_arraybufferview_interpolate(JsVar *parent, JsVarFloat index);
+JsVarFloat jswrap_arraybufferview_interpolate2d(JsVar *parent, JsVarInt width, JsVarFloat x, JsVarFloat y);
diff --git a/components/external/espruino/src/jswrap_functions.c b/components/external/espruino/src/jswrap_functions.c
new file mode 100644
index 0000000000..4445c98537
--- /dev/null
+++ b/components/external/espruino/src/jswrap_functions.c
@@ -0,0 +1,102 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript methods and functions in the global namespace
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_functions.h"
+#include "jslex.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"variable", "name" : "arguments",
+ "description" : "A variable containing the arguments given to the function",
+ "generate" : "jswrap_arguments",
+ "return" : ["JsVar", "An array containing all the arguments given to the function"]
+}*/
+extern JsExecInfo execInfo;
+JsVar *jswrap_arguments() {
+ JsVar *scope = 0;
+ if (execInfo.scopeCount>0)
+ scope = jsvLock(execInfo.scopes[execInfo.scopeCount-1]);
+ if (!jsvIsFunction(scope)) {
+ jsvUnLock(scope);
+ jsError("Can only use 'arguments' variable inside a function");
+ return 0;
+ }
+
+ JsVar *args = jsvNewWithFlags(JSV_ARRAY);
+ if (!args) return 0; // out of memory
+
+ JsObjectIterator it;
+ jsvObjectIteratorNew(&it, scope);
+ while (jsvObjectIteratorHasElement(&it)) {
+ JsVar *idx = jsvObjectIteratorGetKey(&it);
+ if (jsvIsFunctionParameter(idx)) {
+ JsVar *val = jsvSkipOneName(idx);
+ jsvArrayPushAndUnLock(args, val);
+ }
+ jsvUnLock(idx);
+ jsvObjectIteratorNext(&it);
+ }
+ jsvObjectIteratorFree(&it);
+ jsvUnLock(scope);
+
+ return args;
+}
+
+
+/*JSON{ "type":"function", "name" : "eval",
+ "description" : "Evaluate a string containing JavaScript code",
+ "generate" : "jswrap_eval",
+ "params" : [ [ "code", "JsVar", ""] ],
+ "return" : ["JsVar", "The result of evaluating the string"]
+}*/
+JsVar *jswrap_eval(JsVar *v) {
+ if (!v) return 0;
+ JsVar *s = jsvAsString(v, false); // get as a string
+ JsVar *result = jspEvaluateVar(jsiGetParser(), s, 0);
+ jsvUnLock(s);
+ return result;
+}
+
+/*JSON{ "type":"function", "name" : "parseInt",
+ "description" : "Convert a string representing a number into an integer",
+ "generate" : "jswrap_parseInt",
+ "params" : [ [ "string", "JsVar", ""],
+ [ "radix", "JsVar", "The Radix of the string (optional)"] ],
+ "return" : ["JsVar", "The integer value of the string (or NaN)"]
+}*/
+JsVar *jswrap_parseInt(JsVar *v, JsVar *radixVar) {
+ int radix = 0/*don't force radix*/;
+ if (jsvIsNumeric(radixVar))
+ radix = (int)jsvGetInteger(radixVar);
+
+ char buffer[JS_NUMBER_BUFFER_SIZE];
+ jsvGetString(v, buffer, JS_NUMBER_BUFFER_SIZE);
+ bool hasError;
+ JsVarInt i = stringToIntWithRadix(buffer, radix, &hasError);
+ if (hasError) return jsvNewFromFloat(NAN);
+ return jsvNewFromInteger(i);
+}
+
+/*JSON{ "type":"function", "name" : "parseFloat",
+ "description" : "Convert a string representing a number into an float",
+ "generate" : "jswrap_parseFloat",
+ "params" : [ [ "string", "JsVar", ""] ],
+ "return" : ["float", "The value of the string"]
+}*/
+JsVarFloat jswrap_parseFloat(JsVar *v) {
+ char buffer[JS_NUMBER_BUFFER_SIZE];
+ jsvGetString(v, buffer, JS_NUMBER_BUFFER_SIZE);
+ return stringToFloat(buffer);
+}
diff --git a/components/external/espruino/src/jswrap_functions.h b/components/external/espruino/src/jswrap_functions.h
new file mode 100644
index 0000000000..dae42cab25
--- /dev/null
+++ b/components/external/espruino/src/jswrap_functions.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript methods and functions in the global namespace
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_arguments();
+JsVar *jswrap_eval(JsVar *v);
+JsVar *jswrap_parseInt(JsVar *v, JsVar *radixVar);
+JsVarFloat jswrap_parseFloat(JsVar *v);
diff --git a/components/external/espruino/src/jswrap_interactive.c b/components/external/espruino/src/jswrap_interactive.c
new file mode 100644
index 0000000000..a2e86aaf08
--- /dev/null
+++ b/components/external/espruino/src/jswrap_interactive.c
@@ -0,0 +1,203 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript methods and functions for the interactive shell
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_interactive.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"function", "name" : "setBusyIndicator",
+ "description" : "When Espruino is busy, set the pin specified here high. Set this to undefined to disable the feature.",
+ "generate" : "jswrap_interface_setBusyIndicator",
+ "params" : [ [ "pin", "JsVar", ""] ]
+}*/
+void jswrap_interface_setBusyIndicator(JsVar *pinVar) {
+ Pin oldPin = pinBusyIndicator;
+ pinBusyIndicator = jshGetPinFromVar(pinVar);
+ // we should be busy right now anyway, so set stuff up right
+ if (pinBusyIndicator!=oldPin) {
+ if (oldPin!=PIN_UNDEFINED) jshPinOutput(oldPin, 0);
+ if (pinBusyIndicator!=PIN_UNDEFINED) jshPinOutput(pinBusyIndicator, 1);
+ }
+}
+
+/*JSON{ "type":"function", "name" : "setSleepIndicator",
+ "description" : "When Espruino is asleep, set the pin specified here high. Set this to undefined to disable the feature.",
+ "generate" : "jswrap_interface_setSleepIndicator",
+ "params" : [ [ "pin", "JsVar", ""] ]
+}*/
+void jswrap_interface_setSleepIndicator(JsVar *pinVar) {
+ pinSleepIndicator = jshGetPinFromVar(pinVar);
+}
+
+/*JSON{ "type":"function", "name" : "setDeepSleep",
+ "description" : [ "Set whether we can enter deep sleep mode, which reduces power consumption to around 1mA. This only works on the Espruino Board.",
+ "Deep Sleep is currently beta. Espruino will only enter Deep Sleep when there are no timers and it is not connected to USB. USB will not wake Espruino from Deep Sleep, nor will Serial comms (only setWatch will wake it). The System Timer will also pause." ],
+ "generate" : "jswrap_interface_setDeepSleep",
+ "params" : [ [ "sleep", "bool", ""] ]
+}*/
+void jswrap_interface_setDeepSleep(bool sleep) {
+ allowDeepSleep = sleep;
+}
+
+
+/*JSON{ "type":"function", "name" : "trace", "ifndef" : "SAVE_ON_FLASH",
+ "description" : "Output debugging information",
+ "generate" : "jswrap_interface_trace",
+ "params" : [ [ "root", "JsVarName", "The symbol to output (optional). If nothing is specified, everything will be output"] ]
+}*/
+void jswrap_interface_trace(JsVar *root) {
+ if (jsvIsUndefined(root)) {
+ jsvTrace(jsvGetRef(jsiGetParser()->root), 0);
+ } else {
+ jsvTrace(jsvGetRef(root), 0);
+ }
+}
+
+/*XXX{ "type":"function", "name" : "dotty",
+ "description" : "Output dotty-format graph of debugging information",
+ "generate_full" : "jsvDottyOutput()"
+}*/
+/*JSON{ "type":"function", "name" : "dump",
+ "description" : ["Output current interpreter state in a text form such that it can be copied to a new device",
+ "Note: 'Internal' functions are currently not handled correctly. You will need to recreate these in the onInit function."],
+ "generate_full" : "jsiDumpState()"
+}*/
+/*JSON{ "type":"function", "name" : "load",
+ "description" : ["Load program memory out of flash",
+ "This command only executes when the Interpreter returns to the Idle state - for instance ```a=1;load();a=2;``` will still leave 'a' as undefined (or what it was set to in the saved program)."],
+ "generate_full" : "jsiSetTodo(TODO_FLASH_LOAD)"
+}*/
+/*JSON{ "type":"function", "name" : "save",
+ "description" : ["Save program memory into flash. It will then be loaded automatically every time Espruino powers on or is hard-reset.",
+ "This command only executes when the Interpreter returns to the Idle state - for instance ```a=1;save();a=2;``` will save 'a' as 2.",
+ "In order to stop the program saved with this command being loaded automatically, hold down Button 1 while also pressing reset. On some boards, Button 1 enters bootloader mode, so you will need to press Reset with Button 1 raised, and then hold Button 1 down a fraction of a second later."],
+ "generate_full" : "jsiSetTodo(TODO_FLASH_SAVE)"
+}*/
+/*JSON{ "type":"function", "name" : "reset",
+ "description" : ["Reset the interpreter - clear program memory, and do not load a saved program from flash. This does NOT reset the underlying hardware (which allows you to reset the device without it disconnecting from USB).",
+ "This command only executes when the Interpreter returns to the Idle state - for instance ```a=1;reset();a=2;``` will still leave 'a' as undefined.",
+ "The safest way to do a full reset is to hit the reset button."],
+ "generate_full" : "jsiSetTodo(TODO_RESET)"
+}*/
+/*JSON{ "type":"function", "name" : "print",
+ "description" : "Print the supplied string",
+ "generate" : "jswrap_interface_print",
+ "params" : [ [ "text", "JsVarArray", ""] ]
+}*/
+/*JSON{ "type":"staticmethod", "class":"console", "name" : "log",
+ "description" : "Print the supplied string(s)",
+ "generate" : "jswrap_interface_print",
+ "params" : [ [ "text", "JsVarArray", "One or more arguments to print"] ]
+}*/
+void jswrap_interface_print(JsVar *v) {
+ assert(jsvIsArray(v));
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, v);
+ while (jsvArrayIteratorHasElement(&it)) {
+ JsVar *v = jsvAsString(jsvArrayIteratorGetElement(&it), true);
+ jsiConsoleRemoveInputLine();
+ jsiConsolePrintStringVar(v);
+ jsvUnLock(v);
+ jsvArrayIteratorNext(&it);
+ if (jsvArrayIteratorHasElement(&it))
+ jsiConsolePrint(" ");
+ }
+ jsvArrayIteratorFree(&it);
+ jsiConsolePrint("\n");
+}
+
+/*JSON{ "type":"function", "name" : "edit",
+ "description" : ["Fill the console with the contents of the given function, so you can edit it.",
+ "NOTE: This is a convenience function - it will not edit 'inner functions'. For that, you must edit the 'outer function' and re-execute it."],
+ "generate" : "jswrap_interface_edit",
+ "params" : [ [ "funcName", "JsVarName", "The name of the function to edit (either a string or just the unquoted name)"] ]
+}*/
+void jswrap_interface_edit(JsVar *funcName) {
+ if (jsvIsString(funcName)) {
+ JsVar *func = 0;
+ if (jsvIsName(funcName))
+ func = jsvSkipName(funcName);
+ else
+ func = jsvSkipNameAndUnLock(jsvFindChildFromVar(jsiGetParser()->root, funcName, 0));
+ if (jsvIsFunction(func)) {
+ JsVar *scopeVar = jsvFindChildFromString(func, JSPARSE_FUNCTION_SCOPE_NAME, false);
+ JsVarRef scope = jsvGetRef(scopeVar);
+ jsvUnLock(scopeVar);
+ JsVar *newLine = jsvNewFromEmptyString();
+ if (newLine) { // could be out of memory
+ jsvAppendStringVarComplete(newLine, funcName);
+ if (scope) {
+ // If we have a scope, it's an internal function so we will need to write different code
+ jsvAppendString(newLine, ".replaceWith(");
+ } else {
+ jsvAppendString(newLine, " = ");
+ }
+ JsVar *funcData = jsvAsString(func, false);
+ if (funcData)
+ jsvAppendStringVarComplete(newLine, funcData);
+ jsvUnLock(funcData);
+ if (scope) {
+ jsvAppendString(newLine, ");");
+ } else {
+ jsvAppendString(newLine, ";");
+ }
+ jsiReplaceInputLine(newLine);
+ jsvUnLock(newLine);
+ }
+ } else {
+ jsError("Edit should be called with the name of a function");
+ }
+ jsvUnLock(func);
+ } else {
+ jsError("Edit should be called with edit(funcName) or edit('funcName')");
+ }
+}
+
+
+/*JSON{ "type":"function", "name" : "echo",
+ "description" : "Should TinyJS echo what you type back to you? true = yes (Default), false = no. When echo is off, the result of executing a command is not returned. Instead, you must use 'print' to send output.",
+ "generate" : "jswrap_interface_echo",
+ "params" : [ [ "echoOn", "bool", ""] ]
+}*/
+void jswrap_interface_echo(bool echoOn) {
+ echo = echoOn;
+}
+
+/*JSON{ "type":"function", "name" : "getTime",
+ "description" : "Return the current system time in Seconds (as a floating point number)",
+ "generate_full" : "(JsVarFloat)jshGetSystemTime() / (JsVarFloat)jshGetTimeFromMilliseconds(1000)",
+ "return" : ["float", ""]
+}*/
+
+
+/*JSON{ "type":"function", "name" : "getSerial",
+ "description" : "Get the serial number of this board",
+ "generate" : "jswrap_interface_getSerial",
+ "return" : ["JsVar", "The board's serial number"]
+}*/
+JsVar *jswrap_interface_getSerial() {
+ char buf[8];
+ unsigned char serial[32];
+ int i, serialSize = jshGetSerialNumber(serial, sizeof(serial));
+
+ JsVar *str = jsvNewFromEmptyString();
+ if (!str) return 0;
+
+ for (i=0;i<serialSize;i++) {
+ if ((i&3)==0 && i) jsvAppendString(str, "-");
+ itoa(serial[i] | 0x100, buf, 16);
+ jsvAppendString(str, &buf[1]);
+ }
+ return str;
+}
diff --git a/components/external/espruino/src/jswrap_interactive.h b/components/external/espruino/src/jswrap_interactive.h
new file mode 100644
index 0000000000..b9cee2a4f2
--- /dev/null
+++ b/components/external/espruino/src/jswrap_interactive.h
@@ -0,0 +1,24 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript methods and functions for the interactive shell
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jsinteractive.h"
+
+void jswrap_interface_setBusyIndicator(JsVar *pinVar);
+void jswrap_interface_setSleepIndicator(JsVar *pinVar);
+void jswrap_interface_setDeepSleep(bool sleep);
+void jswrap_interface_trace(JsVar *root);
+void jswrap_interface_print(JsVar *v);
+void jswrap_interface_edit(JsVar *funcName);
+void jswrap_interface_echo(bool echoOn);
+JsVar *jswrap_interface_getSerial();
diff --git a/components/external/espruino/src/jswrap_io.c b/components/external/espruino/src/jswrap_io.c
new file mode 100644
index 0000000000..f03cd216a8
--- /dev/null
+++ b/components/external/espruino/src/jswrap_io.c
@@ -0,0 +1,437 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript Hardware IO Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_io.h"
+#include "jsvar.h"
+
+
+/*JSON{ "type":"function", "name" : "peek8",
+ "description" : [ "Read 8 bits of memory at the given location - DANGEROUS!" ],
+ "generate_full" : "(JsVarInt)*(unsigned char*)jsvGetInteger(addr)",
+ "params" : [ [ "addr", "int", "The address in memory to read"] ],
+ "return" : ["int", "The value of memory at the given location"]
+}*/
+/*JSON{ "type":"function", "name" : "poke8",
+ "description" : [ "Write 8 bits of memory at the given location - VERY DANGEROUS!" ],
+ "generate_full" : "(*(unsigned char*)jsvGetInteger(addr)) = (unsigned char)jsvGetInteger(value)",
+ "params" : [ [ "addr", "int", "The address in memory to write"],
+ [ "value", "int", "The value to write"] ]
+}*/
+/*JSON{ "type":"function", "name" : "peek16",
+ "description" : [ "Read 16 bits of memory at the given location - DANGEROUS!" ],
+ "generate_full" : "(JsVarInt)*(unsigned short*)jsvGetInteger(addr)",
+ "params" : [ [ "addr", "int", "The address in memory to read"] ],
+ "return" : ["int", "The value of memory at the given location"]
+}*/
+/*JSON{ "type":"function", "name" : "poke16",
+ "description" : [ "Write 16 bits of memory at the given location - VERY DANGEROUS!" ],
+ "generate_full" : "(*(unsigned short*)jsvGetInteger(addr)) = (unsigned short)jsvGetInteger(value)",
+ "params" : [ [ "addr", "int", "The address in memory to write"],
+ [ "value", "int", "The value to write"] ]
+}*/
+/*JSON{ "type":"function", "name" : "peek32",
+ "description" : [ "Read 32 bits of memory at the given location - DANGEROUS!" ],
+ "generate_full" : "(JsVarInt)*(unsigned int*)jsvGetInteger(addr)",
+ "params" : [ [ "addr", "int", "The address in memory to read"] ],
+ "return" : ["int", "The value of memory at the given location"]
+}*/
+/*JSON{ "type":"function", "name" : "poke32",
+ "description" : [ "Write 32 bits of memory at the given location - VERY DANGEROUS!" ],
+ "generate_full" : "(*(unsigned int*)jsvGetInteger(addr)) = (unsigned int)jsvGetInteger(value)",
+ "params" : [ [ "addr", "int", "The address in memory to write"],
+ [ "value", "int", "The value to write"] ]
+}*/
+
+/*JSON{ "type":"function", "name" : "analogRead",
+ "description" : ["Get the analog value of the given pin",
+ "This is different to Arduino which only returns an integer between 0 and 1023",
+ "However only pins connected to an ADC will work (see the datasheet)"],
+ "generate" : "jshPinAnalog",
+ "params" : [ [ "pin", "pin", "The pin to use"] ],
+ "return" : ["float", "The analog Value of the Pin between 0 and 1"]
+}*/
+/*JSON{ "type":"function", "name" : "analogWrite",
+ "description" : "Set the analog Value of a pin. It will be output using PWM",
+ "generate" : "jswrap_io_analogWrite",
+ "params" : [ [ "pin", "pin", "The pin to use"],
+ [ "value", "float", "A value between 0 and 1"],
+ [ "options", "JsVar", ["An object containing options.",
+ "Currently only freq (pulse frequency in Hz) is available: ```analogWrite(LED1,0.5,{ freq : 10 });``` ",
+ "Note that specifying a frequency will force PWM output, even if the pin has a DAC"] ] ]
+}*/
+void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) {
+ JsVarFloat freq = 0;
+ if (jsvIsObject(options)) {
+ freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0));
+ }
+
+ jshPinAnalogOutput(pin, value, freq);
+}
+
+/*JSON{ "type":"function", "name" : "digitalPulse",
+ "description" : ["Pulse the pin with the value for the given time in milliseconds",
+ "eg. ```pulse(A0,1,5);``` pulses A0 high for 5ms",
+ "digitalPulse is for SHORT pulses that need to be very accurate. If you're doing anything over a few milliseconds, use setTimeout instead" ],
+ "generate" : "jswrap_io_digitalPulse",
+ "params" : [ [ "pin", "pin", "The pin to use"],
+ [ "value", "bool", "Whether to pulse high (true) or low (false)"],
+ [ "time", "float", "A time in milliseconds"] ]
+}*/
+void jswrap_io_digitalPulse(Pin pin, bool value, JsVarFloat time) {
+ if (time<=0) {
+ jsWarn("Pulse Time given for digitalPulse is less that or equal to 0");
+ } else {
+ //jsPrintInt((JsVarInt)(time*1000));
+ jshPinPulse(pin, value, time);
+ }
+}
+
+/*JSON{ "type":"function", "name" : "digitalWrite",
+ "description" : ["Set the digital value of the given pin",
+ "If pin is an array of pins, eg. ```[A2,A1,A0]``` the value will be treated as an integer where the first array element is the MSB" ],
+ "generate" : "jswrap_io_digitalWrite",
+ "params" : [ [ "pin", "JsVar", "The pin to use"],
+ [ "value", "int", "Whether to pulse high (true) or low (false)"] ]
+}*/
+void jswrap_io_digitalWrite(JsVar *pinVar, JsVarInt value) {
+ if (jsvIsArray(pinVar)) {
+ JsVarRef pinName = pinVar->lastChild; // NOTE: start at end and work back!
+ while (pinName) {
+ JsVar *pinNamePtr = jsvLock(pinName);
+ JsVar *pinPtr = jsvSkipName(pinNamePtr);
+ jshPinOutput(jshGetPinFromVar(pinPtr), value&1);
+ jsvUnLock(pinPtr);
+ pinName = pinNamePtr->prevSibling;
+ jsvUnLock(pinNamePtr);
+ value = value>>1; // next bit down
+ }
+ } else {
+ Pin pin = jshGetPinFromVar(pinVar);
+ jshPinOutput(pin, value!=0);
+ }
+}
+
+
+/*JSON{ "type":"function", "name" : "digitalRead",
+ "description" : ["Get the digital value of the given pin",
+ "If pin is an array of pins, eg. ```[A2,A1,A0]``` the value will be treated as an integer where the first array element is the MSB" ],
+ "generate" : "jswrap_io_digitalRead",
+ "params" : [ [ "pin", "JsVar", "The pin to use"] ],
+ "return" : ["int", "The digital Value of the Pin"]
+}*/
+JsVarInt jswrap_io_digitalRead(JsVar *pinVar) {
+ if (jsvIsArray(pinVar)) {
+ int pins = 0;
+ JsVarInt value = 0;
+ JsVarRef pinName = pinVar->firstChild;
+ while (pinName) {
+ JsVar *pinNamePtr = jsvLock(pinName);
+ JsVar *pinPtr = jsvSkipName(pinNamePtr);
+ value = (value<<1) | jshPinInput(jshGetPinFromVar(pinPtr));
+ jsvUnLock(pinPtr);
+ pinName = pinNamePtr->nextSibling;
+ jsvUnLock(pinNamePtr);
+ pins++;
+ }
+ if (pins==0) return 0; // return undefined if array empty
+ return value;
+ } else {
+ Pin pin = jshGetPinFromVar(pinVar);
+ return jshPinInput(pin);
+ }
+}
+
+/*JSON{ "type":"function", "name" : "pinMode",
+ "description" : ["Set the mode of the given pin - note that digitalRead/digitalWrite/etc set this automatically unless pinMode has been called first. If you want digitalRead/etc to set the pin mode automatically after you have called pinMode, simply call it again with no mode argument: ```pinMode(pin)```" ],
+ "generate" : "jswrap_io_pinMode",
+ "params" : [ [ "pin", "pin", "The pin to use"], [ "mode", "JsVar", "The mode - a string that is either 'input', 'output', 'input_pullup', or 'input_pulldown'. Do not include this argument if you want to revert to automatic pin mode setting."] ]
+}*/
+void jswrap_io_pinMode(Pin pin, JsVar *mode) {
+ if (!jshIsPinValid(pin)) {
+ jsError("Invalid pin");
+ return;
+ }
+ JshPinState m = JSHPINSTATE_UNDEFINED;
+ if (jsvIsString(mode)) {
+ if (jsvIsStringEqual(mode, "input")) m = JSHPINSTATE_GPIO_IN;
+ if (jsvIsStringEqual(mode, "input_pullup")) m = JSHPINSTATE_GPIO_IN_PULLUP;
+ if (jsvIsStringEqual(mode, "input_pulldown")) m = JSHPINSTATE_GPIO_IN_PULLDOWN;
+ if (jsvIsStringEqual(mode, "output")) m = JSHPINSTATE_GPIO_OUT;
+ }
+ if (m != JSHPINSTATE_UNDEFINED) {
+ jshSetPinStateIsManual(pin, true);
+ jshPinSetState(pin, m);
+ } else {
+ jshSetPinStateIsManual(pin, false);
+ if (!jsvIsUndefined(mode)) {
+ jsError("Unknown pin mode");
+ }
+ }
+}
+
+
+/*XXXX{ "type":"function", "name" : "bitBang",
+ "description" : ["bitBang out a message in a one-wire style BROKEN CURRENTLY" ],
+ "generate" : "jshBitBang",
+ "params" : [ [ "pin", "pin", "The pin to use"],
+ [ "t0h", "float", "The time (in milliseconds) to spend high for a 0"],
+ [ "t0l", "float", "The time (in milliseconds) to spend low for a 0"],
+ [ "t1h", "float", "The time (in milliseconds) to spend high for a 1"],
+ [ "t1l", "float", "The time (in milliseconds) to spend low for a 1"],
+ [ "data", "JsVar", "A string representing the data"] ]
+}*/
+
+
+/*JSON{ "type":"function", "name" : "setInterval",
+ "description" : ["Call the function specified REPEATEDLY after the timeout in milliseconds.",
+ "The function that is being called may also take an argument, which is an object containing a field called 'time' (the time in seconds at which the timer happened)",
+ "for example: ```setInterval(function (e) { print(e.time); }, 1000);```",
+ "This can also be removed using clearInterval" ],
+ "generate" : "jswrap_interface_setInterval",
+ "params" : [ [ "function", "JsVarName", "A Function or String to be executed"],
+ [ "timeout", "float", "The time between calls to the function" ] ],
+ "return" : ["JsVar", "An ID that can be passed to clearInterval"]
+}*/
+/*JSON{ "type":"function", "name" : "setTimeout",
+ "description" : ["Call the function specified ONCE after the timeout in milliseconds.",
+ "The function that is being called may also take an argument, which is an object containing a field called 'time' (the time in seconds at which the timer happened)",
+ "for example: ```setTimeout(function (e) { print(e.time); }, 1000);```",
+ "This can also be removed using clearTimeout" ],
+ "generate" : "jswrap_interface_setTimeout",
+ "params" : [ [ "function", "JsVarName", "A Function or String to be executed"],
+ [ "timeout", "float", "The time until the function will be executed" ] ],
+ "return" : ["JsVar", "An ID that can be passed to clearTimeout"]
+}*/
+JsVar *_jswrap_interface_setTimeoutOrInterval(JsVar *func, JsVarFloat interval, bool isTimeout) {
+ JsVar *skippedFunc = jsvSkipName(func);
+ JsVar *itemIndex = 0;
+ if (!jsvIsFunction(skippedFunc) && !jsvIsString(skippedFunc)) {
+ jsError("Function or String not supplied!");
+ } else {
+ // Create a new timer
+ JsVar *timerPtr = jsvNewWithFlags(JSV_OBJECT);
+ if (interval<TIMER_MIN_INTERVAL) interval=TIMER_MIN_INTERVAL;
+ JsVar *v;
+ v = jsvNewFromInteger(jshGetSystemTime() + jshGetTimeFromMilliseconds(interval));
+ jsvUnLock(jsvAddNamedChild(timerPtr, v, "time"));
+ jsvUnLock(v);
+ v = jsvNewFromFloat(interval);
+ jsvUnLock(jsvAddNamedChild(timerPtr, v, "interval"));
+ jsvUnLock(v);
+ v = jsvNewFromBool(!isTimeout);
+ jsvUnLock(jsvAddNamedChild(timerPtr, v, "recur"));
+ jsvUnLock(v);
+ jsvUnLock(jsvAddNamedChild(timerPtr, func, "callback"));
+ //jsPrint("TIMER BEFORE ADD\n"); jsvTrace(timerArray,5);
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ itemIndex = jsvNewFromInteger(jsvArrayPushWithInitialSize(timerArrayPtr, timerPtr, 1) - 1);
+ //jsPrint("TIMER AFTER ADD\n"); jsvTrace(timerArray,5);
+ jsvUnLock(timerArrayPtr);
+ jsvUnLock(timerPtr);
+ }
+ jsvUnLock(skippedFunc);
+ //jsvTrace(jsiGetParser()->root, 0);
+ return itemIndex;
+}
+JsVar *jswrap_interface_setInterval(JsVar *func, JsVarFloat timeout) {
+ return _jswrap_interface_setTimeoutOrInterval(func, timeout, false);
+}
+JsVar *jswrap_interface_setTimeout(JsVar *func, JsVarFloat timeout) {
+ return _jswrap_interface_setTimeoutOrInterval(func, timeout, true);
+}
+
+/*JSON{ "type":"function", "name" : "setWatch",
+ "description" : ["Call the function specified when the pin changes",
+ "The function may also take an argument, which is an object containing a field called 'time', which is the time in seconds at which the pin changed state, and 'state', which is the current state of the pin",
+ " This can also be removed using clearWatch" ],
+ "generate" : "jswrap_interface_setWatch",
+ "params" : [ [ "function", "JsVarName", "A Function or String to be executed"],
+ [ "pin", "pin", "The pin to watch" ],
+ [ "options", "JsVar", ["If this is a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true)",
+ "If this is an object, it can contain the following information: ```{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default)}```" ] ] ],
+ "return" : ["JsVar", "An ID that can be passed to clearWatch"]
+}*/
+JsVar *jswrap_interface_setWatch(JsVar *funcVar, Pin pin, JsVar *repeatOrObject) {
+ bool repeat = false;
+ int edge = 0;
+ if (jsvIsObject(repeatOrObject)) {
+ JsVar *v;
+ repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "repeat", 0));
+ v = jsvObjectGetChild(repeatOrObject, "edge", 0);
+ if (jsvIsString(v)) {
+ if (jsvIsStringEqual(v, "rising")) edge=1;
+ else if (jsvIsStringEqual(v, "falling")) edge=-1;
+ else if (jsvIsStringEqual(v, "both")) edge=0;
+ else jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'");
+ } else if (!jsvIsUndefined(v))
+ jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'");
+ jsvUnLock(v);
+ } else
+ repeat = jsvGetBool(repeatOrObject);
+
+ JsVarInt itemIndex = -1;
+ JsVar *skippedFunc = jsvSkipName(funcVar);
+ if (!jsvIsFunction(skippedFunc) && !jsvIsString(skippedFunc)) {
+ jsError("Function or String not supplied!");
+ } else {
+ // Create a new watch
+ JsVar *watchPtr = jsvNewWithFlags(JSV_OBJECT);
+ JsVar *v;
+ v = jsvNewFromPin(pin);
+ jsvUnLock(jsvAddNamedChild(watchPtr, v, "pin"));
+ jsvUnLock(v);
+ v = jsvNewFromBool(repeat);
+ jsvUnLock(jsvAddNamedChild(watchPtr, v, "recur"));
+ jsvUnLock(v);
+ v = jsvNewFromInteger(edge);
+ jsvUnLock(jsvAddNamedChild(watchPtr, v, "edge"));
+ jsvUnLock(v);
+ jsvUnLock(jsvAddNamedChild(watchPtr, funcVar, "callback"));
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ itemIndex = jsvArrayPushWithInitialSize(watchArrayPtr, watchPtr, 1) - 1;
+ jsvUnLock(watchArrayPtr);
+ jsvUnLock(watchPtr);
+ jshPinWatch(pin, true);
+ }
+ jsvUnLock(skippedFunc);
+ return (itemIndex>=0) ? jsvNewFromInteger(itemIndex) : 0/*undefined*/;
+}
+
+/*JSON{ "type":"function", "name" : "clearInterval",
+ "description" : ["Clear the Interval that was created with setInterval, for example:",
+ "```var id = setInterval(function () { print('foo'); }, 1000);```",
+ "```clearInterval(id);```",
+ "If no argument is supplied, all timers and intervals are stopped" ],
+ "generate" : "jswrap_interface_clearInterval",
+ "params" : [ [ "id", "JsVar", "The id returned by a previous call to setInterval"] ]
+}*/
+/*JSON{ "type":"function", "name" : "clearTimeout",
+ "description" : ["Clear the Timeout that was created with setTimeout, for example:",
+ "```var id = setTimeout(function () { print('foo'); }, 1000);```",
+ "```clearTimeout(id);```",
+ "If no argument is supplied, all timers and intervals are stopped" ],
+ "generate" : "jswrap_interface_clearTimeout",
+ "params" : [ [ "id", "JsVar", "The id returned by a previous call to setTimeout"] ]
+}*/
+void _jswrap_interface_clearTimeoutOrInterval(JsVar *idVar, bool isTimeout) {
+ if (jsvIsUndefined(idVar)) {
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ jsvRemoveAllChildren(timerArrayPtr);
+ jsvUnLock(timerArrayPtr);
+ } else {
+ JsVar *child = jsvIsBasic(idVar) ? jsvFindChildFromVarRef(timerArray, idVar, false) : 0;
+ if (child) {
+ JsVar *timerArrayPtr = jsvLock(timerArray);
+ jsvRemoveChild(timerArrayPtr, child);
+ jsvUnLock(child);
+ jsvUnLock(timerArrayPtr);
+ } else {
+ jsError(isTimeout ? "Unknown Timeout" : "Unknown Interval");
+ }
+ }
+}
+void jswrap_interface_clearInterval(JsVar *idVar) {
+ _jswrap_interface_clearTimeoutOrInterval(idVar, false);
+}
+void jswrap_interface_clearTimeout(JsVar *idVar) {
+ _jswrap_interface_clearTimeoutOrInterval(idVar, true);
+}
+
+/*JSON{ "type":"function", "name" : "changeInterval",
+ "description" : ["Change the Interval on a callback created with setInterval, for example:",
+ "```var id = setInterval(function () { print('foo'); }, 1000); // every second```",
+ "```changeInterval(id, 1500); // now runs every 1.5 seconds```",
+ "This takes effect the text time the callback is called (so it is not immediate)."],
+ "generate" : "jswrap_interface_changeInterval",
+ "params" : [ [ "id", "JsVar", "The id returned by a previous call to setInterval"],
+ [ "time","float","The new time period in ms" ] ]
+}*/
+void jswrap_interface_changeInterval(JsVar *idVar, JsVarFloat interval) {
+ if (interval<TIMER_MIN_INTERVAL) interval=TIMER_MIN_INTERVAL;
+ JsVar *timerName = jsvIsBasic(idVar) ? jsvFindChildFromVarRef(timerArray, idVar, false) : 0;
+
+ if (timerName) {
+ JsVar *timer = jsvSkipNameAndUnLock(timerName);
+ JsVar *v;
+ v = jsvNewFromFloat(interval);
+ jsvUnLock(jsvSetNamedChild(timer, v, "interval"));
+ jsvUnLock(v);
+ v = jsvNewFromInteger(jshGetSystemTime() + jshGetTimeFromMilliseconds(interval));
+ jsvUnLock(jsvSetNamedChild(timer, v, "time"));
+ jsvUnLock(v);
+ jsvUnLock(timer);
+ // timerName already unlocked
+ } else {
+ jsError("Unknown Interval");
+ }
+}
+
+/*JSON{ "type":"function", "name" : "clearWatch",
+ "description" : [ "Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed." ],
+ "generate" : "jswrap_interface_clearWatch",
+ "params" : [ [ "id", "JsVar", "The id returned by a previous call to setWatch"] ]
+}*/
+void jswrap_interface_clearWatch(JsVar *idVar) {
+ if (jsvIsUndefined(idVar)) {
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ // unwatch all pins
+ JsVarRef watch = watchArrayPtr->firstChild;
+ while (watch) {
+ JsVar *watchNamePtr = jsvLock(watch); // effectively the array index
+ JsVar *pinVar = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ jshPinWatch(jshGetPinFromVar(pinVar), false); // 'unwatch' pin because we know that we're removing ALL watches
+ jsvUnLock(pinVar);
+ watch = watchNamePtr->nextSibling;
+ jsvUnLock(watchNamePtr);
+ }
+ // remove all items
+ jsvRemoveAllChildren(watchArrayPtr);
+ jsvUnLock(watchArrayPtr);
+ } else {
+ JsVar *watchNamePtr = jsvFindChildFromVarRef(watchArray, idVar, false);
+ if (watchNamePtr) { // child is a 'name'
+ JsVar *pinVar = jsvSkipNameAndUnLock(jsvFindChildFromStringRef(watchNamePtr->firstChild, "pin", false));
+ Pin pin = jshGetPinFromVar(pinVar);
+ jsvUnLock(pinVar);
+
+ JsVar *watchArrayPtr = jsvLock(watchArray);
+ jsvRemoveChild(watchArrayPtr, watchNamePtr);
+ jsvUnLock(watchNamePtr);
+
+ // Now check if this pin is still being watched
+ bool stillWatched = false;
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, watchArrayPtr);
+ while (jsvArrayIteratorHasElement(&it)) {
+ JsVar *watchPtr = jsvArrayIteratorGetElement(&it);
+ JsVar *pinVar = jsvObjectGetChild(watchPtr, "pin", 0);
+ if (jshGetPinFromVar(pinVar) == pin)
+ stillWatched = true;
+ jsvUnLock(pinVar);
+ jsvUnLock(watchPtr);
+ jsvArrayIteratorNext(&it);
+ }
+ jsvArrayIteratorFree(&it);
+ jsvUnLock(watchArrayPtr);
+
+ if (!stillWatched)
+ jshPinWatch(pin, false); // 'unwatch' pin
+ } else {
+ jsError("Unknown Watch");
+ }
+ }
+}
+
+
diff --git a/components/external/espruino/src/jswrap_io.h b/components/external/espruino/src/jswrap_io.h
new file mode 100644
index 0000000000..68760c103c
--- /dev/null
+++ b/components/external/espruino/src/jswrap_io.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript Hardware IO Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jsinteractive.h"
+
+void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options);
+void jswrap_io_digitalPulse(Pin pin, bool value, JsVarFloat time);
+void jswrap_io_digitalWrite(JsVar *pinVar, JsVarInt value);
+JsVarInt jswrap_io_digitalRead(JsVar *pinVar);
+void jswrap_io_pinMode(Pin pin, JsVar *mode);
+
+JsVar *jswrap_interface_setInterval(JsVar *func, JsVarFloat timeout);
+JsVar *jswrap_interface_setTimeout(JsVar *func, JsVarFloat timeout);
+JsVar *jswrap_interface_setWatch(JsVar *funcVar, Pin pin, JsVar *repeatOrObject);
+void jswrap_interface_clearInterval(JsVar *idVar);
+void jswrap_interface_clearTimeout(JsVar *idVar);
+void jswrap_interface_changeInterval(JsVar *idVar, JsVarFloat interval);
+void jswrap_interface_clearWatch(JsVar *idVar);
diff --git a/components/external/espruino/src/jswrap_json.c b/components/external/espruino/src/jswrap_json.c
new file mode 100644
index 0000000000..9d16c10f13
--- /dev/null
+++ b/components/external/espruino/src/jswrap_json.c
@@ -0,0 +1,167 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript JSON-handling Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_json.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+#include "jswrapper.h"
+
+/*JSON{ "type":"staticmethod",
+ "class" : "JSON", "name" : "stringify",
+ "description" : "Convert the given object into a JSON string which can subsequently be parsed with JSON.parse or eval",
+ "generate" : "jswrap_json_stringify",
+ "params" : [ [ "data", "JsVar", "The data to be converted to a JSON string"] ],
+ "return" : ["JsVar", "A JSON string"]
+}*/
+JsVar *jswrap_json_stringify(JsVar *v) {
+ JsVar *result = jsvNewFromEmptyString();
+ if (result) // could be out of memory
+ jsfGetJSON(v, result);
+ return result;
+}
+
+/*JSON{ "type":"staticmethod",
+ "class" : "JSON", "name" : "parse",
+ "description" : "Parse the given JSON string into a JavaScript object",
+ "generate" : "jswrap_json_parse",
+ "params" : [ [ "string", "JsVar", "A JSON string"] ],
+ "return" : ["JsVar", "The JavaScript object created by parsing the data string"]
+}*/
+JsVar *jswrap_json_parse(JsVar *v) {
+ JsVar *res = 0;
+ JsVar *bracketed = jsvNewFromString("(");
+ if (bracketed) { // could be out of memory
+ v = jsvAsString(v, true); // try and get this as a string
+ jsvAppendStringVarComplete(bracketed, v);
+ jsvAppendString(bracketed, ")");
+ res = jspEvaluateVar(jsiGetParser(), bracketed, 0);
+ jsvUnLock(bracketed);
+ }
+ return res;
+}
+
+/* This is like jsfGetJSONWithCallback, but handles ONLY functions (and does not print the initial 'function' text) */
+void jsfGetJSONForFunctionWithCallback(JsVar *var, JsfGetJSONCallbackString callbackString, JsfGetJSONCallbackVar callbackVar, void *callbackData) {
+ assert(jsvIsFunction(var));
+ JsVarRef coderef = 0; // TODO: this should really be in jsvAsString
+ JsVarRef childref = var->firstChild;
+ bool firstParm = true;
+ callbackString(callbackData, "(");
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ childref = child->nextSibling;
+ if (jsvIsFunctionParameter(child)) {
+ if (firstParm)
+ firstParm=false;
+ else
+ callbackString(callbackData, ",");
+ callbackVar(callbackData, child); // FIXME: escape the string
+ } else if (jsvIsString(child) && jsvIsStringEqual(child, JSPARSE_FUNCTION_CODE_NAME)) {
+ coderef = child->firstChild;
+ }
+ jsvUnLock(child);
+ }
+ callbackString(callbackData, ") ");
+ if (coderef) {
+ JsVar *codeVar = jsvLock(coderef);
+ callbackVar(callbackData, codeVar);
+ jsvUnLock(codeVar);
+ } else callbackString(callbackData, "{}");
+}
+
+void jsfGetJSONWithCallback(JsVar *var, JsfGetJSONCallbackString callbackString, JsfGetJSONCallbackVar callbackVar, void *callbackData) {
+ if (jsvIsUndefined(var)) {
+ callbackString(callbackData, "undefined");
+ } else if (jsvIsArray(var)) {
+ int length = (int)jsvGetArrayLength(var);
+ int i;
+ callbackString(callbackData, "[");
+ for (i=0;i<length;i++) {
+ JsVar *item = jsvGetArrayItem(var, i);
+ jsfGetJSONWithCallback(item, callbackString, callbackVar, callbackData);
+ jsvUnLock(item);
+ if (i<length-1) callbackString(callbackData, ",");
+ }
+ callbackString(callbackData, "]");
+ } else if (jsvIsArrayBuffer(var)) {
+ callbackString(callbackData, "new ");
+ callbackString(callbackData, jswGetBasicObjectName(var));
+ callbackString(callbackData, "([");
+ callbackVar(callbackData, jsvAsString(var, false));
+ callbackString(callbackData, "])");
+ } else if (jsvIsObject(var)) {
+ bool first = true;
+ JsVarRef childref = var->firstChild;
+ callbackString(callbackData, "{");
+ while (childref) {
+ JsVar *child = jsvLock(childref);
+ bool hidden = jsvIsInternalObjectKey(child);
+ if (!hidden) {
+ if (first)
+ first = false;
+ else
+ callbackString(callbackData, ",");
+
+ callbackString(callbackData, "\"");
+ callbackVar(callbackData, child); // FIXME: escape the string
+ callbackString(callbackData, "\":");
+ }
+ JsVar *childVar = child->firstChild ? jsvLock(child->firstChild) : 0;
+ childref = child->nextSibling;
+ jsvUnLock(child);
+
+ if (!hidden) {
+ jsfGetJSONWithCallback(childVar, callbackString, callbackVar, callbackData);
+ }
+ jsvUnLock(childVar);
+ }
+ callbackString(callbackData, "}");
+ } else if (jsvIsFunction(var)) {
+ callbackString(callbackData, "function ");
+ jsfGetJSONForFunctionWithCallback(var, callbackString, callbackVar, callbackData);
+ } else if (jsvIsString(var) && !jsvIsName(var)) {
+ // escape the string
+ callbackString(callbackData, "\"");
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, var, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ char ch = jsvStringIteratorGetChar(&it);
+ callbackString(callbackData, escapeCharacter(ch));
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ callbackString(callbackData, "\"");
+ } else {
+ JsVar *str = jsvAsString(var, false);
+ if (str) {
+ callbackVar(callbackData, str);
+ jsvUnLock(str);
+ }
+ }
+}
+
+void jsfGetJSON(JsVar *var, JsVar *result) {
+ assert(jsvIsString(result));
+ jsfGetJSONWithCallback(var, (JsfGetJSONCallbackString)jsvAppendString, (JsfGetJSONCallbackVar)jsvAppendStringVarComplete, result);
+}
+
+void _jsfPrintJSON_str(void *data, const char *str) { NOT_USED(data); jsiConsolePrint(str); }
+void _jsfPrintJSON_var(void *data, JsVar *var) { NOT_USED(data); jsiConsolePrintStringVar(var); }
+void jsfPrintJSON(JsVar *var) {
+ jsfGetJSONWithCallback(var, _jsfPrintJSON_str, _jsfPrintJSON_var, 0);
+}
+void jsfPrintJSONForFunction(JsVar *var) {
+ jsfGetJSONForFunctionWithCallback(var, _jsfPrintJSON_str, _jsfPrintJSON_var, 0);
+}
diff --git a/components/external/espruino/src/jswrap_json.h b/components/external/espruino/src/jswrap_json.h
new file mode 100644
index 0000000000..2452f604de
--- /dev/null
+++ b/components/external/espruino/src/jswrap_json.h
@@ -0,0 +1,33 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript JSON-handling Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_json_stringify(JsVar *v);
+JsVar *jswrap_json_parse(JsVar *v);
+
+typedef void (*JsfGetJSONCallbackString)(void *data, const char *string);
+typedef void (*JsfGetJSONCallbackVar)(void *data, JsVar *var);
+
+/* This is like jsfGetJSONWithCallback, but handles ONLY functions (and does not print the initial 'function' text) */
+void jsfGetJSONForFunctionWithCallback(JsVar *var, JsfGetJSONCallbackString callbackString, JsfGetJSONCallbackVar callbackVar, void *callbackData);
+/* Dump to JSON, using the given callbacks for printing data */
+void jsfGetJSONWithCallback(JsVar *var, JsfGetJSONCallbackString callbackString, JsfGetJSONCallbackVar callbackVar, void *callbackData);
+
+/* Convenience function for using jsfGetJSONWithCallback - print to var */
+void jsfGetJSON(JsVar *var, JsVar *result);
+
+/* Convenience function for using jsfGetJSONWithCallback - print to console */
+void jsfPrintJSON(JsVar *var);
+/* Convenience function for using jsfGetJSONForFunctionWithCallback - print to console */
+void jsfPrintJSONForFunction(JsVar *var);
diff --git a/components/external/espruino/src/jswrap_modules.c b/components/external/espruino/src/jswrap_modules.c
new file mode 100644
index 0000000000..a0f9da7ccc
--- /dev/null
+++ b/components/external/espruino/src/jswrap_modules.c
@@ -0,0 +1,195 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript Functions for handling Modules
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_functions.h"
+#include "jslex.h"
+#include "jsvar.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+#include "jswrapper.h"
+#ifdef USE_FILESYSTEM
+#include "../libs/jswrap_fat.h"
+#endif
+
+static JsVar *jswrap_modules_getModuleList() {
+ JsVar *moduleListName = jsvFindChildFromString(jsiGetParser()->root, JSPARSE_MODULE_CACHE_NAME, true);
+ if (!moduleListName) return 0; // out of memory
+ JsVar *moduleList = jsvSkipName(moduleListName);
+ if (!moduleList) {
+ moduleList = jsvNewWithFlags(JSV_OBJECT);
+ if (!moduleList) { jsvUnLock(moduleListName); return 0; } // out of memory
+ jsvSetValueOfName(moduleListName, moduleList); // no need to unlock
+ }
+ jsvUnLock(moduleListName);
+ return moduleList;
+}
+
+/*JSON{ "type":"function", "name" : "require",
+ "description" : "Load the given module, and return the exported functions",
+ "generate" : "jswrap_require",
+ "params" : [ [ "moduleName", "JsVar", "A String containing the name of the given module"] ],
+ "return" : ["JsVar", "The result of evaluating the string"]
+}*/
+JsVar *jswrap_require(JsVar *moduleName) {
+ if (!jsvIsString(moduleName)) {
+ jsWarn("Expecting a module name as a string, but got %t", moduleName);
+ return 0;
+ }
+ // Search to see if we have already loaded this module
+
+ JsVar *moduleList = jswrap_modules_getModuleList();
+ if (!moduleList) return 0; // out of memory
+ JsVar *moduleExportName = jsvFindChildFromVar(moduleList, moduleName, true);
+ jsvUnLock(moduleList);
+ if (!moduleExportName) return 0; // out of memory
+ JsVar *moduleExport = jsvSkipName(moduleExportName);
+ if (moduleExport) {
+ // Found the module!
+ jsvUnLock(moduleExportName);
+ return moduleExport;
+ }
+
+ // Now check if it is built-in
+ char moduleNameBuf[32];
+ jsvGetString(moduleName, moduleNameBuf, sizeof(moduleNameBuf));
+ if (jswIsBuiltInLibrary(moduleNameBuf)) {
+ // create a 'fake' module that Espruino can use to map its built-in functions against
+ moduleExport = jspNewBuiltin(moduleNameBuf);
+ } else {
+ // Now try and load it
+ JsVar *fileContents = 0;
+ //if (jsvIsStringEqual(moduleName,"http")) {}
+ //if (jsvIsStringEqual(moduleName,"fs")) {}
+ #ifdef USE_FILESYSTEM
+ JsVar *modulePath = jsvNewFromString(
+ #ifdef LINUX
+ "node_modules/"
+ #else
+ "NODE_M~1/"
+ #endif
+ );
+ if (!modulePath) { jsvUnLock(moduleExportName); return 0; } // out of memory
+ jsvAppendStringVarComplete(modulePath, moduleName);
+ jsvAppendString(modulePath,".js");
+ fileContents = wrap_fat_readFile(modulePath);
+ jsvUnLock(modulePath);
+ #endif
+ if (!fileContents || jsvIsStringEqual(fileContents,"")) {
+ jsvUnLock(moduleExportName);
+ jsvUnLock(fileContents);
+ jsWarn("Module not found");
+ return 0;
+ }
+ moduleExport = jspEvaluateModule(jsiGetParser(), fileContents);
+ jsvUnLock(fileContents);
+ }
+
+ assert(moduleExport);
+ jsvSetValueOfName(moduleExportName, moduleExport); // save in cache
+ jsvUnLock(moduleExportName);
+ return moduleExport;
+}
+
+/*JSON{ "type":"staticmethod",
+ "class" : "Modules", "name" : "getCached",
+ "description" : "Return an array of module names that have been cached",
+ "generate" : "jswrap_modules_getCached",
+ "return" : ["JsVar", "An array of module names"]
+}*/
+JsVar *jswrap_modules_getCached() {
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ if (!arr) return 0; // out of memory
+
+ JsVar *moduleList = jswrap_modules_getModuleList();
+ if (!moduleList) return arr; // out of memory
+
+ JsObjectIterator it;
+ jsvObjectIteratorNew(&it, moduleList);
+ while (jsvObjectIteratorHasElement(&it)) {
+ JsVar *idx = jsvObjectIteratorGetKey(&it);
+ JsVar *idxCopy = jsvCopyNameOnly(idx, false, false);
+ jsvArrayPushAndUnLock(arr, idxCopy);
+ jsvUnLock(idx);
+ jsvObjectIteratorNext(&it);
+ }
+ jsvObjectIteratorFree(&it);
+ jsvUnLock(moduleList);
+ return arr;
+}
+
+/*JSON{ "type":"staticmethod",
+ "class" : "Modules", "name" : "removeCached",
+ "description" : "Remove the given module from the list of cached modules",
+ "generate" : "jswrap_modules_removeCached",
+ "params" : [ [ "id", "JsVar", "The module name to remove"] ]
+}*/
+void jswrap_modules_removeCached(JsVar *id) {
+ if (!jsvIsString(id)) {
+ jsError("The argument to removeCached must be a string");
+ return;
+ }
+ JsVar *moduleList = jswrap_modules_getModuleList();
+ if (!moduleList) return; // out of memory
+
+ JsVar *moduleExportName = jsvFindChildFromVar(moduleList, id, false);
+ if (!moduleExportName) {
+ jsWarn("Module not found");
+ } else {
+ jsvRemoveChild(moduleList, moduleExportName);
+ jsvUnLock(moduleExportName);
+ }
+
+ jsvUnLock(moduleList);
+}
+
+/*JSON{ "type":"staticmethod",
+ "class" : "Modules", "name" : "removeAllCached",
+ "description" : "Remove all cached modules",
+ "generate" : "jswrap_modules_removeAllCached"
+}*/
+void jswrap_modules_removeAllCached() {
+ JsVar *moduleList = jswrap_modules_getModuleList();
+ if (!moduleList) return; // out of memory
+ jsvRemoveAllChildren(moduleList);
+ jsvUnLock(moduleList);
+}
+
+/*JSON{ "type":"staticmethod",
+ "class" : "Modules", "name" : "addCached",
+ "description" : "Add the given module to the cache",
+ "generate" : "jswrap_modules_addCached",
+ "params" : [ [ "id", "JsVar", "The module name to add" ],
+ [ "sourcecode", "JsVar", "The module's sourcecode" ] ]
+}*/
+void jswrap_modules_addCached(JsVar *id, JsVar *sourceCode) {
+ if (!jsvIsString(id) || !jsvIsString(sourceCode)) {
+ jsError("Both arguments to addCached must be strings");
+ return;
+ }
+
+ JsVar *moduleList = jswrap_modules_getModuleList();
+ if (!moduleList) return; // out of memory
+
+ JsVar *moduleExport = jspEvaluateModule(jsiGetParser(), sourceCode);
+ if (!moduleExport) {
+ jsWarn("Unable to load module");
+ } else {
+ JsVar *moduleName = jsvFindChildFromVar(moduleList, id, true);
+ if (moduleName) jsvSetValueOfName(moduleName, moduleExport);
+ jsvUnLock(moduleExport);
+ }
+ jsvUnLock(moduleList);
+
+}
diff --git a/components/external/espruino/src/jswrap_modules.h b/components/external/espruino/src/jswrap_modules.h
new file mode 100644
index 0000000000..2d2c07a345
--- /dev/null
+++ b/components/external/espruino/src/jswrap_modules.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript Functions for handling Modules
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_require(JsVar *modulename);
+
+JsVar *jswrap_modules_getCached();
+void jswrap_modules_removeCached(JsVar *id);
+void jswrap_modules_removeAllCached();
+void jswrap_modules_addCached(JsVar *id, JsVar *sourceCode);
diff --git a/components/external/espruino/src/jswrap_object.c b/components/external/espruino/src/jswrap_object.c
new file mode 100644
index 0000000000..809ce9b486
--- /dev/null
+++ b/components/external/espruino/src/jswrap_object.c
@@ -0,0 +1,332 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript methods for Objects and Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_object.h"
+#include "jsparse.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"class",
+ "class" : "Hardware",
+ "check" : "jsvIsRoot(var)",
+ "description" : ["This is the built-in class for the Espruino device. It is the 'root scope', as 'Window' is for JavaScript on the desktop." ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Object",
+ "check" : "jsvIsObject(var)",
+ "description" : ["This is the built-in class for Objects" ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Function",
+ "check" : "jsvIsFunction(var)",
+ "description" : ["This is the built-in class for Functions" ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Integer",
+ "check" : "jsvIsInt(var)",
+ "description" : ["This is the built-in class for Integer values" ]
+}*/
+/*JSON{ "type":"class",
+ "class" : "Double",
+ "check" : "jsvIsFloat(var)",
+ "description" : ["This is the built-in class for Floating Point values" ]
+}*/
+
+/*JSON{ "type":"property", "class": "Object", "name" : "length",
+ "description" : "Find the length of the object",
+ "generate" : "jswrap_object_length",
+ "return" : ["JsVar", "The value of the string"]
+}*/
+JsVar *jswrap_object_length(JsVar *parent) {
+ if (jsvIsArray(parent)) {
+ return jsvNewFromInteger(jsvGetArrayLength(parent));
+ } else if (jsvIsArrayBuffer(parent)) {
+ return jsvNewFromInteger((JsVarInt)jsvGetArrayBufferLength(parent));
+ } else if (jsvIsString(parent)) {
+ return jsvNewFromInteger((JsVarInt)jsvGetStringLength(parent));
+ }
+ return 0;
+}
+
+/*JSON{ "type":"method", "class": "Object", "name" : "toString",
+ "description" : "Convert the Object to a string",
+ "generate" : "jswrap_object_toString",
+ "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."] ],
+ "return" : ["JsVar", "A String representing the object"]
+}*/
+JsVar *jswrap_object_toString(JsVar *parent, JsVar *arg0) {
+ if (jsvIsInt(arg0) && jsvIsInt(parent)) {
+ JsVarInt radix = jsvGetInteger(arg0);
+ if (radix>=2 && radix<=36) {
+ char buf[JS_NUMBER_BUFFER_SIZE];
+ itoa(parent->varData.integer, buf, (unsigned int)radix);
+ return jsvNewFromString(buf);
+ }
+ }
+ return jsvAsString(parent, false);
+}
+
+/*JSON{ "type":"method", "class": "Object", "name" : "clone",
+ "description" : "Copy this object completely",
+ "generate" : "jswrap_object_clone",
+ "return" : ["JsVar", "A copy of this Object"]
+}*/
+JsVar *jswrap_object_clone(JsVar *parent) {
+ return jsvCopy(parent);
+}
+
+/*JSON{ "type":"staticmethod", "class": "Object", "name" : "keys",
+ "description" : "Return all enumerable keys of the given object",
+ "generate" : "jswrap_object_keys",
+ "params" : [ [ "object", "JsVar", "The object to return keys for"] ],
+ "return" : ["JsVar", "An array of strings - one for each key on the given object"]
+}*/
+JsVar *jswrap_object_keys(JsVar *obj) {
+ if (jsvIsIterable(obj)) {
+ bool (*checkerFunction)(JsVar*) = 0;
+ if (jsvIsFunction(obj)) checkerFunction = jsvIsInternalFunctionKey;
+ else if (jsvIsObject(obj)) checkerFunction = jsvIsInternalObjectKey;
+
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ if (!arr) return 0;
+ JsvIterator it;
+ jsvIteratorNew(&it, obj);
+ while (jsvIteratorHasElement(&it)) {
+ JsVar *key = jsvIteratorGetKey(&it);
+ if (!(checkerFunction && checkerFunction(key))) {
+ JsVar *name = jsvCopyNameOnly(key,false,false);
+ if (name) {
+ jsvArrayPushAndUnLock(arr, name);
+ }
+ }
+ jsvUnLock(key);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ return arr;
+ } else {
+ jsWarn("Object.keys called on non-object");
+ return 0;
+ }
+}
+
+/*JSON{ "type":"method", "class": "Object", "name" : "on",
+ "description" : ["Register an event listener for this object, for instance ```http.on('data', function(d) {...})```. See Node.js's EventEmitter."],
+ "generate" : "jswrap_object_on",
+ "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"],
+ [ "listener", "JsVar", "The listener to call when this event is received"] ]
+}*/
+void jswrap_object_on(JsVar *parent, JsVar *event, JsVar *listener) {
+ if (!jsvIsObject(parent)) {
+ jsWarn("Parent must be a proper object - not a String, Integer, etc.");
+ return;
+ }
+ if (!jsvIsString(event)) {
+ jsWarn("First argument to EventEmitter.on(..) must be a string");
+ return;
+ }
+ if (!jsvIsFunction(listener) && !jsvIsString(listener)) {
+ jsWarn("Second argument to EventEmitter.on(..) must be a function or a String (containing code)");
+ return;
+ }
+ char eventName[16] = "#on";
+ jsvGetString(event, &eventName[3], sizeof(eventName)-4);
+
+ JsVar *eventList = jsvFindChildFromString(parent, eventName, true);
+ JsVar *eventListeners = jsvSkipName(eventList);
+ if (jsvIsUndefined(eventListeners)) {
+ // just add
+ jsvSetValueOfName(eventList, listener);
+ } else {
+ if (jsvIsArray(eventListeners)) {
+ // we already have an array, just add to it
+ jsvArrayPush(eventListeners, listener);
+ } else {
+ // not an array - we need to make it an array
+ JsVar *arr = jsvNewWithFlags(JSV_ARRAY);
+ jsvArrayPush(arr, eventListeners);
+ jsvArrayPush(arr, listener);
+ jsvSetValueOfName(eventList, arr);
+ jsvUnLock(arr);
+ }
+ }
+ jsvUnLock(eventListeners);
+ jsvUnLock(eventList);
+}
+
+/*JSON{ "type":"method", "class": "Object", "name" : "emit",
+ "description" : ["Call the event listeners for this object, for instance ```http.emit('data', 'Foo')```. See Node.js's EventEmitter."],
+ "generate" : "jswrap_object_emit",
+ "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"],
+ [ "v1", "JsVar", "Optional argument 1"],
+ [ "v2", "JsVar", "Optional argument 2"] ]
+}*/
+void jswrap_object_emit(JsVar *parent, JsVar *event, JsVar *v1, JsVar *v2) {
+ if (!jsvIsObject(parent)) {
+ jsWarn("Parent must be a proper object - not a String, Integer, etc.");
+ return;
+ }
+ if (!jsvIsString(event)) {
+ jsWarn("First argument to EventEmitter.emit(..) must be a string");
+ return;
+ }
+ char eventName[16] = "#on";
+ jsvGetString(event, &eventName[3], sizeof(eventName)-4);
+ jsiQueueObjectCallbacks(parent, eventName, v1, v2);
+}
+
+/*JSON{ "type":"method", "class": "Object", "name" : "removeAllListeners",
+ "description" : ["Removes all listeners, or those of the specified event."],
+ "generate" : "jswrap_object_removeAllListeners",
+ "params" : [ [ "event", "JsVar", "The name of the event, for instance 'data'"] ]
+}*/
+void jswrap_object_removeAllListeners(JsVar *parent, JsVar *event) {
+ if (!jsvIsObject(parent)) {
+ jsWarn("Parent must be a proper object - not a String, Integer, etc.");
+ return;
+ }
+ if (jsvIsString(event)) {
+ // remove the whole child containing listeners
+ char eventName[16] = "#on";
+ jsvGetString(event, &eventName[3], sizeof(eventName)-4);
+ JsVar *eventList = jsvFindChildFromString(parent, eventName, true);
+ if (eventList) {
+ jsvRemoveChild(parent, eventList);
+ jsvUnLock(eventList);
+ }
+ } else if (jsvIsUndefined(event)) {
+ // Eep. We must remove everything beginning with '#on'
+ JsObjectIterator it;
+ jsvObjectIteratorNew(&it, parent);
+ while (jsvObjectIteratorHasElement(&it)) {
+ JsVar *key = jsvObjectIteratorGetKey(&it);
+ jsvObjectIteratorNext(&it);
+ if (jsvIsString(key) &&
+ key->varData.str[0]=='#' &&
+ key->varData.str[1]=='o' &&
+ key->varData.str[2]=='n') {
+ // begins with #on - we must kill it
+ jsvRemoveChild(parent, key);
+ }
+ jsvUnLock(key);
+ }
+ jsvObjectIteratorFree(&it);
+ } else {
+ jsWarn("First argument to EventEmitter.removeAllListeners(..) must be a string, or undefined");
+ return;
+ }
+}
+
+// ------------------------------------------------------------------------------
+
+/*JSON{ "type":"method", "class": "Function", "name" : "replaceWith",
+ "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."],
+ "generate" : "jswrap_function_replaceWith",
+ "params" : [ [ "newFunc", "JsVar", "The new function to replace this function with"] ]
+}*/
+void jswrap_function_replaceWith(JsVar *oldFunc, JsVar *newFunc) {
+ if (!jsvIsFunction(newFunc)) {
+ jsWarn("First argument of replaceWith should be a function - ignoring");
+ return;
+ }
+ // Grab scope - the one thing we want to keep
+ JsVar *scope = jsvFindChildFromString(oldFunc, JSPARSE_FUNCTION_SCOPE_NAME, false);
+ // so now remove all existing entries
+ jsvRemoveAllChildren(oldFunc);
+ // now re-add scope
+ jsvAddName(oldFunc, scope);
+ jsvUnLock(scope);
+ // now re-add other entries
+ JsObjectIterator it;
+ jsvObjectIteratorNew(&it, newFunc);
+ while (jsvObjectIteratorHasElement(&it)) {
+ JsVar *el = jsvObjectIteratorGetKey(&it);
+ jsvObjectIteratorNext(&it);
+ if (!jsvIsStringEqual(el, JSPARSE_FUNCTION_SCOPE_NAME)) {
+ JsVar *copy = jsvCopy(el);
+ if (copy) {
+ jsvAddName(oldFunc, copy);
+ jsvUnLock(copy);
+ }
+ }
+ }
+ jsvObjectIteratorFree(&it);
+
+}
+
+/*JSON{ "type":"method", "class": "Function", "name" : "call",
+ "description" : ["This executes the function with the supplied 'this' argument and parameters"],
+ "generate" : "jswrap_function_call",
+ "params" : [ [ "this", "JsVar", "The value to use as the 'this' argument when executing the function"],
+ [ "a", "JsVar", "Optional Parameter 1"],
+ [ "b", "JsVar", "Optional Parameter 2"],
+ [ "c", "JsVar", "Optional Parameter 3"],
+ [ "d", "JsVar", "Optional Parameter 4"]
+ ],
+ "return" : [ "JsVar", "The return value of executing this function" ]
+}*/
+JsVar *jswrap_function_call(JsVar *parent, JsVar *thisArg, JsVar *a, JsVar *b, JsVar *c, JsVar *d) {
+ JsVar *args[4] = {a,b,c,d};
+ int argC = 0;
+ while (argC<4 && args[argC]!=0) argC++;
+ return jspeFunctionCall(parent, 0, thisArg, false, argC, args);
+}
+
+/*JSON{ "type":"method", "class": "Function", "name" : "apply",
+ "description" : ["This executes the function with the supplied 'this' argument and parameters"],
+ "generate" : "jswrap_function_apply",
+ "params" : [ [ "this", "JsVar", "The value to use as the 'this' argument when executing the function"],
+ [ "args", "JsVar", "Optional Array of Aruments"]
+ ],
+ "return" : [ "JsVar", "The return value of executing this function" ]
+}*/
+JsVar *jswrap_function_apply(JsVar *parent, JsVar *thisArg, JsVar *argsArray) {
+ unsigned int i;
+ JsVar **args = 0;
+ size_t argC = 0;
+
+ if (jsvIsArray(argsArray)) {
+ argC = (unsigned int)jsvGetArrayLength(argsArray);
+ if (argC>64) argC=64; // sanity
+#ifdef RT_USING_JS
+ args = (JsVar**)rt_malloc((size_t)argC * sizeof(JsVar*));
+#else
+ args = (JsVar**)alloca((size_t)argC * sizeof(JsVar*));
+#endif
+
+ for (i=0;i<argC;i++) args[i] = 0;
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, argsArray);
+ while (jsvArrayIteratorHasElement(&it)) {
+ JsVarInt idx = jsvGetIntegerAndUnLock(jsvArrayIteratorGetIndex(&it));
+ if (idx>=0 && idx<argC) {
+ assert(!args[idx]); // just in case there were dups
+ args[idx] = jsvArrayIteratorGetElement(&it);
+ }
+ jsvArrayIteratorNext(&it);
+ }
+ jsvArrayIteratorFree(&it);
+ } else if (!jsvIsUndefined(argsArray)) {
+ jsWarn("Second argument to Function.apply must be an array");
+ }
+
+ JsVar *r = jspeFunctionCall(parent, 0, thisArg, false, (int)argC, args);
+ for (i=0;i<argC;i++) jsvUnLock(args[i]);
+#ifdef RT_USING_JS
+ rt_free(args);
+#else
+#error "please define RT_USING_JS on rtconfig.h"
+#endif
+ return r;
+}
diff --git a/components/external/espruino/src/jswrap_object.h b/components/external/espruino/src/jswrap_object.h
new file mode 100644
index 0000000000..a9eff4eb4c
--- /dev/null
+++ b/components/external/espruino/src/jswrap_object.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript methods for Objects and Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_object_length(JsVar *parent);
+JsVar *jswrap_object_toString(JsVar *parent, JsVar *arg0);
+JsVar *jswrap_object_clone(JsVar *parent);
+JsVar *jswrap_object_keys(JsVar *obj);
+
+void jswrap_object_on(JsVar *parent, JsVar *event, JsVar *listener);
+void jswrap_object_emit(JsVar *parent, JsVar *event, JsVar *v1, JsVar *v2);
+void jswrap_object_removeAllListeners(JsVar *parent, JsVar *event);
+
+void jswrap_function_replaceWith(JsVar *parent, JsVar *newFunc);
+JsVar *jswrap_function_call(JsVar *parent, JsVar *thisArg, JsVar *a, JsVar *b, JsVar *c, JsVar *d);
+JsVar *jswrap_function_apply(JsVar *parent, JsVar *thisArg, JsVar *argsArray);
diff --git a/components/external/espruino/src/jswrap_onewire.c b/components/external/espruino/src/jswrap_onewire.c
new file mode 100644
index 0000000000..f6c670cc6e
--- /dev/null
+++ b/components/external/espruino/src/jswrap_onewire.c
@@ -0,0 +1,332 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript OneWire Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_onewire.h"
+#include "jsdevices.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"class",
+ "class" : "OneWire",
+ "description" : ["This class provides a software-defined OneWire master. It is designed to be similar to Arduino's OneWire library." ]
+}*/
+
+static inline Pin onewire_getpin(JsVar *parent) {
+ return jshGetPinFromVarAndUnLock(jsvObjectGetChild(parent, "pin", 0));
+}
+
+
+/** Reset one-wire, return true if a device was present */
+static bool NO_INLINE OneWireReset(Pin pin) {
+ jshPinSetState(pin, JSHPINSTATE_GPIO_OUT_OPENDRAIN);
+ //jshInterruptOff();
+ jshPinSetValue(pin, 0);
+ jshDelayMicroseconds(500);
+ jshPinSetValue(pin, 1);
+ jshDelayMicroseconds(80);
+ bool hasDevice = !jshPinGetValue(pin);
+ //jshInterruptOn();
+ jshDelayMicroseconds(420);
+ return hasDevice;
+}
+
+/** Write 'bits' bits, and return what was read (to read, you must send all 1s) */
+static JsVarInt NO_INLINE OneWireRead(Pin pin, int bits) {
+ jshPinSetState(pin, JSHPINSTATE_GPIO_OUT_OPENDRAIN);
+ JsVarInt result = 0;
+ JsVarInt mask = 1;
+ while (bits-- > 0) {
+ jshInterruptOff();
+ jshPinSetValue(pin, 0);
+ jshDelayMicroseconds(3);
+ jshPinSetValue(pin, 1);
+ jshDelayMicroseconds(10); // leave time to let it rise
+ if (jshPinGetValue(pin))
+ result = result | mask;
+ jshInterruptOn();
+ jshDelayMicroseconds(53);
+ mask = mask << 1;
+ }
+
+ return result;
+}
+
+/** Write 'bits' bits, and return what was read (to read, you must send all 1s) */
+static void NO_INLINE OneWireWrite(Pin pin, int bits, JsVarInt data) {
+ jshPinSetState(pin, JSHPINSTATE_GPIO_OUT_OPENDRAIN);
+ JsVarInt mask = 1;
+ while (bits-- > 0) {
+ if (data & mask) { // short pulse
+ jshInterruptOff();
+ jshPinSetValue(pin, 0);
+ jshDelayMicroseconds(10);
+ jshPinSetValue(pin, 1);
+ jshInterruptOn();
+ jshDelayMicroseconds(55);
+ } else { // long pulse
+ jshInterruptOff();
+ jshPinSetValue(pin, 0);
+ jshDelayMicroseconds(65);
+ jshPinSetValue(pin, 1);
+ jshInterruptOn();
+ jshDelayMicroseconds(5);
+ }
+ mask = mask << 1;
+ }
+}
+
+/*JSON{ "type":"constructor", "class": "OneWire", "name": "OneWire",
+ "description" : "Create a software OneWire implementation on the given pin",
+ "generate" : "jswrap_onewire_constructor",
+ "params" : [ [ "pin", "pin", "The pin to implement OneWire on" ] ],
+ "return" : [ "JsVar", "A OneWire object" ]
+
+}*/
+JsVar *jswrap_onewire_constructor(Pin pin) {
+ JsVar *ow = jspNewObject(jsiGetParser(), 0, "OneWire");
+ if (ow) {
+ JsVar *pinVar = jsvFindChildFromString(ow, "pin", true);
+ if (pinVar) {
+ JsVar *pv = jsvNewFromPin(pin);
+ jsvSetValueOfName(pinVar, pv);
+ jsvUnLock(pv);
+ }
+ jsvUnLock(pinVar);
+ }
+ return ow;
+}
+
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "reset",
+ "description" : "Perform a reset cycle",
+ "generate" : "jswrap_onewire_reset",
+ "return" : [ "bool", "True is a device was present (it held the bus low)" ]
+}*/
+bool jswrap_onewire_reset(JsVar *parent) {
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return 0;
+ return OneWireReset(pin);
+}
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "select",
+ "description" : "Select a ROM - reset needs to be done first",
+ "generate" : "jswrap_onewire_select",
+ "params" : [ [ "rom", "int", "The rom to select" ] ]
+}*/
+void jswrap_onewire_select(JsVar *parent, JsVarInt rom) {
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return;
+ OneWireWrite(pin, 8, 0x55);
+ OneWireWrite(pin, 64, rom);
+}
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "skip",
+ "description" : "Skip a ROM",
+ "generate" : "jswrap_onewire_skip"
+}*/
+void jswrap_onewire_skip(JsVar *parent) {
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return;
+ OneWireWrite(pin, 8, 0xCC);
+}
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "write",
+ "description" : "Write a byte",
+ "generate" : "jswrap_onewire_write",
+ "params" : [ [ "data", "int", "A byte to write" ],
+ [ "power", "bool", "Whether to leave power on after write (default is false)" ] ]
+}*/
+void jswrap_onewire_write(JsVar *parent, int data, bool leavePowerOn) {
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return;
+ OneWireWrite(pin, 8, data);
+
+ if (!leavePowerOn) {
+ jshPinSetState(pin, JSHPINSTATE_GPIO_IN);
+ jshPinSetValue(pin, 0);
+ }
+}
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "read",
+ "description" : "Read a byte",
+ "generate" : "jswrap_onewire_read",
+ "return" : [ "int", "The byte that was read" ]
+}*/
+JsVarInt jswrap_onewire_read(JsVar *parent) {
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return -1;
+ return OneWireRead(pin, 8);
+}
+
+
+/*JSON{ "type":"method", "class": "OneWire", "name" : "search",
+ "description" : "Search for devices",
+ "generate" : "jswrap_onewire_search",
+ "return" : [ "JsVar", "An array of devices that were found" ]
+}*/
+JsVar *jswrap_onewire_search(JsVar *parent) {
+ // search - code from http://www.maximintegrated.com/app-notes/index.mvp/id/187
+ Pin pin = onewire_getpin(parent);
+ if (!jshIsPinValid(pin)) return 0;
+
+ JsVar *array = jsvNewWithFlags(JSV_ARRAY);
+ if (!array) return 0;
+
+ // global search state
+ unsigned char ROM_NO[8];
+ int LastDiscrepancy;
+ int LastFamilyDiscrepancy;
+ int LastDeviceFlag;
+
+ // reset the search state
+ LastDiscrepancy = 0;
+ LastDeviceFlag = FALSE;
+ LastFamilyDiscrepancy = 0;
+
+ int search_result = true;
+
+ while (search_result) {
+
+ int id_bit_number;
+ int last_zero, rom_byte_number;
+ unsigned char id_bit, cmp_id_bit;
+ unsigned char rom_byte_mask, search_direction;
+
+ // initialize for search
+ id_bit_number = 1;
+ last_zero = 0;
+ rom_byte_number = 0;
+ rom_byte_mask = 1;
+ search_result = 0;
+
+ // if the last call was not the last one
+ if (!LastDeviceFlag)
+ {
+ // 1-Wire reset
+ if (!OneWireReset(pin))
+ {
+ // reset the search
+ LastDiscrepancy = 0;
+ LastDeviceFlag = FALSE;
+ LastFamilyDiscrepancy = 0;
+ return array;
+ }
+
+ // issue the search command
+ OneWireWrite(pin, 8, 0xF0);
+
+ // loop to do the search
+ do
+ {
+ // read a bit and its complement
+ id_bit = (unsigned char)OneWireRead(pin, 1);
+ cmp_id_bit = (unsigned char)OneWireRead(pin, 1);
+
+ // check for no devices on 1-wire
+ if ((id_bit == 1) && (cmp_id_bit == 1))
+ break;
+ else
+ {
+ // all devices coupled have 0 or 1
+ if (id_bit != cmp_id_bit)
+ search_direction = id_bit; // bit write value for search
+ else
+ {
+ // if this discrepancy if before the Last Discrepancy
+ // on a previous next then pick the same as last time
+ if (id_bit_number < LastDiscrepancy)
+ search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
+ else
+ // if equal to last pick 1, if not then pick 0
+ search_direction = (id_bit_number == LastDiscrepancy);
+
+ // if 0 was picked then record its position in LastZero
+ if (search_direction == 0)
+ {
+ last_zero = id_bit_number;
+
+ // check for Last discrepancy in family
+ if (last_zero < 9)
+ LastFamilyDiscrepancy = last_zero;
+ }
+ }
+
+ // set or clear the bit in the ROM byte rom_byte_number
+ // with mask rom_byte_mask
+ if (search_direction == 1)
+ ROM_NO[rom_byte_number] |= rom_byte_mask;
+ else
+ ROM_NO[rom_byte_number] &= (unsigned char)~rom_byte_mask;
+
+ // serial number search direction write bit
+ OneWireWrite(pin, 1, search_direction);
+
+ // increment the byte counter id_bit_number
+ // and shift the mask rom_byte_mask
+ id_bit_number++;
+ rom_byte_mask = (unsigned char)(rom_byte_mask << 1);
+
+ // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
+ if (rom_byte_mask == 0)
+ {
+ rom_byte_number++;
+ rom_byte_mask = 1;
+ }
+ }
+ }
+ while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
+
+ // if the search was successful then
+ if (!((id_bit_number < 65)))
+ {
+ // search successful so set LastDiscrepancy,LastDeviceFlag,search_result
+ LastDiscrepancy = last_zero;
+
+ // check for last device
+ if (LastDiscrepancy == 0)
+ LastDeviceFlag = TRUE;
+
+ search_result = TRUE;
+ }
+ }
+
+ // if no device found then reset counters so next 'search' will be like a first
+ if (!search_result || !ROM_NO[0])
+ {
+ LastDiscrepancy = 0;
+ LastDeviceFlag = FALSE;
+ LastFamilyDiscrepancy = 0;
+ search_result = FALSE;
+ }
+
+ if (search_result) {
+ JsVar *val = jsvNewFromInteger(
+ (((JsVarInt)ROM_NO[7])<<(7*8)) |
+ (((JsVarInt)ROM_NO[6])<<(6*8)) |
+ (((JsVarInt)ROM_NO[5])<<(5*8)) |
+ (((JsVarInt)ROM_NO[4])<<(4*8)) |
+ (((JsVarInt)ROM_NO[3])<<(3*8)) |
+ (((JsVarInt)ROM_NO[2])<<(2*8)) |
+ (((JsVarInt)ROM_NO[1])<<(1*8)) |
+ ((JsVarInt)ROM_NO[0])
+ );
+ if (val) jsvArrayPushAndUnLock(array, val);
+ }
+
+ NOT_USED(LastFamilyDiscrepancy);
+ }
+
+ return array;
+}
+
diff --git a/components/external/espruino/src/jswrap_onewire.h b/components/external/espruino/src/jswrap_onewire.h
new file mode 100644
index 0000000000..5377bbff32
--- /dev/null
+++ b/components/external/espruino/src/jswrap_onewire.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript OneWire Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jspin.h"
+
+JsVar *jswrap_onewire_constructor(Pin pin);
+bool jswrap_onewire_reset(JsVar *parent);
+void jswrap_onewire_select(JsVar *parent, JsVarInt rom);
+void jswrap_onewire_skip(JsVar *parent);
+void jswrap_onewire_write(JsVar *parent, int data, bool leavePowerOn);
+JsVarInt jswrap_onewire_read(JsVar *parent);
+JsVar *jswrap_onewire_search(JsVar *parent);
diff --git a/components/external/espruino/src/jswrap_pin.c b/components/external/espruino/src/jswrap_pin.c
new file mode 100644
index 0000000000..247ec1897a
--- /dev/null
+++ b/components/external/espruino/src/jswrap_pin.c
@@ -0,0 +1,73 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript Pin Object Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_pin.h"
+
+/*JSON{ "type":"class",
+ "class" : "Pin",
+ "check" : "jsvIsPin(var)",
+ "description" : ["This is the built-in class for Pins, such as D0,D1,LED1, or BTN",
+ "You can call the methods on Pin, or you can use Wiring-style functions such as digitalWrite" ]
+}*/
+
+/*JSON{ "type":"method", "class": "Pin", "name" : "read",
+ "description" : "Returns the input state of the pin as a boolean",
+ "generate" : "jswrap_pin_read",
+ "return" : ["bool", "Whether pin is a logical 1 or 0"]
+}*/
+bool jswrap_pin_read(JsVar *parent) {
+ Pin pin = jshGetPinFromVar(parent);
+ return jshPinInput(pin);
+}
+
+/*JSON{ "type":"method", "class": "Pin", "name" : "set",
+ "description" : "Sets the output state of the pin to a 1",
+ "generate" : "jswrap_pin_set"
+}*/
+void jswrap_pin_set(JsVar *parent) {
+ Pin pin = jshGetPinFromVar(parent);
+ jshPinOutput(pin, 1);
+}
+
+/*JSON{ "type":"method", "class": "Pin", "name" : "reset",
+ "description" : "Sets the output state of the pin to a 0",
+ "generate" : "jswrap_pin_reset"
+}*/
+void jswrap_pin_reset(JsVar *parent) {
+ Pin pin = jshGetPinFromVar(parent);
+ jshPinOutput(pin, 0);
+}
+
+/*JSON{ "type":"method", "class": "Pin", "name" : "write",
+ "description" : "Sets the output state of the pin to the parameter given",
+ "generate" : "jswrap_pin_write",
+ "params" : [ [ "value", "bool", "Whether to set output high (true/1) or low (false/0)"] ]
+}*/
+void jswrap_pin_write(JsVar *parent, bool value) {
+ Pin pin = jshGetPinFromVar(parent);
+ jshPinOutput(pin, value);
+}
+
+/*JSON{ "type":"method", "class": "Pin", "name" : "writeAtTime", "ifndef" : "SAVE_ON_FLASH",
+ "description" : "Sets the output state of the pin to the parameter given at the specified time",
+ "generate" : "jswrap_pin_writeAtTime",
+ "params" : [ [ "value", "bool", "Whether to set output high (true/1) or low (false/0)"],
+ ["time", "float", "Time at which to write"] ]
+}*/
+void jswrap_pin_writeAtTime(JsVar *parent, bool value, JsVarFloat time) {
+ Pin pin = jshGetPinFromVar(parent);
+ JsSysTime sTime = jshGetTimeFromMilliseconds(time*1000);
+ jshPinOutputAtTime(sTime, pin, value);
+}
diff --git a/components/external/espruino/src/jswrap_pin.h b/components/external/espruino/src/jswrap_pin.h
new file mode 100644
index 0000000000..26acac5ba1
--- /dev/null
+++ b/components/external/espruino/src/jswrap_pin.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript Pin Object Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jsinteractive.h"
+
+bool jswrap_pin_read(JsVar *parent);
+void jswrap_pin_set(JsVar *parent);
+void jswrap_pin_reset(JsVar *parent);
+void jswrap_pin_write(JsVar *parent, bool value);
+void jswrap_pin_writeAtTime(JsVar *parent, bool value, JsVarFloat time);
diff --git a/components/external/espruino/src/jswrap_process.c b/components/external/espruino/src/jswrap_process.c
new file mode 100644
index 0000000000..29ffe713fc
--- /dev/null
+++ b/components/external/espruino/src/jswrap_process.c
@@ -0,0 +1,94 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript 'process' object - for information about the Espruino board
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jswrap_process.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"class",
+ "class" : "process",
+ "description" : "This class contains information about Espruino itself"
+}*/
+
+/*JSON{ "type":"staticproperty",
+ "class" : "process", "name" : "version",
+ "description" : "Returns the version of Espruino as a String",
+ "generate_full" : "jsvNewFromString(JS_VERSION)",
+ "return" : ["JsVar", "The version of Espruino"]
+}*/
+
+/*JSON{ "type":"staticproperty",
+ "class" : "process", "name" : "env",
+ "description" : "Returns an Object containing various pre-defined variables. standard ones are BOARD, VERSION",
+ "generate" : "jswrap_process_env",
+ "return" : ["JsVar", "An object"]
+}*/
+JsVar *jswrap_process_env() {
+ JsVar *obj = jsvNewWithFlags(JSV_OBJECT);
+ jsvUnLock(jsvObjectSetChild(obj, "VERSION", jsvNewFromString(JS_VERSION)));
+ jsvUnLock(jsvObjectSetChild(obj, "BUILD_DATE", jsvNewFromString(__DATE__)));
+ jsvUnLock(jsvObjectSetChild(obj, "BUILD_TIME", jsvNewFromString(__TIME__)));
+ jsvUnLock(jsvObjectSetChild(obj, "BOARD", jsvNewFromString(PC_BOARD_ID)));
+ jsvUnLock(jsvObjectSetChild(obj, "CHIP", jsvNewFromString(PC_BOARD_CHIP)));
+ jsvUnLock(jsvObjectSetChild(obj, "CHIP_FAMILY", jsvNewFromString(PC_BOARD_CHIP_FAMILY)));
+ jsvUnLock(jsvObjectSetChild(obj, "FLASH", jsvNewFromInteger(FLASH_TOTAL)));
+ jsvUnLock(jsvObjectSetChild(obj, "RAM", jsvNewFromInteger(RAM_TOTAL)));
+
+ return obj;
+}
+
+
+/*JSON{ "type":"staticmethod",
+ "class" : "process", "name" : "memory",
+ "description" : ["Run a Garbage Collection pass, and return an object containing information on memory usage.",
+ "free : Memory that is available to be used",
+ "usage : Memory that has been used",
+ "total : Total memory",
+ "history : Memory used for command history - that is freed if memory is low. Note that this is INCLUDED in the figure for 'free'.",
+ "On ARM, stackEndAddress is the address (that can be used with peek/poke/etc) of the END of the stack. The stack grows down, so unless you do a lot of recursion, the bytes above this can be used."],
+ "generate" : "jswrap_process_memory",
+ "return" : ["JsVar", "Information about memory usage"]
+}*/
+#ifdef ARM
+extern int _end; // end of ram used (variables)
+extern int _etext; // end of flash text (binary) section
+#endif
+JsVar *jswrap_process_memory() {
+ jsvGarbageCollect();
+ JsVar *obj = jsvNewWithFlags(JSV_OBJECT);
+ if (obj) {
+ unsigned int history = 0;
+ JsVar *historyVar = jsvObjectGetChild(jsiGetParser()->root, JSI_HISTORY_NAME, 0);
+ if (historyVar) {
+ history = (unsigned int)jsvCountJsVarsUsed(historyVar); // vars used to store history
+ jsvUnLock(historyVar);
+ }
+ unsigned int usage = jsvGetMemoryUsage() - history;
+ unsigned int total = jsvGetMemoryTotal();
+ jsvUnLock(jsvObjectSetChild(obj, "free", jsvNewFromInteger(total-usage)));
+ jsvUnLock(jsvObjectSetChild(obj, "usage", jsvNewFromInteger(usage)));
+ jsvUnLock(jsvObjectSetChild(obj, "total", jsvNewFromInteger(total)));
+ jsvUnLock(jsvObjectSetChild(obj, "history", jsvNewFromInteger(history)));
+
+#ifdef ARM
+ jsvUnLock(jsvObjectSetChild(obj, "stackEndAddress", jsvNewFromInteger((JsVarInt)(unsigned int)&_end)));
+ jsvUnLock(jsvObjectSetChild(obj, "flash_start", jsvNewFromInteger((JsVarInt)FLASH_START)));
+ jsvUnLock(jsvObjectSetChild(obj, "flash_binary_end", jsvNewFromInteger((JsVarInt)(unsigned int)&_etext)));
+ jsvUnLock(jsvObjectSetChild(obj, "flash_code_start", jsvNewFromInteger((JsVarInt)FLASH_SAVED_CODE_START)));
+ jsvUnLock(jsvObjectSetChild(obj, "flash_length", jsvNewFromInteger((JsVarInt)FLASH_TOTAL)));
+#endif
+ }
+ return obj;
+}
diff --git a/components/external/espruino/src/jswrap_process.h b/components/external/espruino/src/jswrap_process.h
new file mode 100644
index 0000000000..604be68550
--- /dev/null
+++ b/components/external/espruino/src/jswrap_process.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript 'process' object - for information about the Espruino board
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_process_env();
+JsVar *jswrap_process_memory();
diff --git a/components/external/espruino/src/jswrap_serial.c b/components/external/espruino/src/jswrap_serial.c
new file mode 100644
index 0000000000..61dcf6e7b0
--- /dev/null
+++ b/components/external/espruino/src/jswrap_serial.c
@@ -0,0 +1,197 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript Serial Port Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_serial.h"
+#include "jsdevices.h"
+#include "jsinteractive.h"
+
+/*JSON{ "type":"class",
+ "class" : "Serial",
+ "description" : ["This class allows use of the built-in USARTs",
+ "Methods may be called on the USB, Serial1, Serial2, Serial3, Serial4, Serial5 and Serial6 objects. While different processors provide different numbers of USARTs, you can always rely on at least Serial1 and Serial2" ]
+}*/
+
+/*JSON{ "type":"object", "name":"USB", "instanceof" : "Serial",
+ "description" : ["The USB Serial port" ],
+ "#if" : "defined(USB)"
+}*/
+/*JSON{ "type":"object", "name":"Serial1", "instanceof" : "Serial",
+ "description" : ["The first Serial (USART) port" ],
+ "#if" : "USARTS>=1"
+}*/
+/*JSON{ "type":"object", "name":"Serial2", "instanceof" : "Serial",
+ "description" : ["The second Serial (USART) port" ],
+ "#if" : "USARTS>=2"
+}*/
+/*JSON{ "type":"object", "name":"Serial3", "instanceof" : "Serial",
+ "description" : ["The third Serial (USART) port" ],
+ "#if" : "USARTS>=3"
+}*/
+/*JSON{ "type":"object", "name":"Serial4", "instanceof" : "Serial",
+ "description" : ["The fourth Serial (USART) port" ],
+ "#if" : "USARTS>=4"
+}*/
+/*JSON{ "type":"object", "name":"Serial5", "instanceof" : "Serial",
+ "description" : ["The fifth Serial (USART) port" ],
+ "#if" : "USARTS>=5"
+}*/
+/*JSON{ "type":"object", "name":"Serial6", "instanceof" : "Serial",
+ "description" : ["The sixth Serial (USART) port" ],
+ "#if" : "USARTS>=6"
+}*/
+
+
+
+/*JSON{ "type":"method", "class": "Serial", "name" : "setConsole",
+ "description" : "Set this Serial port as the port for the console",
+ "generate_full" : "jsiSetConsoleDevice(jsiGetDeviceFromClass(parent))"
+}*/
+
+/*JSON{ "type":"method", "class": "Serial", "name" : "setup",
+ "description" : "Setup this Serial port with the given baud rate and options",
+ "generate" : "jswrap_serial_setup",
+ "params" : [ [ "baudrate", "int", "The baud rate - the default is 9600"],
+ [ "options", "JsVar", ["An optional structure containing extra information on initialising the serial port.",
+ "```{rx:pin,tx:pin}```",
+ "Note that even after changing the RX and TX pins, if you have called setup before then the previous RX and TX pins will still be connected to the Serial port as well - until you set them to something else using digitalWrite" ] ] ]
+}*/
+void jswrap_serial_setup(JsVar *parent, JsVarInt baud, JsVar *options) {
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ JshUSARTInfo inf;
+ jshUSARTInitInfo(&inf);
+
+ if (baud>0) inf.baudRate = (int)baud;
+
+ if (jsvIsObject(options)) {
+
+ inf.pinRX = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "rx", 0));
+ inf.pinTX = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "tx", 0));
+ inf.bytesize = (unsigned char)jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "bytesize", 0));
+
+ JsVar *v;
+ v = jsvObjectGetChild(options, "parity", 0);
+
+ if(jsvIsNull(v)) {
+ inf.parity = 0;
+ }
+ else if(jsvIsString(v)) {
+ inf.parity = 0xFF;
+ char s[8] = "";
+
+ jsvGetString(v, s, sizeof(s) - 1);
+
+ if(!strcmp(s, "o") || !strcmp(s, "odd")) {
+ inf.parity = 1;
+ }
+ else if(!strcmp(s, "e") || !strcmp(s, "even")) {
+ inf.parity = 2;
+ }
+ }
+ else if(jsvIsInt(v)) {
+ inf.parity = (unsigned char)jsvGetInteger(v);
+ }
+
+ jsvUnLock(v);
+
+ v = jsvObjectGetChild(options, "stopbits", 0);
+ inf.stopbits = (unsigned char)jsvGetInteger(v);
+ jsvUnLock(v);
+ }
+
+ jshUSARTSetup(device, &inf);
+ // Set baud rate in object, so we can initialise it on startup
+ if (baud != DEFAULT_BAUD_RATE) {
+ JsVar *baudVar = jsvNewFromInteger(baud);
+ jsvUnLock(jsvSetNamedChild(parent, baudVar, USART_BAUDRATE_NAME));
+ jsvUnLock(baudVar);
+ } else
+ jsvRemoveNamedChild(parent, USART_BAUDRATE_NAME);
+ // Do the same for options
+ if (options)
+ jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME));
+ else
+ jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME);
+}
+
+/*JSON{ "type":"method", "class": "Serial", "name" : "print",
+ "description" : "Print a string to the serial port - without a line feed",
+ "generate" : "jswrap_serial_print",
+ "params" : [ [ "string", "JsVar", "A String to print"] ]
+}*/
+/*JSON{ "type":"method", "class": "Serial", "name" : "println",
+ "description" : "Print a line to the serial port (newline character sent are '\r\n')",
+ "generate" : "jswrap_serial_println",
+ "params" : [ [ "string", "JsVar", "A String to print"] ]
+}*/
+void _jswrap_serial_print(JsVar *parent, JsVar *str, bool newLine) {
+ NOT_USED(parent);
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ str = jsvAsString(str, false);
+ jsiTransmitStringVar(device,str);
+ jsvUnLock(str);
+ if (newLine) {
+ jshTransmit(device, (unsigned char)'\r');
+ jshTransmit(device, (unsigned char)'\n');
+ }
+}
+void jswrap_serial_print(JsVar *parent, JsVar *str) {
+ _jswrap_serial_print(parent, str, false);
+}
+void jswrap_serial_println(JsVar *parent, JsVar *str) {
+ _jswrap_serial_print(parent, str, true);
+}
+/*JSON{ "type":"method", "class": "Serial", "name" : "write",
+ "description" : "Write a character or array of characters to the serial port - without a line feed",
+ "generate" : "jswrap_serial_write",
+ "params" : [ [ "data", "JsVar", "A byte, a string, or an array of bytes to write"] ]
+}*/
+void jswrap_serial_write(JsVar *parent, JsVar *data) {
+ NOT_USED(parent);
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ if (jsvIsNumeric(data)) {
+ jshTransmit(device, (unsigned char)jsvGetInteger(data));
+ } else if (jsvIsIterable(data)) {
+ JsvIterator it;
+ jsvIteratorNew(&it, data);
+ while (jsvIteratorHasElement(&it)) {
+ jshTransmit(device, (unsigned char)jsvIteratorGetIntegerValue(&it));
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ } else {
+ jsWarn("Data supplied was not an integer - or iterable");
+ }
+}
+
+/*JSON{ "type":"method", "class": "Serial", "name" : "onData",
+ "description" : ["When a character is received on this serial port, the function supplied to onData gets called.",
+ "Only one function can ever be supplied, so calling onData(undefined) will stop any function being called"],
+ "generate" : "jswrap_serial_onData",
+ "params" : [ [ "function", "JsVarName", "A function to call when data arrives. It takes one argument, which is an object with a 'data' field"] ]
+}*/
+void jswrap_serial_onData(JsVar *parent, JsVar *funcVar) {
+ JsVar *skippedFunc = jsvSkipName(funcVar);
+ if (!jsvIsFunction(skippedFunc) && !jsvIsString(skippedFunc)) {
+ jsiConsolePrint("Function or String not supplied - removing onData handler.\n");
+ JsVar *handler = jsvFindChildFromString(parent, USART_CALLBACK_NAME, false);
+ if (handler) {
+ jsvRemoveChild(parent, handler);
+ jsvUnLock(handler);
+ }
+ } else {
+ jsvUnLock(jsvSetNamedChild(parent, funcVar, USART_CALLBACK_NAME));
+ }
+ jsvUnLock(skippedFunc);
+}
diff --git a/components/external/espruino/src/jswrap_serial.h b/components/external/espruino/src/jswrap_serial.h
new file mode 100644
index 0000000000..ff98596753
--- /dev/null
+++ b/components/external/espruino/src/jswrap_serial.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript Serial Port Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+
+void jswrap_serial_setup(JsVar *parent, JsVarInt baud, JsVar *options);
+void jswrap_serial_print(JsVar *parent, JsVar *str);
+void jswrap_serial_println(JsVar *parent, JsVar *str);
+void jswrap_serial_write(JsVar *parent, JsVar *data);
+void jswrap_serial_onData(JsVar *parent, JsVar *funcVar);
diff --git a/components/external/espruino/src/jswrap_spi_i2c.c b/components/external/espruino/src/jswrap_spi_i2c.c
new file mode 100644
index 0000000000..3a55d82deb
--- /dev/null
+++ b/components/external/espruino/src/jswrap_spi_i2c.c
@@ -0,0 +1,455 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript SPI and I2C Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_spi_i2c.h"
+#include "jsdevices.h"
+#include "jsinteractive.h"
+#include "jswrap_arraybuffer.h"
+
+/*JSON{ "type":"class",
+ "class" : "SPI",
+ "description" : ["This class allows use of the built-in SPI ports. Currently it is SPI master only." ]
+}*/
+
+/*JSON{ "type":"object", "name":"SPI1", "instanceof" : "SPI",
+ "description" : ["The first SPI port" ],
+ "#if" : "SPIS>=1"
+}*/
+/*JSON{ "type":"object", "name":"SPI2", "instanceof" : "SPI",
+ "description" : ["The second SPI port" ],
+ "#if" : "SPIS>=2"
+}*/
+/*JSON{ "type":"object", "name":"SPI3", "instanceof" : "SPI",
+ "description" : ["The third SPI port" ],
+ "#if" : "SPIS>=3"
+}*/
+
+
+typedef unsigned char (*unsigned_char_map_function)(IOEventFlags device, unsigned char data);
+
+JsVar *map_var_as_unsigned_char(JsVar *src, IOEventFlags device, unsigned_char_map_function map) {
+ if (jsvIsNumeric(src)) {
+ return jsvNewFromInteger(map(device, (unsigned char)jsvGetInteger(src)));
+ } else if (jsvIsString(src)) {
+ JsVar *dst = jsvNewFromEmptyString();
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, src, 0);
+ while (jsvStringIteratorHasChar(&it)) {
+ unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it);
+ unsigned char out = map(device, in);
+ jsvAppendStringBuf(dst, (char*)&out, 1);
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ return dst;
+ } else if (jsvIsIterable(src)) {
+ // OPT: could use ArrayBuffer for return values
+ JsVar *dst = jsvNewWithFlags(JSV_ARRAY);
+ JsvIterator it;
+ jsvIteratorNew(&it, src);
+ while (jsvIteratorHasElement(&it)) {
+ unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it);
+ unsigned char out = map(device, in);
+ JsVar *outVar = jsvNewFromInteger(out);
+ jsvArrayPushAndUnLock(dst, outVar);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ return dst;
+ } else {
+ jsError("Variable type not suited to transmit operation");
+ return 0;
+ }
+}
+
+
+/*JSON{ "type":"method", "class": "SPI", "name" : "setup",
+ "description" : "Set up this SPI port. Master, MSB first, no checksum",
+ "generate" : "jswrap_spi_setup",
+ "params" : [ [ "options", "JsVar", ["An optional structure containing extra information on initialising the SPI port",
+ "Please note that baud rate is set to the nearest that can be managed - which may be -+ 50%",
+ "```{sck:pin, miso:pin, mosi:pin, baud:integer, mode:integer=0 }```",
+ "If sck,miso and mosi are left out, they will automatically be chosen. However if one or more is specified then the unspecified pins will not be set up.",
+ "The SPI ```mode``` is between 0 and 3 - see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase",
+ "On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually grouped on the chip - and you can't mix pins from two groups). Espruino will not warn you about this." ] ] ]
+}*/
+void jswrap_spi_setup(JsVar *parent, JsVar *options) {
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ JshSPIInfo inf;
+ jshSPIInitInfo(&inf);
+ if (jsvIsObject(options)) {
+ inf.pinSCK = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sck", 0));
+ inf.pinMISO = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "miso", 0));
+ inf.pinMOSI = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "mosi", 0));
+ JsVar *v;
+ v = jsvObjectGetChild(options, "baud", 0);
+ if (jsvIsNumeric(v))
+ inf.baudRate = (int)jsvGetInteger(v);
+ v = jsvObjectGetChild(options, "mode", 0);
+ if (jsvIsNumeric(v))
+ inf.spiMode = ((int)jsvGetInteger(v))&3;;
+ jsvUnLock(v);
+ }
+ jshSPISetup(device, &inf);
+ // Set up options, so we can initialise it on startup
+ if (options)
+ jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME));
+ else
+ jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME);
+}
+
+
+/*JSON{ "type":"method", "class": "SPI", "name" : "send",
+ "description" : [ "Send data down SPI, and return the result",
+ "Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds." ],
+ "generate" : "jswrap_spi_send",
+ "params" : [ [ "data", "JsVar", "The data to send - either an integer, array, or string (which is the most efficient)" ],
+ [ "nss_pin", "pin", "An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised." ] ],
+ "return" : [ "JsVar", "The data that was returned" ]
+
+}*/
+JsVar *jswrap_spi_send(JsVar *parent, JsVar *srcdata, Pin nss_pin) {
+ NOT_USED(parent);
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+
+ if (!jshIsDeviceInitialised(device)) {
+ JshSPIInfo inf;
+ jshSPIInitInfo(&inf);
+ jshSPISetup(device, &inf);
+ }
+
+ JsVar *dst = 0;
+
+ // assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false);
+
+ // send data
+ if (jsvIsNumeric(srcdata)) {
+ jshSPISend(device, (unsigned char)jsvGetInteger(srcdata));
+ dst = jsvNewFromInteger(jshSPISend(device, -1)); // retrieve the byte (no send!)
+ } else if (jsvIsArray(srcdata)) {
+ dst = jsvNewWithFlags(JSV_ARRAY);
+ JsArrayIterator it;
+ jsvArrayIteratorNew(&it, srcdata);
+ int incount = 0, outcount = 0;
+ while (jsvArrayIteratorHasElement(&it)) {
+ unsigned char in = (unsigned char)jsvGetIntegerAndUnLock(jsvArrayIteratorGetElement(&it));
+ incount++;
+ int out = jshSPISend(device, in); // this returns -1 only if no data (so if -1 gets in an array it is an error!)
+ if (out>=0) {
+ outcount++;
+ JsVar *outVar = jsvNewFromInteger(out);
+ jsvArrayPushAndUnLock(dst, outVar);
+ }
+ jsvArrayIteratorNext(&it);
+ }
+ jsvArrayIteratorFree(&it);
+ // finally add the remaining bytes (no send!)
+ while (outcount < incount) {
+ outcount++;
+ int out = jshSPISend(device, -1);
+ JsVar *outVar = jsvNewFromInteger(out);
+ jsvArrayPushAndUnLock(dst, outVar);
+ }
+ } else if (jsvIsString(srcdata)) {
+ dst = jsvNewFromEmptyString();
+ JsvStringIterator it;
+ jsvStringIteratorNew(&it, srcdata, 0);
+ int incount = 0, outcount = 0;
+ while (jsvStringIteratorHasChar(&it)) {
+ unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it);
+ incount++;
+ int out = jshSPISend(device, in);
+ if (out>=0) {
+ outcount++;
+ char outc = (char)out;
+ jsvAppendStringBuf(dst, (char*)&outc, 1);
+ }
+ jsvStringIteratorNext(&it);
+ }
+ jsvStringIteratorFree(&it);
+ // finally add the remaining bytes (no send!)
+ while (outcount < incount) {
+ outcount++;
+ unsigned char out = (unsigned char)jshSPISend(device, -1);
+ jsvAppendStringBuf(dst, (char*)&out, 1);
+ }
+ } else if (jsvIsIterable(srcdata)) {
+ JsVar *lenVar = jsvNewFromInteger(jsvGetLength(srcdata));
+ dst = jswrap_typedarray_constructor(ARRAYBUFFERVIEW_UINT8, lenVar,0,0);
+ jsvUnLock(lenVar);
+ JsvIterator it;
+ JsvArrayBufferIterator dstit;
+ jsvIteratorNew(&it, srcdata);
+ jsvArrayBufferIteratorNew(&dstit, dst, 0);
+ while (jsvIteratorHasElement(&it)) {
+ unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it);
+ int out = jshSPISend(device, in);
+ jsvArrayBufferIteratorSetIntegerValue(&dstit, out);
+ jsvIteratorNext(&it);
+ jsvArrayBufferIteratorNext(&dstit);
+ }
+ jsvIteratorFree(&it);
+ jsvArrayBufferIteratorFree(&dstit);
+ } else {
+ jsError("Variable type not suited to transmit operation");
+ dst = 0;
+ }
+
+ // de-assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true);
+ return dst;
+}
+
+// used by jswrap_spi_send4bit
+void spi_send4bit(IOEventFlags device, unsigned char data, int bit0, int bit1) {
+ unsigned char lookup[] = {
+ (unsigned char)((bit0<<4) | bit0),
+ (unsigned char)((bit0<<4) | bit1),
+ (unsigned char)((bit1<<4) | bit0),
+ (unsigned char)((bit1<<4) | bit1),
+ };
+ // Send each bit as 4 bits, MSB first
+ /*jshSPISend(device, lookup[(data>>6)&3]);
+ jshSPISend(device, lookup[(data>>4)&3]);
+ jshSPISend(device, lookup[(data>>2)&3]);
+ jshSPISend(device, lookup[(data )&3]);*/
+ jshSPISend16(device, (lookup[(data>>6)&3]<<8) | lookup[(data>>4)&3]);
+ jshSPISend16(device, (lookup[(data>>2)&3]<<8) | lookup[(data )&3]);
+}
+
+// used by jswrap_spi_send8bit
+void spi_send8bit(IOEventFlags device, unsigned char data, int bit0, int bit1) {
+ // Send each bit as 8 bits, MSB first
+ /*int i;
+ for (i=7;i>=0;i--)
+ jshSPISend(device, (unsigned char)(((data>>i)&1) ? bit1 : bit0));*/
+ jshSPISend(device, ((((data>>7)&1) ? bit1 : bit0)<<8) | (((data>>6)&1) ? bit1 : bit0));
+ jshSPISend(device, ((((data>>5)&1) ? bit1 : bit0)<<8) | (((data>>4)&1) ? bit1 : bit0));
+ jshSPISend(device, ((((data>>3)&1) ? bit1 : bit0)<<8) | (((data>>2)&1) ? bit1 : bit0));
+ jshSPISend(device, ((((data>>1)&1) ? bit1 : bit0)<<8) | (((data>>0)&1) ? bit1 : bit0));
+}
+
+/*JSON{ "type":"method", "class": "SPI", "name" : "send4bit",
+ "description" : ["Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols",
+ "Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds."],
+ "generate" : "jswrap_spi_send4bit",
+ "params" : [ [ "data", "JsVar", "The data to send - either an integer, array, or string" ],
+ [ "bit0", "int", "The 4 bits to send for a 0 (MSB first)" ],
+ [ "bit1", "int", "The 4 bits to send for a 1 (MSB first)" ],
+ [ "nss_pin", "pin", "An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised." ] ]
+}*/
+void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) {
+ NOT_USED(parent);
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ jshSPISet16(device, true); // 16 bit output
+
+ if (bit0==0 && bit1==0) {
+ bit0 = 0x01;
+ bit1 = 0x03;
+ }
+ bit0 = bit0 & 0x0F;
+ bit1 = bit1 & 0x0F;
+
+ if (!jshIsDeviceInitialised(device)) {
+ JshSPIInfo inf;
+ jshSPIInitInfo(&inf);
+ jshSPISetup(device, &inf);
+ }
+
+ // assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false);
+
+ // send data
+ if (jsvIsNumeric(srcdata)) {
+ spi_send4bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1);
+ } else if (jsvIsIterable(srcdata)) {
+ jshInterruptOff();
+ JsvIterator it;
+ jsvIteratorNew(&it, srcdata);
+ while (jsvIteratorHasElement(&it)) {
+ unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it);
+ spi_send4bit(device, in, bit0, bit1);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ jshInterruptOn();
+ } else {
+ jsError("Variable type not suited to transmit operation");
+ }
+
+ // de-assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true);
+ jshSPISet16(device, false); // back to 8 bit
+}
+
+/*JSON{ "type":"method", "class": "SPI", "name" : "send8bit",
+ "description" : ["Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols",
+"Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds."],
+ "generate" : "jswrap_spi_send8bit",
+ "params" : [ [ "data", "JsVar", "The data to send - either an integer, array, or string" ],
+ [ "bit0", "int", "The 8 bits to send for a 0 (MSB first)" ],
+ [ "bit1", "int", "The 8 bits to send for a 1 (MSB first)" ],
+ [ "nss_pin", "pin", "An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised" ] ]
+}*/
+void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) {
+ NOT_USED(parent);
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ jshSPISet16(device, true); // 16 bit output
+
+ if (bit0==0 && bit1==0) {
+ bit0 = 0x03;
+ bit1 = 0x0F;
+ }
+ bit0 = bit0 & 0xFF;
+ bit1 = bit1 & 0xFF;
+
+ if (!jshIsDeviceInitialised(device)) {
+ JshSPIInfo inf;
+ jshSPIInitInfo(&inf);
+ jshSPISetup(device, &inf);
+ }
+
+ // assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false);
+
+ // send data
+ if (jsvIsNumeric(srcdata)) {
+ spi_send8bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1);
+ } else if (jsvIsIterable(srcdata)) {
+ jshInterruptOff();
+ JsvIterator it;
+ jsvIteratorNew(&it, srcdata);
+ while (jsvIteratorHasElement(&it)) {
+ unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it);
+ spi_send8bit(device, in, bit0, bit1);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ jshInterruptOn();
+ } else {
+ jsError("Variable type not suited to transmit operation");
+ }
+
+ // de-assert NSS
+ if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true);
+ jshSPISet16(device, false); // back to 8 bit
+}
+
+/*JSON{ "type":"class",
+ "class" : "I2C",
+ "description" : ["This class allows use of the built-in I2C ports. Currently it allows I2C Master mode only.",
+ "All addresses are in 7 bit format. If you have an 8 bit address then you need to shift it one bit to the right." ]
+}*/
+
+/*JSON{ "type":"object", "name":"I2C1", "instanceof" : "I2C",
+ "description" : ["The first I2C port" ],
+ "#if" : "I2CS>=1"
+}*/
+/*JSON{ "type":"object", "name":"I2C2", "instanceof" : "I2C",
+ "description" : ["The second I2C port" ],
+ "#if" : "I2CS>=2"
+}*/
+/*JSON{ "type":"object", "name":"I2C3", "instanceof" : "I2C",
+ "description" : ["The third I2C port" ],
+ "#if" : "I2CS>=3"
+}*/
+
+
+
+/*JSON{ "type":"method", "class": "I2C", "name" : "setup",
+ "description" : "Set up this I2C port",
+ "generate" : "jswrap_i2c_setup",
+ "params" : [ [ "options", "JsVar", ["An optional structure containing extra information on initialising the I2C port",
+ "{scl:pin, sda:pin}" ] ] ]
+}*/
+void jswrap_i2c_setup(JsVar *parent, JsVar *options) {
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ JshI2CInfo inf;
+ jshI2CInitInfo(&inf);
+ if (jsvIsObject(options)) {
+ inf.pinSCL = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "scl", 0));
+ inf.pinSDA = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sda", 0));
+ }
+ jshI2CSetup(device, &inf);
+ // Set up options, so we can initialise it on startup
+ if (options)
+ jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME));
+ else
+ jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME);
+}
+
+
+
+
+/*JSON{ "type":"method", "class": "I2C", "name" : "writeTo",
+ "description" : "Transmit to the slave device with the given address. This is like Arduino's beginTransmission, write, and endTransmission rolled up into one.",
+ "generate" : "jswrap_i2c_writeTo",
+ "params" : [ [ "address", "int", "The 7 bit address of the device to transmit to" ],
+ [ "data", "JsVar", "The Data to send - either a byte, an array of bytes, or a string" ]]
+}*/
+#define I2C_BUFSIZE 32
+void jswrap_i2c_writeTo(JsVar *parent, int address, JsVar *data) {
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+
+ if (jsvIsNumeric(data)) {
+ unsigned char buf[1];
+ buf[0] = (unsigned char)jsvGetInteger(data);
+ jshI2CWrite(device, (unsigned char)address, 1, buf);
+ } else if (jsvIsIterable(data)) {
+ unsigned char buf[I2C_BUFSIZE];
+ int i=0;
+ JsvIterator it;
+ jsvIteratorNew(&it, data);
+ while (jsvIteratorHasElement(&it)) {
+ if (i<I2C_BUFSIZE) buf[i++] = (unsigned char)jsvIteratorGetIntegerValue(&it);
+ jsvIteratorNext(&it);
+ }
+ jsvIteratorFree(&it);
+ if (i>=I2C_BUFSIZE) jsError("Too many bytes to write - truncating");
+ jshI2CWrite(device, (unsigned char)address, i, buf);
+ } else {
+ jsError("Variable type not suited to writeTo operation");
+ }
+}
+
+/*JSON{ "type":"method", "class": "I2C", "name" : "readFrom",
+ "description" : "Request bytes from the given slave device, and return them as an array. This is like using Arduino Wire's requestFrom, available and read functions. Sends a STOP",
+ "generate" : "jswrap_i2c_readFrom",
+ "params" : [ [ "address", "int", "The 7 bit address of the device to request bytes from" ],
+ [ "quantity", "int", "The number of bytes to request" ] ],
+ "return" : [ "JsVar", "The data that was returned - an array of bytes" ]
+}*/
+JsVar *jswrap_i2c_readFrom(JsVar *parent, int address, int nBytes) {
+ unsigned char buf[I2C_BUFSIZE];
+ if (nBytes>I2C_BUFSIZE) {
+ jsError("Too many bytes to read - truncating");
+ nBytes = I2C_BUFSIZE;
+ }
+
+ IOEventFlags device = jsiGetDeviceFromClass(parent);
+ jshI2CRead(device, (unsigned char)address, nBytes, buf);
+
+ // OPT: could use ArrayBuffer for return values
+ JsVar *array = jsvNewWithFlags(JSV_ARRAY);
+ if (array) {
+ int i;
+ for (i=0;i<nBytes;i++) {
+ JsVar *v = jsvNewFromInteger(buf[i]);
+ jsvArrayPushAndUnLock(array, v);
+ }
+ }
+ return array;
+}
diff --git a/components/external/espruino/src/jswrap_spi_i2c.h b/components/external/espruino/src/jswrap_spi_i2c.h
new file mode 100644
index 0000000000..d9326d8649
--- /dev/null
+++ b/components/external/espruino/src/jswrap_spi_i2c.h
@@ -0,0 +1,24 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript SPI and I2C Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+#include "jspin.h"
+
+void jswrap_spi_setup(JsVar *parent, JsVar *options);
+JsVar *jswrap_spi_send(JsVar *parent, JsVar *data, Pin nss_pin);
+void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin);
+void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin);
+
+void jswrap_i2c_setup(JsVar *parent, JsVar *options);
+void jswrap_i2c_writeTo(JsVar *parent, int address, JsVar *data);
+JsVar *jswrap_i2c_readFrom(JsVar *parent, int address, int nBytes);
diff --git a/components/external/espruino/src/jswrap_string.c b/components/external/espruino/src/jswrap_string.c
new file mode 100644
index 0000000000..23a272b7d5
--- /dev/null
+++ b/components/external/espruino/src/jswrap_string.c
@@ -0,0 +1,173 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * This file is designed to be parsed during the build process
+ *
+ * JavaScript String Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jswrap_string.h"
+
+/*JSON{ "type":"class",
+ "class" : "String",
+ "check" : "jsvIsString(var)",
+ "description" : ["This is the built-in class for Text Strings.",
+ "Text Strings in Espruino are not zero-terminated, so you can store zeros in them." ]
+}*/
+
+/*JSON{ "type":"constructor", "class": "String", "name": "String",
+ "description" : "Create a new String",
+ "generate" : "jswrap_string_constructor",
+ "params" : [ [ "str", "JsVar", "A value to turn into a string. If undefined or not supplied, an empty String is created." ] ],
+ "return" : [ "JsVar", "A String" ]
+
+}*/
+JsVar *jswrap_string_constructor(JsVar *a) {
+ if (!a) return jsvNewFromEmptyString(); // no argument - return an empty string
+ return jsvAsString(a, false);
+}
+
+/*JSON{ "type":"staticmethod", "class": "String", "name" : "fromCharCode",
+ "description" : "Return the character represented by the given character code.",
+ "generate" : "jswrap_string_fromCharCode",
+ "params" : [ [ "code", "int", "The character code to create a character from (range 0-255)"] ],
+ "return" : ["JsVar", "The character"]
+}*/
+JsVar *jswrap_string_fromCharCode(JsVarInt code) {
+ // We do this so we can handle '/0' in a string
+ JsVar *r = jsvNewFromEmptyString();
+ if (r) { // out of mem?
+ char ch = (char)code;
+ jsvAppendStringBuf(r, &ch, 1);
+ }
+ return r;
+}
+
+/*JSON{ "type":"method", "class": "String", "name" : "charAt",
+ "description" : "Return a single character at the given position in the String.",
+ "generate" : "jswrap_string_charAt",
+ "params" : [ [ "pos", "int", "The character number in the string. Negative values return characters from end of string (-1 = last char)"] ],
+ "return" : ["JsVar", "The character in the string"]
+}*/
+JsVar *jswrap_string_charAt(JsVar *parent, JsVarInt idx) {
+ // We do this so we can handle '/0' in a string
+ JsVar *r = jsvNewFromEmptyString();
+ if (r) { // out of mem?
+ char ch = jsvGetCharInString(parent, (int)idx);
+ jsvAppendStringBuf(r, &ch, 1);
+ }
+ return r;
+}
+
+/*JSON{ "type":"method", "class": "String", "name" : "charCodeAt",
+ "description" : ["Return the integer value of a single character at the given position in the String.",
+ "Note that this returns 0 not 'NaN' for out of bounds characters"],
+ "generate" : "jswrap_string_charCodeAt",
+ "params" : [ [ "pos", "int", "The character number in the string. Negative values return characters from end of string (-1 = last char)"] ],
+ "return" : ["int", "The integer value of a character in the string"]
+}*/
+JsVarInt jswrap_string_charCodeAt(JsVar *parent, JsVarInt idx) {
+ return jsvGetCharInString(parent, (int)idx);
+}
+
+
+/*JSON{ "type":"method", "class": "String", "name" : "indexOf",
+ "description" : "Return the index of substring in this string, or -1 if not found",
+ "generate" : "jswrap_string_indexOf",
+ "params" : [ [ "substring", "JsVar", "The string to search for"] ],
+ "return" : ["int", "The index of the string, or -1 if not found"]
+}*/
+JsVarInt jswrap_string_indexOf(JsVar *parent, JsVar *v) {
+ // slow, but simple!
+ v = jsvAsString(v, false);
+ if (!v) return 0; // out of memory
+ int idx = -1;
+ int l = (int)jsvGetStringLength(parent) - (int)jsvGetStringLength(v);
+ for (idx=0;idx<=l;idx++) {
+ if (jsvCompareString(parent, v, idx, 0, true)==0) {
+ jsvUnLock(v);
+ return idx;
+ }
+ }
+ jsvUnLock(v);
+ return -1;
+}
+
+/*JSON{ "type":"method", "class": "String", "name" : "substring",
+ "generate" : "jswrap_string_substring",
+ "params" : [ [ "start", "int", "The start character index"],
+ [ "end", "JsVar", "The end character index"] ],
+ "return" : ["JsVar", "The part of this string between start and end"]
+}*/
+JsVar *jswrap_string_substring(JsVar *parent, JsVarInt pStart, JsVar *vEnd) {
+ JsVar *res;
+ JsVarInt pEnd = jsvIsUndefined(vEnd) ? JSVAPPENDSTRINGVAR_MAXLENGTH : (int)jsvGetInteger(vEnd);
+ if (pStart<0) pStart=0;
+ if (pEnd<0) pEnd=0;
+ if (pEnd<pStart) {
+ JsVarInt l = pStart;
+ pStart = pEnd;
+ pEnd = l;
+ }
+ res = jsvNewWithFlags(JSV_STRING);
+ if (!res) return 0; // out of memory
+ jsvAppendStringVar(res, parent, (int)pStart, (int)(pEnd-pStart));
+ return res;
+}
+
+/*JSON{ "type":"method", "class": "String", "name" : "substr",
+ "generate" : "jswrap_string_substr",
+ "params" : [ [ "start", "int", "The start character index"],
+ [ "len", "JsVar", "The number of characters"] ],
+ "return" : ["JsVar", "Part of this string from start for len characters"]
+}*/
+JsVar *jswrap_string_substr(JsVar *parent, JsVarInt pStart, JsVar *vLen) {
+ JsVar *res;
+ JsVarInt pLen = jsvIsUndefined(vLen) ? JSVAPPENDSTRINGVAR_MAXLENGTH : (int)jsvGetInteger(vLen);
+ if (pLen<0) pLen=0;
+ res = jsvNewWithFlags(JSV_STRING);
+ if (!res) return 0; // out of memory
+ jsvAppendStringVar(res, parent, (int)pStart, (int)pLen);
+ return res;
+}
+
+/*JSON{ "type":"method", "class": "String", "name" : "split",
+ "description" : "Return an array made by splitting this string up by the separator. eg. ```'1,2,3'.split(',')==[1,2,3]```",
+ "generate" : "jswrap_string_split",
+ "params" : [ [ "separator", "JsVar", "The start character index"] ],
+ "return" : ["JsVar", "Part of this string from start for len characters"]
+}*/
+JsVar *jswrap_string_split(JsVar *parent, JsVar *split) {
+ JsVar *array;
+ int last, idx, arraylen=0;
+ int splitlen = (int)jsvGetStringLength(split);
+ int l = (int)jsvGetStringLength(parent) - splitlen;
+ last = 0;
+
+ array = jsvNewWithFlags(JSV_ARRAY);
+ if (!array) return 0; // out of memory
+
+ for (idx=0;idx<=l;idx++) {
+ if (idx==l || jsvCompareString(parent, split, idx, 0, true)==0) {
+ JsVar *part = jsvNewFromEmptyString();
+ if (!part) break; // out of memory
+ JsVar *idxvar = jsvMakeIntoVariableName(jsvNewFromInteger(arraylen++), part);
+ if (idxvar) { // could be out of memory
+ if (idx==l) idx=l+splitlen; // if the last element, do to the end of the string
+ jsvAppendStringVar(part, parent, last, idx-last);
+ jsvAddName(array, idxvar);
+ last = idx+splitlen;
+ jsvUnLock(idxvar);
+ }
+ jsvUnLock(part);
+ }
+ }
+ return array;
+}
diff --git a/components/external/espruino/src/jswrap_string.h b/components/external/espruino/src/jswrap_string.h
new file mode 100644
index 0000000000..a8179aee05
--- /dev/null
+++ b/components/external/espruino/src/jswrap_string.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * JavaScript String Functions
+ * ----------------------------------------------------------------------------
+ */
+#include "jsvar.h"
+
+JsVar *jswrap_string_constructor(JsVar *a);
+JsVar *jswrap_string_fromCharCode(JsVarInt code);
+JsVar *jswrap_string_charAt(JsVar *parent, JsVarInt idx);
+JsVarInt jswrap_string_charCodeAt(JsVar *parent, JsVarInt idx);
+JsVarInt jswrap_string_indexOf(JsVar *parent, JsVar *v);
+JsVar *jswrap_string_substring(JsVar *parent, JsVarInt pStart, JsVar *vEnd);
+JsVar *jswrap_string_substr(JsVar *parent, JsVarInt pStart, JsVar *vLen);
+JsVar *jswrap_string_split(JsVar *parent, JsVar *split);
diff --git a/components/external/espruino/src/jswrapper.h b/components/external/espruino/src/jswrapper.h
new file mode 100644
index 0000000000..294b7d5547
--- /dev/null
+++ b/components/external/espruino/src/jswrapper.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
+ *
+ * Copyright (C) 2013 Gordon Williams <[email protected]>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ----------------------------------------------------------------------------
+ * Header for auto-generated Wrapper functions
+ * ----------------------------------------------------------------------------
+ */
+
+#include "jsutils.h"
+#include "jsparse.h"
+
+#define JSW_HANDLEFUNCTIONCALL_UNHANDLED ((JsVar*)-1)
+
+/** If 'name' is something that belongs to an internal function, execute it. */
+JsVar *jswHandleFunctionCall(JsVar *parent, JsVar *parentName, const char *name);
+
+/// Given the name of an Object, see if we should set it up as a builtin or not
+bool jswIsBuiltInObject(const char *name);
+
+/// If we get this in 'require', should we make an object with this name?
+bool jswIsBuiltInLibrary(const char *name);
+
+/** Given a variable, return the basic object name of it */
+const char *jswGetBasicObjectName(JsVar *var);
+
+/** Given the name of a Basic Object, eg, Uint8Array, String, etc. Return the prototype object's name - or 0.
+ * For instance jswGetBasicObjectPrototypeName("Object")==0, jswGetBasicObjectPrototypeName("Integer")=="Object",
+ * jswGetBasicObjectPrototypeName("Uint8Array")=="ArrayBufferView"
+ * */
+const char *jswGetBasicObjectPrototypeName(const char *name);