/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2013 Gordon Williams * * 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;i1) { // 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 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;ifirstChild; 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;iroot), 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+10 && jsvGetCharInString(inputLine,inputCursorPos-1)!='\n') { if (jsiShowInputLine()) jsiConsolePrintChar(0x08); inputCursorPos--; } } void jsiHandleEnd() { int l = (int)jsvGetStringLength(inputLine); while (inputCursorPoslines) 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 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;ifirstChild; 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; }