diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e973e0f..12b675e3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,5 @@ Changed for individual apps are listed in `apps/appname/ChangeLog` * Add `Favourite` functionality * Version number now clickable even when you're at the latest version (fix #291) * Rewrite 'getInstalledApps' to minimize RAM usage +* Added code to handle Settings +* Added espruinotools.js for pretokenisation diff --git a/apps.json b/apps.json index 6d67d8b40..02567f994 100644 --- a/apps.json +++ b/apps.json @@ -2,7 +2,7 @@ { "id": "boot", "name": "Bootloader", "icon": "bootloader.png", - "version":"0.14", + "version":"0.15", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "tags": "tool,system", "type":"bootloader", @@ -53,7 +53,7 @@ { "id": "about", "name": "About", "icon": "app.png", - "version":"0.04", + "version":"0.05", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "tags": "tool,system", "allow_emulator":true, @@ -122,9 +122,10 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.18", + "version":"0.19", "description": "A menu for setting up Bangle.js", "tags": "tool,system", + "readme": "README.md", "storage": [ {"name":"setting.app.js","url":"settings.js"}, {"name":"setting.boot.js","url":"boot.js"}, @@ -163,10 +164,23 @@ {"name":"wclock.img","url":"clock-word-icon.js","evaluate":true} ] }, + { "id": "impwclock", + "name": "Imprecise Word Clock", + "icon": "clock-impword.png", + "version":"0.01", + "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"impwclock.app.js","url":"clock-impword.js"}, + {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true} + ] + }, { "id": "aclock", "name": "Analog Clock", "icon": "clock-analog.png", - "version": "0.11", + "version": "0.12", "description": "An Analog Clock", "tags": "clock", "type":"clock", @@ -467,7 +481,7 @@ "name": "Bluetooth Music Controls", "shortName": "Music Control", "icon": "hid-music.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", "tags": "bluetooth", "storage": [ @@ -479,7 +493,7 @@ "name": "Bluetooth Keyboard", "shortName": "Bluetooth Kbd", "icon": "hid-keyboard.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", "tags": "bluetooth", "storage": [ @@ -491,7 +505,7 @@ "name": "Binary Bluetooth Keyboard", "shortName": "Binary BT Kbd", "icon": "hid-binary-keyboard.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want", "tags": "bluetooth", "storage": [ @@ -1012,7 +1026,7 @@ { "id": "barclock", "name": "Bar Clock", "icon": "clock-bar.png", - "version":"0.04", + "version":"0.05", "description": "A simple digital clock showing seconds as a bar", "tags": "clock", "type":"clock", @@ -1175,7 +1189,7 @@ "name": "Active Pedometer", "shortName":"Active Pedometer", "icon": "app.png", - "version":"0.03", + "version":"0.04", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", "tags": "outdoors,widget", "readme": "README.md", @@ -1241,7 +1255,7 @@ "name": "Battery Chart", "shortName":"Battery Chart", "icon": "app.png", - "version":"0.08", + "version":"0.09", "readme": "README.md", "description": "A widget and an app for recording and visualizing battery percentage over time.", "tags": "app,widget,battery,time,record,chart,tool", @@ -1391,6 +1405,7 @@ "name": "Metronome", "icon": "metronome_icon.png", "version": "0.03", + "readme": "README.md", "description": "Makes the watch blinking and vibrating with a given rate", "tags": "tool", "allow_emulator": true, @@ -1423,7 +1438,7 @@ "name": "Camera shutter", "shortName":"Cam shutter", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", "tags": "tools", "storage": [ @@ -1473,14 +1488,64 @@ "name": "Pong", "shortName": "Pong", "icon": "pong.png", - "version": "0.01", + "version": "0.02", "description": "A clone of the Atari game Pong", "tags": "game", "type": "app", "allow_emulator": true, + "readme": "README.md", "storage": [ {"name":"pong.app.js","url":"app.js"}, {"name":"pong.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "ballmaze", + "name": "Ball Maze", + "icon": "icon.png", + "version": "0.01", + "description": "Navigate a ball through a maze by tilting your watch.", + "readme": "README.md", + "tags": "game", + "type": "app", + "storage": [ + {"name": "ballmaze.app.js","url":"app.js"}, + {"name": "ballmaze.img","url":"icon.js","evaluate": true} + ], + "data": [ + {"name": "ballmaze.json"} + ] + }, + { + "id": "calendar", + "name": "Calendar", + "icon": "calendar.png", + "version": "0.01", + "description": "Simple calendar", + "tags": "calendar", + "readme": "README.md", + "allow_emulator": true, + "storage": [ + { + "name": "calendar.app.js", + "url": "calendar.js" + }, + { + "name": "calendar.img", + "url": "calendar-icon.js", + "evaluate": true + } + ] + }, + { "id": "hidjoystick", + "name": "Bluetooth Joystick", + "shortName": "Joystick", + "icon": "app.png", + "version":"0.01", + "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.", + "tags": "bluetooth", + "storage": [ + {"name":"hidjoystick.app.js","url":"app.js"}, + {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 2c81c0537..16aea0610 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -2,3 +2,4 @@ 0.02: Update version checker for new filename type 0.03: Actual pixels as of 5 Mar 2020 0.04: Actual pixels as of 9 Mar 2020 +0.05: Actual pixels as of 27 Apr 2020 diff --git a/apps/about/app.js b/apps/about/app.js index dc7b0cad8..57c85563d 100644 --- a/apps/about/app.js +++ b/apps/about/app.js @@ -29,5 +29,5 @@ g.drawString(NRF.getAddress(),120,232); g.flip(); // Pixel chooser image -g.drawImage(require("heatshrink").decompress(atob("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")),0,135); +g.drawImage(require("heatshrink").decompress(atob("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")),0,135); g.flip(); diff --git a/apps/aclock/ChangeLog b/apps/aclock/ChangeLog index 98e3da8e7..a289fba1f 100644 --- a/apps/aclock/ChangeLog +++ b/apps/aclock/ChangeLog @@ -6,3 +6,4 @@ 0.09: center date, remove box around it, internal refactor to remove redundant code. 0.10: remove debug, refactor seconds to show elapsed secs each time app is displayed 0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date +0.12: Fix regression after 0.11 diff --git a/apps/aclock/clock-analog.js b/apps/aclock/clock-analog.js index 7b60a728f..8cd0af915 100644 --- a/apps/aclock/clock-analog.js +++ b/apps/aclock/clock-analog.js @@ -1,7 +1,3 @@ -// eliminate ide undefined errors -let g; -let Bangle; - // http://forum.espruino.com/conversations/345155/#comment15172813 const locale = require('locale'); const p = Math.PI / 2; diff --git a/apps/activepedom/ChangeLog b/apps/activepedom/ChangeLog index c1b9ec011..ca26a648a 100644 --- a/apps/activepedom/ChangeLog +++ b/apps/activepedom/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Widget! 0.02: Distance calculation and display -0.03: Data logging and display \ No newline at end of file +0.03: Data logging and display +0.04: Steps are set to 0 in log on new day \ No newline at end of file diff --git a/apps/activepedom/README.md b/apps/activepedom/README.md index f45297e57..a2a351a12 100644 --- a/apps/activepedom/README.md +++ b/apps/activepedom/README.md @@ -18,7 +18,7 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a * 10600 steps ![](10600.png) -## Features +## Features Widget * Two line display * Can display distance (in km) or steps in each line @@ -32,22 +32,23 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a * Steps are saved to a file and read-in at start (to not lose step progress) * Settings can be changed in Settings - App/widget settings - Active Pedometer +## Features App + +* The app accesses the data stored for the current day +* Timespan is choseable (1h, 4h, 8h, 12h, 16h, 20, 24h), standard is 24h, the whole current day + ## Data storage -* Data is stored to a file +* Data is stored to a file named activepedomYYYYMMDD.data (activepedom20200427.data) +* One file is created for each day * Format: now,stepsCounted,active,stepsTooShort,stepsTooLong,stepsOutsideTime -* now is UNIX timestamp in ms -* You can chose the app to watch a steps graph +* 'now' is UNIX timestamp in ms +* You can use the app to watch a steps graph * You can import the file into Excel * The file does not include a header * You can convert UNIX timestamp to a date in Excel using this formula: =DATUM(1970;1;1)+(LINKS(A2;10)/86400) * You have to format the cell with the formula to a date cell. Example: JJJJ-MM-TT-hh-mm-ss -## App - -* The app accesses the data stored for the current day -* Timespan is choseable (1h, 4h, 8h, 12h, 16h, 20, 24h), standard is 24h, the whole current day - ## Settings * Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100 diff --git a/apps/activepedom/app.js b/apps/activepedom/app.js index 0a9b3b93f..cc875f371 100644 --- a/apps/activepedom/app.js +++ b/apps/activepedom/app.js @@ -162,4 +162,4 @@ settings = storage.readJSON(SETTINGS_FILE, 1) || {}; drawMenu(); -})(); +})(); \ No newline at end of file diff --git a/apps/activepedom/widget.js b/apps/activepedom/widget.js index c6bd410ce..2ae1b9b62 100644 --- a/apps/activepedom/widget.js +++ b/apps/activepedom/widget.js @@ -33,27 +33,28 @@ function storeData() { now = new Date(); - month = now.getMonth() + 1; - if (month < 10) month = "0" + month; - filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; + month = now.getMonth() + 1; //month is 0-based + if (month < 10) month = "0" + month; //leading 0 + filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; //new file for each day dataFile = s.open(filename,"a"); - if (dataFile) { + if (dataFile) { //check if filen already exists if (dataFile.getLength() == 0) { - stepsToWrite = 0; - } - else { - stepsToWrite = stepsCounted; + //new day, set steps to 0 + stepsCounted = 0; + stepsTooShort = 0; + stepsTooLong = 0; + stepsOutsideTime = 0; } dataFile.write([ now.getTime(), - stepsToWrite, + stepsCounted, active, stepsTooShort, stepsTooLong, stepsOutsideTime, ].join(",")+"\n"); } - dataFile = undefined; + dataFile = undefined; //save memory } //return setting diff --git a/apps/ballmaze/README.md b/apps/ballmaze/README.md new file mode 100644 index 000000000..22a295686 --- /dev/null +++ b/apps/ballmaze/README.md @@ -0,0 +1,15 @@ +# Ball Maze + +Navigate a ball through a maze by tilting your watch. + +![Screenshot](size_select.png) +![Screenshot](maze.png) + +## Usage + +Select a maze size to begin the game. +Tilt your watch to steer the ball towards the target and advance to the next level. + +## Creator + +Richard de Boer diff --git a/apps/ballmaze/app.js b/apps/ballmaze/app.js new file mode 100644 index 000000000..3e26277b7 --- /dev/null +++ b/apps/ballmaze/app.js @@ -0,0 +1,552 @@ +(() => { + let intervalID; + let settings = require("Storage").readJSON("ballmaze.json",true) || {}; + + // density, elasticity of bounces, "drag coefficient" + const rho = 100, e = 0.3, C = 0.01; + // screen width & height in pixels + const sW = 240, sH = 160; + // gravity constant (lowercase was already taken) + const G = 9.80665; + + // wall bit flags + const TOP = 1<<0, LEFT = 1<<1, BOTTOM = 1<<2, RIGHT = 1<<3, + LINKED = 1<<4; // used in maze generation + + // The play area is 240x160, sizes are the ball radius, so we can use common + // denominators of 120x80 to get square rooms + // Reverse the order to show the easiest on top of the menu + const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), + // even size 1 actually works, but larger mazes take forever to generate + minSize = 4, defaultSize = 10; + const sizeNames = { + 1: "Insane", 2: "Gigantic", 4: "Enormous", 5: "Huge", 8: "Large", + 10: "Medium", 16: "Small", 20: "Tiny", 40: "Trivial", + }; + + /** + * Draw something to all screen buffers + * @param draw {function} Callback which performs the drawing + */ + function drawAll(draw) { + draw(); + g.flip(); + draw(); + g.flip(); + } + + /** + * Clear all buffers + */ + function clearAll() { + drawAll(() => g.clear()); + } + + // use unbuffered graphics for UI stuff + function showMessage(message, title) { + Bangle.setLCDMode(); + return E.showMessage(message, title); + } + + function showPrompt(prompt, options) { + Bangle.setLCDMode(); + return E.showPrompt(prompt, options); + } + + function showMenu(menu) { + Bangle.setLCDMode(); + return E.showMenu(menu); + } + + const sign = (n) => n<0?-1:1; // we don't really care about zero + + /** + * Play the game, using a ball with radius size + * @param size {number} + */ + function playMaze(size) { + const r = size; + // ball mass, weight, "drag" + // Yes, larger maze = larger ball = heavier ball + // (atm our physics is so oversimplified that mass cancels out though) + const m = rho*(r*r*r), w = G*m, d = C*w; + + // number of columns/rows + const cols = Math.round(sW/(r*2.5)), + rows = Math.round(sH/(r*2.5)); + // width & height of one column/row in pixels + const cW = sW/cols, rH = sH/rows; + + // list of rooms, every room can have one or more wall bits set + // actual layout: 0 1 2 + // 3 4 5 + // this means that for room with index "i": (except edge cases!) + // i-1 = room to the left + // i+1 = room to the right + // i-cols = room above + // i+cols = room below + let rooms = new Uint8Array(rows*cols); + // shortest route from start to finish + let route; + + let x, y, // current position + px, py, ppx, ppy, // previous positions (for erasing old image) + vx, vy; // velocity + + function start() { + // start in top left corner + x = cW/2; + y = rH/2; + vx = vy = 0; + ppx = px = x; + ppy = py = y; + + generateMaze(); // this shows unbuffered progress messages + if (settings.cheat && r>1) findRoute(); // not enough memory for r==1 :-( + + Bangle.setLCDMode("doublebuffered"); + clearAll(); + drawAll(drawMaze); + intervalID = setInterval(tick, 100); + } + + // Position conversions + // index: index of room in rooms[] + // rowcol: position measured in roomsizes + // xy: position measured in pixels + /** + * Index from RowCol + * @param row {number} + * @param col {number} + * @returns {number} rooms[] index + */ + function iFromRC(row, col) { + return row*cols+col; + } + + /** + * RowCol from index + * @param index {number} + * @returns {(number)[]} [row,column] + */ + function rcFromI(index) { + return [ + Math.floor(index/cols), + index%cols, + ]; + } + + /** + * RowCol from Xy + * @param x {number} + * @param y {number} + * @returns {(number)[]} [row,column] + */ + function rcFromXy(x, y) { + return [ + Math.floor(y/sH*rows), + Math.floor(x/sW*cols), + ]; + } + + /** + * Link another room up + * @param index {number} Dig from already linked room with this index + * @param dir {number} in this direction + * @return {number} index of room we just linked up + */ + function dig(index, dir) { + rooms[index] &= ~dir; + let neighbour; + switch(dir) { + case LEFT: + neighbour = index-1; + rooms[neighbour] &= ~RIGHT; + break; + case RIGHT: + neighbour = index+1; + rooms[neighbour] &= ~LEFT; + break; + case TOP: + neighbour = index-cols; + rooms[neighbour] &= ~BOTTOM; + break; + case BOTTOM: + neighbour = index+cols; + rooms[neighbour] &= ~TOP; + break; + } + rooms[neighbour] |= LINKED; + return neighbour; + } + + /** + * Generate the maze + */ + function generateMaze() { + // Maze generation basically works like this: + // 1. Start with all rooms set to completely walled off and "unlinked" + // 2. Then mark a room as "linked", and add it to the "to do" list + // 3. When the "to do" list is empty, we're done + // 4. pick a random room from the list + // 5. if all adjacent rooms are linked -> remove room from list, goto 3 + // 6. pick a random unlinked adjacent room + // 7. remove the walls between the rooms + // 8. mark the adjacent room as linked and add it to the "to do" list + // 9. go to 4 + let pdotnum = 0; + const title = "Please wait", + message = "Generating maze\n", + showProgress = (done, total) => { + const dotnum = Math.floor(done/total*10); + if (dotnum>pdotnum) { + const dots = ".".repeat(dotnum)+" ".repeat(10-dotnum); + showMessage(message+dots, title); + pdotnum = dotnum; + } + }; + showProgress(0, 100); + // start with all rooms completely walled off + rooms.fill(TOP|LEFT|BOTTOM|RIGHT); + const + // is room at row,col already linked? + linked = (row, col) => !!(rooms[iFromRC(row, col)]&LINKED), + // pick random array element + pickRandom = (arr) => arr[Math.floor(Math.random()*arr.length)]; + // starting with top-right room seems to generate more interesting mazes + rooms[cols] |= LINKED; + let todo = [cols], done = 1; + while(todo.length) { + const index = pickRandom(todo); + const rc = rcFromI(index), + row = rc[0], col = rc[1]; + let sides = []; + if ((col>0) && !linked(row, col-1)) sides.push(LEFT); + if ((col0) && !linked(row-1, col)) sides.push(TOP); + if ((row0 && !(walls&LEFT) && dist[i-1]>d+1) { + dist[i-1] = d+1; + todo.push(i-1); + } + if (row>0 && !(walls&TOP) && dist[i-cols]>d+1) { + dist[i-cols] = d+1; + todo.push(i-cols); + } + if (cold+1) { + dist[i+1] = d+1; + todo.push(i+1); + } + if (rowd+1) { + dist[i+cols] = d+1; + todo.push(i+cols); + } + } + + route = [rooms.length-1]; + while(true) { + const i = route[0], d = dist[i], walls = rooms[i], + rc = rcFromI(i), + row = rc[0], col = rc[1]; + if (i===0) { break; } + if (col0 && !(walls&TOP) && dist[i-cols]0 && !(walls&LEFT) && dist[i-1] { + const rc = rcFromI(i), + row = rc[0], col = rc[1], + x = (col+0.5)*cW, y = (row+0.5)*rH; + g.lineTo(x, y); + }); + } + + /** + * Move the ball + */ + function move() { + const a = Bangle.getAccel(); + const fx = (-a.x*w)-(sign(vx)*d*a.z), fy = (-a.y*w)-(sign(vy)*d*a.z); + vx += fx/m; + vy += fy/m; + const s = Math.ceil(Math.max(Math.abs(vx), Math.abs(vy))); + for(let n = s; n>0; n--) { + x += vx/s; + y += vy/s; + bounce(); + } + if (x>sW-cW && y>sH-rH) win(); + } + + /** + * Check whether we hit any walls, and if so: Bounce. + * + * Bounce = reverse velocity in bounce direction, multiply with elasticity + * Also apply drag in perpendicular direction ("friction with the wall") + */ + function bounce() { + const row = Math.floor(y/sH*rows), col = Math.floor(x/sW*cols), + i = row*cols+col, walls = rooms[i]; + const left = col*cW, + right = (col+1)*cW, + top = row*rH, + bottom = (row+1)*rH; + let bounced = false; + if (vx<0) { + if ((walls&LEFT) && x<=left+r) { + x += (1+e)*(left+r-x); + const fy = sign(vy)*d*Math.abs(vx); + vy -= fy/m; + vx = -vx*e; + bounced = true; + } + } else { + if ((walls&RIGHT) && x>=right-r) { + x -= (1+e)*(x+r-right); + const fy = sign(vy)*d*Math.abs(vx); + vy -= fy/m; + vx = -vx*e; + bounced = true; + } + } + if (vy<0) { + if ((walls&TOP) && y<=top+r) { + y += (1+e)*(top+r-y); + const fx = sign(vx)*d*Math.abs(vy); + vx -= fx/m; + vy = -vy*e; + bounced = true; + } + } else { + if ((walls&BOTTOM) && y>=bottom-r) { + y -= (1+e)*(y+r-bottom); + const fx = sign(vx)*d*Math.abs(vy); + vx -= fx/m; + vy = -vy*e; + bounced = true; + } + } + if (bounced) return; + let cx, cy; + if ((rooms[i-1]&TOP) || rooms[i-cols]&LEFT) { + if ((x-left)*(x-left)+(y-top)*(y-top)<=r*r) { + cx = left; + cy = top; + } + } + else if ((rooms[i-1]&BOTTOM) || rooms[i+cols]&LEFT) { + if ((x-left)*(x-left)+(bottom-y)*(bottom-y)<=r*r) { + cx = left; + cy = bottom; + } + } + else if ((rooms[i+1]&TOP) || rooms[i-cols]&RIGHT) { + if ((right-x)*(right-x)+(y-top)*(y-top)<=r*r) { + cx = right; + cy = top; + } + } + else if ((rooms[i+1]&BOTTOM) || rooms[i+cols]&RIGHT) { + if ((right-x)*(right-x)+(bottom-y)*(bottom-y)<=r*r) { + cx = right; + cy = bottom; + } + } + if (!cx) return; + let nx = x-cx, ny = y-cy; + const l = Math.sqrt(nx*nx+ny*ny); + nx /= l; + ny /= l; + const p = vx*nx+vy*ny; + vx -= 2*p*nx*e; + vy -= 2*p*ny*e; + } + + /** + * You reached the bottom-right corner, you win! + */ + function win() { + clearInterval(intervalID); + Bangle.buzz().then(askAgain); + } + + /** + * You solved the maze, try the next one? + */ + function askAgain() { + const nextLevel = (size>minSize)?"next level":"again"; + const nextSize = (size>minSize)?sizes[sizes.indexOf(size)+1]:size; + showPrompt(`Well done!\n\nPlay ${nextLevel}?`, + {"title": "Congratulations!"}) + .then(function(again) { + if (again) { + playMaze(nextSize); + } else { + startGame(); + } + }); + } + + function tick() { + ppx = px; + ppy = py; + px = x; + py = y; + move(); + drawUpdate(); + } + + start(); + } + + /** + * Ask player what size maze they would like to play + */ + function startGame() { + let menu = { + "": { + title: "Select Maze Size", + selected: sizes.indexOf(settings.size || defaultSize), + }, + }; + sizes.filter(s => s>=minSize).forEach(size => { + let name = sizeNames[size]; + if (size { + // remember chosen size + settings.size = size; + require("Storage").write("ballmaze.json", settings); + playMaze(size); + }; + }); + menu["< Exit"] = () => load(); + showMenu(menu); + } + + startGame(); +})(); diff --git a/apps/ballmaze/icon.js b/apps/ballmaze/icon.js new file mode 100644 index 000000000..10b5a502e --- /dev/null +++ b/apps/ballmaze/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4AU9wAOCw0OC5/gFyowHC+Hs5gACC7HhiMRjwXSCoIADC5wCB4MSkIXDGIoXKiUikQwJC5PhCwIXFGAgXJFwRHEGAnOC5HhC5IwC5gXJIw4XF4AXKFwwXEGAoXCiKlFMAzNCgDpDC4QAKcgZJBC6wADF6kAhgXP5xfEC58SC4iNCC4nhC5McC4S/DC6a9DC4IACC5MhC4XOC5HuLxPMC4PuC5IwHkUeC44ABA4IACFw5cBC5owEkUhjwXPGAyMCC5wxDLgIACC54ADC94AGC7sOCx/gC4owQCwwA/AH4AMA")) diff --git a/apps/ballmaze/icon.png b/apps/ballmaze/icon.png new file mode 100644 index 000000000..44697db4b Binary files /dev/null and b/apps/ballmaze/icon.png differ diff --git a/apps/ballmaze/maze.png b/apps/ballmaze/maze.png new file mode 100644 index 000000000..7bda56d9b Binary files /dev/null and b/apps/ballmaze/maze.png differ diff --git a/apps/ballmaze/size_select.png b/apps/ballmaze/size_select.png new file mode 100644 index 000000000..cac278820 Binary files /dev/null and b/apps/ballmaze/size_select.png differ diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 2e0fd088c..616ee66e9 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -2,3 +2,4 @@ 0.02: Apply locale, 12-hour setting 0.03: Fix dates drawing over each other at midnight 0.04: Small bugfix +0.05: Clock does not start if app Languages is not installed \ No newline at end of file diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index da436daee..0f2609298 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -12,7 +12,12 @@ date.setMonth(1, 3) // februari: months are zero-indexed const localized = locale.date(date, true) locale.dayFirst = /3.*2/.test(localized) - locale.hasMeridian = (locale.meridian(date) !== '') + + locale.hasMeridian = false + if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed + locale.hasMeridian = (locale.meridian(date) !== '') + } + } const screen = { width: g.getWidth(), diff --git a/apps/batchart/ChangeLog b/apps/batchart/ChangeLog index 439d877be..66b40fbbf 100644 --- a/apps/batchart/ChangeLog +++ b/apps/batchart/ChangeLog @@ -5,4 +5,5 @@ 0.05: Display temperature and LCD state in chart 0.06: Fixes widget events and charting of component states 0.07: Improve logging and charting of component states and add widget icon -0.08: Fix for Home button in the app and README added. \ No newline at end of file +0.08: Fix for Home button in the app and README added. +0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state \ No newline at end of file diff --git a/apps/batchart/app.js b/apps/batchart/app.js index 2d0d8e585..472fb3a8a 100644 --- a/apps/batchart/app.js +++ b/apps/batchart/app.js @@ -8,7 +8,7 @@ const GraphXMax = GraphXZero + MaxValueCount; const GraphLcdY = GraphYZero + 10; const GraphCompassY = GraphYZero + 16; -// const GraphBluetoothY = GraphYZero + 22; +const GraphBluetoothY = GraphYZero + 22; const GraphGpsY = GraphYZero + 28; const GraphHrmY = GraphYZero + 34; @@ -175,13 +175,13 @@ function renderData(dataArray) { g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1); } - // // Bluetooth state - // if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) { - // g.setColor(0, 0, 1); - // g.setFontAlign(1, -1, 0); - // g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true); - // g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1); - // } + // Bluetooth state + if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.bluetooth) { + g.setColor(0, 0, 1); + g.setFontAlign(1, -1, 0); + g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true); + g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1); + } // Gps state if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.gps) { diff --git a/apps/batchart/widget.js b/apps/batchart/widget.js index 1b8ce79ba..96f8b4b25 100644 --- a/apps/batchart/widget.js +++ b/apps/batchart/widget.js @@ -71,8 +71,10 @@ enabledConsumers = enabledConsumers | switchableConsumers.gps; if (hrmEventReceived) enabledConsumers = enabledConsumers | switchableConsumers.hrm; - //if (Bangle.isBluetoothOn()) - // enabledConsumers = enabledConsumers | switchableConsumers.bluetooth; + + // Very coarse first approach to check if the BLE device is on. + if (NRF.getSecurityStatus().connected) + enabledConsumers = enabledConsumers | switchableConsumers.bluetooth; // Reset the event registration vars compassEventReceived = false; @@ -110,19 +112,14 @@ } function reload() { - WIDGETS.batchart.width = 24; + WIDGETS["batchart"].width = 24; recordingInterval = setInterval(logBatteryData, recordingInterval10Min); - - logBatteryData(); } // add the widget - WIDGETS.batchart = { - area: "tl", width: 24, draw: draw, reload: function () { - reload(); - Bangle.drawWidgets(); - } + WIDGETS["batchart"] = { + area: "tl", width: 24, draw: draw, reload: reload }; reload(); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7ab79a5a5..cf5c243f8 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -13,3 +13,4 @@ 0.13: Now automatically load *.boot.js at startup Move alarm code into alarm.boot.js 0.14: Move welcome loaders to *.boot.js +0.15: Added BLE HID option for Joystick and bare Keyboard diff --git a/apps/boot/boot0.js b/apps/boot/boot0.js index dd3b3a9ba..84b3460e1 100644 --- a/apps/boot/boot0.js +++ b/apps/boot/boot0.js @@ -4,7 +4,9 @@ E.setFlags({pretokenise:1}); var s = require('Storage').readJSON('setting.json',1)||{}; if (s.ble!==false) { if (s.HID) { // Human interface device - Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==")); + if (s.HID=="joy") Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA=")); + else if (s.HID=="kb") Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA==")); + else /*kbmedia*/Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==")); NRF.setServices({}, {uart:true, hid:Bangle.HID}); } } diff --git a/apps/boot/hid_info.txt b/apps/boot/hid_info.txt new file mode 100644 index 000000000..873b50f63 --- /dev/null +++ b/apps/boot/hid_info.txt @@ -0,0 +1,88 @@ + +## Joystick: + +https://github.com/espruino/BangleApps/issues/349#issuecomment-620231524 + +``` +0x05, 0x01, // Usage Page (Generic Desktop) +0x09, 0x04, // Usage (Joystick) +0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + // Buttons + 0x05, 0x09, // Usage Page (Buttons) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x05, // Usage Maximum (5) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + // padding bits + 0x95, 0x03, // Report Count (3) + 0x75, 0x01, // Report Size (1) + 0x81, 0x03, // Input (Constant) + + // Stick + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7f, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0xC0, // End Collection (Physical) +0xC0 // End Collection (Application) +``` + +## Keyboard + +http://www.espruino.com/BLE+Keyboard + +``` +0x05, 0x01, // Usage Page (Generic Desktop) +0x09, 0x06, // Usage (Keyboard) +0xA1, 0x01, // Collection (Application) +0x05, 0x07, // Usage Page (Key Codes) +0x19, 0xe0, // Usage Minimum (224) +0x29, 0xe7, // Usage Maximum (231) +0x15, 0x00, // Logical Minimum (0) +0x25, 0x01, // Logical Maximum (1) +0x75, 0x01, // Report Size (1) +0x95, 0x08, // Report Count (8) +0x81, 0x02, // Input (Data, Variable, Absolute) + +0x95, 0x01, // Report Count (1) +0x75, 0x08, // Report Size (8) +0x81, 0x01, // Input (Constant) reserved byte(1) + +0x95, 0x05, // Report Count (5) +0x75, 0x01, // Report Size (1) +0x05, 0x08, // Usage Page (Page# for LEDs) +0x19, 0x01, // Usage Minimum (1) +0x29, 0x05, // Usage Maximum (5) +0x91, 0x02, // Output (Data, Variable, Absolute), Led report +0x95, 0x01, // Report Count (1) +0x75, 0x03, // Report Size (3) +0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding + +0x95, 0x06, // Report Count (6) +0x75, 0x08, // Report Size (8) +0x15, 0x00, // Logical Minimum (0) +0x25, 0x73, // Logical Maximum (115 - include F13, etc) +0x05, 0x07, // Usage Page (Key codes) +0x19, 0x00, // Usage Minimum (0) +0x29, 0x73, // Usage Maximum (115 - include F13, etc) +0x81, 0x00, // Input (Data, Array) Key array(6 bytes) + +0x09, 0x05, // Usage (Vendor Defined) +0x15, 0x00, // Logical Minimum (0) +0x26, 0xFF, 0x00, // Logical Maximum (255) +0x75, 0x08, // Report Count (2) +0x95, 0x02, // Report Size (8 bit) +0xB1, 0x02, // Feature (Data, Variable, Absolute) + +0xC0 // End Collection (Application) +``` diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog new file mode 100644 index 000000000..3cf79ffe8 --- /dev/null +++ b/apps/calendar/ChangeLog @@ -0,0 +1 @@ +0.01: Basic calendar diff --git a/apps/calendar/README.md b/apps/calendar/README.md new file mode 100644 index 000000000..19a60afc0 --- /dev/null +++ b/apps/calendar/README.md @@ -0,0 +1,8 @@ +# Calendar + +Basic calendar + +## Usage + +- Use `BTN4` (left screen tap) to go to the previous month +- Use `BTN5` (right screen tap) to go to the next month diff --git a/apps/calendar/calendar-icon.js b/apps/calendar/calendar-icon.js new file mode 100644 index 000000000..ed1bf3667 --- /dev/null +++ b/apps/calendar/calendar-icon.js @@ -0,0 +1,5 @@ +require("heatshrink").decompress( + atob( + "mEwxH+AH4A/ADuIUCARRDhgePCKIv13YAEDoYJFAA4RJFyQvcGBYRGy4dDy4uLCJgv/DoOBDgOBF5oRLF6IeBDgIvNCJYvQDwQuNCJovRADov/F9OsAEgv/F/4vhwIACAqYv/F/4vnd94vvX/4v/F/7vvF96//F/4v/d94v/F/4wsFxQwjFxgA/AH4A/AH4AZA==" + ) +) diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js new file mode 100644 index 000000000..720986162 --- /dev/null +++ b/apps/calendar/calendar.js @@ -0,0 +1,160 @@ +const maxX = 240; +const maxY = 240; +const rowN = 7; +const colN = 7; +const headerH = maxY / 7; +const rowH = (maxY - headerH) / rowN; +const colW = maxX / colN; +const color1 = "#035AA6"; +const color2 = "#4192D9"; +const color3 = "#026873"; +const color4 = "#038C8C"; +const color5 = "#03A696"; +const black = "#000000"; +const white = "#ffffff"; +const gray1 = "#444444"; +const gray2 = "#888888"; +const gray3 = "#bbbbbb"; +const red = "#d41706"; + +function drawCalendar(date) { + g.setBgColor(color4); + g.clearRect(0, 0, maxX, maxY); + g.setBgColor(color1); + g.clearRect(0, 0, maxX, headerH); + g.setBgColor(color2); + g.clearRect(0, headerH, maxX, headerH + rowH); + g.setBgColor(color3); + g.clearRect(colW * 5, headerH + rowH, maxX, maxY); + for (let y = headerH; y < maxY; y += rowH) { + g.drawLine(0, y, maxX, y); + } + for (let x = 0; x < maxX; x += colW) { + g.drawLine(x, headerH, x, maxY); + } + + const month = date.getMonth(); + const year = date.getFullYear(); + const monthMap = { + 0: "January", + 1: "February", + 2: "March", + 3: "April", + 4: "May", + 5: "June", + 6: "July", + 7: "August", + 8: "September", + 9: "October", + 10: "November", + 11: "December" + }; + g.setFontAlign(0, 0); + g.setFont("6x8", 2); + g.setColor(white); + g.drawString(`${monthMap[month]} ${year}`, maxX / 2, headerH / 2); + g.drawPoly([10, headerH / 2, 20, 10, 20, headerH - 10], true); + g.drawPoly( + [maxX - 10, headerH / 2, maxX - 20, 10, maxX - 20, headerH - 10], + true + ); + + g.setFont("6x8", 2); + const dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; + dowLbls.forEach((lbl, i) => { + g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2); + }); + + date.setDate(1); + const dow = date.getDay(); + const dowNorm = dow === 0 ? 7 : dow; + + const monthMaxDayMap = { + 0: 31, + 1: (2020 - year) % 4 === 0 ? 29 : 28, + 2: 31, + 3: 30, + 4: 31, + 5: 30, + 6: 31, + 7: 31, + 8: 30, + 9: 31, + 10: 30, + 11: 31 + }; + + let days = []; + let nextMonthDay = 1; + let thisMonthDay = 51; + let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm; + for (let i = 0; i < colN * (rowN - 1) + 1; i++) { + if (i < dowNorm) { + days.push(prevMonthDay); + prevMonthDay++; + } else if (thisMonthDay <= monthMaxDayMap[month] + 50) { + days.push(thisMonthDay); + thisMonthDay++; + } else { + days.push(nextMonthDay); + nextMonthDay++; + } + } + + let i = 0; + for (y = 0; y < rowN - 1; y++) { + for (x = 0; x < colN; x++) { + i++; + const day = days[i]; + const isToday = + today.year === year && today.month === month && today.day === day - 50; + if (isToday) { + g.setColor(red); + g.drawRect( + x * colW, + y * rowH + headerH + rowH, + x * colW + colW - 1, + y * rowH + headerH + rowH + rowH + ); + } + g.setColor(day < 50 ? gray3 : white); + g.drawString( + (day > 50 ? day - 50 : day).toString(), + x * colW + colW / 2, + headerH + rowH + y * rowH + rowH / 2 + ); + } + } +} + +const date = new Date(); +const today = { + day: date.getDate(), + month: date.getMonth(), + year: date.getFullYear() +}; +drawCalendar(date); +clearWatch(); +setWatch( + () => { + const month = date.getMonth(); + const prevMonth = month > 0 ? month - 1 : 11; + if (prevMonth === 11) date.setFullYear(date.getFullYear() - 1); + date.setMonth(prevMonth); + drawCalendar(date); + }, + BTN4, + { repeat: true } +); +setWatch( + () => { + const month = date.getMonth(); + const prevMonth = month < 11 ? month + 1 : 0; + if (prevMonth === 0) date.setFullYear(date.getFullYear() + 1); + date.setMonth(month + 1); + drawCalendar(date); + }, + BTN5, + { repeat: true } +); +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); diff --git a/apps/calendar/calendar.png b/apps/calendar/calendar.png new file mode 100644 index 000000000..056cab3b7 Binary files /dev/null and b/apps/calendar/calendar.png differ diff --git a/apps/hidbkbd/ChangeLog b/apps/hidbkbd/ChangeLog new file mode 100644 index 000000000..459bf40b9 --- /dev/null +++ b/apps/hidbkbd/ChangeLog @@ -0,0 +1,2 @@ +0.01: Core functionnality +0.02: Offer to enable HID if disabled. Handle with/without media keys diff --git a/apps/hidbkbd/hid-binary-keyboard.js b/apps/hidbkbd/hid-binary-keyboard.js index fa1017714..81838b42d 100644 --- a/apps/hidbkbd/hid-binary-keyboard.js +++ b/apps/hidbkbd/hid-binary-keyboard.js @@ -45,13 +45,7 @@ const KEY = { 0 : 39 }; -function sendHID(code) { - return new Promise(resolve=>{ - NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { - NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], resolve); - }); - }); -}; +var sendHID; function showChars(x,chars) { var lines = Math.round(Math.sqrt(chars.length)*2); @@ -103,10 +97,24 @@ function startKeyboardHID() { }).then(startKeyboardHID); }; -if (!settings.HID) { - E.showMessage('HID disabled'); - setTimeout(load, 1000); -} else { +if (settings.HID=="kb" || settings.HID=="kbmedia") { + if (settings.HID=="kbmedia") { + sendHID = function(code) { + return new Promise(resolve=>{ + NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], resolve); + }); + }); + }; + } else { + sendHID = function(code) { + return new Promise(resolve=>{ + NRF.sendHIDReport([0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([0,0,0,0,0,0,0,0], resolve); + }); + }); + }; + } startKeyboardHID(); setWatch(() => { sendHID(44); // space @@ -114,4 +122,12 @@ if (!settings.HID) { setWatch(() => { sendHID(40); // enter }, BTN3, {repeat:true}); +} else { + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kb"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidbkbd.app.js"); + } else setTimeout(load, 1000); + }); } diff --git a/apps/hidcam/ChangeLog b/apps/hidcam/ChangeLog index 73b3268b7..2823e1f1d 100644 --- a/apps/hidcam/ChangeLog +++ b/apps/hidcam/ChangeLog @@ -1 +1,2 @@ 0.01: Core functionnality +0.02: Offer to enable HID if disabled diff --git a/apps/hidcam/app.js b/apps/hidcam/app.js index 89b8ac4a1..adb1a4b29 100644 --- a/apps/hidcam/app.js +++ b/apps/hidcam/app.js @@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, camShot, profile; -if (settings.HID) { +if (settings.HID=="kbmedia") { profile = 'camShutter'; sendHid = function (code, cb) { try { @@ -19,8 +19,13 @@ if (settings.HID) { }; camShot = function (cb) { sendHid(0x80, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kbmedia"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidcam.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { g.clear(); diff --git a/apps/hidjoystick/app-icon.js b/apps/hidjoystick/app-icon.js new file mode 100644 index 000000000..21d10dd00 --- /dev/null +++ b/apps/hidjoystick/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4ADhvd6AWVAAIYTCwQABC9JGDJCYX/R+7XYgEE7tACycAgczmAX/C/4X/C6kBiMQCyoABDB0N7vdAgIWCAAIXjxAAQCwkIC6OAC/4X/C/4XbgAXRCwgA/AH4ANA")) diff --git a/apps/hidjoystick/app.js b/apps/hidjoystick/app.js new file mode 100644 index 000000000..0b3187a53 --- /dev/null +++ b/apps/hidjoystick/app.js @@ -0,0 +1,74 @@ +var storage = require('Storage'); +const settings = storage.readJSON('setting.json',1) || { HID: false }; + +var sendInProgress = false; // Only send one message at a time, do not flood + +const sendHid = function (x, y, btn1, btn2, btn3, btn4, btn5, cb) { + try { + const buttons = (btn5<<4) | (btn4<<3) | (btn3<<2) | (btn2<<1) | (btn1<<0); + if (!sendInProgress) { + sendInProgress = true; + NRF.sendHIDReport([buttons, x, y], () => { + sendInProgress = false; + if (cb) cb(); + }); + } + } catch(e) { + print(e); + } +}; + +function drawApp() { + g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + g.drawString("Joystick", 120, 120); + const d = g.getWidth() - 18; + + function c(a) { + return { + width: 8, + height: a.length, + bpp: 1, + buffer: (new Uint8Array(a)).buffer + }; + } + + g.drawImage(c([16,56,124,254,16,16,16,16]),d,40); + g.drawImage(c([16,16,16,16,254,124,56,16]),d,194); + g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); +} + +function update() { + const btn1 = BTN1.read(); + const btn2 = BTN2.read(); + const btn3 = BTN3.read(); + const btn4 = BTN4.read(); + const btn5 = BTN5.read(); + const acc = Bangle.getAccel(); + var x = acc.x*-127; + var y = acc.y*-127; + + // check limits + if (x > 127) x = 127; + else if (x < -127) x = -127; + if (y > 127) y = 127; + else if (y < -127) y = -127; + + sendHid(x & 0xff, y & 0xff, btn1, btn2, btn3, btn4, btn5); +} + +if (settings.HID === "joy") { + drawApp(); + setInterval(update, 100); // 10 Hz +} else { + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "joy"; + storage.write('setting.json', settings); + setTimeout(load, 1000, "hidjoystick.app.js"); + } else { + setTimeout(load, 1000); + } + }); +} diff --git a/apps/hidjoystick/app.png b/apps/hidjoystick/app.png new file mode 100644 index 000000000..aca42a818 Binary files /dev/null and b/apps/hidjoystick/app.png differ diff --git a/apps/hidkbd/ChangeLog b/apps/hidkbd/ChangeLog new file mode 100644 index 000000000..459bf40b9 --- /dev/null +++ b/apps/hidkbd/ChangeLog @@ -0,0 +1,2 @@ +0.01: Core functionnality +0.02: Offer to enable HID if disabled. Handle with/without media keys diff --git a/apps/hidkbd/hid-keyboard.js b/apps/hidkbd/hid-keyboard.js index ed406e093..0d489bc0d 100644 --- a/apps/hidkbd/hid-keyboard.js +++ b/apps/hidkbd/hid-keyboard.js @@ -4,27 +4,46 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; -if (settings.HID) { +if (settings.HID=="kb" || settings.HID=="kbmedia") { profile = 'Keyboard'; - sendHid = function (code, cb) { - try { - NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { - NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => { - if (cb) cb(); + if (settings.HID=="kbmedia") { + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => { + if (cb) cb(); + }); }); - }); - } catch(e) { - print(e); - } - }; + } catch(e) { + print(e); + } + }; + } else { + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([0,0,0,0,0,0,0,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; + } next = function (cb) { sendHid(0x4f, cb); }; prev = function (cb) { sendHid(0x50, cb); }; toggle = function (cb) { sendHid(0x2c, cb); }; up = function (cb) {sendHid(0x52, cb); }; down = function (cb) { sendHid(0x51, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kb"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidkbd.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { diff --git a/apps/hidmsic/ChangeLog b/apps/hidmsic/ChangeLog new file mode 100644 index 000000000..73b3268b7 --- /dev/null +++ b/apps/hidmsic/ChangeLog @@ -0,0 +1 @@ +0.01: Core functionnality diff --git a/apps/hidmsic/hid-music.js b/apps/hidmsic/hid-music.js index 034bbd231..db81744f3 100644 --- a/apps/hidmsic/hid-music.js +++ b/apps/hidmsic/hid-music.js @@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; -if (settings.HID) { +if (settings.HID=="kbmedia") { profile = 'Music'; sendHid = function (code, cb) { try { @@ -23,8 +23,13 @@ if (settings.HID) { up = function (cb) {sendHid(0x40, cb); }; down = function (cb) { sendHid(0x80, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kbmedia"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidmsc.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { diff --git a/apps/impwclock/README.md b/apps/impwclock/README.md new file mode 100644 index 000000000..30e42c95e --- /dev/null +++ b/apps/impwclock/README.md @@ -0,0 +1,4 @@ +# Imprecise Word Clock + +This clock tells time in very rough approximation, as in "Late morning" or "Early afternoon." Good for vacations and weekends. Press button 1 to see the time in accurate, digital form. But do you really need to know the exact time? + diff --git a/apps/impwclock/clock-impword-icon.js b/apps/impwclock/clock-impword-icon.js new file mode 100644 index 000000000..f5ed47f1f --- /dev/null +++ b/apps/impwclock/clock-impword-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A3iIBEn8ggP//8wgX/+cQl8Agc/BQPyCokQgHzmEB+ET+EfmMj+AXCmABBF4MBiIABiEC+PxC4Uwn4NB+QXMBAMzI4UxmYOBC5sfCgIvBgPzF4cfC5BgCFAMPkPwiXzL4cPmMvkAXDPAnzEgMxR4wDCGITl/AH4ApgUQbIICBAgXwBYMD+UAYoP/l4CBiUhd4QXFgIXCh73BfQUfAgIPBC4cQiIACC4cvj4PBC5AuCC48zgcwC4ZHBC5sBCAIEBF5EAC4RgDCQItCPAIXLCoQBBFgM/IoZHER4QA/AH4Anj8wgXzgX/+cQWoPyYQK9Bn/zj/wb4MTCAMf+MDAYMxkfwj8BmYXBmEzCYMf+cDmPzkMvj8zAIM/eoPyC4fy+IXDl8TmfwI4UvmYABAwIXB//xgPwBIIXCgYFBmEP/8fh/yF4sDC4QjBC4RvBF4UPB4JUBL4kAn8ROIJbBC4IIBL4hDBmaPEgBuB+EB+aPCUQUjCALn/AH4A/A")) \ No newline at end of file diff --git a/apps/impwclock/clock-impword.js b/apps/impwclock/clock-impword.js new file mode 100644 index 000000000..c54fa7976 --- /dev/null +++ b/apps/impwclock/clock-impword.js @@ -0,0 +1,160 @@ +/* Imprecise Word Clock - A. Blanton +A remix of word clock +by Gordon Williams https://github.com/gfwilliams +- Changes the representation of time to be more general +- Shows accurate digital time when button 1 is pressed +*/ +/* jshint esversion: 6 */ + +const allWords = [ + "AEARLYDN", + "LATEYRZO", + "MORNINGO", + "KMIDDLEN", + "AFTERDAY", + "OFDZTHEC", + "EVENINGR", + "ORMNIGHT" +]; + + +const timeOfDay = { + 0: ["", 0, 0], + 1: ["EARLYMORNING", 10, 20, 30, 40, 50, 02, 12, 22, 32, 42, 52, 62], + 2: ["MORNING", 02, 12, 22, 32, 42, 52, 62], + 3: ["LATEMORNING", 01, 11, 21, 31, 02, 12, 22, 32, 42, 52, 62], + 4: ["MIDDAY", 13, 23, 33, 54, 64, 74], + 5: ["EARLYAFTERNOON", 10, 20, 30, 40, 50, 04, 14, 24, 34, 44, 70, 71, 72, 73], + 6: ["AFTERNOON", 04, 14, 24, 34, 44, 70, 71, 72, 73], + 7: ["LATEAFTERNOON", 01, 11, 21, 31, 04, 14, 24, 34, 44, 70, 71, 72, 73], + 8: ["EARLYEVENING", 10, 20, 30, 40, 50, 06, 16, 26, 36, 46, 56, 66], + 9: ["EVENING", 06, 16, 26, 36, 46, 56, 66], + 10: ["NIGHT", 37, 47, 57, 67, 77], + 11: ["MIDDLEOFTHENIGHT", 13, 23, 33, 43, 53, 63, 05, 15, 45, 55, 65, 37,47,57,67,77 ], +}; + + +// offsets and increments +const xs = 35; +const ys = 31; +const dy = 22; +const dx = 25; + +// font size and color +const fontSize = 3; // "6x8" +const passivColor = 0x3186 /*grey*/ ; +const activeColorNight = 0xF800 /*red*/ ; +const activeColorDay = 0xFFFF /* white */; + +function drawWordClock() { + + + // get time + var t = new Date(); + var h = t.getHours(); + var m = t.getMinutes(); + var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2); + var day = t.getDay(); + + var hidx; + + var activeColor = activeColorDay; + if(h < 7 || h > 19) {activeColor = activeColorNight;} + + g.setFont("6x8",fontSize); + g.setColor(passivColor); + g.setFontAlign(0, -1, 0); + + // draw allWords + var c; + var y = ys; + var x = xs; + allWords.forEach((line) => { + x = xs; + for (c in line) { + g.drawString(line[c], x, y); + x += dx; + } + y += dy; + }); + + + // Switch case isn't good for this in Js apparently so... + if(h < 3){ + // Middle of the Night + hidx = 11; + } + else if (h < 7){ + // Early Morning + hidx = 1; + } + else if (h < 10){ + // Morning + hidx = 2; + } + else if (h < 12){ + // Late Morning + hidx = 3; + } + else if (h < 13){ + // Midday + hidx = 4; + } + else if (h < 14){ + // Early afternoon + hidx = 5; + } + else if (h < 16){ + // Afternoon + hidx = 6; + } + else if (h < 17){ + // Late Afternoon + hidx = 7; + } + else if (h < 19){ + // Early evening + hidx = 8; + } + else if (h < 21){ + // evening + hidx = 9; + } + else if (h < 24){ + // Night + hidx = 10; + } + + // write hour in active color + g.setColor(activeColor); + timeOfDay[hidx][0].split('').forEach((c, pos) => { + x = xs + (timeOfDay[hidx][pos + 1] / 10 | 0) * dx; + y = ys + (timeOfDay[hidx][pos + 1] % 10) * dy; + g.drawString(c, x, y); + }); + + + // Display digital time while button 1 is pressed + if (BTN1.read()){ + g.setColor(activeColor); + g.clearRect(0, 215, 240, 240); + g.drawString(time, 120, 215); + } else { g.clearRect(0, 215, 240, 240); } + +} + +Bangle.on('lcdPower', function(on) { + if (on) drawWordClock(); +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +setInterval(drawWordClock, 1E4); +drawWordClock(); + +// Show digital time while top button is pressed +setWatch(drawWordClock, BTN1, {repeat:true,edge:"both"}); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/impwclock/clock-impword.png b/apps/impwclock/clock-impword.png new file mode 100644 index 000000000..e7ed0e828 Binary files /dev/null and b/apps/impwclock/clock-impword.png differ diff --git a/apps/mclock/clock-morphing-faster.js b/apps/mclock/clock-morphing-faster.js new file mode 100644 index 000000000..69cd72707 --- /dev/null +++ b/apps/mclock/clock-morphing-faster.js @@ -0,0 +1,206 @@ +var locale = require("locale"); +var CHARW = 34; +var CHARP = 2; +var Y = 50; +// Offscreen buffer +var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); +var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; +// The last time that we displayed +var lastTime = " "; +// If animating, this is the interval's id +var animInterval; +var timeInterval; + +/* Get array of lines from digit d to d+1. + n is the amount (0..1) + maxFive is true is this digit only counts 0..5 */ +const DIGITS = { +" ":(g,s,p,n)=>{}, +"0":(g,s,p,n)=>{ +g.fillRect(1+s*n,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1+s*n,1+s-p, 1+s*n,1+2*s+p); +g.fillRect(1+s*n-p,1, 1+s*n+p,1+s)}, +"1":(g,s,p,n)=>{ +g.fillRect(1+(1-n)*s,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+(1-n)*s,1+s-p, 1+s,1+s+p); +g.fillRect(1-p+(1-n)*s,1+s, 1+p+(1-n)*s,1+2*s); +g.fillRect(1+(1-n)*s,1-p+2*s, 1+s,1+p+2*s)}, +"2":(g,s,p,n)=>{ +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1-p,1+(1+n)*s, 1+p,1+2*s); +g.fillRect(1+s-p,1+(2-n)*s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p)}, +"3":(g,s,p,n)=>{ +g.fillRect(1,1-p, 1+(1-n)*s,1+p); +g.fillRect(1-p,1, 1+p,n); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p)}, +"4":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1+s,1-p, 1+(1-n)*s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+(1-n)*s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+(1-n)*s,1+2*s-p, 1+s,1+2*s+p)}, +"5to0": (g,s,p,n)=>{ // 5 -> 0 +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s*n,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s*p, 1+s,1+2*s+p); +g.fillRect(1,1+2*s-p, 1,1+2*s+p); +g.fillRect(1+s-p,1+(1-n)*s, 1+s+p,1+s); +g.fillRect(1-p,1+s, 1+p,1+(1+n)*s)}, +"5to6": (g,s,p,n)=>{ // 5 -> 6 +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,2-n, 1+p,1+2*s)}, +"6":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+(1-n)*s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s*n,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+(1-n)*s, 1+s+p,1+s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,1+(1-n)*s, 1+p,1+s*(2-2*n))}, +"7":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,n); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+(1-n)*s,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+(1-n)*s,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1+(1-n)*s-p,1+s, 1+(1-n)*s+p,1+2*s)}, +"8":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,1+s, 1+p,1+s*(2-n))}, +"9":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+(1-n)*s,1+s+p); +g.fillRect(1-p,1+s, 1+p,1+(1+n)*s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p)}, +":":(g,s,p,n)=>{ +g.fillRect(1+s*0.4,1+s*0.4-p, 1+s*0.6,1+s*0.4+p); +g.fillRect(1+s*0.6-p,1+s*0.4, 1+s*0.6+p,1+s*0.6); +g.fillRect(1+s*0.6,1+s*0.6-p, 1+s*0.4,1+s*0.6+p); +g.fillRect(1+s*0.4-p,1+s*0.4, 1+s*0.4+p,1+s*0.6); +g.fillRect(1+s*0.4,1+s*1.4-p, 1+s*0.6,1+s*1.4+p); +g.fillRect(1+s*0.6-p,1+s*1.4, 1+s*0.6+p,1+s*1.6); +g.fillRect(1+s*0.6,1+s*1.6-p, 1+s*0.4,1+s*1.6+p); +g.fillRect(1+s*0.4-p,1+s*1.4, 1+s*0.4+p,1+s*1.6) +}}; + +/* Draw a transition between lastText and thisText. + 'n' is the amount - 0..1 */ +function drawDigits(lastText,thisText,n) { + const p = CHARP; // padding around digits + const s = CHARW; // character size + var x = p; // x offset + var y = Y+p; // y offset + g.reset(); + for (var i=0;i=1) { + n=1; + clearInterval(animInterval); + animInterval = undefined; + } + drawDigits(l,t,n); + }, 20); + lastTime = t; +} + +Bangle.on('lcdPower',function(on) { + if (animInterval) { + clearInterval(animInterval); + animInterval = undefined; + } + if (timeInterval) { + clearInterval(timeInterval); + timeInterval = undefined; + } + if (on) { + showTime(); + timeInterval = setInterval(showTime, 1000); + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Update time once a second +timeInterval = setInterval(showTime, 1000); +showTime(); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/pong/ChangeLog b/apps/pong/ChangeLog index 5560f00bc..6433ebce4 100644 --- a/apps/pong/ChangeLog +++ b/apps/pong/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: 2 players local + improve ai diff --git a/apps/pong/README.md b/apps/pong/README.md new file mode 100644 index 000000000..ea4939539 --- /dev/null +++ b/apps/pong/README.md @@ -0,0 +1,28 @@ +# Pong + +A clone of the Atari game Pong + + + +## Features + +- Play against a dumb AI +- Play local Multiplayer against your friends + +## Controls + +Player's controls: +- UP: BTN1 +- DOWN: BTN2 +long press to move faster + +Restart a game: +- RESET: BTN3 + +Buttons for player 2: +- UP: BTN4 +- DOWN: BTN5 + +## Creator + + diff --git a/apps/pong/app.js b/apps/pong/app.js index 4531b3af8..ba34d60b5 100644 --- a/apps/pong/app.js +++ b/apps/pong/app.js @@ -8,6 +8,7 @@ * - Let's make pong, One Man Army Studios, Youtube * - Pong.js, KanoComputing, Github * - Coding Challenge #67: Pong!, The Coding Train, Youtube + * - Pixl.js Multiplayer Pong, espruino website */ const SCREEN_WIDTH = 240; @@ -15,6 +16,13 @@ const FPS = 16; const MAX_SCORE = 11; let scores = [0, 0]; let aiSpeedRandom = 0; +let winnerMessage = ''; + +const sound = { + ping: () => Bangle.beep(8, 466), + pong: () => Bangle.beep(8, 220), + fall: () => Bangle.beep(16*3, 494).then(_ => Bangle.beep(32*3, 3322)) +}; function Vector(x, y) { this.x = x; @@ -28,12 +36,18 @@ Vector.prototype.add = function (x) { const constrain = (n, low, high) => Math.max(Math.min(n, high), low); const random = (min, max) => Math.random() * (max - min) + min; -const intersects = (circ, rect) => { - var c1 = circ.pos, c2 = {x: circ.pos.x+circ.r, y: circ.pos.y+circ.r}; - var r1 = rect.pos, r2 = {x: rect.pos.x+rect.width*2, y: rect.pos.y+rect.height}; - return !(c1.x > r2.x || c2.x < r1.x || - c1.y > r2.y || c2.y < r1.y); -}; +const intersects = (circ, rect, right) => { + var c = circ.pos; + var r = circ.r; + if (c.y - r < rect.pos.y + rect.height && c.y + r > rect.pos.y) { + if (right) { + return c.x + r > rect.pos.x - rect.width*2 && c.x < rect.pos.x + rect.width + } else { + return c.x - r < rect.pos.x + rect.width*2 && c.x > rect.pos.x - rect.width + } + } + return false; +} ///////////////////////////// Ball ////////////////////////////////////////// @@ -45,12 +59,26 @@ function Ball() { this.reset(); } -Ball.prototype.show = function () { +Ball.prototype.reset = function() { + this.speed = this.originalSpeed; + var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; + var bounceAngle = Math.PI/6; + this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); + this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); + this.ballReturn = 0; +}; +Ball.prototype.restart = function() { + this.reset(); + ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); + player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); + this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); +}; +Ball.prototype.show = function (invert) { if (this.prevPos != null) { - g.setColor(0); + g.setColor(invert ? -1 : 0); g.fillCircle(this.prevPos.x, this.prevPos.y, this.prevPos.r); } - g.setColor(-1); + g.setColor(invert ? 0 : -1); g.fillCircle(this.pos.x, this.pos.y, this.r); this.prevPos = { x: this.pos.x, @@ -58,55 +86,62 @@ Ball.prototype.show = function () { r: this.r }; }; -Ball.prototype.bouncePlayer = function (multiplyX, multiplyY, player) { +function bounceAngle(playerY, ballY, playerHeight, maxHangle) { + let relativeIntersectY = (playerY + (playerHeight/2)) - ballY; + let normalizedRelativeIntersectionY = relativeIntersectY / (playerHeight/2); + let bounceAngle = normalizedRelativeIntersectionY * maxHangle; + return { x: Math.cos(bounceAngle), y: -Math.sin(bounceAngle) }; +} +Ball.prototype.bouncePlayer = function (directionX, directionY, player) { + this.ballReturn++; this.speed = constrain(this.speed + 2, this.originalSpeed, this.maxSpeed); - var relativeIntersectY = (player.pos.y+(player.height/2)) - this.pos.y; - var normalizedRelativeIntersectionY = (relativeIntersectY/(player.height/2)); var MAX_BOUNCE_ANGLE = 4 * Math.PI/12; - var bounceAngle = normalizedRelativeIntersectionY * MAX_BOUNCE_ANGLE; - this.velocity.x = this.speed * Math.cos(bounceAngle) * multiplyX; - this.velocity.y = this.speed * -Math.sin(bounceAngle) * multiplyY; + var angle = bounceAngle(player.pos.y, this.pos.y, player.height, MAX_BOUNCE_ANGLE) + this.velocity.x = this.speed * angle.x * directionX; + this.velocity.y = this.speed * angle.y * directionY; + this.ballReturn % 2 === 0 ? sound.ping() : sound.pong(); }; -Ball.prototype.bounce = function (multiplyX, multiplyY, player) { +Ball.prototype.bounce = function (directionX, directionY, player) { if (player) - return this.bouncePlayer(multiplyX, multiplyY, player); + return this.bouncePlayer(directionX, directionY, player); - if (multiplyX) { - this.velocity.x = Math.abs(this.velocity.x) * multiplyX; + if (directionX) { + this.velocity.x = Math.abs(this.velocity.x) * directionX; } - if (multiplyY) { - this.velocity.y = Math.abs(this.velocity.y) * multiplyY; + if (directionY) { + this.velocity.y = Math.abs(this.velocity.y) * directionY; } }; -Ball.prototype.checkWallsCollision = function () { +Ball.prototype.fall = function (playerId) { + scores[playerId]++; + if (scores[playerId] >= MAX_SCORE) { + this.restart(); + state = 3; + if (playerId === 1) { + winnerMessage = startOption === 0 ? "AI Wins!" : "Player 2 Wins!"; + } else { + winnerMessage = startOption === 0 ? "You Win!" : "Player 1 Wins!"; + } + } else { + sound.fall(); + this.reset(); + } +}; +Ball.prototype.wallCollision = function () { if (this.pos.y < 0) { this.bounce(0, 1); } else if (this.pos.y > SCREEN_WIDTH) { this.bounce(0, -1); } else if (this.pos.x < 0) { - scores[1]++; - if (scores[1] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "AI Wins!"; - } else { - this.reset(); - } + this.fall(1); } else if (this.pos.x > SCREEN_WIDTH) { - scores[0]++; - if (scores[0] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "You Win!"; - } else { - this.reset(); - } + this.fall(0); } else { return false; } return true; }; -Ball.prototype.checkPlayerCollision = function (player) { +Ball.prototype.playerCollision = function (player) { if (intersects(this, player)) { if (this.pos.x < SCREEN_WIDTH/2) { this.bounce(1, 1, player); @@ -120,8 +155,8 @@ Ball.prototype.checkPlayerCollision = function (player) { } return false; }; -Ball.prototype.checkCollisions = function () { - return this.checkWallsCollision() || this.checkPlayerCollision(player) || this.checkPlayerCollision(ai); +Ball.prototype.collisions = function () { + return this.wallCollision() || this.playerCollision(player) || this.playerCollision(ai); }; Ball.prototype.updatePosition = function () { var elapsed = new Date().getTime() - this.lastUpdate; @@ -132,31 +167,20 @@ Ball.prototype.updatePosition = function () { Ball.prototype.update = function () { this.updatePosition(); this.lastUpdate = new Date().getTime(); - this.checkCollisions(); -}; -Ball.prototype.reset = function() { - this.speed = this.originalSpeed; - var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; - var bounceAngle = Math.PI/6; - this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); - this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); -}; -Ball.prototype.restart = function() { - ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); - player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); - this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); + this.collisions(); }; //////////////////////////// Player ///////////////////////////////////////// -function Player() { +function Player(right) { this.width = 4; this.height = 30; - this.pos = new Vector(this.width*2, SCREEN_WIDTH/2 - this.height/2); + this.pos = new Vector(right ? SCREEN_WIDTH-this.width : this.width, SCREEN_WIDTH/2 - this.height/2); this.acc = new Vector(0, 0); this.speed = 15; this.maxSpeed = 25; this.prevPos = null; + this.right = right; } Player.prototype.show = function () { if (this.prevPos != null) { @@ -196,11 +220,14 @@ function AI() { AI.prototype = Object.create(Player.prototype); AI.prototype.constructor = Player; AI.prototype.update = function () { - var y = ball.pos.y - (this.height/2 * aiSpeedRandom); - var yConstrained = constrain(y, 0, SCREEN_WIDTH-this.height); + var y = ball.pos.y - this.height/2; + var randomizedY = ball.ballReturn < 3 ? y : y + (aiSpeedRandom * this.height/2); + var yConstrained = constrain(randomizedY, 0, SCREEN_WIDTH-this.height); this.pos = new Vector(this.pos.x, yConstrained); }; +/////////////////////////////// Scenes //////////////////////////////////////// + function net() { var dashSize = 5; for (let y = dashSize/2; y < SCREEN_WIDTH; y += dashSize*2) { @@ -210,12 +237,6 @@ function net() { } } -var player = new Player(); -var ai = new AI(); -var ball = new Ball(); -var state = 0; -var prevScores = [0, 0]; - function drawScores() { let x1 = SCREEN_WIDTH/4-5; let x2 = SCREEN_WIDTH*3/4-5; @@ -233,10 +254,80 @@ function drawScores() { function drawGameOver() { g.setFont("Vector", 20); - g.drawString(winnerMessage, 75, SCREEN_WIDTH/2 - 10); + g.drawString(winnerMessage, startOption === 0 ? 55 : 75, SCREEN_WIDTH/2 - 10); } -function draw() { +function showControls(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 8); + var topArrowString = ` + ######## + ## + ## ## + ### ## + ### ## + ### +## +`; + + var arrows = [Graphics.createImage(topArrowString), Graphics.createImage(` + ## + ## +#################### + ## + ## +`), Graphics.createImage(topArrowString.split('\n').reverse().join('\n')) + ]; + + g.drawString('UP', 170, 50); + g.drawImage(arrows[0], 200, 40); + g.drawString('DOWN', 156, 120); + g.drawImage(arrows[1], 200, 120); + g.drawString('START', 152, 190); + g.drawImage(arrows[2], 200, 200); +} + +function drawStartScreen(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 10); + g.drawString("1 PLAYER", 95, 80); + g.drawString("2 PLAYERS", 95, 110); + + const ball1 = new Ball(); + ball1.prevPos = null; + ball1.pos = new Vector(87, 86); + ball1.show(hide || !(startOption === 0)); + + const ball2 = new Ball(); + ball2.prevPos = null; + ball2.pos = new Vector(87, 116); + ball2.show(hide || !(startOption === 1)); +} + +function drawStartTimer(count, callback) { + setTimeout(_ => { + player.show(); + ai.show(); + net(); + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + if (count >= 0) { + g.setFont("Vector", 10); + g.drawString(count+1, 115, 115); + g.setColor(-1); + g.drawString(count === 0 ? 'Go!' : count, 115 - (count === 0 ? 4: 0), 115); + drawStartTimer(count - 1, callback); + } else { + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + callback(); + } + }, 800); +} + +//////////////////////////////// Main ///////////////////////////////////////// + +function onFrame() { if (state === 1) { ball.update(); player.update(); @@ -261,22 +352,73 @@ function draw() { drawScores(); } +function startThatGame() { + player.show(); + ai.show(); + net(); + drawScores(); + drawStartTimer(3, () => setInterval(onFrame, 1000 / FPS)); +} + +var player = new Player(); +var ai; +var ball = new Ball(); +var state = 0; +var prevScores = [0, 0]; +var playerBle = null; +var startOption = 0; + g.clear(); g.setColor(0); g.fillRect(0,0,240,240); +showControls(); +setTimeout(() => { + showControls(true); + drawStartScreen(); +}, 2000); -setInterval(draw, 1000 / FPS); +////////////////////////////// Controls /////////////////////////////////////// -setWatch(o => o.state ? player.up() : player.stop(), BTN1, {repeat: true, edge: 'both'}); -setWatch(o => o.state ? player.down() : player.stop(), BTN3, {repeat: true, edge: 'both'}); -//setWatch(o => o.state ? player.down() : player.stop(), BTN5, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 0 ? startOption : startOption - 1; + drawStartScreen(); + } + } else o.state ? player.up() : player.stop(); +}, BTN1, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 1 ? startOption : startOption + 1; + drawStartScreen(); + } + } else o.state ? player.down() : player.stop(); +}, BTN2, {repeat: true, edge: 'both'}); setWatch(o => { state++; + clearInterval(); if (state >= 2) { - ball.restart(); g.setColor(0); - g.fillRect(0,0,240,240); + g.fillRect(0, 0, 240, 240); + ball.show(true); scores = [0, 0]; + playerBle = null; + ball = new Ball(); state = 1; + startThatGame(); + } else { + drawStartScreen(true); + showControls(true); + if (startOption === 1) { + ai = new Player(true); + startThatGame(); + } else { + ai = new AI(); + startThatGame(); + } } -}, BTN2, {repeat: true}); +}, BTN3, {repeat: true}); + +setWatch(o => startOption === 1 && (o.state ? ai.up() : ai.stop()), BTN4, {repeat: true, edge: 'both'}); +setWatch(o => startOption === 1 && (o.state ? ai.down() : ai.stop()), BTN5, {repeat: true, edge: 'both'}); diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 9263b3b13..f168a1fe5 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -20,3 +20,4 @@ 0.16: Reduce memory usage further when running app settings page 0.17: Remove need for "settings" in appid.info 0.18: Don't overwrite existing settings on app update +0.19: Allow BLE HID settings, add README.md diff --git a/apps/setting/README.md b/apps/setting/README.md new file mode 100644 index 000000000..4052da0ff --- /dev/null +++ b/apps/setting/README.md @@ -0,0 +1,18 @@ +# Settings + +This is Bangle.js's settings menu + +* **Make Connectable** regardless of the current Bluetooth settings, makes Bangle.js so you can connect to it (while the window is up) +* **App/Widget Settings** settings specific to installed applications +* **BLE** is Bluetooth LE enabled and the watch connectable? +* **Programmable** if BLE is on, can the watch be connected to in order to program/upload apps? +* **Debug Info** should debug info be shown on the watch's screen or not? +* **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected +* **Vibration** enable/disable the vibration motor +* **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks) +* **Select Clock** if you have more than one clock face, select the default one +* **HID** When Bluetooth is enabled, Bangle.js can appear as a Bluetooth Keyboard/Joystick/etc to send keypresses to a connected device. **Note:** on some platforms enabling HID can cause you problems when trying to connect to Bangle.js to upload apps. +* **Set Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader +* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on. +* **Reset Settings** Reset the settings to defaults +* **Turn Off** Turn Bangle.js off diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 97ce464ad..55048a9d4 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -61,6 +61,8 @@ const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { var beepV = [false, true, "vib"]; var beepN = ["Off", "Piezo", "Vibrate"]; + var hidV = [false, "kbmedia", "kb", "joy"]; + var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; const mainmenu = { '': { 'title': 'Settings' }, 'Make Connectable': ()=>makeConnectable(), @@ -115,10 +117,11 @@ function showMainMenu() { 'Locale': ()=>showLocaleMenu(), 'Select Clock': ()=>showClockMenu(), 'HID': { - value: settings.HID, - format: boolFormat, - onchange: () => { - settings.HID = !settings.HID; + value: 0 | hidV.indexOf(settings.HID), + min: 0, max: 3, + format: v => hidN[v], + onchange: v => { + settings.HID = hidV[v]; updateSettings(); } }, diff --git a/index.html b/index.html index 3c8b440e4..3815c12eb 100644 --- a/index.html +++ b/index.html @@ -138,6 +138,14 @@

+

Settings

+
+ + +
@@ -156,6 +164,7 @@ + diff --git a/js/appinfo.js b/js/appinfo.js index 9fff7c92a..45ed3d8a2 100644 --- a/js/appinfo.js +++ b/js/appinfo.js @@ -11,6 +11,16 @@ var AppInfo = { return Promise.resolve(storageFile); else if (storageFile.url) return fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => { + if (storageFile.url.endsWith(".js") && !storageFile.url.endsWith(".min.js")) { // if original file ends in '.js'... + return Espruino.transform(content, { + SET_TIME_ON_WRITE : false, + PRETOKENISE : SETTINGS.pretokenise, + //MINIFICATION_LEVEL : "ESPRIMA", // disable due to https://github.com/espruino/BangleApps/pull/355#issuecomment-620124162 + builtinModules : "Flash,Storage,heatshrink,tensorflow,locale" + }); + } else + return content; + }).then(content => { return { name : storageFile.name, content : content, diff --git a/js/espruinotools.js b/js/espruinotools.js new file mode 100644 index 000000000..c81a69c08 --- /dev/null +++ b/js/espruinotools.js @@ -0,0 +1,6803 @@ +// EspruinoTools bundle (https://github.com/espruino/EspruinoTools) +// Created with https://github.com/espruino/EspruinoWebIDE/blob/gh-pages/extras/create_espruinotools_js.sh +// Based on EspruinoWebIDE 0.73.4 +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Initialisation code + ------------------------------------------------------------------ +**/ +"use strict"; + +var Espruino; + +(function() { + + /** List of processors. These are functions that are called one + * after the other with the data received from the last one. + * + * Common processors are: + * + * sending - sending code to Espruino (no data) + * transformForEspruino - transform code ready to be sent to Espruino + * transformModuleForEspruino({code,name}) + * - transform module code before it's sent to Espruino with Modules.addCached (we only do this if we don't think it's been minified before) + * connected - connected to Espruino (no data) + * disconnected - disconnected from Espruino (no data) + * environmentVar - Board's process.env loaded (object to be saved into Espruino.Env.environmentData) + * boardJSONLoaded - Board's JSON was loaded into environmentVar + * getModule - Called with data={moduleName:"foo", moduleCode:undefined} - moduleCode should be filled in if the module can be found + * getURL - Called with data={url:"http://....", data:undefined) - data should be filled in if the URL is handled (See Espruino.Core.Utils.getURL to use this) + * terminalClear - terminal has been cleared + * terminalPrompt - we've received a '>' character (eg, `>` or `debug>`). The argument is the current line's contents. + * terminalNewLine - When we get a new line on the terminal, this gets called with the last line's contents + * debugMode - called with true or false when debug mode is entered or left + * editorHover - called with { node : htmlNode, showTooltip : function(htmlNode) } when something is hovered over + * notification - called with { mdg, type:"success","error"/"warning"/"info" } + **/ + var processors = {}; + + function init() { + + Espruino.Core.Config.loadConfiguration(function() { + // Initialise all modules + function initModule(modName, mod) { + console.log("Initialising "+modName); + if (mod.init !== undefined) + mod.init(); + } + + var module; + for (module in Espruino.Core) initModule(module, Espruino.Core[module]); + for (module in Espruino.Plugins) initModule(module, Espruino.Plugins[module]); + + callProcessor("initialised", undefined, function() { + // We need the delay because of background.js's url_handler... + setTimeout(function() { + Espruino.initialised = true; + }, 1000); + }); + }); + } + + // Automatically start up when all is loaded + document.addEventListener("DOMContentLoaded", init); + + /** Add a processor function of type function(data,callback) */ + function addProcessor(eventType, processor) { + if (processors[eventType]===undefined) + processors[eventType] = []; + processors[eventType].push(processor); + } + + /** Call a processor function */ + function callProcessor(eventType, data, callback) { + var p = processors[eventType]; + // no processors + if (p===undefined || p.length==0) { + if (callback!==undefined) callback(data); + return; + } + // now go through all processors + var n = 0; + var cbCalled = false; + var cb = function(inData) { + if (cbCalled) throw new Error("Internal error in "+eventType+" processor. Callback is called TWICE."); + cbCalled = true; + if (n < p.length) { + cbCalled = false; + p[n++](inData, cb); + } else { + if (callback!==undefined) callback(inData); + } + }; + cb(data); + } + + // ----------------------------------- + Espruino = { + Core : { }, + Plugins : { }, + addProcessor : addProcessor, + callProcessor : callProcessor, + initialised : false, + init : init, // just in case we need to initialise this by hand + }; + + return Espruino; +})(); +Espruino.Core.Notifications = { + success : function(e) { console.log(e); }, + error : function(e) { console.error(e); }, + warning : function(e) { console.warn(e); }, + info : function(e) { console.log(e); }, +}; +Espruino.Core.Status = { + setStatus : function(e,len) { console.log(e); }, + hasProgress : function() { return false; }, + incrementProgress : function(amt) {} +}; +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.acorn = {}))); +}(this, (function (exports) { 'use strict'; + +// Reserved word lists for various dialects of the language + +var reservedWords = { + 3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile", + 5: "class enum extends super const export import", + 6: "enum", + strict: "implements interface let package private protected public static yield", + strictBind: "eval arguments" +}; + +// And the keywords + +var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"; + +var keywords = { + 5: ecma5AndLessKeywords, + 6: ecma5AndLessKeywords + " const class extends export import super" +}; + +var keywordRelationalOperator = /^in(stanceof)?$/; + +// ## Character categories + +// Big ugly regular expressions that match characters in the +// whitespace, identifier, and identifier-start categories. These +// are only applied when a character is found to actually have a +// code point above 128. +// Generated by `bin/generate-identifier-regex.js`. + +var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0-\u08b4\u08b6-\u08bd\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c88\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fd5\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7ae\ua7b0-\ua7b7\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab65\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; +var nonASCIIidentifierChars = "\u200c\u200d\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08d4-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c03\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d01-\u0d03\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf2-\u1cf4\u1cf8\u1cf9\u1dc0-\u1df5\u1dfb-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua900-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + +var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); +var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + +nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; + +// These are a run-length and offset encoded representation of the +// >0xffff code points that are a valid part of identifiers. The +// offset starts at 0x10000, and each pair of numbers represents an +// offset to the next range, and then a size of the range. They were +// generated by bin/generate-identifier-regex.js + +// eslint-disable-next-line comma-spacing +var astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,785,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,54,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,86,25,391,63,32,0,449,56,264,8,2,36,18,0,50,29,881,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,881,68,12,0,67,12,65,0,32,6124,20,754,9486,1,3071,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,4149,196,60,67,1213,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,10591,541]; + +// eslint-disable-next-line comma-spacing +var astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,10,2,4,9,83,11,7,0,161,11,6,9,7,3,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,87,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,423,9,838,7,2,7,17,9,57,21,2,13,19882,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,2214,6,110,6,6,9,792487,239]; + +// This has a complexity linear to the value of the code. The +// assumption is that looking up astral identifier characters is +// rare. +function isInAstralSet(code, set) { + var pos = 0x10000; + for (var i = 0; i < set.length; i += 2) { + pos += set[i]; + if (pos > code) { return false } + pos += set[i + 1]; + if (pos >= code) { return true } + } +} + +// Test whether a given character code starts an identifier. + +function isIdentifierStart(code, astral) { + if (code < 65) { return code === 36 } + if (code < 91) { return true } + if (code < 97) { return code === 95 } + if (code < 123) { return true } + if (code <= 0xffff) { return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)) } + if (astral === false) { return false } + return isInAstralSet(code, astralIdentifierStartCodes) +} + +// Test whether a given character is part of an identifier. + +function isIdentifierChar(code, astral) { + if (code < 48) { return code === 36 } + if (code < 58) { return true } + if (code < 65) { return false } + if (code < 91) { return true } + if (code < 97) { return code === 95 } + if (code < 123) { return true } + if (code <= 0xffff) { return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)) } + if (astral === false) { return false } + return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes) +} + +// ## Token types + +// The assignment of fine-grained, information-carrying type objects +// allows the tokenizer to store the information it has about a +// token in a way that is very cheap for the parser to look up. + +// All token type variables start with an underscore, to make them +// easy to recognize. + +// The `beforeExpr` property is used to disambiguate between regular +// expressions and divisions. It is set on all token types that can +// be followed by an expression (thus, a slash after them would be a +// regular expression). +// +// The `startsExpr` property is used to check if the token ends a +// `yield` expression. It is set on all token types that either can +// directly start an expression (like a quotation mark) or can +// continue an expression (like the body of a string). +// +// `isLoop` marks a keyword as starting a loop, which is important +// to know when parsing a label, in order to allow or disallow +// continue jumps to that label. + +var TokenType = function TokenType(label, conf) { + if ( conf === void 0 ) conf = {}; + + this.label = label; + this.keyword = conf.keyword; + this.beforeExpr = !!conf.beforeExpr; + this.startsExpr = !!conf.startsExpr; + this.isLoop = !!conf.isLoop; + this.isAssign = !!conf.isAssign; + this.prefix = !!conf.prefix; + this.postfix = !!conf.postfix; + this.binop = conf.binop || null; + this.updateContext = null; +}; + +function binop(name, prec) { + return new TokenType(name, {beforeExpr: true, binop: prec}) +} +var beforeExpr = {beforeExpr: true}; +var startsExpr = {startsExpr: true}; + +// Map keyword names to token types. + +var keywords$1 = {}; + +// Succinct definitions of keyword token types +function kw(name, options) { + if ( options === void 0 ) options = {}; + + options.keyword = name; + return keywords$1[name] = new TokenType(name, options) +} + +var types = { + num: new TokenType("num", startsExpr), + regexp: new TokenType("regexp", startsExpr), + string: new TokenType("string", startsExpr), + name: new TokenType("name", startsExpr), + eof: new TokenType("eof"), + + // Punctuation token types. + bracketL: new TokenType("[", {beforeExpr: true, startsExpr: true}), + bracketR: new TokenType("]"), + braceL: new TokenType("{", {beforeExpr: true, startsExpr: true}), + braceR: new TokenType("}"), + parenL: new TokenType("(", {beforeExpr: true, startsExpr: true}), + parenR: new TokenType(")"), + comma: new TokenType(",", beforeExpr), + semi: new TokenType(";", beforeExpr), + colon: new TokenType(":", beforeExpr), + dot: new TokenType("."), + question: new TokenType("?", beforeExpr), + arrow: new TokenType("=>", beforeExpr), + template: new TokenType("template"), + invalidTemplate: new TokenType("invalidTemplate"), + ellipsis: new TokenType("...", beforeExpr), + backQuote: new TokenType("`", startsExpr), + dollarBraceL: new TokenType("${", {beforeExpr: true, startsExpr: true}), + + // Operators. These carry several kinds of properties to help the + // parser use them properly (the presence of these properties is + // what categorizes them as operators). + // + // `binop`, when present, specifies that this operator is a binary + // operator, and will refer to its precedence. + // + // `prefix` and `postfix` mark the operator as a prefix or postfix + // unary operator. + // + // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as + // binary operators with a very low precedence, that should result + // in AssignmentExpression nodes. + + eq: new TokenType("=", {beforeExpr: true, isAssign: true}), + assign: new TokenType("_=", {beforeExpr: true, isAssign: true}), + incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}), + prefix: new TokenType("!/~", {beforeExpr: true, prefix: true, startsExpr: true}), + logicalOR: binop("||", 1), + logicalAND: binop("&&", 2), + bitwiseOR: binop("|", 3), + bitwiseXOR: binop("^", 4), + bitwiseAND: binop("&", 5), + equality: binop("==/!=/===/!==", 6), + relational: binop("/<=/>=", 7), + bitShift: binop("<>/>>>", 8), + plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}), + modulo: binop("%", 10), + star: binop("*", 10), + slash: binop("/", 10), + starstar: new TokenType("**", {beforeExpr: true}), + + // Keyword token types. + _break: kw("break"), + _case: kw("case", beforeExpr), + _catch: kw("catch"), + _continue: kw("continue"), + _debugger: kw("debugger"), + _default: kw("default", beforeExpr), + _do: kw("do", {isLoop: true, beforeExpr: true}), + _else: kw("else", beforeExpr), + _finally: kw("finally"), + _for: kw("for", {isLoop: true}), + _function: kw("function", startsExpr), + _if: kw("if"), + _return: kw("return", beforeExpr), + _switch: kw("switch"), + _throw: kw("throw", beforeExpr), + _try: kw("try"), + _var: kw("var"), + _const: kw("const"), + _while: kw("while", {isLoop: true}), + _with: kw("with"), + _new: kw("new", {beforeExpr: true, startsExpr: true}), + _this: kw("this", startsExpr), + _super: kw("super", startsExpr), + _class: kw("class", startsExpr), + _extends: kw("extends", beforeExpr), + _export: kw("export"), + _import: kw("import"), + _null: kw("null", startsExpr), + _true: kw("true", startsExpr), + _false: kw("false", startsExpr), + _in: kw("in", {beforeExpr: true, binop: 7}), + _instanceof: kw("instanceof", {beforeExpr: true, binop: 7}), + _typeof: kw("typeof", {beforeExpr: true, prefix: true, startsExpr: true}), + _void: kw("void", {beforeExpr: true, prefix: true, startsExpr: true}), + _delete: kw("delete", {beforeExpr: true, prefix: true, startsExpr: true}) +}; + +// Matches a whole line break (where CRLF is considered a single +// line break). Used to count lines. + +var lineBreak = /\r\n?|\n|\u2028|\u2029/; +var lineBreakG = new RegExp(lineBreak.source, "g"); + +function isNewLine(code) { + return code === 10 || code === 13 || code === 0x2028 || code === 0x2029 +} + +var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; + +var skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g; + +var ref = Object.prototype; +var hasOwnProperty = ref.hasOwnProperty; +var toString = ref.toString; + +// Checks if an object has a property. + +function has(obj, propName) { + return hasOwnProperty.call(obj, propName) +} + +var isArray = Array.isArray || (function (obj) { return ( + toString.call(obj) === "[object Array]" +); }); + +// These are used when `options.locations` is on, for the +// `startLoc` and `endLoc` properties. + +var Position = function Position(line, col) { + this.line = line; + this.column = col; +}; + +Position.prototype.offset = function offset (n) { + return new Position(this.line, this.column + n) +}; + +var SourceLocation = function SourceLocation(p, start, end) { + this.start = start; + this.end = end; + if (p.sourceFile !== null) { this.source = p.sourceFile; } +}; + +// The `getLineInfo` function is mostly useful when the +// `locations` option is off (for performance reasons) and you +// want to find the line/column position for a given character +// offset. `input` should be the code string that the offset refers +// into. + +function getLineInfo(input, offset) { + for (var line = 1, cur = 0;;) { + lineBreakG.lastIndex = cur; + var match = lineBreakG.exec(input); + if (match && match.index < offset) { + ++line; + cur = match.index + match[0].length; + } else { + return new Position(line, offset - cur) + } + } +} + +// A second optional argument can be given to further configure +// the parser process. These options are recognized: + +var defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3, 5, 6 (2015), 7 (2016), or 8 (2017). This influences support + // for strict mode, the set of reserved words, and support for + // new syntax features. The default is 7. + ecmaVersion: 7, + // `sourceType` indicates the mode the code should be parsed in. + // Can be either `"script"` or `"module"`. This influences global + // strict mode and parsing of `import` and `export` declarations. + sourceType: "script", + // `onInsertedSemicolon` can be a callback that will be called + // when a semicolon is automatically inserted. It will be passed + // th position of the comma as an offset, and if `locations` is + // enabled, it is given the location as a `{line, column}` object + // as second argument. + onInsertedSemicolon: null, + // `onTrailingComma` is similar to `onInsertedSemicolon`, but for + // trailing commas. + onTrailingComma: null, + // By default, reserved words are only enforced if ecmaVersion >= 5. + // Set `allowReserved` to a boolean value to explicitly turn this on + // an off. When this option has the value "never", reserved words + // and keywords can also not be used as property names. + allowReserved: null, + // When enabled, a return at the top level is not considered an + // error. + allowReturnOutsideFunction: false, + // When enabled, import/export statements are not constrained to + // appearing at the top of the program. + allowImportExportEverywhere: false, + // When enabled, hashbang directive in the beginning of file + // is allowed and treated as a line comment. + allowHashBang: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // A function can be passed as `onToken` option, which will + // cause Acorn to call that function with object in the same + // format as tokens returned from `tokenizer().getToken()`. Note + // that you are not allowed to call the parser from the + // callback—that will corrupt its internal state. + onToken: null, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. Note that you are not allowed to call the + // parser from the callback—that will corrupt its internal state. + onComment: null, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `locations` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null, + // This value, if given, is stored in every node, whether + // `locations` is on or off. + directSourceFile: null, + // When enabled, parenthesized expressions are represented by + // (non-standard) ParenthesizedExpression nodes + preserveParens: false, + plugins: {} +}; + +// Interpret and default an options object + +function getOptions(opts) { + var options = {}; + + for (var opt in defaultOptions) + { options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; } + + if (options.ecmaVersion >= 2015) + { options.ecmaVersion -= 2009; } + + if (options.allowReserved == null) + { options.allowReserved = options.ecmaVersion < 5; } + + if (isArray(options.onToken)) { + var tokens = options.onToken; + options.onToken = function (token) { return tokens.push(token); }; + } + if (isArray(options.onComment)) + { options.onComment = pushComment(options, options.onComment); } + + return options +} + +function pushComment(options, array) { + return function(block, text, start, end, startLoc, endLoc) { + var comment = { + type: block ? "Block" : "Line", + value: text, + start: start, + end: end + }; + if (options.locations) + { comment.loc = new SourceLocation(this, startLoc, endLoc); } + if (options.ranges) + { comment.range = [start, end]; } + array.push(comment); + } +} + +// Registered plugins +var plugins = {}; + +function keywordRegexp(words) { + return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$") +} + +var Parser = function Parser(options, input, startPos) { + this.options = options = getOptions(options); + this.sourceFile = options.sourceFile; + this.keywords = keywordRegexp(keywords[options.ecmaVersion >= 6 ? 6 : 5]); + var reserved = ""; + if (!options.allowReserved) { + for (var v = options.ecmaVersion;; v--) + { if (reserved = reservedWords[v]) { break } } + if (options.sourceType == "module") { reserved += " await"; } + } + this.reservedWords = keywordRegexp(reserved); + var reservedStrict = (reserved ? reserved + " " : "") + reservedWords.strict; + this.reservedWordsStrict = keywordRegexp(reservedStrict); + this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + reservedWords.strictBind); + this.input = String(input); + + // Used to signal to callers of `readWord1` whether the word + // contained any escape sequences. This is needed because words with + // escape sequences must not be interpreted as keywords. + this.containsEsc = false; + + // Load plugins + this.loadPlugins(options.plugins); + + // Set up token state + + // The current position of the tokenizer in the input. + if (startPos) { + this.pos = startPos; + this.lineStart = this.input.lastIndexOf("\n", startPos - 1) + 1; + this.curLine = this.input.slice(0, this.lineStart).split(lineBreak).length; + } else { + this.pos = this.lineStart = 0; + this.curLine = 1; + } + + // Properties of the current token: + // Its type + this.type = types.eof; + // For tokens that include more information than their type, the value + this.value = null; + // Its start and end offset + this.start = this.end = this.pos; + // And, if locations are used, the {line, column} object + // corresponding to those offsets + this.startLoc = this.endLoc = this.curPosition(); + + // Position information for the previous token + this.lastTokEndLoc = this.lastTokStartLoc = null; + this.lastTokStart = this.lastTokEnd = this.pos; + + // The context stack is used to superficially track syntactic + // context to predict whether a regular expression is allowed in a + // given position. + this.context = this.initialContext(); + this.exprAllowed = true; + + // Figure out if it's a module code. + this.inModule = options.sourceType === "module"; + this.strict = this.inModule || this.strictDirective(this.pos); + + // Used to signify the start of a potential arrow function + this.potentialArrowAt = -1; + + // Flags to track whether we are in a function, a generator, an async function. + this.inFunction = this.inGenerator = this.inAsync = false; + // Positions to delayed-check that yield/await does not exist in default parameters. + this.yieldPos = this.awaitPos = 0; + // Labels in scope. + this.labels = []; + + // If enabled, skip leading hashbang line. + if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === "#!") + { this.skipLineComment(2); } + + // Scope tracking for duplicate variable names (see scope.js) + this.scopeStack = []; + this.enterFunctionScope(); +}; + +// DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them +Parser.prototype.isKeyword = function isKeyword (word) { return this.keywords.test(word) }; +Parser.prototype.isReservedWord = function isReservedWord (word) { return this.reservedWords.test(word) }; + +Parser.prototype.extend = function extend (name, f) { + this[name] = f(this[name]); +}; + +Parser.prototype.loadPlugins = function loadPlugins (pluginConfigs) { + var this$1 = this; + + for (var name in pluginConfigs) { + var plugin = plugins[name]; + if (!plugin) { throw new Error("Plugin '" + name + "' not found") } + plugin(this$1, pluginConfigs[name]); + } +}; + +Parser.prototype.parse = function parse () { + var node = this.options.program || this.startNode(); + this.nextToken(); + return this.parseTopLevel(node) +}; + +var pp = Parser.prototype; + +// ## Parser utilities + +var literal = /^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/; +pp.strictDirective = function(start) { + var this$1 = this; + + for (;;) { + skipWhiteSpace.lastIndex = start; + start += skipWhiteSpace.exec(this$1.input)[0].length; + var match = literal.exec(this$1.input.slice(start)); + if (!match) { return false } + if ((match[1] || match[2]) == "use strict") { return true } + start += match[0].length; + } +}; + +// Predicate that tests whether the next token is of the given +// type, and if yes, consumes it as a side effect. + +pp.eat = function(type) { + if (this.type === type) { + this.next(); + return true + } else { + return false + } +}; + +// Tests whether parsed token is a contextual keyword. + +pp.isContextual = function(name) { + return this.type === types.name && this.value === name && !this.containsEsc +}; + +// Consumes contextual keyword if possible. + +pp.eatContextual = function(name) { + if (!this.isContextual(name)) { return false } + this.next(); + return true +}; + +// Asserts that following token is given contextual keyword. + +pp.expectContextual = function(name) { + if (!this.eatContextual(name)) { this.unexpected(); } +}; + +// Test whether a semicolon can be inserted at the current position. + +pp.canInsertSemicolon = function() { + return this.type === types.eof || + this.type === types.braceR || + lineBreak.test(this.input.slice(this.lastTokEnd, this.start)) +}; + +pp.insertSemicolon = function() { + if (this.canInsertSemicolon()) { + if (this.options.onInsertedSemicolon) + { this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc); } + return true + } +}; + +// Consume a semicolon, or, failing that, see if we are allowed to +// pretend that there is a semicolon at this position. + +pp.semicolon = function() { + if (!this.eat(types.semi) && !this.insertSemicolon()) { this.unexpected(); } +}; + +pp.afterTrailingComma = function(tokType, notNext) { + if (this.type == tokType) { + if (this.options.onTrailingComma) + { this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc); } + if (!notNext) + { this.next(); } + return true + } +}; + +// Expect a token of a given type. If found, consume it, otherwise, +// raise an unexpected token error. + +pp.expect = function(type) { + this.eat(type) || this.unexpected(); +}; + +// Raise an unexpected token error. + +pp.unexpected = function(pos) { + this.raise(pos != null ? pos : this.start, "Unexpected token"); +}; + +function DestructuringErrors() { + this.shorthandAssign = + this.trailingComma = + this.parenthesizedAssign = + this.parenthesizedBind = + this.doubleProto = + -1; +} + +pp.checkPatternErrors = function(refDestructuringErrors, isAssign) { + if (!refDestructuringErrors) { return } + if (refDestructuringErrors.trailingComma > -1) + { this.raiseRecoverable(refDestructuringErrors.trailingComma, "Comma is not permitted after the rest element"); } + var parens = isAssign ? refDestructuringErrors.parenthesizedAssign : refDestructuringErrors.parenthesizedBind; + if (parens > -1) { this.raiseRecoverable(parens, "Parenthesized pattern"); } +}; + +pp.checkExpressionErrors = function(refDestructuringErrors, andThrow) { + if (!refDestructuringErrors) { return false } + var shorthandAssign = refDestructuringErrors.shorthandAssign; + var doubleProto = refDestructuringErrors.doubleProto; + if (!andThrow) { return shorthandAssign >= 0 || doubleProto >= 0 } + if (shorthandAssign >= 0) + { this.raise(shorthandAssign, "Shorthand property assignments are valid only in destructuring patterns"); } + if (doubleProto >= 0) + { this.raiseRecoverable(doubleProto, "Redefinition of __proto__ property"); } +}; + +pp.checkYieldAwaitInDefaultParams = function() { + if (this.yieldPos && (!this.awaitPos || this.yieldPos < this.awaitPos)) + { this.raise(this.yieldPos, "Yield expression cannot be a default value"); } + if (this.awaitPos) + { this.raise(this.awaitPos, "Await expression cannot be a default value"); } +}; + +pp.isSimpleAssignTarget = function(expr) { + if (expr.type === "ParenthesizedExpression") + { return this.isSimpleAssignTarget(expr.expression) } + return expr.type === "Identifier" || expr.type === "MemberExpression" +}; + +var pp$1 = Parser.prototype; + +// ### Statement parsing + +// Parse a program. Initializes the parser, reads any number of +// statements, and wraps them in a Program node. Optionally takes a +// `program` argument. If present, the statements will be appended +// to its body instead of creating a new node. + +pp$1.parseTopLevel = function(node) { + var this$1 = this; + + var exports = {}; + if (!node.body) { node.body = []; } + while (this.type !== types.eof) { + var stmt = this$1.parseStatement(true, true, exports); + node.body.push(stmt); + } + this.adaptDirectivePrologue(node.body); + this.next(); + if (this.options.ecmaVersion >= 6) { + node.sourceType = this.options.sourceType; + } + return this.finishNode(node, "Program") +}; + +var loopLabel = {kind: "loop"}; +var switchLabel = {kind: "switch"}; + +pp$1.isLet = function() { + if (this.options.ecmaVersion < 6 || !this.isContextual("let")) { return false } + skipWhiteSpace.lastIndex = this.pos; + var skip = skipWhiteSpace.exec(this.input); + var next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next); + if (nextCh === 91 || nextCh == 123) { return true } // '{' and '[' + if (isIdentifierStart(nextCh, true)) { + var pos = next + 1; + while (isIdentifierChar(this.input.charCodeAt(pos), true)) { ++pos; } + var ident = this.input.slice(next, pos); + if (!keywordRelationalOperator.test(ident)) { return true } + } + return false +}; + +// check 'async [no LineTerminator here] function' +// - 'async /*foo*/ function' is OK. +// - 'async /*\n*/ function' is invalid. +pp$1.isAsyncFunction = function() { + if (this.options.ecmaVersion < 8 || !this.isContextual("async")) + { return false } + + skipWhiteSpace.lastIndex = this.pos; + var skip = skipWhiteSpace.exec(this.input); + var next = this.pos + skip[0].length; + return !lineBreak.test(this.input.slice(this.pos, next)) && + this.input.slice(next, next + 8) === "function" && + (next + 8 == this.input.length || !isIdentifierChar(this.input.charAt(next + 8))) +}; + +// Parse a single statement. +// +// If expecting a statement and finding a slash operator, parse a +// regular expression literal. This is to handle cases like +// `if (foo) /blah/.exec(foo)`, where looking at the previous token +// does not help. + +pp$1.parseStatement = function(declaration, topLevel, exports) { + var starttype = this.type, node = this.startNode(), kind; + + if (this.isLet()) { + starttype = types._var; + kind = "let"; + } + + // Most types of statements are recognized by the keyword they + // start with. Many are trivial to parse, some require a bit of + // complexity. + + switch (starttype) { + case types._break: case types._continue: return this.parseBreakContinueStatement(node, starttype.keyword) + case types._debugger: return this.parseDebuggerStatement(node) + case types._do: return this.parseDoStatement(node) + case types._for: return this.parseForStatement(node) + case types._function: + if (!declaration && this.options.ecmaVersion >= 6) { this.unexpected(); } + return this.parseFunctionStatement(node, false) + case types._class: + if (!declaration) { this.unexpected(); } + return this.parseClass(node, true) + case types._if: return this.parseIfStatement(node) + case types._return: return this.parseReturnStatement(node) + case types._switch: return this.parseSwitchStatement(node) + case types._throw: return this.parseThrowStatement(node) + case types._try: return this.parseTryStatement(node) + case types._const: case types._var: + kind = kind || this.value; + if (!declaration && kind != "var") { this.unexpected(); } + return this.parseVarStatement(node, kind) + case types._while: return this.parseWhileStatement(node) + case types._with: return this.parseWithStatement(node) + case types.braceL: return this.parseBlock() + case types.semi: return this.parseEmptyStatement(node) + case types._export: + case types._import: + if (!this.options.allowImportExportEverywhere) { + if (!topLevel) + { this.raise(this.start, "'import' and 'export' may only appear at the top level"); } + if (!this.inModule) + { this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'"); } + } + return starttype === types._import ? this.parseImport(node) : this.parseExport(node, exports) + + // If the statement does not start with a statement keyword or a + // brace, it's an ExpressionStatement or LabeledStatement. We + // simply start parsing an expression, and afterwards, if the + // next token is a colon and the expression was a simple + // Identifier node, we switch to interpreting it as a label. + default: + if (this.isAsyncFunction()) { + if (!declaration) { this.unexpected(); } + this.next(); + return this.parseFunctionStatement(node, true) + } + + var maybeName = this.value, expr = this.parseExpression(); + if (starttype === types.name && expr.type === "Identifier" && this.eat(types.colon)) + { return this.parseLabeledStatement(node, maybeName, expr) } + else { return this.parseExpressionStatement(node, expr) } + } +}; + +pp$1.parseBreakContinueStatement = function(node, keyword) { + var this$1 = this; + + var isBreak = keyword == "break"; + this.next(); + if (this.eat(types.semi) || this.insertSemicolon()) { node.label = null; } + else if (this.type !== types.name) { this.unexpected(); } + else { + node.label = this.parseIdent(); + this.semicolon(); + } + + // Verify that there is an actual destination to break or + // continue to. + var i = 0; + for (; i < this.labels.length; ++i) { + var lab = this$1.labels[i]; + if (node.label == null || lab.name === node.label.name) { + if (lab.kind != null && (isBreak || lab.kind === "loop")) { break } + if (node.label && isBreak) { break } + } + } + if (i === this.labels.length) { this.raise(node.start, "Unsyntactic " + keyword); } + return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement") +}; + +pp$1.parseDebuggerStatement = function(node) { + this.next(); + this.semicolon(); + return this.finishNode(node, "DebuggerStatement") +}; + +pp$1.parseDoStatement = function(node) { + this.next(); + this.labels.push(loopLabel); + node.body = this.parseStatement(false); + this.labels.pop(); + this.expect(types._while); + node.test = this.parseParenExpression(); + if (this.options.ecmaVersion >= 6) + { this.eat(types.semi); } + else + { this.semicolon(); } + return this.finishNode(node, "DoWhileStatement") +}; + +// Disambiguating between a `for` and a `for`/`in` or `for`/`of` +// loop is non-trivial. Basically, we have to parse the init `var` +// statement or expression, disallowing the `in` operator (see +// the second parameter to `parseExpression`), and then check +// whether the next token is `in` or `of`. When there is no init +// part (semicolon immediately after the opening parenthesis), it +// is a regular `for` loop. + +pp$1.parseForStatement = function(node) { + this.next(); + var awaitAt = (this.options.ecmaVersion >= 9 && this.inAsync && this.eatContextual("await")) ? this.lastTokStart : -1; + this.labels.push(loopLabel); + this.enterLexicalScope(); + this.expect(types.parenL); + if (this.type === types.semi) { + if (awaitAt > -1) { this.unexpected(awaitAt); } + return this.parseFor(node, null) + } + var isLet = this.isLet(); + if (this.type === types._var || this.type === types._const || isLet) { + var init$1 = this.startNode(), kind = isLet ? "let" : this.value; + this.next(); + this.parseVar(init$1, true, kind); + this.finishNode(init$1, "VariableDeclaration"); + if ((this.type === types._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init$1.declarations.length === 1 && + !(kind !== "var" && init$1.declarations[0].init)) { + if (this.options.ecmaVersion >= 9) { + if (this.type === types._in) { + if (awaitAt > -1) { this.unexpected(awaitAt); } + } else { node.await = awaitAt > -1; } + } + return this.parseForIn(node, init$1) + } + if (awaitAt > -1) { this.unexpected(awaitAt); } + return this.parseFor(node, init$1) + } + var refDestructuringErrors = new DestructuringErrors; + var init = this.parseExpression(true, refDestructuringErrors); + if (this.type === types._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) { + if (this.options.ecmaVersion >= 9) { + if (this.type === types._in) { + if (awaitAt > -1) { this.unexpected(awaitAt); } + } else { node.await = awaitAt > -1; } + } + this.toAssignable(init, false, refDestructuringErrors); + this.checkLVal(init); + return this.parseForIn(node, init) + } else { + this.checkExpressionErrors(refDestructuringErrors, true); + } + if (awaitAt > -1) { this.unexpected(awaitAt); } + return this.parseFor(node, init) +}; + +pp$1.parseFunctionStatement = function(node, isAsync) { + this.next(); + return this.parseFunction(node, true, false, isAsync) +}; + +pp$1.parseIfStatement = function(node) { + this.next(); + node.test = this.parseParenExpression(); + // allow function declarations in branches, but only in non-strict mode + node.consequent = this.parseStatement(!this.strict && this.type == types._function); + node.alternate = this.eat(types._else) ? this.parseStatement(!this.strict && this.type == types._function) : null; + return this.finishNode(node, "IfStatement") +}; + +pp$1.parseReturnStatement = function(node) { + if (!this.inFunction && !this.options.allowReturnOutsideFunction) + { this.raise(this.start, "'return' outside of function"); } + this.next(); + + // In `return` (and `break`/`continue`), the keywords with + // optional arguments, we eagerly look for a semicolon or the + // possibility to insert one. + + if (this.eat(types.semi) || this.insertSemicolon()) { node.argument = null; } + else { node.argument = this.parseExpression(); this.semicolon(); } + return this.finishNode(node, "ReturnStatement") +}; + +pp$1.parseSwitchStatement = function(node) { + var this$1 = this; + + this.next(); + node.discriminant = this.parseParenExpression(); + node.cases = []; + this.expect(types.braceL); + this.labels.push(switchLabel); + this.enterLexicalScope(); + + // Statements under must be grouped (by label) in SwitchCase + // nodes. `cur` is used to keep the node that we are currently + // adding statements to. + + var cur; + for (var sawDefault = false; this.type != types.braceR;) { + if (this$1.type === types._case || this$1.type === types._default) { + var isCase = this$1.type === types._case; + if (cur) { this$1.finishNode(cur, "SwitchCase"); } + node.cases.push(cur = this$1.startNode()); + cur.consequent = []; + this$1.next(); + if (isCase) { + cur.test = this$1.parseExpression(); + } else { + if (sawDefault) { this$1.raiseRecoverable(this$1.lastTokStart, "Multiple default clauses"); } + sawDefault = true; + cur.test = null; + } + this$1.expect(types.colon); + } else { + if (!cur) { this$1.unexpected(); } + cur.consequent.push(this$1.parseStatement(true)); + } + } + this.exitLexicalScope(); + if (cur) { this.finishNode(cur, "SwitchCase"); } + this.next(); // Closing brace + this.labels.pop(); + return this.finishNode(node, "SwitchStatement") +}; + +pp$1.parseThrowStatement = function(node) { + this.next(); + if (lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) + { this.raise(this.lastTokEnd, "Illegal newline after throw"); } + node.argument = this.parseExpression(); + this.semicolon(); + return this.finishNode(node, "ThrowStatement") +}; + +// Reused empty array added for node fields that are always empty. + +var empty = []; + +pp$1.parseTryStatement = function(node) { + this.next(); + node.block = this.parseBlock(); + node.handler = null; + if (this.type === types._catch) { + var clause = this.startNode(); + this.next(); + this.expect(types.parenL); + clause.param = this.parseBindingAtom(); + this.enterLexicalScope(); + this.checkLVal(clause.param, "let"); + this.expect(types.parenR); + clause.body = this.parseBlock(false); + this.exitLexicalScope(); + node.handler = this.finishNode(clause, "CatchClause"); + } + node.finalizer = this.eat(types._finally) ? this.parseBlock() : null; + if (!node.handler && !node.finalizer) + { this.raise(node.start, "Missing catch or finally clause"); } + return this.finishNode(node, "TryStatement") +}; + +pp$1.parseVarStatement = function(node, kind) { + this.next(); + this.parseVar(node, false, kind); + this.semicolon(); + return this.finishNode(node, "VariableDeclaration") +}; + +pp$1.parseWhileStatement = function(node) { + this.next(); + node.test = this.parseParenExpression(); + this.labels.push(loopLabel); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, "WhileStatement") +}; + +pp$1.parseWithStatement = function(node) { + if (this.strict) { this.raise(this.start, "'with' in strict mode"); } + this.next(); + node.object = this.parseParenExpression(); + node.body = this.parseStatement(false); + return this.finishNode(node, "WithStatement") +}; + +pp$1.parseEmptyStatement = function(node) { + this.next(); + return this.finishNode(node, "EmptyStatement") +}; + +pp$1.parseLabeledStatement = function(node, maybeName, expr) { + var this$1 = this; + + for (var i$1 = 0, list = this$1.labels; i$1 < list.length; i$1 += 1) + { + var label = list[i$1]; + + if (label.name === maybeName) + { this$1.raise(expr.start, "Label '" + maybeName + "' is already declared"); + } } + var kind = this.type.isLoop ? "loop" : this.type === types._switch ? "switch" : null; + for (var i = this.labels.length - 1; i >= 0; i--) { + var label$1 = this$1.labels[i]; + if (label$1.statementStart == node.start) { + // Update information about previous labels on this node + label$1.statementStart = this$1.start; + label$1.kind = kind; + } else { break } + } + this.labels.push({name: maybeName, kind: kind, statementStart: this.start}); + node.body = this.parseStatement(true); + if (node.body.type == "ClassDeclaration" || + node.body.type == "VariableDeclaration" && node.body.kind != "var" || + node.body.type == "FunctionDeclaration" && (this.strict || node.body.generator)) + { this.raiseRecoverable(node.body.start, "Invalid labeled declaration"); } + this.labels.pop(); + node.label = expr; + return this.finishNode(node, "LabeledStatement") +}; + +pp$1.parseExpressionStatement = function(node, expr) { + node.expression = expr; + this.semicolon(); + return this.finishNode(node, "ExpressionStatement") +}; + +// Parse a semicolon-enclosed block of statements, handling `"use +// strict"` declarations when `allowStrict` is true (used for +// function bodies). + +pp$1.parseBlock = function(createNewLexicalScope) { + var this$1 = this; + if ( createNewLexicalScope === void 0 ) createNewLexicalScope = true; + + var node = this.startNode(); + node.body = []; + this.expect(types.braceL); + if (createNewLexicalScope) { + this.enterLexicalScope(); + } + while (!this.eat(types.braceR)) { + var stmt = this$1.parseStatement(true); + node.body.push(stmt); + } + if (createNewLexicalScope) { + this.exitLexicalScope(); + } + return this.finishNode(node, "BlockStatement") +}; + +// Parse a regular `for` loop. The disambiguation code in +// `parseStatement` will already have parsed the init statement or +// expression. + +pp$1.parseFor = function(node, init) { + node.init = init; + this.expect(types.semi); + node.test = this.type === types.semi ? null : this.parseExpression(); + this.expect(types.semi); + node.update = this.type === types.parenR ? null : this.parseExpression(); + this.expect(types.parenR); + this.exitLexicalScope(); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, "ForStatement") +}; + +// Parse a `for`/`in` and `for`/`of` loop, which are almost +// same from parser's perspective. + +pp$1.parseForIn = function(node, init) { + var type = this.type === types._in ? "ForInStatement" : "ForOfStatement"; + this.next(); + if (type == "ForInStatement") { + if (init.type === "AssignmentPattern" || + (init.type === "VariableDeclaration" && init.declarations[0].init != null && + (this.strict || init.declarations[0].id.type !== "Identifier"))) + { this.raise(init.start, "Invalid assignment in for-in loop head"); } + } + node.left = init; + node.right = type == "ForInStatement" ? this.parseExpression() : this.parseMaybeAssign(); + this.expect(types.parenR); + this.exitLexicalScope(); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, type) +}; + +// Parse a list of variable declarations. + +pp$1.parseVar = function(node, isFor, kind) { + var this$1 = this; + + node.declarations = []; + node.kind = kind; + for (;;) { + var decl = this$1.startNode(); + this$1.parseVarId(decl, kind); + if (this$1.eat(types.eq)) { + decl.init = this$1.parseMaybeAssign(isFor); + } else if (kind === "const" && !(this$1.type === types._in || (this$1.options.ecmaVersion >= 6 && this$1.isContextual("of")))) { + this$1.unexpected(); + } else if (decl.id.type != "Identifier" && !(isFor && (this$1.type === types._in || this$1.isContextual("of")))) { + this$1.raise(this$1.lastTokEnd, "Complex binding patterns require an initialization value"); + } else { + decl.init = null; + } + node.declarations.push(this$1.finishNode(decl, "VariableDeclarator")); + if (!this$1.eat(types.comma)) { break } + } + return node +}; + +pp$1.parseVarId = function(decl, kind) { + decl.id = this.parseBindingAtom(kind); + this.checkLVal(decl.id, kind, false); +}; + +// Parse a function declaration or literal (depending on the +// `isStatement` parameter). + +pp$1.parseFunction = function(node, isStatement, allowExpressionBody, isAsync) { + this.initFunction(node); + if (this.options.ecmaVersion >= 9 || this.options.ecmaVersion >= 6 && !isAsync) + { node.generator = this.eat(types.star); } + if (this.options.ecmaVersion >= 8) + { node.async = !!isAsync; } + + if (isStatement) { + node.id = isStatement === "nullableID" && this.type != types.name ? null : this.parseIdent(); + if (node.id) { + this.checkLVal(node.id, "var"); + } + } + + var oldInGen = this.inGenerator, oldInAsync = this.inAsync, + oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction; + this.inGenerator = node.generator; + this.inAsync = node.async; + this.yieldPos = 0; + this.awaitPos = 0; + this.inFunction = true; + this.enterFunctionScope(); + + if (!isStatement) + { node.id = this.type == types.name ? this.parseIdent() : null; } + + this.parseFunctionParams(node); + this.parseFunctionBody(node, allowExpressionBody); + + this.inGenerator = oldInGen; + this.inAsync = oldInAsync; + this.yieldPos = oldYieldPos; + this.awaitPos = oldAwaitPos; + this.inFunction = oldInFunc; + return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression") +}; + +pp$1.parseFunctionParams = function(node) { + this.expect(types.parenL); + node.params = this.parseBindingList(types.parenR, false, this.options.ecmaVersion >= 8); + this.checkYieldAwaitInDefaultParams(); +}; + +// Parse a class declaration or literal (depending on the +// `isStatement` parameter). + +pp$1.parseClass = function(node, isStatement) { + var this$1 = this; + + this.next(); + + this.parseClassId(node, isStatement); + this.parseClassSuper(node); + var classBody = this.startNode(); + var hadConstructor = false; + classBody.body = []; + this.expect(types.braceL); + while (!this.eat(types.braceR)) { + var member = this$1.parseClassMember(classBody); + if (member && member.type === "MethodDefinition" && member.kind === "constructor") { + if (hadConstructor) { this$1.raise(member.start, "Duplicate constructor in the same class"); } + hadConstructor = true; + } + } + node.body = this.finishNode(classBody, "ClassBody"); + return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression") +}; + +pp$1.parseClassMember = function(classBody) { + var this$1 = this; + + if (this.eat(types.semi)) { return null } + + var method = this.startNode(); + var tryContextual = function (k, noLineBreak) { + if ( noLineBreak === void 0 ) noLineBreak = false; + + var start = this$1.start, startLoc = this$1.startLoc; + if (!this$1.eatContextual(k)) { return false } + if (this$1.type !== types.parenL && (!noLineBreak || !this$1.canInsertSemicolon())) { return true } + if (method.key) { this$1.unexpected(); } + method.computed = false; + method.key = this$1.startNodeAt(start, startLoc); + method.key.name = k; + this$1.finishNode(method.key, "Identifier"); + return false + }; + + method.kind = "method"; + method.static = tryContextual("static"); + var isGenerator = this.eat(types.star); + var isAsync = false; + if (!isGenerator) { + if (this.options.ecmaVersion >= 8 && tryContextual("async", true)) { + isAsync = true; + isGenerator = this.options.ecmaVersion >= 9 && this.eat(types.star); + } else if (tryContextual("get")) { + method.kind = "get"; + } else if (tryContextual("set")) { + method.kind = "set"; + } + } + if (!method.key) { this.parsePropertyName(method); } + var key = method.key; + if (!method.computed && !method.static && (key.type === "Identifier" && key.name === "constructor" || + key.type === "Literal" && key.value === "constructor")) { + if (method.kind !== "method") { this.raise(key.start, "Constructor can't have get/set modifier"); } + if (isGenerator) { this.raise(key.start, "Constructor can't be a generator"); } + if (isAsync) { this.raise(key.start, "Constructor can't be an async method"); } + method.kind = "constructor"; + } else if (method.static && key.type === "Identifier" && key.name === "prototype") { + this.raise(key.start, "Classes may not have a static property named prototype"); + } + this.parseClassMethod(classBody, method, isGenerator, isAsync); + if (method.kind === "get" && method.value.params.length !== 0) + { this.raiseRecoverable(method.value.start, "getter should have no params"); } + if (method.kind === "set" && method.value.params.length !== 1) + { this.raiseRecoverable(method.value.start, "setter should have exactly one param"); } + if (method.kind === "set" && method.value.params[0].type === "RestElement") + { this.raiseRecoverable(method.value.params[0].start, "Setter cannot use rest params"); } + return method +}; + +pp$1.parseClassMethod = function(classBody, method, isGenerator, isAsync) { + method.value = this.parseMethod(isGenerator, isAsync); + classBody.body.push(this.finishNode(method, "MethodDefinition")); +}; + +pp$1.parseClassId = function(node, isStatement) { + node.id = this.type === types.name ? this.parseIdent() : isStatement === true ? this.unexpected() : null; +}; + +pp$1.parseClassSuper = function(node) { + node.superClass = this.eat(types._extends) ? this.parseExprSubscripts() : null; +}; + +// Parses module export declaration. + +pp$1.parseExport = function(node, exports) { + var this$1 = this; + + this.next(); + // export * from '...' + if (this.eat(types.star)) { + this.expectContextual("from"); + if (this.type !== types.string) { this.unexpected(); } + node.source = this.parseExprAtom(); + this.semicolon(); + return this.finishNode(node, "ExportAllDeclaration") + } + if (this.eat(types._default)) { // export default ... + this.checkExport(exports, "default", this.lastTokStart); + var isAsync; + if (this.type === types._function || (isAsync = this.isAsyncFunction())) { + var fNode = this.startNode(); + this.next(); + if (isAsync) { this.next(); } + node.declaration = this.parseFunction(fNode, "nullableID", false, isAsync); + } else if (this.type === types._class) { + var cNode = this.startNode(); + node.declaration = this.parseClass(cNode, "nullableID"); + } else { + node.declaration = this.parseMaybeAssign(); + this.semicolon(); + } + return this.finishNode(node, "ExportDefaultDeclaration") + } + // export var|const|let|function|class ... + if (this.shouldParseExportStatement()) { + node.declaration = this.parseStatement(true); + if (node.declaration.type === "VariableDeclaration") + { this.checkVariableExport(exports, node.declaration.declarations); } + else + { this.checkExport(exports, node.declaration.id.name, node.declaration.id.start); } + node.specifiers = []; + node.source = null; + } else { // export { x, y as z } [from '...'] + node.declaration = null; + node.specifiers = this.parseExportSpecifiers(exports); + if (this.eatContextual("from")) { + if (this.type !== types.string) { this.unexpected(); } + node.source = this.parseExprAtom(); + } else { + // check for keywords used as local names + for (var i = 0, list = node.specifiers; i < list.length; i += 1) { + var spec = list[i]; + + this$1.checkUnreserved(spec.local); + } + + node.source = null; + } + this.semicolon(); + } + return this.finishNode(node, "ExportNamedDeclaration") +}; + +pp$1.checkExport = function(exports, name, pos) { + if (!exports) { return } + if (has(exports, name)) + { this.raiseRecoverable(pos, "Duplicate export '" + name + "'"); } + exports[name] = true; +}; + +pp$1.checkPatternExport = function(exports, pat) { + var this$1 = this; + + var type = pat.type; + if (type == "Identifier") + { this.checkExport(exports, pat.name, pat.start); } + else if (type == "ObjectPattern") + { for (var i = 0, list = pat.properties; i < list.length; i += 1) + { + var prop = list[i]; + + this$1.checkPatternExport(exports, prop); + } } + else if (type == "ArrayPattern") + { for (var i$1 = 0, list$1 = pat.elements; i$1 < list$1.length; i$1 += 1) { + var elt = list$1[i$1]; + + if (elt) { this$1.checkPatternExport(exports, elt); } + } } + else if (type == "Property") + { this.checkPatternExport(exports, pat.value); } + else if (type == "AssignmentPattern") + { this.checkPatternExport(exports, pat.left); } + else if (type == "RestElement") + { this.checkPatternExport(exports, pat.argument); } + else if (type == "ParenthesizedExpression") + { this.checkPatternExport(exports, pat.expression); } +}; + +pp$1.checkVariableExport = function(exports, decls) { + var this$1 = this; + + if (!exports) { return } + for (var i = 0, list = decls; i < list.length; i += 1) + { + var decl = list[i]; + + this$1.checkPatternExport(exports, decl.id); + } +}; + +pp$1.shouldParseExportStatement = function() { + return this.type.keyword === "var" || + this.type.keyword === "const" || + this.type.keyword === "class" || + this.type.keyword === "function" || + this.isLet() || + this.isAsyncFunction() +}; + +// Parses a comma-separated list of module exports. + +pp$1.parseExportSpecifiers = function(exports) { + var this$1 = this; + + var nodes = [], first = true; + // export { x, y as z } [from '...'] + this.expect(types.braceL); + while (!this.eat(types.braceR)) { + if (!first) { + this$1.expect(types.comma); + if (this$1.afterTrailingComma(types.braceR)) { break } + } else { first = false; } + + var node = this$1.startNode(); + node.local = this$1.parseIdent(true); + node.exported = this$1.eatContextual("as") ? this$1.parseIdent(true) : node.local; + this$1.checkExport(exports, node.exported.name, node.exported.start); + nodes.push(this$1.finishNode(node, "ExportSpecifier")); + } + return nodes +}; + +// Parses import declaration. + +pp$1.parseImport = function(node) { + this.next(); + // import '...' + if (this.type === types.string) { + node.specifiers = empty; + node.source = this.parseExprAtom(); + } else { + node.specifiers = this.parseImportSpecifiers(); + this.expectContextual("from"); + node.source = this.type === types.string ? this.parseExprAtom() : this.unexpected(); + } + this.semicolon(); + return this.finishNode(node, "ImportDeclaration") +}; + +// Parses a comma-separated list of module imports. + +pp$1.parseImportSpecifiers = function() { + var this$1 = this; + + var nodes = [], first = true; + if (this.type === types.name) { + // import defaultObj, { x, y as z } from '...' + var node = this.startNode(); + node.local = this.parseIdent(); + this.checkLVal(node.local, "let"); + nodes.push(this.finishNode(node, "ImportDefaultSpecifier")); + if (!this.eat(types.comma)) { return nodes } + } + if (this.type === types.star) { + var node$1 = this.startNode(); + this.next(); + this.expectContextual("as"); + node$1.local = this.parseIdent(); + this.checkLVal(node$1.local, "let"); + nodes.push(this.finishNode(node$1, "ImportNamespaceSpecifier")); + return nodes + } + this.expect(types.braceL); + while (!this.eat(types.braceR)) { + if (!first) { + this$1.expect(types.comma); + if (this$1.afterTrailingComma(types.braceR)) { break } + } else { first = false; } + + var node$2 = this$1.startNode(); + node$2.imported = this$1.parseIdent(true); + if (this$1.eatContextual("as")) { + node$2.local = this$1.parseIdent(); + } else { + this$1.checkUnreserved(node$2.imported); + node$2.local = node$2.imported; + } + this$1.checkLVal(node$2.local, "let"); + nodes.push(this$1.finishNode(node$2, "ImportSpecifier")); + } + return nodes +}; + +// Set `ExpressionStatement#directive` property for directive prologues. +pp$1.adaptDirectivePrologue = function(statements) { + for (var i = 0; i < statements.length && this.isDirectiveCandidate(statements[i]); ++i) { + statements[i].directive = statements[i].expression.raw.slice(1, -1); + } +}; +pp$1.isDirectiveCandidate = function(statement) { + return ( + statement.type === "ExpressionStatement" && + statement.expression.type === "Literal" && + typeof statement.expression.value === "string" && + // Reject parenthesized strings. + (this.input[statement.start] === "\"" || this.input[statement.start] === "'") + ) +}; + +var pp$2 = Parser.prototype; + +// Convert existing expression atom to assignable pattern +// if possible. + +pp$2.toAssignable = function(node, isBinding, refDestructuringErrors) { + var this$1 = this; + + if (this.options.ecmaVersion >= 6 && node) { + switch (node.type) { + case "Identifier": + if (this.inAsync && node.name === "await") + { this.raise(node.start, "Can not use 'await' as identifier inside an async function"); } + break + + case "ObjectPattern": + case "ArrayPattern": + case "RestElement": + break + + case "ObjectExpression": + node.type = "ObjectPattern"; + if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); } + for (var i = 0, list = node.properties; i < list.length; i += 1) { + var prop = list[i]; + + this$1.toAssignable(prop, isBinding); + // Early error: + // AssignmentRestProperty[Yield, Await] : + // `...` DestructuringAssignmentTarget[Yield, Await] + // + // It is a Syntax Error if |DestructuringAssignmentTarget| is an |ArrayLiteral| or an |ObjectLiteral|. + if ( + prop.type === "RestElement" && + (prop.argument.type === "ArrayPattern" || prop.argument.type === "ObjectPattern") + ) { + this$1.raise(prop.argument.start, "Unexpected token"); + } + } + break + + case "Property": + // AssignmentProperty has type == "Property" + if (node.kind !== "init") { this.raise(node.key.start, "Object pattern can't contain getter or setter"); } + this.toAssignable(node.value, isBinding); + break + + case "ArrayExpression": + node.type = "ArrayPattern"; + if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); } + this.toAssignableList(node.elements, isBinding); + break + + case "SpreadElement": + node.type = "RestElement"; + this.toAssignable(node.argument, isBinding); + if (node.argument.type === "AssignmentPattern") + { this.raise(node.argument.start, "Rest elements cannot have a default value"); } + break + + case "AssignmentExpression": + if (node.operator !== "=") { this.raise(node.left.end, "Only '=' operator can be used for specifying default value."); } + node.type = "AssignmentPattern"; + delete node.operator; + this.toAssignable(node.left, isBinding); + // falls through to AssignmentPattern + + case "AssignmentPattern": + break + + case "ParenthesizedExpression": + this.toAssignable(node.expression, isBinding); + break + + case "MemberExpression": + if (!isBinding) { break } + + default: + this.raise(node.start, "Assigning to rvalue"); + } + } else if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); } + return node +}; + +// Convert list of expression atoms to binding list. + +pp$2.toAssignableList = function(exprList, isBinding) { + var this$1 = this; + + var end = exprList.length; + for (var i = 0; i < end; i++) { + var elt = exprList[i]; + if (elt) { this$1.toAssignable(elt, isBinding); } + } + if (end) { + var last = exprList[end - 1]; + if (this.options.ecmaVersion === 6 && isBinding && last && last.type === "RestElement" && last.argument.type !== "Identifier") + { this.unexpected(last.argument.start); } + } + return exprList +}; + +// Parses spread element. + +pp$2.parseSpread = function(refDestructuringErrors) { + var node = this.startNode(); + this.next(); + node.argument = this.parseMaybeAssign(false, refDestructuringErrors); + return this.finishNode(node, "SpreadElement") +}; + +pp$2.parseRestBinding = function() { + var node = this.startNode(); + this.next(); + + // RestElement inside of a function parameter must be an identifier + if (this.options.ecmaVersion === 6 && this.type !== types.name) + { this.unexpected(); } + + node.argument = this.parseBindingAtom(); + + return this.finishNode(node, "RestElement") +}; + +// Parses lvalue (assignable) atom. + +pp$2.parseBindingAtom = function() { + if (this.options.ecmaVersion >= 6) { + switch (this.type) { + case types.bracketL: + var node = this.startNode(); + this.next(); + node.elements = this.parseBindingList(types.bracketR, true, true); + return this.finishNode(node, "ArrayPattern") + + case types.braceL: + return this.parseObj(true) + } + } + return this.parseIdent() +}; + +pp$2.parseBindingList = function(close, allowEmpty, allowTrailingComma) { + var this$1 = this; + + var elts = [], first = true; + while (!this.eat(close)) { + if (first) { first = false; } + else { this$1.expect(types.comma); } + if (allowEmpty && this$1.type === types.comma) { + elts.push(null); + } else if (allowTrailingComma && this$1.afterTrailingComma(close)) { + break + } else if (this$1.type === types.ellipsis) { + var rest = this$1.parseRestBinding(); + this$1.parseBindingListItem(rest); + elts.push(rest); + if (this$1.type === types.comma) { this$1.raise(this$1.start, "Comma is not permitted after the rest element"); } + this$1.expect(close); + break + } else { + var elem = this$1.parseMaybeDefault(this$1.start, this$1.startLoc); + this$1.parseBindingListItem(elem); + elts.push(elem); + } + } + return elts +}; + +pp$2.parseBindingListItem = function(param) { + return param +}; + +// Parses assignment pattern around given atom if possible. + +pp$2.parseMaybeDefault = function(startPos, startLoc, left) { + left = left || this.parseBindingAtom(); + if (this.options.ecmaVersion < 6 || !this.eat(types.eq)) { return left } + var node = this.startNodeAt(startPos, startLoc); + node.left = left; + node.right = this.parseMaybeAssign(); + return this.finishNode(node, "AssignmentPattern") +}; + +// Verify that a node is an lval — something that can be assigned +// to. +// bindingType can be either: +// 'var' indicating that the lval creates a 'var' binding +// 'let' indicating that the lval creates a lexical ('let' or 'const') binding +// 'none' indicating that the binding should be checked for illegal identifiers, but not for duplicate references + +pp$2.checkLVal = function(expr, bindingType, checkClashes) { + var this$1 = this; + + switch (expr.type) { + case "Identifier": + if (this.strict && this.reservedWordsStrictBind.test(expr.name)) + { this.raiseRecoverable(expr.start, (bindingType ? "Binding " : "Assigning to ") + expr.name + " in strict mode"); } + if (checkClashes) { + if (has(checkClashes, expr.name)) + { this.raiseRecoverable(expr.start, "Argument name clash"); } + checkClashes[expr.name] = true; + } + if (bindingType && bindingType !== "none") { + if ( + bindingType === "var" && !this.canDeclareVarName(expr.name) || + bindingType !== "var" && !this.canDeclareLexicalName(expr.name) + ) { + this.raiseRecoverable(expr.start, ("Identifier '" + (expr.name) + "' has already been declared")); + } + if (bindingType === "var") { + this.declareVarName(expr.name); + } else { + this.declareLexicalName(expr.name); + } + } + break + + case "MemberExpression": + if (bindingType) { this.raiseRecoverable(expr.start, "Binding member expression"); } + break + + case "ObjectPattern": + for (var i = 0, list = expr.properties; i < list.length; i += 1) + { + var prop = list[i]; + + this$1.checkLVal(prop, bindingType, checkClashes); + } + break + + case "Property": + // AssignmentProperty has type == "Property" + this.checkLVal(expr.value, bindingType, checkClashes); + break + + case "ArrayPattern": + for (var i$1 = 0, list$1 = expr.elements; i$1 < list$1.length; i$1 += 1) { + var elem = list$1[i$1]; + + if (elem) { this$1.checkLVal(elem, bindingType, checkClashes); } + } + break + + case "AssignmentPattern": + this.checkLVal(expr.left, bindingType, checkClashes); + break + + case "RestElement": + this.checkLVal(expr.argument, bindingType, checkClashes); + break + + case "ParenthesizedExpression": + this.checkLVal(expr.expression, bindingType, checkClashes); + break + + default: + this.raise(expr.start, (bindingType ? "Binding" : "Assigning to") + " rvalue"); + } +}; + +// A recursive descent parser operates by defining functions for all +// syntactic elements, and recursively calling those, each function +// advancing the input stream and returning an AST node. Precedence +// of constructs (for example, the fact that `!x[1]` means `!(x[1])` +// instead of `(!x)[1]` is handled by the fact that the parser +// function that parses unary prefix operators is called first, and +// in turn calls the function that parses `[]` subscripts — that +// way, it'll receive the node for `x[1]` already parsed, and wraps +// *that* in the unary operator node. +// +// Acorn uses an [operator precedence parser][opp] to handle binary +// operator precedence, because it is much more compact than using +// the technique outlined above, which uses different, nesting +// functions to specify precedence, for all of the ten binary +// precedence levels that JavaScript defines. +// +// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser + +var pp$3 = Parser.prototype; + +// Check if property name clashes with already added. +// Object/class getters and setters are not allowed to clash — +// either with each other or with an init property — and in +// strict mode, init properties are also not allowed to be repeated. + +pp$3.checkPropClash = function(prop, propHash, refDestructuringErrors) { + if (this.options.ecmaVersion >= 9 && prop.type === "SpreadElement") + { return } + if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) + { return } + var key = prop.key; + var name; + switch (key.type) { + case "Identifier": name = key.name; break + case "Literal": name = String(key.value); break + default: return + } + var kind = prop.kind; + if (this.options.ecmaVersion >= 6) { + if (name === "__proto__" && kind === "init") { + if (propHash.proto) { + if (refDestructuringErrors && refDestructuringErrors.doubleProto < 0) { refDestructuringErrors.doubleProto = key.start; } + // Backwards-compat kludge. Can be removed in version 6.0 + else { this.raiseRecoverable(key.start, "Redefinition of __proto__ property"); } + } + propHash.proto = true; + } + return + } + name = "$" + name; + var other = propHash[name]; + if (other) { + var redefinition; + if (kind === "init") { + redefinition = this.strict && other.init || other.get || other.set; + } else { + redefinition = other.init || other[kind]; + } + if (redefinition) + { this.raiseRecoverable(key.start, "Redefinition of property"); } + } else { + other = propHash[name] = { + init: false, + get: false, + set: false + }; + } + other[kind] = true; +}; + +// ### Expression parsing + +// These nest, from the most general expression type at the top to +// 'atomic', nondivisible expression types at the bottom. Most of +// the functions will simply let the function(s) below them parse, +// and, *if* the syntactic construct they handle is present, wrap +// the AST node that the inner parser gave them in another node. + +// Parse a full expression. The optional arguments are used to +// forbid the `in` operator (in for loops initalization expressions) +// and provide reference for storing '=' operator inside shorthand +// property assignment in contexts where both object expression +// and object pattern might appear (so it's possible to raise +// delayed syntax error at correct position). + +pp$3.parseExpression = function(noIn, refDestructuringErrors) { + var this$1 = this; + + var startPos = this.start, startLoc = this.startLoc; + var expr = this.parseMaybeAssign(noIn, refDestructuringErrors); + if (this.type === types.comma) { + var node = this.startNodeAt(startPos, startLoc); + node.expressions = [expr]; + while (this.eat(types.comma)) { node.expressions.push(this$1.parseMaybeAssign(noIn, refDestructuringErrors)); } + return this.finishNode(node, "SequenceExpression") + } + return expr +}; + +// Parse an assignment expression. This includes applications of +// operators like `+=`. + +pp$3.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) { + if (this.inGenerator && this.isContextual("yield")) { return this.parseYield() } + + var ownDestructuringErrors = false, oldParenAssign = -1, oldTrailingComma = -1; + if (refDestructuringErrors) { + oldParenAssign = refDestructuringErrors.parenthesizedAssign; + oldTrailingComma = refDestructuringErrors.trailingComma; + refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = -1; + } else { + refDestructuringErrors = new DestructuringErrors; + ownDestructuringErrors = true; + } + + var startPos = this.start, startLoc = this.startLoc; + if (this.type == types.parenL || this.type == types.name) + { this.potentialArrowAt = this.start; } + var left = this.parseMaybeConditional(noIn, refDestructuringErrors); + if (afterLeftParse) { left = afterLeftParse.call(this, left, startPos, startLoc); } + if (this.type.isAssign) { + var node = this.startNodeAt(startPos, startLoc); + node.operator = this.value; + node.left = this.type === types.eq ? this.toAssignable(left, false, refDestructuringErrors) : left; + if (!ownDestructuringErrors) { DestructuringErrors.call(refDestructuringErrors); } + refDestructuringErrors.shorthandAssign = -1; // reset because shorthand default was used correctly + this.checkLVal(left); + this.next(); + node.right = this.parseMaybeAssign(noIn); + return this.finishNode(node, "AssignmentExpression") + } else { + if (ownDestructuringErrors) { this.checkExpressionErrors(refDestructuringErrors, true); } + } + if (oldParenAssign > -1) { refDestructuringErrors.parenthesizedAssign = oldParenAssign; } + if (oldTrailingComma > -1) { refDestructuringErrors.trailingComma = oldTrailingComma; } + return left +}; + +// Parse a ternary conditional (`?:`) operator. + +pp$3.parseMaybeConditional = function(noIn, refDestructuringErrors) { + var startPos = this.start, startLoc = this.startLoc; + var expr = this.parseExprOps(noIn, refDestructuringErrors); + if (this.checkExpressionErrors(refDestructuringErrors)) { return expr } + if (this.eat(types.question)) { + var node = this.startNodeAt(startPos, startLoc); + node.test = expr; + node.consequent = this.parseMaybeAssign(); + this.expect(types.colon); + node.alternate = this.parseMaybeAssign(noIn); + return this.finishNode(node, "ConditionalExpression") + } + return expr +}; + +// Start the precedence parser. + +pp$3.parseExprOps = function(noIn, refDestructuringErrors) { + var startPos = this.start, startLoc = this.startLoc; + var expr = this.parseMaybeUnary(refDestructuringErrors, false); + if (this.checkExpressionErrors(refDestructuringErrors)) { return expr } + return expr.start == startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn) +}; + +// Parse binary operators with the operator precedence parsing +// algorithm. `left` is the left-hand side of the operator. +// `minPrec` provides context that allows the function to stop and +// defer further parser to one of its callers when it encounters an +// operator that has a lower precedence than the set it is parsing. + +pp$3.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) { + var prec = this.type.binop; + if (prec != null && (!noIn || this.type !== types._in)) { + if (prec > minPrec) { + var logical = this.type === types.logicalOR || this.type === types.logicalAND; + var op = this.value; + this.next(); + var startPos = this.start, startLoc = this.startLoc; + var right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn); + var node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical); + return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn) + } + } + return left +}; + +pp$3.buildBinary = function(startPos, startLoc, left, right, op, logical) { + var node = this.startNodeAt(startPos, startLoc); + node.left = left; + node.operator = op; + node.right = right; + return this.finishNode(node, logical ? "LogicalExpression" : "BinaryExpression") +}; + +// Parse unary operators, both prefix and postfix. + +pp$3.parseMaybeUnary = function(refDestructuringErrors, sawUnary) { + var this$1 = this; + + var startPos = this.start, startLoc = this.startLoc, expr; + if (this.inAsync && this.isContextual("await")) { + expr = this.parseAwait(); + sawUnary = true; + } else if (this.type.prefix) { + var node = this.startNode(), update = this.type === types.incDec; + node.operator = this.value; + node.prefix = true; + this.next(); + node.argument = this.parseMaybeUnary(null, true); + this.checkExpressionErrors(refDestructuringErrors, true); + if (update) { this.checkLVal(node.argument); } + else if (this.strict && node.operator === "delete" && + node.argument.type === "Identifier") + { this.raiseRecoverable(node.start, "Deleting local variable in strict mode"); } + else { sawUnary = true; } + expr = this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + } else { + expr = this.parseExprSubscripts(refDestructuringErrors); + if (this.checkExpressionErrors(refDestructuringErrors)) { return expr } + while (this.type.postfix && !this.canInsertSemicolon()) { + var node$1 = this$1.startNodeAt(startPos, startLoc); + node$1.operator = this$1.value; + node$1.prefix = false; + node$1.argument = expr; + this$1.checkLVal(expr); + this$1.next(); + expr = this$1.finishNode(node$1, "UpdateExpression"); + } + } + + if (!sawUnary && this.eat(types.starstar)) + { return this.buildBinary(startPos, startLoc, expr, this.parseMaybeUnary(null, false), "**", false) } + else + { return expr } +}; + +// Parse call, dot, and `[]`-subscript expressions. + +pp$3.parseExprSubscripts = function(refDestructuringErrors) { + var startPos = this.start, startLoc = this.startLoc; + var expr = this.parseExprAtom(refDestructuringErrors); + var skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")"; + if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) { return expr } + var result = this.parseSubscripts(expr, startPos, startLoc); + if (refDestructuringErrors && result.type === "MemberExpression") { + if (refDestructuringErrors.parenthesizedAssign >= result.start) { refDestructuringErrors.parenthesizedAssign = -1; } + if (refDestructuringErrors.parenthesizedBind >= result.start) { refDestructuringErrors.parenthesizedBind = -1; } + } + return result +}; + +pp$3.parseSubscripts = function(base, startPos, startLoc, noCalls) { + var this$1 = this; + + var maybeAsyncArrow = this.options.ecmaVersion >= 8 && base.type === "Identifier" && base.name === "async" && + this.lastTokEnd == base.end && !this.canInsertSemicolon() && this.input.slice(base.start, base.end) === "async"; + for (var computed = (void 0);;) { + if ((computed = this$1.eat(types.bracketL)) || this$1.eat(types.dot)) { + var node = this$1.startNodeAt(startPos, startLoc); + node.object = base; + node.property = computed ? this$1.parseExpression() : this$1.parseIdent(true); + node.computed = !!computed; + if (computed) { this$1.expect(types.bracketR); } + base = this$1.finishNode(node, "MemberExpression"); + } else if (!noCalls && this$1.eat(types.parenL)) { + var refDestructuringErrors = new DestructuringErrors, oldYieldPos = this$1.yieldPos, oldAwaitPos = this$1.awaitPos; + this$1.yieldPos = 0; + this$1.awaitPos = 0; + var exprList = this$1.parseExprList(types.parenR, this$1.options.ecmaVersion >= 8, false, refDestructuringErrors); + if (maybeAsyncArrow && !this$1.canInsertSemicolon() && this$1.eat(types.arrow)) { + this$1.checkPatternErrors(refDestructuringErrors, false); + this$1.checkYieldAwaitInDefaultParams(); + this$1.yieldPos = oldYieldPos; + this$1.awaitPos = oldAwaitPos; + return this$1.parseArrowExpression(this$1.startNodeAt(startPos, startLoc), exprList, true) + } + this$1.checkExpressionErrors(refDestructuringErrors, true); + this$1.yieldPos = oldYieldPos || this$1.yieldPos; + this$1.awaitPos = oldAwaitPos || this$1.awaitPos; + var node$1 = this$1.startNodeAt(startPos, startLoc); + node$1.callee = base; + node$1.arguments = exprList; + base = this$1.finishNode(node$1, "CallExpression"); + } else if (this$1.type === types.backQuote) { + var node$2 = this$1.startNodeAt(startPos, startLoc); + node$2.tag = base; + node$2.quasi = this$1.parseTemplate({isTagged: true}); + base = this$1.finishNode(node$2, "TaggedTemplateExpression"); + } else { + return base + } + } +}; + +// Parse an atomic expression — either a single token that is an +// expression, an expression started by a keyword like `function` or +// `new`, or an expression wrapped in punctuation like `()`, `[]`, +// or `{}`. + +pp$3.parseExprAtom = function(refDestructuringErrors) { + var node, canBeArrow = this.potentialArrowAt == this.start; + switch (this.type) { + case types._super: + if (!this.inFunction) + { this.raise(this.start, "'super' outside of function or class"); } + node = this.startNode(); + this.next(); + // The `super` keyword can appear at below: + // SuperProperty: + // super [ Expression ] + // super . IdentifierName + // SuperCall: + // super Arguments + if (this.type !== types.dot && this.type !== types.bracketL && this.type !== types.parenL) + { this.unexpected(); } + return this.finishNode(node, "Super") + + case types._this: + node = this.startNode(); + this.next(); + return this.finishNode(node, "ThisExpression") + + case types.name: + var startPos = this.start, startLoc = this.startLoc, containsEsc = this.containsEsc; + var id = this.parseIdent(this.type !== types.name); + if (this.options.ecmaVersion >= 8 && !containsEsc && id.name === "async" && !this.canInsertSemicolon() && this.eat(types._function)) + { return this.parseFunction(this.startNodeAt(startPos, startLoc), false, false, true) } + if (canBeArrow && !this.canInsertSemicolon()) { + if (this.eat(types.arrow)) + { return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], false) } + if (this.options.ecmaVersion >= 8 && id.name === "async" && this.type === types.name && !containsEsc) { + id = this.parseIdent(); + if (this.canInsertSemicolon() || !this.eat(types.arrow)) + { this.unexpected(); } + return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], true) + } + } + return id + + case types.regexp: + var value = this.value; + node = this.parseLiteral(value.value); + node.regex = {pattern: value.pattern, flags: value.flags}; + return node + + case types.num: case types.string: + return this.parseLiteral(this.value) + + case types._null: case types._true: case types._false: + node = this.startNode(); + node.value = this.type === types._null ? null : this.type === types._true; + node.raw = this.type.keyword; + this.next(); + return this.finishNode(node, "Literal") + + case types.parenL: + var start = this.start, expr = this.parseParenAndDistinguishExpression(canBeArrow); + if (refDestructuringErrors) { + if (refDestructuringErrors.parenthesizedAssign < 0 && !this.isSimpleAssignTarget(expr)) + { refDestructuringErrors.parenthesizedAssign = start; } + if (refDestructuringErrors.parenthesizedBind < 0) + { refDestructuringErrors.parenthesizedBind = start; } + } + return expr + + case types.bracketL: + node = this.startNode(); + this.next(); + node.elements = this.parseExprList(types.bracketR, true, true, refDestructuringErrors); + return this.finishNode(node, "ArrayExpression") + + case types.braceL: + return this.parseObj(false, refDestructuringErrors) + + case types._function: + node = this.startNode(); + this.next(); + return this.parseFunction(node, false) + + case types._class: + return this.parseClass(this.startNode(), false) + + case types._new: + return this.parseNew() + + case types.backQuote: + return this.parseTemplate() + + default: + this.unexpected(); + } +}; + +pp$3.parseLiteral = function(value) { + var node = this.startNode(); + node.value = value; + node.raw = this.input.slice(this.start, this.end); + this.next(); + return this.finishNode(node, "Literal") +}; + +pp$3.parseParenExpression = function() { + this.expect(types.parenL); + var val = this.parseExpression(); + this.expect(types.parenR); + return val +}; + +pp$3.parseParenAndDistinguishExpression = function(canBeArrow) { + var this$1 = this; + + var startPos = this.start, startLoc = this.startLoc, val, allowTrailingComma = this.options.ecmaVersion >= 8; + if (this.options.ecmaVersion >= 6) { + this.next(); + + var innerStartPos = this.start, innerStartLoc = this.startLoc; + var exprList = [], first = true, lastIsComma = false; + var refDestructuringErrors = new DestructuringErrors, oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, spreadStart; + this.yieldPos = 0; + this.awaitPos = 0; + while (this.type !== types.parenR) { + first ? first = false : this$1.expect(types.comma); + if (allowTrailingComma && this$1.afterTrailingComma(types.parenR, true)) { + lastIsComma = true; + break + } else if (this$1.type === types.ellipsis) { + spreadStart = this$1.start; + exprList.push(this$1.parseParenItem(this$1.parseRestBinding())); + if (this$1.type === types.comma) { this$1.raise(this$1.start, "Comma is not permitted after the rest element"); } + break + } else { + exprList.push(this$1.parseMaybeAssign(false, refDestructuringErrors, this$1.parseParenItem)); + } + } + var innerEndPos = this.start, innerEndLoc = this.startLoc; + this.expect(types.parenR); + + if (canBeArrow && !this.canInsertSemicolon() && this.eat(types.arrow)) { + this.checkPatternErrors(refDestructuringErrors, false); + this.checkYieldAwaitInDefaultParams(); + this.yieldPos = oldYieldPos; + this.awaitPos = oldAwaitPos; + return this.parseParenArrowList(startPos, startLoc, exprList) + } + + if (!exprList.length || lastIsComma) { this.unexpected(this.lastTokStart); } + if (spreadStart) { this.unexpected(spreadStart); } + this.checkExpressionErrors(refDestructuringErrors, true); + this.yieldPos = oldYieldPos || this.yieldPos; + this.awaitPos = oldAwaitPos || this.awaitPos; + + if (exprList.length > 1) { + val = this.startNodeAt(innerStartPos, innerStartLoc); + val.expressions = exprList; + this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc); + } else { + val = exprList[0]; + } + } else { + val = this.parseParenExpression(); + } + + if (this.options.preserveParens) { + var par = this.startNodeAt(startPos, startLoc); + par.expression = val; + return this.finishNode(par, "ParenthesizedExpression") + } else { + return val + } +}; + +pp$3.parseParenItem = function(item) { + return item +}; + +pp$3.parseParenArrowList = function(startPos, startLoc, exprList) { + return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList) +}; + +// New's precedence is slightly tricky. It must allow its argument to +// be a `[]` or dot subscript expression, but not a call — at least, +// not without wrapping it in parentheses. Thus, it uses the noCalls +// argument to parseSubscripts to prevent it from consuming the +// argument list. + +var empty$1 = []; + +pp$3.parseNew = function() { + var node = this.startNode(); + var meta = this.parseIdent(true); + if (this.options.ecmaVersion >= 6 && this.eat(types.dot)) { + node.meta = meta; + var containsEsc = this.containsEsc; + node.property = this.parseIdent(true); + if (node.property.name !== "target" || containsEsc) + { this.raiseRecoverable(node.property.start, "The only valid meta property for new is new.target"); } + if (!this.inFunction) + { this.raiseRecoverable(node.start, "new.target can only be used in functions"); } + return this.finishNode(node, "MetaProperty") + } + var startPos = this.start, startLoc = this.startLoc; + node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true); + if (this.eat(types.parenL)) { node.arguments = this.parseExprList(types.parenR, this.options.ecmaVersion >= 8, false); } + else { node.arguments = empty$1; } + return this.finishNode(node, "NewExpression") +}; + +// Parse template expression. + +pp$3.parseTemplateElement = function(ref) { + var isTagged = ref.isTagged; + + var elem = this.startNode(); + if (this.type === types.invalidTemplate) { + if (!isTagged) { + this.raiseRecoverable(this.start, "Bad escape sequence in untagged template literal"); + } + elem.value = { + raw: this.value, + cooked: null + }; + } else { + elem.value = { + raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, "\n"), + cooked: this.value + }; + } + this.next(); + elem.tail = this.type === types.backQuote; + return this.finishNode(elem, "TemplateElement") +}; + +pp$3.parseTemplate = function(ref) { + var this$1 = this; + if ( ref === void 0 ) ref = {}; + var isTagged = ref.isTagged; if ( isTagged === void 0 ) isTagged = false; + + var node = this.startNode(); + this.next(); + node.expressions = []; + var curElt = this.parseTemplateElement({isTagged: isTagged}); + node.quasis = [curElt]; + while (!curElt.tail) { + this$1.expect(types.dollarBraceL); + node.expressions.push(this$1.parseExpression()); + this$1.expect(types.braceR); + node.quasis.push(curElt = this$1.parseTemplateElement({isTagged: isTagged})); + } + this.next(); + return this.finishNode(node, "TemplateLiteral") +}; + +pp$3.isAsyncProp = function(prop) { + return !prop.computed && prop.key.type === "Identifier" && prop.key.name === "async" && + (this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL || this.type.keyword || (this.options.ecmaVersion >= 9 && this.type === types.star)) && + !lineBreak.test(this.input.slice(this.lastTokEnd, this.start)) +}; + +// Parse an object literal or binding pattern. + +pp$3.parseObj = function(isPattern, refDestructuringErrors) { + var this$1 = this; + + var node = this.startNode(), first = true, propHash = {}; + node.properties = []; + this.next(); + while (!this.eat(types.braceR)) { + if (!first) { + this$1.expect(types.comma); + if (this$1.afterTrailingComma(types.braceR)) { break } + } else { first = false; } + + var prop = this$1.parseProperty(isPattern, refDestructuringErrors); + if (!isPattern) { this$1.checkPropClash(prop, propHash, refDestructuringErrors); } + node.properties.push(prop); + } + return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression") +}; + +pp$3.parseProperty = function(isPattern, refDestructuringErrors) { + var prop = this.startNode(), isGenerator, isAsync, startPos, startLoc; + if (this.options.ecmaVersion >= 9 && this.eat(types.ellipsis)) { + if (isPattern) { + prop.argument = this.parseIdent(false); + if (this.type === types.comma) { + this.raise(this.start, "Comma is not permitted after the rest element"); + } + return this.finishNode(prop, "RestElement") + } + // To disallow parenthesized identifier via `this.toAssignable()`. + if (this.type === types.parenL && refDestructuringErrors) { + if (refDestructuringErrors.parenthesizedAssign < 0) { + refDestructuringErrors.parenthesizedAssign = this.start; + } + if (refDestructuringErrors.parenthesizedBind < 0) { + refDestructuringErrors.parenthesizedBind = this.start; + } + } + // Parse argument. + prop.argument = this.parseMaybeAssign(false, refDestructuringErrors); + // To disallow trailing comma via `this.toAssignable()`. + if (this.type === types.comma && refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { + refDestructuringErrors.trailingComma = this.start; + } + // Finish + return this.finishNode(prop, "SpreadElement") + } + if (this.options.ecmaVersion >= 6) { + prop.method = false; + prop.shorthand = false; + if (isPattern || refDestructuringErrors) { + startPos = this.start; + startLoc = this.startLoc; + } + if (!isPattern) + { isGenerator = this.eat(types.star); } + } + var containsEsc = this.containsEsc; + this.parsePropertyName(prop); + if (!isPattern && !containsEsc && this.options.ecmaVersion >= 8 && !isGenerator && this.isAsyncProp(prop)) { + isAsync = true; + isGenerator = this.options.ecmaVersion >= 9 && this.eat(types.star); + this.parsePropertyName(prop, refDestructuringErrors); + } else { + isAsync = false; + } + this.parsePropertyValue(prop, isPattern, isGenerator, isAsync, startPos, startLoc, refDestructuringErrors, containsEsc); + return this.finishNode(prop, "Property") +}; + +pp$3.parsePropertyValue = function(prop, isPattern, isGenerator, isAsync, startPos, startLoc, refDestructuringErrors, containsEsc) { + if ((isGenerator || isAsync) && this.type === types.colon) + { this.unexpected(); } + + if (this.eat(types.colon)) { + prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors); + prop.kind = "init"; + } else if (this.options.ecmaVersion >= 6 && this.type === types.parenL) { + if (isPattern) { this.unexpected(); } + prop.kind = "init"; + prop.method = true; + prop.value = this.parseMethod(isGenerator, isAsync); + } else if (!isPattern && !containsEsc && + this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && + (prop.key.name === "get" || prop.key.name === "set") && + (this.type != types.comma && this.type != types.braceR)) { + if (isGenerator || isAsync) { this.unexpected(); } + prop.kind = prop.key.name; + this.parsePropertyName(prop); + prop.value = this.parseMethod(false); + var paramCount = prop.kind === "get" ? 0 : 1; + if (prop.value.params.length !== paramCount) { + var start = prop.value.start; + if (prop.kind === "get") + { this.raiseRecoverable(start, "getter should have no params"); } + else + { this.raiseRecoverable(start, "setter should have exactly one param"); } + } else { + if (prop.kind === "set" && prop.value.params[0].type === "RestElement") + { this.raiseRecoverable(prop.value.params[0].start, "Setter cannot use rest params"); } + } + } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { + this.checkUnreserved(prop.key); + prop.kind = "init"; + if (isPattern) { + prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); + } else if (this.type === types.eq && refDestructuringErrors) { + if (refDestructuringErrors.shorthandAssign < 0) + { refDestructuringErrors.shorthandAssign = this.start; } + prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); + } else { + prop.value = prop.key; + } + prop.shorthand = true; + } else { this.unexpected(); } +}; + +pp$3.parsePropertyName = function(prop) { + if (this.options.ecmaVersion >= 6) { + if (this.eat(types.bracketL)) { + prop.computed = true; + prop.key = this.parseMaybeAssign(); + this.expect(types.bracketR); + return prop.key + } else { + prop.computed = false; + } + } + return prop.key = this.type === types.num || this.type === types.string ? this.parseExprAtom() : this.parseIdent(true) +}; + +// Initialize empty function node. + +pp$3.initFunction = function(node) { + node.id = null; + if (this.options.ecmaVersion >= 6) { + node.generator = false; + node.expression = false; + } + if (this.options.ecmaVersion >= 8) + { node.async = false; } +}; + +// Parse object or class method. + +pp$3.parseMethod = function(isGenerator, isAsync) { + var node = this.startNode(), oldInGen = this.inGenerator, oldInAsync = this.inAsync, + oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction; + + this.initFunction(node); + if (this.options.ecmaVersion >= 6) + { node.generator = isGenerator; } + if (this.options.ecmaVersion >= 8) + { node.async = !!isAsync; } + + this.inGenerator = node.generator; + this.inAsync = node.async; + this.yieldPos = 0; + this.awaitPos = 0; + this.inFunction = true; + this.enterFunctionScope(); + + this.expect(types.parenL); + node.params = this.parseBindingList(types.parenR, false, this.options.ecmaVersion >= 8); + this.checkYieldAwaitInDefaultParams(); + this.parseFunctionBody(node, false); + + this.inGenerator = oldInGen; + this.inAsync = oldInAsync; + this.yieldPos = oldYieldPos; + this.awaitPos = oldAwaitPos; + this.inFunction = oldInFunc; + return this.finishNode(node, "FunctionExpression") +}; + +// Parse arrow function expression with given parameters. + +pp$3.parseArrowExpression = function(node, params, isAsync) { + var oldInGen = this.inGenerator, oldInAsync = this.inAsync, + oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction; + + this.enterFunctionScope(); + this.initFunction(node); + if (this.options.ecmaVersion >= 8) + { node.async = !!isAsync; } + + this.inGenerator = false; + this.inAsync = node.async; + this.yieldPos = 0; + this.awaitPos = 0; + this.inFunction = true; + + node.params = this.toAssignableList(params, true); + this.parseFunctionBody(node, true); + + this.inGenerator = oldInGen; + this.inAsync = oldInAsync; + this.yieldPos = oldYieldPos; + this.awaitPos = oldAwaitPos; + this.inFunction = oldInFunc; + return this.finishNode(node, "ArrowFunctionExpression") +}; + +// Parse function body and check parameters. + +pp$3.parseFunctionBody = function(node, isArrowFunction) { + var isExpression = isArrowFunction && this.type !== types.braceL; + var oldStrict = this.strict, useStrict = false; + + if (isExpression) { + node.body = this.parseMaybeAssign(); + node.expression = true; + this.checkParams(node, false); + } else { + var nonSimple = this.options.ecmaVersion >= 7 && !this.isSimpleParamList(node.params); + if (!oldStrict || nonSimple) { + useStrict = this.strictDirective(this.end); + // If this is a strict mode function, verify that argument names + // are not repeated, and it does not try to bind the words `eval` + // or `arguments`. + if (useStrict && nonSimple) + { this.raiseRecoverable(node.start, "Illegal 'use strict' directive in function with non-simple parameter list"); } + } + // Start a new scope with regard to labels and the `inFunction` + // flag (restore them to their old value afterwards). + var oldLabels = this.labels; + this.labels = []; + if (useStrict) { this.strict = true; } + + // Add the params to varDeclaredNames to ensure that an error is thrown + // if a let/const declaration in the function clashes with one of the params. + this.checkParams(node, !oldStrict && !useStrict && !isArrowFunction && this.isSimpleParamList(node.params)); + node.body = this.parseBlock(false); + node.expression = false; + this.adaptDirectivePrologue(node.body.body); + this.labels = oldLabels; + } + this.exitFunctionScope(); + + if (this.strict && node.id) { + // Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval' + this.checkLVal(node.id, "none"); + } + this.strict = oldStrict; +}; + +pp$3.isSimpleParamList = function(params) { + for (var i = 0, list = params; i < list.length; i += 1) + { + var param = list[i]; + + if (param.type !== "Identifier") { return false + } } + return true +}; + +// Checks function params for various disallowed patterns such as using "eval" +// or "arguments" and duplicate parameters. + +pp$3.checkParams = function(node, allowDuplicates) { + var this$1 = this; + + var nameHash = {}; + for (var i = 0, list = node.params; i < list.length; i += 1) + { + var param = list[i]; + + this$1.checkLVal(param, "var", allowDuplicates ? null : nameHash); + } +}; + +// Parses a comma-separated list of expressions, and returns them as +// an array. `close` is the token type that ends the list, and +// `allowEmpty` can be turned on to allow subsequent commas with +// nothing in between them to be parsed as `null` (which is needed +// for array literals). + +pp$3.parseExprList = function(close, allowTrailingComma, allowEmpty, refDestructuringErrors) { + var this$1 = this; + + var elts = [], first = true; + while (!this.eat(close)) { + if (!first) { + this$1.expect(types.comma); + if (allowTrailingComma && this$1.afterTrailingComma(close)) { break } + } else { first = false; } + + var elt = (void 0); + if (allowEmpty && this$1.type === types.comma) + { elt = null; } + else if (this$1.type === types.ellipsis) { + elt = this$1.parseSpread(refDestructuringErrors); + if (refDestructuringErrors && this$1.type === types.comma && refDestructuringErrors.trailingComma < 0) + { refDestructuringErrors.trailingComma = this$1.start; } + } else { + elt = this$1.parseMaybeAssign(false, refDestructuringErrors); + } + elts.push(elt); + } + return elts +}; + +pp$3.checkUnreserved = function(ref) { + var start = ref.start; + var end = ref.end; + var name = ref.name; + + if (this.inGenerator && name === "yield") + { this.raiseRecoverable(start, "Can not use 'yield' as identifier inside a generator"); } + if (this.inAsync && name === "await") + { this.raiseRecoverable(start, "Can not use 'await' as identifier inside an async function"); } + if (this.isKeyword(name)) + { this.raise(start, ("Unexpected keyword '" + name + "'")); } + if (this.options.ecmaVersion < 6 && + this.input.slice(start, end).indexOf("\\") != -1) { return } + var re = this.strict ? this.reservedWordsStrict : this.reservedWords; + if (re.test(name)) { + if (!this.inAsync && name === "await") + { this.raiseRecoverable(start, "Can not use keyword 'await' outside an async function"); } + this.raiseRecoverable(start, ("The keyword '" + name + "' is reserved")); + } +}; + +// Parse the next token as an identifier. If `liberal` is true (used +// when parsing properties), it will also convert keywords into +// identifiers. + +pp$3.parseIdent = function(liberal, isBinding) { + var node = this.startNode(); + if (liberal && this.options.allowReserved == "never") { liberal = false; } + if (this.type === types.name) { + node.name = this.value; + } else if (this.type.keyword) { + node.name = this.type.keyword; + + // To fix https://github.com/acornjs/acorn/issues/575 + // `class` and `function` keywords push new context into this.context. + // But there is no chance to pop the context if the keyword is consumed as an identifier such as a property name. + // If the previous token is a dot, this does not apply because the context-managing code already ignored the keyword + if ((node.name === "class" || node.name === "function") && + (this.lastTokEnd !== this.lastTokStart + 1 || this.input.charCodeAt(this.lastTokStart) !== 46)) { + this.context.pop(); + } + } else { + this.unexpected(); + } + this.next(); + this.finishNode(node, "Identifier"); + if (!liberal) { this.checkUnreserved(node); } + return node +}; + +// Parses yield expression inside generator. + +pp$3.parseYield = function() { + if (!this.yieldPos) { this.yieldPos = this.start; } + + var node = this.startNode(); + this.next(); + if (this.type == types.semi || this.canInsertSemicolon() || (this.type != types.star && !this.type.startsExpr)) { + node.delegate = false; + node.argument = null; + } else { + node.delegate = this.eat(types.star); + node.argument = this.parseMaybeAssign(); + } + return this.finishNode(node, "YieldExpression") +}; + +pp$3.parseAwait = function() { + if (!this.awaitPos) { this.awaitPos = this.start; } + + var node = this.startNode(); + this.next(); + node.argument = this.parseMaybeUnary(null, true); + return this.finishNode(node, "AwaitExpression") +}; + +var pp$4 = Parser.prototype; + +// This function is used to raise exceptions on parse errors. It +// takes an offset integer (into the current `input`) to indicate +// the location of the error, attaches the position to the end +// of the error message, and then raises a `SyntaxError` with that +// message. + +pp$4.raise = function(pos, message) { + var loc = getLineInfo(this.input, pos); + message += " (" + loc.line + ":" + loc.column + ")"; + var err = new SyntaxError(message); + err.pos = pos; err.loc = loc; err.raisedAt = this.pos; + throw err +}; + +pp$4.raiseRecoverable = pp$4.raise; + +pp$4.curPosition = function() { + if (this.options.locations) { + return new Position(this.curLine, this.pos - this.lineStart) + } +}; + +var pp$5 = Parser.prototype; + +// Object.assign polyfill +var assign = Object.assign || function(target) { + var sources = [], len = arguments.length - 1; + while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ]; + + for (var i = 0, list = sources; i < list.length; i += 1) { + var source = list[i]; + + for (var key in source) { + if (has(source, key)) { + target[key] = source[key]; + } + } + } + return target +}; + +// The functions in this module keep track of declared variables in the current scope in order to detect duplicate variable names. + +pp$5.enterFunctionScope = function() { + // var: a hash of var-declared names in the current lexical scope + // lexical: a hash of lexically-declared names in the current lexical scope + // childVar: a hash of var-declared names in all child lexical scopes of the current lexical scope (within the current function scope) + // parentLexical: a hash of lexically-declared names in all parent lexical scopes of the current lexical scope (within the current function scope) + this.scopeStack.push({var: {}, lexical: {}, childVar: {}, parentLexical: {}}); +}; + +pp$5.exitFunctionScope = function() { + this.scopeStack.pop(); +}; + +pp$5.enterLexicalScope = function() { + var parentScope = this.scopeStack[this.scopeStack.length - 1]; + var childScope = {var: {}, lexical: {}, childVar: {}, parentLexical: {}}; + + this.scopeStack.push(childScope); + assign(childScope.parentLexical, parentScope.lexical, parentScope.parentLexical); +}; + +pp$5.exitLexicalScope = function() { + var childScope = this.scopeStack.pop(); + var parentScope = this.scopeStack[this.scopeStack.length - 1]; + + assign(parentScope.childVar, childScope.var, childScope.childVar); +}; + +/** + * A name can be declared with `var` if there are no variables with the same name declared with `let`/`const` + * in the current lexical scope or any of the parent lexical scopes in this function. + */ +pp$5.canDeclareVarName = function(name) { + var currentScope = this.scopeStack[this.scopeStack.length - 1]; + + return !has(currentScope.lexical, name) && !has(currentScope.parentLexical, name) +}; + +/** + * A name can be declared with `let`/`const` if there are no variables with the same name declared with `let`/`const` + * in the current scope, and there are no variables with the same name declared with `var` in the current scope or in + * any child lexical scopes in this function. + */ +pp$5.canDeclareLexicalName = function(name) { + var currentScope = this.scopeStack[this.scopeStack.length - 1]; + + return !has(currentScope.lexical, name) && !has(currentScope.var, name) && !has(currentScope.childVar, name) +}; + +pp$5.declareVarName = function(name) { + this.scopeStack[this.scopeStack.length - 1].var[name] = true; +}; + +pp$5.declareLexicalName = function(name) { + this.scopeStack[this.scopeStack.length - 1].lexical[name] = true; +}; + +var Node = function Node(parser, pos, loc) { + this.type = ""; + this.start = pos; + this.end = 0; + if (parser.options.locations) + { this.loc = new SourceLocation(parser, loc); } + if (parser.options.directSourceFile) + { this.sourceFile = parser.options.directSourceFile; } + if (parser.options.ranges) + { this.range = [pos, 0]; } +}; + +// Start an AST node, attaching a start offset. + +var pp$6 = Parser.prototype; + +pp$6.startNode = function() { + return new Node(this, this.start, this.startLoc) +}; + +pp$6.startNodeAt = function(pos, loc) { + return new Node(this, pos, loc) +}; + +// Finish an AST node, adding `type` and `end` properties. + +function finishNodeAt(node, type, pos, loc) { + node.type = type; + node.end = pos; + if (this.options.locations) + { node.loc.end = loc; } + if (this.options.ranges) + { node.range[1] = pos; } + return node +} + +pp$6.finishNode = function(node, type) { + return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc) +}; + +// Finish node at given position + +pp$6.finishNodeAt = function(node, type, pos, loc) { + return finishNodeAt.call(this, node, type, pos, loc) +}; + +// The algorithm used to determine whether a regexp can appear at a +// given point in the program is loosely based on sweet.js' approach. +// See https://github.com/mozilla/sweet.js/wiki/design + +var TokContext = function TokContext(token, isExpr, preserveSpace, override, generator) { + this.token = token; + this.isExpr = !!isExpr; + this.preserveSpace = !!preserveSpace; + this.override = override; + this.generator = !!generator; +}; + +var types$1 = { + b_stat: new TokContext("{", false), + b_expr: new TokContext("{", true), + b_tmpl: new TokContext("${", false), + p_stat: new TokContext("(", false), + p_expr: new TokContext("(", true), + q_tmpl: new TokContext("`", true, true, function (p) { return p.tryReadTemplateToken(); }), + f_stat: new TokContext("function", false), + f_expr: new TokContext("function", true), + f_expr_gen: new TokContext("function", true, false, null, true), + f_gen: new TokContext("function", false, false, null, true) +}; + +var pp$7 = Parser.prototype; + +pp$7.initialContext = function() { + return [types$1.b_stat] +}; + +pp$7.braceIsBlock = function(prevType) { + var parent = this.curContext(); + if (parent === types$1.f_expr || parent === types$1.f_stat) + { return true } + if (prevType === types.colon && (parent === types$1.b_stat || parent === types$1.b_expr)) + { return !parent.isExpr } + + // The check for `tt.name && exprAllowed` detects whether we are + // after a `yield` or `of` construct. See the `updateContext` for + // `tt.name`. + if (prevType === types._return || prevType == types.name && this.exprAllowed) + { return lineBreak.test(this.input.slice(this.lastTokEnd, this.start)) } + if (prevType === types._else || prevType === types.semi || prevType === types.eof || prevType === types.parenR || prevType == types.arrow) + { return true } + if (prevType == types.braceL) + { return parent === types$1.b_stat } + if (prevType == types._var || prevType == types.name) + { return false } + return !this.exprAllowed +}; + +pp$7.inGeneratorContext = function() { + var this$1 = this; + + for (var i = this.context.length - 1; i >= 1; i--) { + var context = this$1.context[i]; + if (context.token === "function") + { return context.generator } + } + return false +}; + +pp$7.updateContext = function(prevType) { + var update, type = this.type; + if (type.keyword && prevType == types.dot) + { this.exprAllowed = false; } + else if (update = type.updateContext) + { update.call(this, prevType); } + else + { this.exprAllowed = type.beforeExpr; } +}; + +// Token-specific context update code + +types.parenR.updateContext = types.braceR.updateContext = function() { + if (this.context.length == 1) { + this.exprAllowed = true; + return + } + var out = this.context.pop(); + if (out === types$1.b_stat && this.curContext().token === "function") { + out = this.context.pop(); + } + this.exprAllowed = !out.isExpr; +}; + +types.braceL.updateContext = function(prevType) { + this.context.push(this.braceIsBlock(prevType) ? types$1.b_stat : types$1.b_expr); + this.exprAllowed = true; +}; + +types.dollarBraceL.updateContext = function() { + this.context.push(types$1.b_tmpl); + this.exprAllowed = true; +}; + +types.parenL.updateContext = function(prevType) { + var statementParens = prevType === types._if || prevType === types._for || prevType === types._with || prevType === types._while; + this.context.push(statementParens ? types$1.p_stat : types$1.p_expr); + this.exprAllowed = true; +}; + +types.incDec.updateContext = function() { + // tokExprAllowed stays unchanged +}; + +types._function.updateContext = types._class.updateContext = function(prevType) { + if (prevType.beforeExpr && prevType !== types.semi && prevType !== types._else && + !((prevType === types.colon || prevType === types.braceL) && this.curContext() === types$1.b_stat)) + { this.context.push(types$1.f_expr); } + else + { this.context.push(types$1.f_stat); } + this.exprAllowed = false; +}; + +types.backQuote.updateContext = function() { + if (this.curContext() === types$1.q_tmpl) + { this.context.pop(); } + else + { this.context.push(types$1.q_tmpl); } + this.exprAllowed = false; +}; + +types.star.updateContext = function(prevType) { + if (prevType == types._function) { + var index = this.context.length - 1; + if (this.context[index] === types$1.f_expr) + { this.context[index] = types$1.f_expr_gen; } + else + { this.context[index] = types$1.f_gen; } + } + this.exprAllowed = true; +}; + +types.name.updateContext = function(prevType) { + var allowed = false; + if (this.options.ecmaVersion >= 6) { + if (this.value == "of" && !this.exprAllowed || + this.value == "yield" && this.inGeneratorContext()) + { allowed = true; } + } + this.exprAllowed = allowed; +}; + +// Object type used to represent tokens. Note that normally, tokens +// simply exist as properties on the parser object. This is only +// used for the onToken callback and the external tokenizer. + +var Token = function Token(p) { + this.type = p.type; + this.value = p.value; + this.start = p.start; + this.end = p.end; + if (p.options.locations) + { this.loc = new SourceLocation(p, p.startLoc, p.endLoc); } + if (p.options.ranges) + { this.range = [p.start, p.end]; } +}; + +// ## Tokenizer + +var pp$8 = Parser.prototype; + +// Are we running under Rhino? +var isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]"; + +// Move to the next token + +pp$8.next = function() { + if (this.options.onToken) + { this.options.onToken(new Token(this)); } + + this.lastTokEnd = this.end; + this.lastTokStart = this.start; + this.lastTokEndLoc = this.endLoc; + this.lastTokStartLoc = this.startLoc; + this.nextToken(); +}; + +pp$8.getToken = function() { + this.next(); + return new Token(this) +}; + +// If we're in an ES6 environment, make parsers iterable +if (typeof Symbol !== "undefined") + { pp$8[Symbol.iterator] = function() { + var this$1 = this; + + return { + next: function () { + var token = this$1.getToken(); + return { + done: token.type === types.eof, + value: token + } + } + } + }; } + +// Toggle strict mode. Re-reads the next number or string to please +// pedantic tests (`"use strict"; 010;` should fail). + +pp$8.curContext = function() { + return this.context[this.context.length - 1] +}; + +// Read a single token, updating the parser object's token-related +// properties. + +pp$8.nextToken = function() { + var curContext = this.curContext(); + if (!curContext || !curContext.preserveSpace) { this.skipSpace(); } + + this.start = this.pos; + if (this.options.locations) { this.startLoc = this.curPosition(); } + if (this.pos >= this.input.length) { return this.finishToken(types.eof) } + + if (curContext.override) { return curContext.override(this) } + else { this.readToken(this.fullCharCodeAtPos()); } +}; + +pp$8.readToken = function(code) { + // Identifier or keyword. '\uXXXX' sequences are allowed in + // identifiers, so '\' also dispatches to that. + if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */) + { return this.readWord() } + + return this.getTokenFromCode(code) +}; + +pp$8.fullCharCodeAtPos = function() { + var code = this.input.charCodeAt(this.pos); + if (code <= 0xd7ff || code >= 0xe000) { return code } + var next = this.input.charCodeAt(this.pos + 1); + return (code << 10) + next - 0x35fdc00 +}; + +pp$8.skipBlockComment = function() { + var this$1 = this; + + var startLoc = this.options.onComment && this.curPosition(); + var start = this.pos, end = this.input.indexOf("*/", this.pos += 2); + if (end === -1) { this.raise(this.pos - 2, "Unterminated comment"); } + this.pos = end + 2; + if (this.options.locations) { + lineBreakG.lastIndex = start; + var match; + while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) { + ++this$1.curLine; + this$1.lineStart = match.index + match[0].length; + } + } + if (this.options.onComment) + { this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos, + startLoc, this.curPosition()); } +}; + +pp$8.skipLineComment = function(startSkip) { + var this$1 = this; + + var start = this.pos; + var startLoc = this.options.onComment && this.curPosition(); + var ch = this.input.charCodeAt(this.pos += startSkip); + while (this.pos < this.input.length && !isNewLine(ch)) { + ch = this$1.input.charCodeAt(++this$1.pos); + } + if (this.options.onComment) + { this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos, + startLoc, this.curPosition()); } +}; + +// Called at the start of the parse and after every token. Skips +// whitespace and comments, and. + +pp$8.skipSpace = function() { + var this$1 = this; + + loop: while (this.pos < this.input.length) { + var ch = this$1.input.charCodeAt(this$1.pos); + switch (ch) { + case 32: case 160: // ' ' + ++this$1.pos; + break + case 13: + if (this$1.input.charCodeAt(this$1.pos + 1) === 10) { + ++this$1.pos; + } + case 10: case 8232: case 8233: + ++this$1.pos; + if (this$1.options.locations) { + ++this$1.curLine; + this$1.lineStart = this$1.pos; + } + break + case 47: // '/' + switch (this$1.input.charCodeAt(this$1.pos + 1)) { + case 42: // '*' + this$1.skipBlockComment(); + break + case 47: + this$1.skipLineComment(2); + break + default: + break loop + } + break + default: + if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++this$1.pos; + } else { + break loop + } + } + } +}; + +// Called at the end of every token. Sets `end`, `val`, and +// maintains `context` and `exprAllowed`, and skips the space after +// the token, so that the next one's `start` will point at the +// right position. + +pp$8.finishToken = function(type, val) { + this.end = this.pos; + if (this.options.locations) { this.endLoc = this.curPosition(); } + var prevType = this.type; + this.type = type; + this.value = val; + + this.updateContext(prevType); +}; + +// ### Token reading + +// This is the function that is called to fetch the next token. It +// is somewhat obscure, because it works in character codes rather +// than characters, and because operator parsing has been inlined +// into it. +// +// All in the name of speed. +// +pp$8.readToken_dot = function() { + var next = this.input.charCodeAt(this.pos + 1); + if (next >= 48 && next <= 57) { return this.readNumber(true) } + var next2 = this.input.charCodeAt(this.pos + 2); + if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.' + this.pos += 3; + return this.finishToken(types.ellipsis) + } else { + ++this.pos; + return this.finishToken(types.dot) + } +}; + +pp$8.readToken_slash = function() { // '/' + var next = this.input.charCodeAt(this.pos + 1); + if (this.exprAllowed) { ++this.pos; return this.readRegexp() } + if (next === 61) { return this.finishOp(types.assign, 2) } + return this.finishOp(types.slash, 1) +}; + +pp$8.readToken_mult_modulo_exp = function(code) { // '%*' + var next = this.input.charCodeAt(this.pos + 1); + var size = 1; + var tokentype = code === 42 ? types.star : types.modulo; + + // exponentiation operator ** and **= + if (this.options.ecmaVersion >= 7 && code == 42 && next === 42) { + ++size; + tokentype = types.starstar; + next = this.input.charCodeAt(this.pos + 2); + } + + if (next === 61) { return this.finishOp(types.assign, size + 1) } + return this.finishOp(tokentype, size) +}; + +pp$8.readToken_pipe_amp = function(code) { // '|&' + var next = this.input.charCodeAt(this.pos + 1); + if (next === code) { return this.finishOp(code === 124 ? types.logicalOR : types.logicalAND, 2) } + if (next === 61) { return this.finishOp(types.assign, 2) } + return this.finishOp(code === 124 ? types.bitwiseOR : types.bitwiseAND, 1) +}; + +pp$8.readToken_caret = function() { // '^' + var next = this.input.charCodeAt(this.pos + 1); + if (next === 61) { return this.finishOp(types.assign, 2) } + return this.finishOp(types.bitwiseXOR, 1) +}; + +pp$8.readToken_plus_min = function(code) { // '+-' + var next = this.input.charCodeAt(this.pos + 1); + if (next === code) { + if (next == 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 62 && + (this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) { + // A `-->` line comment + this.skipLineComment(3); + this.skipSpace(); + return this.nextToken() + } + return this.finishOp(types.incDec, 2) + } + if (next === 61) { return this.finishOp(types.assign, 2) } + return this.finishOp(types.plusMin, 1) +}; + +pp$8.readToken_lt_gt = function(code) { // '<>' + var next = this.input.charCodeAt(this.pos + 1); + var size = 1; + if (next === code) { + size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2; + if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types.assign, size + 1) } + return this.finishOp(types.bitShift, size) + } + if (next == 33 && code == 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 45 && + this.input.charCodeAt(this.pos + 3) == 45) { + // `",device.name); + device.getPorts(function(devicePorts, instantPorts) { + //console.log("getPorts <--",device.name); + if (instantPorts===false) shouldCallAgain = true; + if (devicePorts) { + devicePorts.forEach(function(port) { + var ignored = false; + if (Espruino.Config.SERIAL_IGNORE) + Espruino.Config.SERIAL_IGNORE.split("|").forEach(function(wildcard) { + var regexp = "^"+wildcard.replace(/\./g,"\\.").replace(/\*/g,".*")+"$"; + if (port.path.match(new RegExp(regexp))) + ignored = true; + }); + + if (!ignored) { + if (port.usb && port.usb[0]==0x0483 && port.usb[1]==0x5740) + port.description = "Espruino board"; + ports.push(port); + newPortToDevice[port.path] = device; + } + }); + } + responses++; + if (responses == devices.length) { + portToDevice = newPortToDevice; + ports.sort(function(a,b) { + if (a.unimportant && !b.unimportant) return 1; + if (b.unimportant && !a.unimportant) return -1; + return 0; + }); + callback(ports, shouldCallAgain); + } + }); + }); + }; + + var openSerial=function(serialPort, connectCallback, disconnectCallback) { + return openSerialInternal(serialPort, connectCallback, disconnectCallback, 2); + } + + var openSerialInternal=function(serialPort, connectCallback, disconnectCallback, attempts) { + /* If openSerial is called, we need to have called getPorts first + in order to figure out which one of the serial_ implementations + we must call into. */ + if (portToDevice === undefined) { + portToDevice = []; // stop recursive calls if something errors + return getPorts(function() { + openSerialInternal(serialPort, connectCallback, disconnectCallback, attempts); + }); + } + + if (!(serialPort in portToDevice)) { + if (serialPort.toLowerCase() in portToDevice) { + serialPort = serialPort.toLowerCase(); + } else { + if (attempts>0) { + console.log("Port "+JSON.stringify(serialPort)+" not found - checking ports again ("+attempts+" attempts left)"); + return getPorts(function() { + openSerialInternal(serialPort, connectCallback, disconnectCallback, attempts-1); + }); + } else { + console.error("Port "+JSON.stringify(serialPort)+" not found"); + return connectCallback(undefined); + } + } + } + + connectionInfo = undefined; + flowControlXOFF = false; + currentDevice = portToDevice[serialPort]; + currentDevice.open(serialPort, function(cInfo) { // CONNECT + if (!cInfo) { +// Espruino.Core.Notifications.error("Unable to connect"); + console.error("Unable to open device (connectionInfo="+cInfo+")"); + connectCallback(undefined); + } else { + connectionInfo = cInfo; + connectedPort = serialPort; + console.log("Connected", cInfo); + var portInfo = { port:serialPort }; + if (connectionInfo.portName) + portInfo.portName = connectionInfo.portName; + Espruino.callProcessor("connected", portInfo, function() { + connectCallback(cInfo); + }); + } + }, function(data) { // RECEIEVE DATA + if (!(data instanceof ArrayBuffer)) console.warn("Serial port implementation is not returning ArrayBuffers"); + if (Espruino.Config.SERIAL_FLOW_CONTROL) { + var u = new Uint8Array(data); + for (var i=0;i resume upload"); + flowControlXOFF = false; + } + if (u[i]==19) { // XOFF + console.log("XOFF received => pause upload"); + flowControlXOFF = true; + } + } + } + if (readListener) readListener(data); + }, function() { // DISCONNECT + currentDevice = undefined; + if (!connectionInfo) { + // we got a disconnect when we hadn't connected... + // Just call connectCallback(undefined), don't bother sending disconnect + connectCallback(undefined); + return; + } + connectionInfo = undefined; + if (writeTimeout!==undefined) + clearTimeout(writeTimeout); + writeTimeout = undefined; + writeData = []; + sendingBinary = false; + flowControlXOFF = false; + + Espruino.callProcessor("disconnected", undefined, function() { + disconnectCallback(); + }); + }); + }; + + var str2ab=function(str) { + var buf=new ArrayBuffer(str.length); + var bufView=new Uint8Array(buf); + for (var i=0; i=256) { + console.warn("Attempted to send non-8 bit character - code "+ch); + ch = "?".charCodeAt(0); + } + bufView[i] = ch; + } + return buf; + }; + + var closeSerial=function() { + if (currentDevice) { + currentDevice.close(); + currentDevice = undefined; + } else + console.error("Close called, but serial port not open"); + }; + + var isConnected = function() { + return currentDevice!==undefined; + }; + + var writeSerialWorker = function(isStarting) { + writeTimeout = undefined; // we've been called + // check flow control + if (flowControlXOFF) { + /* flow control was enabled - bit hacky (we could use a callback) + but safe - just check again in a bit to see if we should send */ + writeTimeout = setTimeout(function() { + writeSerialWorker(); + }, 50); + return; + } + + // if we disconnected while sending, empty queue + if (currentDevice === undefined) { + if (writeData[0].callback) + writeData[0].callback(); + writeData.shift(); + if (writeData.length) setTimeout(function() { + writeSerialWorker(false); + }, 1); + return; + } + + if (writeData[0].data === "") { + if (writeData[0].showStatus) + Espruino.Core.Status.setStatus("Sent"); + if (writeData[0].callback) + writeData[0].callback(); + writeData.shift(); // remove this empty first element + if (!writeData.length) return; // anything left to do? + isStarting = true; + } + + if (isStarting) { + var blockSize = 512; + if (currentDevice.maxWriteLength) + blockSize = currentDevice.maxWriteLength; + /* if we're throttling our writes we want to send small + * blocks of data at once. We still limit the size of + * sent blocks to 512 because on Mac we seem to lose + * data otherwise (not on any other platforms!) */ + if (slowWrite) blockSize=19; + writeData[0].blockSize = blockSize; + + writeData[0].showStatus &= writeData[0].data.length>writeData[0].blockSize; + if (writeData[0].showStatus) { + Espruino.Core.Status.setStatus("Sending...", writeData[0].data.length); + console.log("---> "+JSON.stringify(writeData[0].data)); + } + } + + // Initial split use previous, or don't + var d = undefined; + var split = writeData[0].nextSplit || { start:0, end:writeData[0].data.length, delay:0 }; + // if we get something like Ctrl-C or `reset`, wait a bit for it to complete + if (!sendingBinary) { + function findSplitIdx(prev, substr, delay, reason) { + var match = writeData[0].data.match(substr); + // not found + if (match===null) return prev; + // or previous find was earlier in str + var end = match.index + match[0].length; + if (end > prev.end) return prev; + // found, and earlier + prev.start = match.index; + prev.end = end; + prev.delay = delay; + prev.match = match[0]; + prev.reason = reason; + return prev; + } + split = findSplitIdx(split, /\x03/, 250, "Ctrl-C"); // Ctrl-C + split = findSplitIdx(split, /reset\(\);\n/, 250, "reset()"); // Reset + split = findSplitIdx(split, /load\(\);\n/, 250, "load()"); // Load + split = findSplitIdx(split, /Modules.addCached\("[^\n]*"\);\n/, 250, "Modules.addCached"); // Adding a module + split = findSplitIdx(split, /\x10require\("Storage"\).write\([^\n]*\);\n/, 500, "Storage.write"); // Write chunk of data + } + // Otherwise split based on block size + if (!split.match || split.end >= writeData[0].blockSize) { + if (split.match) writeData[0].nextSplit = split; + split = { start:0, end:writeData[0].blockSize, delay:0 }; + } + if (split.match) console.log("Splitting for "+split.reason+", delay "+split.delay); + // Only send some of the data + if (writeData[0].data.length>split.end) { + if (slowWrite && split.delay==0) split.delay=50; + d = writeData[0].data.substr(0,split.end); + writeData[0].data = writeData[0].data.substr(split.end); + if (writeData[0].nextSplit) { + writeData[0].nextSplit.start -= split.end; + writeData[0].nextSplit.end -= split.end; + if (writeData[0].nextSplit.end<=0) + writeData[0].nextSplit = undefined; + } + } else { + d = writeData[0].data; + writeData[0].data = ""; + writeData[0].nextSplit = undefined; + } + // update status + if (writeData[0].showStatus) + Espruino.Core.Status.incrementProgress(d.length); + // actually write data + //console.log("Sending block "+JSON.stringify(d)+", wait "+split.delay+"ms"); + currentDevice.write(d, function() { + // Once written, start timeout + writeTimeout = setTimeout(function() { + writeSerialWorker(); + }, split.delay); + }); + } + + // Throttled serial write + var writeSerial = function(data, showStatus, callback) { + if (showStatus===undefined) showStatus=true; + + /* Queue our data to write. If there was previous data and no callback to + invoke on this data or the previous then just append data. This would happen + if typing in the terminal for example. */ + if (!callback && writeData.length && !writeData[writeData.length-1].callback) { + writeData[writeData.length-1].data += data; + } else { + writeData.push({data:data,callback:callback,showStatus:showStatus}); + /* if this is our first data, start sending now. Otherwise we're already + busy sending and will pull data off writeData when ready */ + if (writeData.length==1) + writeSerialWorker(true); + } + }; + + + // ---------------------------------------------------------- + Espruino.Core.Serial = { + "devices" : [], // List of devices that can provide a serial API + "init" : init, + "getPorts": getPorts, + "open": openSerial, + "isConnected": isConnected, + "startListening": startListening, + "write": writeSerial, + "close": closeSerial, + "isSlowWrite": function() { return slowWrite; }, + "setSlowWrite": function(isOn, force) { + if ((!force) && Espruino.Config.SERIAL_THROTTLE_SEND) { + console.log("ForceThrottle option is set - set Slow Write = true"); + isOn = true; + } else + console.log("Set Slow Write = "+isOn); + slowWrite = isOn; + }, + "setBinary": function(isOn) { + sendingBinary = isOn; + } + }; +})(); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + The plugin that actually writes code out to Espruino + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + function init() { + Espruino.Core.Config.add("RESET_BEFORE_SEND", { + section : "Communications", + name : "Reset before Send", + description : "Reset Espruino before sending code from the editor pane?", + type : "boolean", + defaultValue : true + }); + Espruino.Core.Config.add("STORE_LINE_NUMBERS", { + section : "Communications", + name : "Store line numbers", + description : "Should Espruino store line numbers for each function? This uses one extra variable per function, but allows you to get source code debugging in the Web IDE", + type : "boolean", + defaultValue : true + }); + + } + + function writeToEspruino(code, callback) { + /* hack around non-K&R code formatting that would have + broken Espruino CLI's bracket counting */ + code = reformatCode(code); + if (code === undefined) return; // it should already have errored + + // We want to make sure we've got a prompt before sending. If not, + // this will issue a Ctrl+C + Espruino.Core.Utils.getEspruinoPrompt(function() { + // Make sure code ends in 2 newlines + while (code[code.length-2]!="\n" || code[code.length-1]!="\n") + code += "\n"; + + // If we're supposed to reset Espruino before sending... + if (Espruino.Config.RESET_BEFORE_SEND) { + code = "\x10reset();\n"+code; + } + + //console.log("Sending... "+data); + Espruino.Core.Serial.write(code, true, function() { + // give 5 seconds for sending with save and 2 seconds without save + var count = Espruino.Config.SAVE_ON_SEND ? 50 : 20; + setTimeout(function cb() { + if (Espruino.Core.Terminal!==undefined && + Espruino.Core.Terminal.getTerminalLine()!=">") { + count--; + if (count>0) { + setTimeout(cb, 100); + } else { + Espruino.Core.Notifications.error("Prompt not detected - upload failed. Trying to recover..."); + Espruino.Core.Serial.write("\x03\x03echo(1)\n", false, callback); + } + } else { + if (callback) callback(); + } + }, 100); + }); + }); + }; + + /// Parse and fix issues like `if (false)\n foo` in the root scope + function reformatCode(code) { + var APPLY_LINE_NUMBERS = false; + var lineNumberOffset = 0; + var ENV = Espruino.Core.Env.getData(); + if (ENV && ENV.VERSION_MAJOR && ENV.VERSION_MINOR) { + if (ENV.VERSION_MAJOR>1 || + ENV.VERSION_MINOR>=81.086) { + if (Espruino.Config.STORE_LINE_NUMBERS) + APPLY_LINE_NUMBERS = true; + } + } + // Turn cr/lf into just lf (eg. windows -> unix) + code = code.replace(/\r\n/g,"\n"); + // First off, try and fix funky characters + for (var i=0;i255) && ch!=9/*Tab*/ && ch!=10/*LF*/ && ch!=13/*CR*/) { + console.warn("Funky character code "+ch+" at position "+i+". Replacing with ?"); + code = code.substr(0,i)+"?"+code.substr(i+1); + } + } + + /* Search for lines added to the start of the code by the module handler. + Ideally there would be a better way of doing this so line numbers stayed correct, + but this hack works for now. Fixes EspruinoWebIDE#140 */ + if (APPLY_LINE_NUMBERS) { + var l = code.split("\n"); + var i = 0; + while (l[i] && (l[i].substr(0,8)=="Modules." || + l[i].substr(0,8)=="setTime(")) i++; + lineNumberOffset = -i; + } + + var resultCode = "\x10"; // 0x10 = echo off for line + /** we're looking for: + * `a = \n b` + * `for (.....) \n X` + * `if (.....) \n X` + * `if (.....) { } \n else foo` + * `while (.....) \n X` + * `do \n X` + * `function (.....) \n X` + * `function N(.....) \n X` + * `var a \n , b` `var a = 0 \n, b` + * `var a, \n b` `var a = 0, \n b` + * `a \n . b` + * `foo() \n . b` + * `try { } \n catch \n () \n {}` + * + * These are divided into two groups - where there are brackets + * after the keyword (statementBeforeBrackets) and where there aren't + * (statement) + * + * We fix them by replacing \n with what you get when you press + * Alt+Enter (Ctrl + LF). This tells Espruino that it's a newline + * but NOT to execute. + */ + var lex = Espruino.Core.Utils.getLexer(code); + var brackets = 0; + var curlyBrackets = 0; + var statementBeforeBrackets = false; + var statement = false; + var varDeclaration = false; + var lastIdx = 0; + var lastTok = {str:""}; + var tok = lex.next(); + while (tok!==undefined) { + var previousString = code.substring(lastIdx, tok.startIdx); + var tokenString = code.substring(tok.startIdx, tok.endIdx); + //console.log("prev "+JSON.stringify(previousString)+" next "+tokenString); + + /* Inserting Alt-Enter newline, which adds newline without trying + to execute */ + if (brackets>0 || // we have brackets - sending the alt-enter special newline means Espruino doesn't have to do a search itself - faster. + statement || // statement was before brackets - expecting something else + statementBeforeBrackets || // we have an 'if'/etc + varDeclaration || // variable declaration then newline + tok.str=="," || // comma on newline - there was probably something before + tok.str=="." || // dot on newline - there was probably something before + tok.str=="+" || tok.str=="-" || // +/- on newline - there was probably something before + tok.str=="=" || // equals on newline - there was probably something before + tok.str=="else" || // else on newline + lastTok.str=="else" || // else befgore newline + tok.str=="catch" || // catch on newline - part of try..catch + lastTok.str=="catch" + ) { + //console.log("Possible"+JSON.stringify(previousString)); + previousString = previousString.replace(/\n/g, "\x1B\x0A"); + } + + var previousBrackets = brackets; + if (tok.str=="(" || tok.str=="{" || tok.str=="[") brackets++; + if (tok.str=="{") curlyBrackets++; + if (tok.str==")" || tok.str=="}" || tok.str=="]") brackets--; + if (tok.str=="}") curlyBrackets--; + + if (brackets==0) { + if (tok.str=="for" || tok.str=="if" || tok.str=="while" || tok.str=="function" || tok.str=="throw") { + statementBeforeBrackets = true; + varDeclaration = false; + } else if (tok.str=="var") { + varDeclaration = true; + } else if (tok.type=="ID" && lastTok.str=="function") { + statementBeforeBrackets = true; + } else if (tok.str=="try" || tok.str=="catch") { + statementBeforeBrackets = true; + } else if (tok.str==")" && statementBeforeBrackets) { + statementBeforeBrackets = false; + statement = true; + } else if (["=","^","&&","||","+","+=","-","-=","*","*=","/","/=","%","%=","&","&=","|","|="].indexOf(tok.str)>=0) { + statement = true; + } else { + if (tok.str==";") varDeclaration = false; + statement = false; + statementBeforeBrackets = false; + } + } + /* If we're at root scope and had whitespace/comments between code, + remove it all and replace it with a single newline and a + 0x10 (echo off for line) character. However DON'T do this if we had + an alt-enter in the line, as it was there to stop us executing + prematurely */ + if (previousBrackets==0 && + previousString.indexOf("\n")>=0 && + previousString.indexOf("\x1B\x0A")<0) { + previousString = "\n\x10"; + // Apply line numbers to each new line sent, to aid debugger + if (APPLY_LINE_NUMBERS && tok.lineNumber && (tok.lineNumber+lineNumberOffset)>0) { + // Esc [ 1234 d + // This is the 'set line number' command that we're abusing :) + previousString += "\x1B\x5B"+(tok.lineNumber+lineNumberOffset)+"d"; + } + } + + // add our stuff back together + resultCode += previousString+tokenString; + // next + lastIdx = tok.endIdx; + lastTok = tok; + tok = lex.next(); + } + //console.log(resultCode); + if (brackets>0) { + Espruino.Core.Notifications.error("You have more open brackets than close brackets. Please see the hints in the Editor window."); + return undefined; + } + if (brackets<0) { + Espruino.Core.Notifications.error("You have more close brackets than open brackets. Please see the hints in the Editor window."); + return undefined; + } + return resultCode; + }; + + Espruino.Core.CodeWriter = { + init : init, + writeToEspruino : writeToEspruino, + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Automatically load any referenced modules + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + function init() { + Espruino.Core.Config.add("MODULE_URL", { + section : "Communications", + name : "Module URL", + description : "Where to search online for modules when `require()` is used", + type : "string", + defaultValue : "https://www.espruino.com/modules" + }); + Espruino.Core.Config.add("MODULE_EXTENSIONS", { + section : "Communications", + name : "Module Extensions", + description : "The file extensions to use for each module. These are checked in order and the first that exists is used. One or more file extensions (including the dot) separated by `|`", + type : "string", + defaultValue : ".min.js|.js" + }); + Espruino.Core.Config.add("MODULE_AS_FUNCTION", { + section : "Communications", + name : "Modules uploaded as functions", + description : "Espruino 1v90 and later ONLY. Upload modules as Functions, allowing any functions inside them to be loaded directly from flash when 'Save on Send' is enabled.", + type : "boolean", + defaultValue : true + }); + + Espruino.Core.Config.add("MODULE_PROXY_ENABLED", { + section : "Communications", + name : "Enable Proxy", + description : "Enable Proxy for loading the modules when `require()` is used (only in native IDE)", + type : "boolean", + defaultValue : false + }); + + Espruino.Core.Config.add("MODULE_PROXY_URL", { + section : "Communications", + name : "Proxy URL", + description : "Proxy URL for loading the modules when `require()` is used (only in native IDE)", + type : "string", + defaultValue : "" + }); + + Espruino.Core.Config.add("MODULE_PROXY_PORT", { + section : "Communications", + name : "Proxy Port", + description : "Proxy Port for loading the modules when `require()` is used (only in native IDE)", + type : "string", + defaultValue : "" + }); + + // When code is sent to Espruino, search it for modules and add extra code required to load them + Espruino.addProcessor("transformForEspruino", function(code, callback) { + if (Espruino.Config.ROLLUP) { + return loadModulesRollup(code, callback); + } + loadModules(code, callback); + }); + + // Append the 'getModule' processor as the last (plugins get initialized after Espruino.Core modules) + Espruino.Plugins.CoreModules = { + init: function() { + Espruino.addProcessor("getModule", function(data, callback) { + if (data.moduleCode!==undefined) { // already provided be previous getModule processor + return callback(data); + } + + fetchGetModule(data, callback); + }); + } + }; + } + + function isBuiltIn(module) { + var d = Espruino.Core.Env.getData(); + // If we got data from the device itself, use that as the + // definitive answer + if ("string" == typeof d.MODULES) + return d.MODULES.split(",").indexOf(module)>=0; + // Otherwise try and figure it out from JSON + if ("info" in d && + "builtin_modules" in d.info && + d.info.builtin_modules.indexOf(module)>=0) + return true; + // Otherwise assume we don't have it + return false; + } + + /** Find any instances of require(...) in the code string and return a list */ + var getModulesRequired = function(code) { + var modules = []; + + var lex = Espruino.Core.Utils.getLexer(code); + var tok = lex.next(); + var state = 0; + while (tok!==undefined) { + if (state==0 && tok.str=="require") { + state=1; + } else if (state==1 && tok.str=="(") { + state=2; + } else if (state==2 && (tok.type=="STRING")) { + state=0; + var module = tok.value; + if (!isBuiltIn(module) && modules.indexOf(module)<0) + modules.push(module); + } else + state = 0; + tok = lex.next(); + } + + return modules; + }; + + /** Download modules from MODULE_URL/.. */ + function fetchGetModule(data, callback) { + var fullModuleName = data.moduleName; + + // try and load the module the old way... + console.log("loadModule("+fullModuleName+")"); + + var urls = []; // Array of where to look for this module + var modName; // Simple name of the module + if(Espruino.Core.Utils.isURL(fullModuleName)) { + modName = fullModuleName.substr(fullModuleName.lastIndexOf("/") + 1).split(".")[0]; + urls = [ fullModuleName ]; + } else { + modName = fullModuleName; + Espruino.Config.MODULE_URL.split("|").forEach(function (url) { + url = url.trim(); + if (url.length!=0) + Espruino.Config.MODULE_EXTENSIONS.split("|").forEach(function (extension) { + urls.push(url + "/" + fullModuleName + extension); + }) + }); + }; + + // Recursively go through all the urls + (function download(urls) { + if (urls.length==0) { + return callback(data); + } + var dlUrl = urls[0]; + Espruino.Core.Utils.getURL(dlUrl, function (code) { + if (code!==undefined) { + // we got it! + data.moduleCode = code; + data.isMinified = dlUrl.substr(-7)==".min.js"; + return callback(data); + } else { + // else try next + download(urls.slice(1)); + } + }); + })(urls); + } + + + /** Called from loadModule when a module is loaded. Parse it for other modules it might use + * and resolve dfd after all submodules have been loaded */ + function moduleLoaded(resolve, requires, modName, data, loadedModuleData, alreadyMinified){ + // Check for any modules used from this module that we don't already have + var newRequires = getModulesRequired(data); + console.log(" - "+modName+" requires "+JSON.stringify(newRequires)); + // if we need new modules, set them to load and get their promises + var newPromises = []; + for (var i in newRequires) { + if (requires.indexOf(newRequires[i])<0) { + console.log(" Queueing "+newRequires[i]); + requires.push(newRequires[i]); + newPromises.push(loadModule(requires, newRequires[i], loadedModuleData)); + } else { + console.log(" Already loading "+newRequires[i]); + } + } + + var loadProcessedModule = function (module) { + // if we needed to load something, wait until it's loaded before resolving this + Promise.all(newPromises).then(function(){ + // add the module to end of our array + if (Espruino.Config.MODULE_AS_FUNCTION) + loadedModuleData.push("Modules.addCached(" + JSON.stringify(module.name) + ",function(){" + module.code + "});"); + else + loadedModuleData.push("Modules.addCached(" + JSON.stringify(module.name) + "," + JSON.stringify(module.code) + ");"); + // We're done + resolve(); + }); + } + if (alreadyMinified) + loadProcessedModule({code:data,name:modName}); + else + Espruino.callProcessor("transformModuleForEspruino", {code:data,name:modName}, loadProcessedModule); + } + + /** Given a module name (which could be a URL), try and find it. Return + * a deferred thingybob which signals when we're done. */ + function loadModule(requires, fullModuleName, loadedModuleData) { + return new Promise(function(resolve, reject) { + // First off, try and find this module using callProcessor + Espruino.callProcessor("getModule", + { moduleName:fullModuleName, moduleCode:undefined, isMinified:false }, + function(data) { + if (data.moduleCode===undefined) { + Espruino.Core.Notifications.warning("Module "+fullModuleName+" not found"); + return resolve(); + } + + // great! it found something. Use it. + moduleLoaded(resolve, requires, fullModuleName, data.moduleCode, loadedModuleData, data.isMinified); + }); + }); + } + + /** Finds instances of 'require' and then ensures that + those modules are loaded into the module cache beforehand + (by inserting the relevant 'addCached' commands into 'code' */ + function loadModules(code, callback){ + var loadedModuleData = []; + var requires = getModulesRequired(code); + if (requires.length == 0) { + // no modules needed - just return + callback(code); + } else { + Espruino.Core.Status.setStatus("Loading modules"); + // Kick off the module loading (each returns a promise) + var promises = requires.map(function (moduleName) { + return loadModule(requires, moduleName, loadedModuleData); + }); + // When all promises are complete + Promise.all(promises).then(function(){ + callback(loadedModuleData.join("\n") + "\n" + code); + }); + } + } + + function loadModulesRollup(code, callback) { + rollupTools.loadModulesRollup(code) + .then(generated => { + const minified = generated.code; + console.log('rollup: '+minified.length+' bytes'); + + // FIXME: needs warnings? + Espruino.Core.Notifications.info('Rollup no errors. Bundling ' + code.length + ' bytes to ' + minified.length + ' bytes'); + callback(minified); + }) + .catch(err => { + console.log('rollup:error', err); + Espruino.Core.Notifications.error("Rollup errors - Bundling failed: " + String(err).trim()); + callback(code); + }); + } + + Espruino.Core.Modules = { + init : init + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Board Environment variables (process.env) - queried when board connects + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + var environmentData = {}; + var boardData = {}; + + function init() { + Espruino.Core.Config.add("ENV_ON_CONNECT", { + section : "Communications", + name : "Request board details on connect", + description : 'Just after the board is connected, should we query `process.env` to find out which board we\'re connected to? '+ + 'This enables the Web IDE\'s code completion, compiler features, and firmware update notice.', + type : "boolean", + defaultValue : true, + }); + + Espruino.addProcessor("connected", function(data, callback) { + // Give us some time for any stored data to come in + setTimeout(queryBoardProcess, 200, data, callback); + }); + } + + function queryBoardProcess(data, callback) { + if ((!Espruino.Config.ENV_ON_CONNECT) || + (Espruino.Core.MenuFlasher && Espruino.Core.MenuFlasher.isFlashing())) { + return callback(data); + } + + Espruino.Core.Utils.executeExpression("process.env", function(result) { + var json = {}; + if (result!==undefined) { + try { + json = JSON.parse(result); + } catch (e) { + console.log("JSON parse failed - " + e + " in " + JSON.stringify(result)); + } + } + if (Object.keys(json).length==0) { + Espruino.Core.Notifications.error("Unable to retrieve board information.\nConnection Error?"); + // make sure we don't remember a previous board's info + json = { + VERSION : undefined, + BOARD : undefined, + MODULES : undefined, + EXPTR : undefined + }; + } else { + if (json.BOARD && json.VERSION) + Espruino.Core.Notifications.info("Found " +json.BOARD+", "+json.VERSION); + } + // now process the enviroment variables + for (var k in json) { + boardData[k] = json[k]; + environmentData[k] = json[k]; + } + if (environmentData.VERSION) { + var v = environmentData.VERSION; + var vIdx = v.indexOf("v"); + if (vIdx>=0) { + environmentData.VERSION_MAJOR = parseInt(v.substr(0,vIdx)); + var minor = v.substr(vIdx+1); + var dot = minor.indexOf("."); + if (dot>=0) + environmentData.VERSION_MINOR = parseInt(minor.substr(0,dot)) + parseInt(minor.substr(dot+1))*0.001; + else + environmentData.VERSION_MINOR = parseFloat(minor); + } + } + + Espruino.callProcessor("environmentVar", environmentData, function(envData) { + environmentData = envData; + callback(data); + }); + }); + } + + /** Get all data merged in from the board */ + function getData() { + return environmentData; + } + + /** Get just the board's environment data */ + function getBoardData() { + return boardData; + } + + /** Get a list of boards that we know about */ + function getBoardList(callback) { + var jsonDir = Espruino.Config.BOARD_JSON_URL; + + // ensure jsonDir ends with slash + if (jsonDir.indexOf('/', jsonDir.length - 1) === -1) { + jsonDir += '/'; + } + + Espruino.Core.Utils.getJSONURL(jsonDir + "boards.json", function(boards){ + // now load all the individual JSON files + var promises = []; + for (var boardId in boards) { + promises.push((function() { + var id = boardId; + return new Promise(function(resolve, reject) { + Espruino.Core.Utils.getJSONURL(jsonDir + boards[boardId].json, function (data) { + boards[id]["json"] = data; + resolve(); + }); + }); + })()); + } + + // When all are loaded, load the callback + Promise.all(promises).then(function() { + callback(boards); + }); + }); + } + + Espruino.Core.Env = { + init : init, + getData : getData, + getBoardData : getBoardData, + getBoardList : getBoardList, + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Automatically run an assembler on inline assembler statements + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + var base64_encode; + if (typeof btoa == "undefined") + base64_encode = function(s) { return Buffer.from(s).toString('base64'); }; + else + base64_encode = btoa; + +/* Thumb reference : + http://ece.uwaterloo.ca/~ece222/ARM/ARM7-TDMI-manual-pt3.pdf + + ARM reference + https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf +*/ + + // list of registers (for push/pop type commands) + function rlist_lr(value) { + var regs = value.split(","); + var vals = { r0:1,r1:2,r2:4,r3:8,r4:16,r5:32,r6:64,r7:128,lr:256 }; + var bits = 0; + for (var i in regs) { + var reg = regs[i].trim(); + if (!(reg in vals)) throw "Unknown register name "+reg; + bits |= vals[reg]; + } + return bits; + } + + function reg(reg_offset) { + return function(reg) { + var vals = { r0:0,r1:1,r2:2,r3:3,r4:4,r5:5,r6:6,r7:7 }; + if (!(reg in vals)) throw "Unknown register name "+reg; + return vals[reg]<=0 && regVal<8) + return ((regVal&7)<=0 && v<65536) { + // https://web.eecs.umich.edu/~prabal/teaching/eecs373-f11/readings/ARMv7-M_ARM.pdf page 347 + var imm4,i,imm3,imm8; // what the...? + imm4 = (v>>12)&15; + i = (v>>11)&1; + imm3 = (v>>8)&7; + imm8 = v&255; + return (i<<26) | (imm4<<16) | (imm3<<12) | imm8; + } + throw "Invalid number '"+value+"' - must be between 0 and 65535"; + } + + function _int(offset, bits, shift, signed) { + return function(value, labels) { + var maxValue = ((1<= 0) { + addValue = parseInt(value.substr(maths)); + value = value.substr(0,maths); + } + if (value in labels) + binValue = labels[value] + addValue - labels["PC"]; + else + throw "Unknown label '"+value+"'"; + } + + + //console.log("VALUE----------- "+binValue+" PC "+labels["PC"]+" L "+labels[value]); + + if (binValue>=minValue && binValue<=maxValue && (binValue&((1<> shift) & ((1<>11)&0x7FF)<<16 | (v&0x7FF); + }; + } + + var ops = { + // Format 1: move shifted register + "lsl" :[{ base:"00000-----___---", regex : /(r[0-7]),(r[0-7]),(#[0-9]+)$/, args:[reg(0),reg(3),uint(6,5,0)] }, + { base:"0100000010___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // 5.4 d = d << s + "lsr" :[{ base:"00001-----___---", regex : /(r[0-7]),(r[0-7]),(#[0-9]+)$/, args:[reg(0),reg(3),uint(6,5,0)] }, + { base:"0100000011___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // 5.4 d = d >> s + "asr" :[{ base:"00010-----___---", regex : /(r[0-7]),(r[0-7]),(#[0-9]+)$/, args:[reg(0),reg(3),uint(6,5,0)] }, + { base:"0100000100___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // 5.4 d = d >>> s + // 5.2 Format 2: add/subtract + // 00011 + // 5.3 Format 3: move/compare/add/subtract immediate + "cmp" :[{ base:"00101---________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg(8),uint(0,8,0)] }, // move/compare/subtract immediate + { base:"0100001010___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // 5.4 test d-s + // 5.4 Format 4: ALU operations + "and" :[{ base:"0100000000___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], + "eor" :[{ base:"0100000001___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], + // lsl is above + // lsr is above + // asr is above + "adc" :[{ base:"0100000101___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // d + s + carry + "sbc" :[{ base:"0100000110___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // d - s - !carry + "ror" :[{ base:"0100000111___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // rotate right + "tst" :[{ base:"0100001000___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // test + "neg" :[{ base:"0100001001___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // - s + // cmp is above + "cmn" :[{ base:"0100001011___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // test d+s + "orr" :[{ base:"0100001100___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // | + "mul" :[{ base:"0100001101___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // s*d + "bic" :[{ base:"0100001110___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // d & ~s + "mvn" :[{ base:"0100001111___---", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }], // ~s + // 5.5 Format 5: Hi register operations/branch exchange + // 5.6 Format 6: PC-relative load + // done (below) + // 5.7 Format 7: load/store with register offset + // done (below) + // 5.8 Format 8: load/store sign-extended byte/halfword + // 5.9 Format 9: load/store with immediate offset + // done (below) + // 5.10 Format 10: load/store halfword + // 5.11 Format 11: SP-relative load/store + // 5.12 Format 12: load address + // done (below) + // 5.13 Format 13: add offset to Stack Pointer + // 5.14 Format 14: push/pop registers + // done (below) + // 5.16 Format 16: conditional branch + "beq" :[{ base:"11010000________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bne" :[{ base:"11010001________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bcs" :[{ base:"11010010________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bcc" :[{ base:"11010011________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bmi" :[{ base:"11010100________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bpl" :[{ base:"11010101________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bvs" :[{ base:"11010110________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bvc" :[{ base:"11010111________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bhi" :[{ base:"11011000________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bls" :[{ base:"11011001________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bge" :[{ base:"11011010________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "blt" :[{ base:"11011011________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "bgt" :[{ base:"11011100________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + "ble" :[{ base:"11011101________", regex : /^(.*)$/, args:[sint(0,8,1)] }], // 5.16 Format 16: conditional branch + // 5.17 Format 17: software interrupt + // 5.18 Format 18: unconditional branch + "b" :[{ base:"11100___________", regex : /^(.*)$/, args:[sint(0,11,1)] }], + // 5.19 Format 19: long branch with link + "bl" :[{ base:"11110___________11111___________", regex : /^(.*)$/, args:[bl_addr()] }], + "bx" :[{ base:"010001110----000", regex : /(lr|r[0-9]+)$/, args:[reg4(3)] }], + // .... + + + "adr" :[{ base:"10100---________", regex : /^(r[0-7]),([a-zA-Z_][0-9a-zA-Z_]*)$/,args:[reg(8),uint(0,8,2)] }, // ADR pseudo-instruction to save address (actually ADD PC) + { base:"10100---________", regex : /^(r[0-7]),([a-zA-Z_][0-9a-zA-Z_]*\+[0-9]+)$/,args:[reg(8),uint(0,8,2)] }], + "push" :[{ base:"1011010-________", regex : /^{(.*)}$/, args:[rlist_lr] }], // 5.14 Format 14: push/pop registers + "pop" :[{ base:"1011110-________", regex : /^{(.*)}$/, args:[rlist_lr] }], // 5.14 Format 14: push/pop registers + "add" :[{ base:"00110---________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg(8),uint(0,8,0)] }, // move/compare/subtract immediate + { base:"10100---________", regex : /^(r[0-7]),pc,(#[0-9]+)$/,args:[reg(8),uint(0,8,2)] }, + { base:"10101---________", regex : /^(r[0-7]),sp,(#[0-9]+)$/, args:[reg(8),uint(0,8,2)] }, + { base:"101100000_______", regex : /^sp,(#[0-9]+)$/, args:[uint(0,7,2)] }, + { base:"00011-0___---___", regex : /^(r[0-7]),(r[0-7]),([^,]+)$/, args:[reg(0),reg(3),reg_or_immediate(6,10)] } ], // Format 2: add/subtract + "adds" :[{ base:"00011-0___---___", regex : /^(r[0-7]),(r[0-7]),([^,]+)$/, args:[reg(0),reg(3),reg_or_immediate(6,10)] } ], //? + "adc.w":[{ base:"111010110100----________--------", regex : /^(r[0-7]),(r[0-7]),(r[0-7])$/,args:[reg(16),reg(8),reg(0)] }], // made this up. probably wrong + "add.w":[{ base:"11110001--------________--------", regex : /^(r[0-7]),(r[0-7]),(#[0-9]+)$/,args:[reg(16),reg(8),uint(0,8,0)] }], // made this up. probably wrong + "sub" :[{ base:"00111---________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg(8),uint(0,8,0)] }, // move/compare/subtract immediate + /*{ base:"10100---________", regex : /^([^,]+),pc,(#[0-9]+)$/,args:[reg(8),uint(0,8,2)] },*/ + { base:"101100001_______", regex : /^sp,(#[0-9]+)$/, args:[uint(0,7,2)] }, + { base:"00011-1___---___", regex : /^([^,]+),([^,]+),([^,]+)$/, args:[reg(0),reg(3),reg_or_immediate(6,10)] } ], + + "str" :[{ base:"0101000---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"10010---________", regex : /(r[0-7]),\[sp,(#[0-9]+)\]$/, args:[reg(8),uint(0,8,2)] }, // 5.11 SP-relative store + { base:"0110000000___---", regex : /(r[0-7]),\[(r[0-7])\]$/, args:[reg(0),reg(3)] }, // 5.9 Format 9: load/store with no offset + { base:"0110000---___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,2)] }], // 5.9 Format 9: load/store with immediate offset + "strb" :[{ base:"0101010---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"01110-----___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,0)] }], // 5.9 Format 9: load/store with immediate offset + "strh" :[{ base:"0101001---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"10000-----___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,1)] }], // 5.9 Format 9: load/store with immediate offset + "ldr" :[{ base:"01001---________", regex : /(r[0-7]),\[pc,(#[0-9]+)\]$/, args:[reg(8),uint(0,8,2)] }, // 5.6 Format 6: PC-relative load + { base:"10011---________", regex : /(r[0-7]),\[sp,(#[0-9]+)\]$/, args:[reg(8),uint(0,8,2)] }, // 5.11 SP-relative load + { base:"01001---________", regex : /(r[0-7]),([a-zA-Z_][0-9a-zA-Z_]*)$/, args:[reg(8),uint(0,8,2)] }, // 5.6 Format 6: PC-relative load (using label) + { base:"01001---________", regex : /(r[0-7]),([a-zA-Z_][0-9a-zA-Z_]*\+[0-9]+)$/, args:[reg(8),uint(0,8,2)] }, // 5.6 Format 6: PC-relative load (using label and maths - huge hack) + { base:"0101100---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"0110100000___---", regex : /(r[0-7]),\[(r[0-7])\]$/, args:[reg(0),reg(3)] }, // 5.9 Format 9: load/store with no offset + { base:"0110100---___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,2)] }], // 5.9 Format 9: load/store with immediate offset + + "ldrb" :[{ base:"0101110---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"01111-----___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,0)] }], // 5.9 Format 9: load/store with immediate offset + "ldrsb":[{ base:"0101011---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }], // 5.7 Format 7: load/store with register offset + "ldrh" :[{ base:"0101101---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }, // 5.7 Format 7: load/store with register offset + { base:"10001-----___---", regex : /(r[0-7]),\[(r[0-7]),(#[0-9]+)\]$/, args:[reg(0),reg(3), uint(6,5,1)] }], // 5.9 Format 9: load/store with immediate offset + "ldrsh":[{ base:"0101111---___---", regex : /(r[0-7]),\[(r[0-7]),(r[0-7])\]$/, args:[reg(0),reg(3),reg(6)] }], // 5.7 Format 7: load/store with register offset + "mov" :[{ base:"00100---________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg(8),uint(0,8,0)] }, // move/compare/subtract immediate + { base:"0001110000---___", regex : /(r[0-7]),(r[0-7])$/, args:[reg(0),reg(3)] }, // actually 'add Rd,Rs,#0' + { base:"0100011010---101", regex : /sp,(r[0-7])$/, args:[reg(3)] }], // made up again + "movs" :[{ base:"00100---________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg(8),uint(0,8,0)] }], // is this even in thumb? + "movw" :[{ base:"11110-100100----0___----________", regex : /(r[0-7]),(#[0-9]+)$/, args:[reg4(8),thumb2_immediate_t3] }], + + ".word":[{ base:"--------------------------------", regex : /0x([0-9A-Fa-f]+)$/, args:[function(v){v=parseInt(v,16);return (v>>16)|(v<<16);}] }, + { base:"--------------------------------", regex : /([0-9]+)$/, args:[function(v){v=parseInt(v);return (v>>16)|(v<<16);}] }], + "nop" :[{ base:"0100011011000000", regex : "", args:[] }], // MOV R8,R8 (Format 5) + "cpsie" :[{ base:"1011011001100010", regex : /i/, args:[] }], // made up again + "cpsid" :[{ base:"1011011001110010", regex : /i/, args:[] }], // made up again + "wfe" :[{ base:"1011111100100000", regex : /i/, args:[] }], + "wfi" :[{ base:"1011111100110000", regex : /i/, args:[] }], + + // for this, uint needs to work without a hash +// "swi" :[{ base:"11011111--------", regex : /([0-9]+)$/, args:[uint(0,8,0)] }], // Format 17: software interrupt + }; + + + function getOpCode(binary) { + var base = ""; + for (var b in binary) + if ("-_".indexOf(binary[b])>=0) + base += "0"; + else + base += binary[b]; + var opCode = parseInt(base,2); + if (opCode<0) opCode = opCode + 2147483648.0; + return opCode; + } + + function assemble_internal(asmLines, wordCallback, labels) { + var addr = 0; + var newLabels = {}; + asmLines.forEach(function (line) { + // setup labels + if (labels!==undefined) + labels["PC"] = addr+4; + // handle line + line = line.trim(); + if (line=="") return; + if (line.substr(-1)==":") { + // it's a label + var labelName = line.substr(0,line.length-1); + if (newLabels[labelName] !== undefined) + throw "Label '"+labelName+"' was already defined"; + newLabels[labelName] = addr; + return; + } + + // parse instruction + var firstArgEnd = line.indexOf("\t"); + if (firstArgEnd<0) firstArgEnd = line.indexOf(" "); + if (firstArgEnd<0) firstArgEnd=line.length; + var opName = line.substr(0,firstArgEnd); + var args = line.substr(firstArgEnd).replace(/[ \t]/g,"").trim(); + if (!(opName in ops)) throw "Unknown Op '"+opName+"' in '"+line+"'"; + // search ops + var found = false; + for (var n in ops[opName]) { + var op = ops[opName][n]; + var m; + if (m=args.match(op.regex)) { + found = true; + // work out the base opcode + var opCode = getOpCode(op.base); + + if (labels!==undefined) { + /* If we're properly generating code, parse each argument. + Otherwise we're just working out the size in bytes of each line + and we can skip this */ + for (var i in op.args) { + //console.log(i,m[(i|0)+1]); + var argFunction = op.args[i]; + var bits = argFunction(m[(i|0)+1], labels); + //console.log(" ",bits) + opCode |= bits; + } + } + + if (op.base.length > 16) { + wordCallback((opCode>>>16)); + wordCallback(opCode&0xFFFF); + addr += 4; + } else { + wordCallback(opCode); + addr += 2; + } + break; + } + } + // now parse args + if (!found) + throw "Unknown arg style '"+args+"' in '"+line+"'"; + }); + return newLabels; + } + + function assemble(asmLines, wordCallback) { + // remove line comments + asmLines = asmLines.map(function(l) { + var i; + i = l.indexOf(";"); + if (i>=0) l = l.substr(0,i); + i = l.indexOf("//"); + if (i>=0) l = l.substr(0,i); + return l; + }); + // process assembly to grab labels + var labels = assemble_internal(asmLines, function() {}, undefined); + console.log("Assembler Labels:",labels); + // process again to actually get an output + assemble_internal(asmLines, wordCallback, labels); + } + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + + function init() { + // When code is sent to Espruino, search it for bits of assembler and then assemble them + Espruino.addProcessor("transformForEspruino", function(code, callback) { + findASMBlocks(code, "", callback); + }); + // When a module is sent to Espruino... + Espruino.addProcessor("transformModuleForEspruino", function(module, callback) { + findASMBlocks(module.code, " in "+module.name, function(code) { + module.code = code; + callback(module); + }); + }); + } + + function assembleBlock(asmLines, description) { + var machineCode = []; + try { + assemble(asmLines, function(word) { machineCode.push("0x"+word.toString(16)); }); + } catch (err) { + console.log("Assembler failed: "+err+description); + Espruino.Core.Notifications.error("Assembler failed: "+err+description); + return undefined; + } + + return machineCode; + } + + /* Finds instances of 'E.asm' and replaces them */ + function findASMBlocks(code, description, callback){ + + function match(str, type) { + if (str!==undefined && tok.str!=str) { + Espruino.Core.Notifications.error("Expecting '"+str+"' but got '"+tok.str+description+"'. Should have E.asm('arg spec', 'asmline1', ..., 'asmline2'"); + return false; + } + if (type!==undefined && tok.type!=type) { + Espruino.Core.Notifications.error("Expecting a "+type+" but got "+tok.type+description+". Should have E.asm('arg spec', 'asmline1', ..., 'asmline2'"); + return false; + } + tok = lex.next(); + return true; + } + + var foundAsm = true; + var assembledCode = ""; + var asmBlockCount = 1; + while (foundAsm) { + foundAsm = false; + var lex = Espruino.Core.Utils.getLexer(code); + var tok = lex.next(); + var state = 0; + var startIndex = -1; + while (tok!==undefined) { + if (state==0 && tok.str=="E") { state=1; startIndex = tok.startIdx; tok = lex.next(); + } else if (state==1 && tok.str==".") { state=2; tok = lex.next(); + } else if (state==2 && (tok.str=="asm")) { state=3; tok = lex.next(); + } else if (state==3 && (tok.str=="(")) { + foundAsm = true; + state=0; + tok = lex.next(); // skip ( + var argSpec = tok.value; + var asmLines = []; + if (!match(undefined,"STRING")) return; + if (!match(",",undefined)) return; + while (tok && tok.str!=")") { + var lines = tok.value.split("\n"); + lines.forEach(function(l) { + asmLines.push(l); + }); + if (!match(undefined,"STRING")) return; + if (tok.str!=")") + if (!match(",",undefined)) return; + } + if (!match(")",undefined)) return; + var endIndex = tok.endIdx; + + var machineCode = assembleBlock(asmLines, description); + //console.log(machineCode); + if (machineCode===undefined) return; // There was an error - just leave and don't try to flash + var raw = ""; + machineCode.forEach(function(short) { + var v = parseInt(short,16); + raw += String.fromCharCode(v&255,v>>8); + }); + var base64 = base64_encode(raw); + code = code.substr(0,startIndex) + + 'E.nativeCall(1, '+JSON.stringify(argSpec)+', atob('+JSON.stringify(base64)+'))'+ + code.substr(endIndex); + asmBlockCount++; + + // Break out + tok = undefined; + } else { + state = 0; + tok = lex.next(); + } + } + } + + if (assembledCode!="") { + code = "var ASM_BASE=process.memory().stackEndAddress;\n"+ + assembledCode+ + code; + } + callback(code); + }; + + + Espruino.Plugins.Assembler = { + init : init, + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Try and get any URLS that are from GitHub + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + function init() { + Espruino.addProcessor("getURL", getGitHub); + } + + function getGitHub(data, callback) { + var match = data.url.match(/^https?:\/\/github.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.*)$/); + if (match) { + var git = { + owner : match[1], + repo : match[2], + branch : match[3], + path : match[4] + }; + + var url = "https://raw.githubusercontent.com/"+git.owner+"/"+git.repo+"/"+git.branch+"/"+git.path; + console.log("Found GitHub", JSON.stringify(git)); + callback({url: url}); + } else + callback(data); // no match - continue as normal + } + + Espruino.Plugins.GetGitHub = { + init : init, + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Pretokenise code before it uploads + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + if (typeof acorn == "undefined") { + console.log("pretokenise: needs acorn, disabling."); + } + + function init() { + Espruino.Core.Config.add("PRETOKENISE", { + section : "Minification", + name : "Pretokenise code before upload (BETA)", + description : "All whitespace and comments are removed and all reserved words are converted to tokens before upload. This means a faster upload, less memory used, and increased performance (+10%) at the expense of code readability.", + type : "boolean", + defaultValue : false + }); + + // When code is sent to Espruino, search it for modules and add extra code required to load them + Espruino.addProcessor("transformForEspruino", function(code, callback) { + if (!Espruino.Config.PRETOKENISE) return callback(code); + pretokenise(code, callback); + }); + // When code is sent to Espruino, search it for modules and add extra code required to load them + Espruino.addProcessor("transformModuleForEspruino", function(module, callback) { + if (!Espruino.Config.PRETOKENISE) return callback(module); + pretokenise(module.code, function(code) { + module.code = code; + callback(module); + }); + }); + } + + + var LEX_OPERATOR_START = 138; + var TOKENS = [// plundered from jslex.c +/* 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 : */ "^=", +/* LEX_ARROW_FUNCTION */ "=>", +// reserved words +/*LEX_R_IF : */ "if", +/*LEX_R_ELSE : */ "else", +/*LEX_R_DO : */ "do", +/*LEX_R_WHILE : */ "while", +/*LEX_R_FOR : */ "for", +/*LEX_R_BREAK : */ "break", +/*LEX_R_CONTINUE */ "continue", +/*LEX_R_FUNCTION */ "function", +/*LEX_R_RETURN */ "return", +/*LEX_R_VAR : */ "var", +/*LEX_R_LET : */ "let", +/*LEX_R_CONST : */ "const", +/*LEX_R_THIS : */ "this", +/*LEX_R_THROW : */ "throw", +/*LEX_R_TRY : */ "try", +/*LEX_R_CATCH : */ "catch", +/*LEX_R_FINALLY : */ "finally", +/*LEX_R_TRUE : */ "true", +/*LEX_R_FALSE : */ "false", +/*LEX_R_NULL : */ "null", +/*LEX_R_UNDEFINED */ "undefined", +/*LEX_R_NEW : */ "new", +/*LEX_R_IN : */ "in", +/*LEX_R_INSTANCEOF */ "instanceof", +/*LEX_R_SWITCH */ "switch", +/*LEX_R_CASE */ "case", +/*LEX_R_DEFAULT */ "default", +/*LEX_R_DELETE */ "delete", +/*LEX_R_TYPEOF : */ "typeof", +/*LEX_R_VOID : */ "void", +/*LEX_R_DEBUGGER : */ "debugger", +/*LEX_R_CLASS : */ "class", +/*LEX_R_EXTENDS : */ "extends", +/*LEX_R_SUPER : */ "super", +/*LEX_R_STATIC : */ "static", +/*LEX_R_OF : */ "of" +]; + + + function pretokenise(code, callback) { + var lex = (function() { + var t = acorn.tokenizer(code); + return { next : function() { + var tk = t.getToken(); + if (tk.type.label=="eof") return undefined; + var tp = "?"; + if (tk.type.label=="template" || tk.type.label=="string") tp="STRING"; + if (tk.type.label=="num") tp="NUMBER"; + if (tk.type.keyword) tp="ID"; + return { + startIdx : tk.start, + endIdx : tk.end, + str : code.substr(tk.start, tk.end), + type : tp + }; + }}; + })(); + var brackets = 0; + var resultCode = ""; + var lastIdx = 0; + var lastTok = {str:""}; + var tok = lex.next(); + while (tok!==undefined) { + var previousString = code.substring(lastIdx, tok.startIdx); + var tokenString = code.substring(tok.startIdx, tok.endIdx); + var tokenId = LEX_OPERATOR_START + TOKENS.indexOf(tokenString); + if (tokenId=0) + resultCode += "\n"; + if (tok.str==")" || tok.str=="}" || tok.str=="]") brackets--; + // if we have a token for something, use that - else use the string + if (tokenId) { + //console.log(JSON.stringify(tok.str)+" => "+tokenId); + resultCode += String.fromCharCode(tokenId); + tok.type = "TOKENISED"; + } else { + if ((tok.type=="ID" || tok.type=="NUMBER") && + (lastTok.type=="ID" || lastTok.type=="NUMBER")) + resultCode += " "; + resultCode += tokenString; + } + // next + lastIdx = tok.endIdx; + lastTok = tok; + tok = lex.next(); + } + callback(resultCode); + } + + Espruino.Plugins.Pretokenise = { + init : init, + }; +}()); +/** + Copyright 2014 Gordon Williams (gw@pur3.co.uk), + Victor Nakoryakov (victor@amperka.ru) + + This Source Code is subject to the terms of the Mozilla Public + License, v2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + ------------------------------------------------------------------ + Wrap whole code in `onInit` function before send and save() it + after upload. Wrapping is necessary to avoid execution start + before save() is executed + ------------------------------------------------------------------ +**/ +"use strict"; +(function(){ + + function init() { + Espruino.Core.Config.add("SAVE_ON_SEND", { + section : "Communications", + name : "Save on Send", + descriptionHTML : 'How should code be uploaded? See espruino.com/Saving for more information.
'+ + "NOTE: Avoid 'Direct to flash, even after reset()' for normal development - it can make it hard to recover if your code crashes the device.", + type : { + 0: "To RAM (default) - execute code while uploading. Use 'save()' to save a RAM image to Flash", + 1: "Direct to Flash (execute code at boot)", + 2: "Direct to Flash (execute code at boot, even after 'reset()') - USE WITH CARE", + 3: "To Storage File (see 'File in Storage to send to')", + }, + defaultValue : 0 + }); + Espruino.Core.Config.add("SAVE_STORAGE_FILE", { + section : "Communications", + name : "Send to File in Storage", + descriptionHTML : "If Save on Send is set to To Storage File, this is the name of the file to write to.", + type : "string", + defaultValue : "myapp" + }); + Espruino.Core.Config.add("LOAD_STORAGE_FILE", { + section : "Communications", + name : "Load after saving", + descriptionHTML : "This applies only if saving to Flash (not RAM)", + type : { + 0: "Don't load", + 1: "Load default application", + 2: "Load the Storage File just written to" + }, + defaultValue : 2 + }); + Espruino.addProcessor("transformForEspruino", function(code, callback) { + wrap(code, callback); + }); + } + + function wrap(code, callback) { + var isFlashPersistent = Espruino.Config.SAVE_ON_SEND == 2; + var isStorageUpload = Espruino.Config.SAVE_ON_SEND == 3; + var isFlashUpload = Espruino.Config.SAVE_ON_SEND == 1 || isFlashPersistent || isStorageUpload; + if (!isFlashUpload) return callback(code); + + // Check environment vars + var hasStorage = false; + var ENV = Espruino.Core.Env.getData(); + if (ENV && + ENV.VERSION_MAJOR && + ENV.VERSION_MINOR!==undefined) { + if (ENV.VERSION_MAJOR>1 || + ENV.VERSION_MINOR>=96) { + hasStorage = true; + } + } + + // + console.log("Uploading "+code.length+" bytes to flash"); + if (!hasStorage) { // old style + if (isStorageUpload) { + Espruino.Core.Notifications.error("You have pre-1v96 firmware - unable to upload to Storage"); + code = ""; + } else { + Espruino.Core.Notifications.error("You have pre-1v96 firmware. Upload size is limited by available RAM"); + code = "E.setBootCode("+JSON.stringify(code)+(isFlashPersistent?",true":"")+");load()\n"; + } + } else { // new style + var filename; + if (isStorageUpload) + filename = Espruino.Config.SAVE_STORAGE_FILE; + else + filename = isFlashPersistent ? ".bootrst" : ".bootcde"; + if (!filename || filename.length>28) { + Espruino.Core.Notifications.error("Invalid Storage file name "+JSON.stringify(filename)); + code = ""; + } else { + var CHUNKSIZE = 1024; + var newCode = []; + var len = code.length; + newCode.push('require("Storage").write("'+filename+'",'+JSON.stringify(code.substr(0,CHUNKSIZE))+',0,'+len+');'); + for (var i=CHUNKSIZE;i{ try { @@ -143,23 +147,18 @@ function handleAppInterface(app) { }); } -function getAppFavourites() { - var f = localStorage.getItem(FAVOURITE); - return (f === null) ? ["boot","launch","setting"] : JSON.parse(f); -} - function changeAppFavourite(favourite, app) { - var favourites = getAppFavourites(); + var favourites = SETTINGS.favourites; if (favourite) { - favourites = favourites.concat([app.id]); + SETTINGS.favourites = SETTINGS.favourites.concat([app.id]); } else { if ([ "boot","setting"].includes(app.id)) { showToast(app.name + ' is required, can\'t remove it' , 'warning'); }else { - favourites = favourites.filter(e => e != app.id); + SETTINGS.favourites = SETTINGS.favourites.filter(e => e != app.id); } } - localStorage.setItem(FAVOURITE, JSON.stringify(favourites)); + saveSettings(); refreshLibrary(); } @@ -192,7 +191,7 @@ function refreshFilter(){ function refreshLibrary() { var panelbody = document.querySelector("#librarycontainer .panel-body"); var visibleApps = appJSON; - var favourites = getAppFavourites(); + var favourites = SETTINGS.favourites; if (activeFilter) { if ( activeFilter == "favourites" ) { @@ -590,6 +589,49 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } +// Settings +var SETTINGS_HOOKS = {}; // stuff to get called when a setting is loaded +/// Load settings and update controls +function loadSettings() { + var j = localStorage.getItem("settings"); + if (typeof j != "string") return; + try { + var s = JSON.parse(j); + Object.keys(s).forEach( k => { + SETTINGS[k]=s[k]; + if (SETTINGS_HOOKS[k]) SETTINGS_HOOKS[k](); + } ); + } catch (e) { + console.error("Invalid settings"); + } +} +/// Save settings +function saveSettings() { + localStorage.setItem("settings", JSON.stringify(SETTINGS)); + console.log("Changed settings", SETTINGS); +} +// Link in settings DOM elements +function settingsCheckbox(id, name) { + var setting = document.getElementById(id); + function update() { + setting.checked = SETTINGS[name]; + } + SETTINGS_HOOKS[name] = update; + setting.addEventListener('click', function() { + SETTINGS[name] = setting.checked; + saveSettings(); + }); +} +settingsCheckbox("settings-pretokenise", "pretokenise"); +loadSettings(); + +document.getElementById("defaultsettings").addEventListener("click",event=>{ + SETTINGS = JSON.parse(JSON.stringify(DEFAULTSETTINGS)); // clone + saveSettings(); + loadSettings(); // update all settings + refreshLibrary(); // favourites were in settings +}); + document.getElementById("settime").addEventListener("click",event=>{ Comms.setTime().then(()=>{ showToast("Time set successfully","success"); @@ -620,9 +662,9 @@ document.getElementById("installdefault").addEventListener("click",event=>{ }); }); -// Install all favoutrie apps in one go +// Install all favourite apps in one go document.getElementById("installfavourite").addEventListener("click",event=>{ - var favApps = getAppFavourites(); + var favApps = SETTINGS.favourites; installMultipleApps(favApps, "favourite").catch(err=>{ Progress.hide({sticky:true}); showToast("App Install failed, "+err,"error");