diff --git a/editor.html b/editor.html
index 372fb44e..2ada46a4 100644
--- a/editor.html
+++ b/editor.html
@@ -253,6 +253,7 @@
{{ title }}
+
@@ -270,11 +271,13 @@ {{ title }}
-
+
+
diff --git a/hexlify.js b/hexlify.js
new file mode 100644
index 00000000..56801516
--- /dev/null
+++ b/hexlify.js
@@ -0,0 +1,129 @@
+/**
+ * Module to add and remove Python scripts into and from a MicroPython hex.
+ */
+var upyhex = (function() {
+ 'use strict';
+
+ /** User script located at specific flash address. */
+ var USER_CODE_START_ADDR = 0x3e000;
+ var USER_CODE_LEN = 8 * 1024;
+ var USER_CODE_END_ADDR = USER_CODE_START_ADDR + USER_CODE_LEN;
+
+ /** User code header */
+ var USER_CODE_HEADER_SIZE = 4;
+ var USER_CODE_HEADER_START_B0_INDEX = 0;
+ var USER_CODE_HEADER_START_B1_INDEX = 1;
+ var USER_CODE_HEADER_LEN_LSB_INDEX = 2;
+ var USER_CODE_HEADER_LEN_MSB_INDEX = 3;
+
+ /** Number of data bytes per Intel Hex record (line). */
+ var INTEL_HEX_BYTE_CNT = 16;
+
+ /** Start of user script marked by "MP" + 2 bytes for the script length. */
+ var USER_CODE_HEADER_START_B0 = 77; // 'M'
+ var USER_CODE_HEADER_START_B1 = 80; // 'P'
+
+ /**
+ * String placed inside the MicroPython hex string to indicate where to
+ * paste the Python Code
+ * */
+ var HEX_INSERTION_POINT = ":::::::::::::::::::::::::::::::::::::::::::\n";
+
+ /**
+ * Converts a string into a byte array of characters.
+ * TODO: Update to encode to UTF-8 correctly.
+ * @param {Uint8Array|Object[]} byteArray - Array of bytes to convert.
+ * @return {string} String output from the conversion.
+ */
+ function strToBytes(str) {
+ var data = new Uint8Array(str.length);
+ for (var i = 0; i < str.length; i++) {
+ // TODO: This will only keep the LSB from the UTF-16 code points
+ data[i] = str.charCodeAt(i);
+ }
+ return data;
+ }
+
+ /**
+ * Converts a byte array into a string of characters.
+ * TODO: This currently only deals with single byte characters, so needs to
+ * be expanded to support UTF-8 characters longer than 1 byte.
+ * @param {Uint8Array|Object[]} byteArray - Array of bytes to convert.
+ * @return {string} String output from the conversion.
+ */
+ function bytesToStr(byteArray) {
+ var result = [];
+ for (var i = 0; i < byteArray.length; i++) {
+ result.push(String.fromCharCode(byteArray[i]));
+ }
+ return result.join('');
+ }
+
+ /**
+ * Removes the old insertion line the input Intel Hex string contains it.
+ * @param {string} intelHexStr String with the intel hex lines.
+ * @return {string} The Intel Hex string without insertion line.
+ */
+ function cleanseOldHexFormat(intelHexStr) {
+ return intelHexStr.replace(HEX_INSERTION_POINT, '');
+ }
+
+ /**
+ * Parses through an Intel Hex string to find the Python code at the
+ * allocated address and extracts it.
+ * @param {string} intelHexStr - Intel Hex block to scan for the code.
+ * @return {string} Python code.
+ */
+ function extractPyStrFromIntelHex(intelHexStr) {
+ var pyCodeStr = '';
+ var hexFileMemMap = MemoryMap.fromHex(intelHexStr);
+ // Check that the known flash location has user code
+ if (hexFileMemMap.has(USER_CODE_START_ADDR)) {
+ var pyCodeMemMap = hexFileMemMap.slice(USER_CODE_START_ADDR, USER_CODE_LEN);
+ var codeBytes = pyCodeMemMap.get(USER_CODE_START_ADDR);
+ if ((codeBytes[USER_CODE_HEADER_START_B0_INDEX] === USER_CODE_HEADER_START_B0) &&
+ (codeBytes[USER_CODE_HEADER_START_B1_INDEX] === USER_CODE_HEADER_START_B1)) {
+ pyCodeStr = bytesToStr(codeBytes.slice(USER_CODE_HEADER_SIZE));
+ // Clean null terminators at the end
+ pyCodeStr = pyCodeStr.replace(/\0/g, '');
+ }
+ }
+ return pyCodeStr;
+ }
+
+ /**
+ * Converts the Python code into the Intel Hex format expected by
+ * MicroPython and injects it into a Intel Hex string containing a marker.
+ * @param {string} intelHexStr - Intel Hex block to inject the code.
+ * @param {string} pyStr - Python code string.
+ * @return {string} Intel Hex string with the Python code injected.
+ */
+ function injectPyStrIntoIntelHex(intelHexStr, pyStr) {
+ var codeBytes = strToBytes(pyStr);
+ var blockLength = USER_CODE_HEADER_SIZE + codeBytes.length;
+ // Check the data block fits in the allocated flash area
+ if (blockLength > USER_CODE_LEN) {
+ throw new RangeError('Too long');
+ }
+ // Older DAPLink versions need the last line to be padded
+ blockLength += INTEL_HEX_BYTE_CNT - (blockLength % INTEL_HEX_BYTE_CNT);
+ // The user script block has to start with "MP" marker + script length
+ var blockBytes = new Uint8Array(blockLength);
+ blockBytes[0] = USER_CODE_HEADER_START_B0;
+ blockBytes[1] = USER_CODE_HEADER_START_B1;
+ blockBytes[2] = codeBytes.length & 0xff;
+ blockBytes[3] = (codeBytes.length >> 8) & 0xff;
+ blockBytes.set(codeBytes, USER_CODE_HEADER_SIZE);
+ // Convert to Intel Hex format
+ intelHexStr = cleanseOldHexFormat(intelHexStr);
+ var intelHexMap = MemoryMap.fromHex(intelHexStr);
+ intelHexMap.set(USER_CODE_START_ADDR, blockBytes);
+ // Older versions of DAPLink need the file to end in a new line
+ return intelHexMap.asHexString() + '\n';
+ }
+
+ return {
+ extractPyStrFromIntelHex: extractPyStrFromIntelHex,
+ injectPyStrIntoIntelHex: injectPyStrIntoIntelHex,
+ };
+}());
diff --git a/python-main.js b/python-main.js
index 2d18be8f..7c7cd0a6 100644
--- a/python-main.js
+++ b/python-main.js
@@ -19,7 +19,7 @@ function pythonEditor(id) {
var ACE = ace.edit(id); // The editor is in the tag with the referenced id.
ACE.setOptions({
enableSnippets: true // Enable code snippets.
- })
+ });
ACE.setTheme("ace/theme/kr_theme"); // Make it look nice.
ACE.getSession().setMode("ace/mode/python"); // We're editing Python.
ACE.getSession().setTabSize(4); // Tab=4 spaces.
@@ -40,7 +40,7 @@ function pythonEditor(id) {
// Give the editor user input focus.
editor.focus = function() {
ACE.focus();
- }
+ };
// Set a handler function to be run if code in the editor changes.
editor.on_change = function(handler) {
@@ -62,110 +62,10 @@ function pythonEditor(id) {
}
};
- /*
- Turn a Python script into Intel HEX format to be concatenated at the
- end of the MicroPython firmware.hex. A simple header is added to the
- script.
-
- - takes a Python script as a string
- - returns hexlified string, with newlines between lines
- */
- editor.hexlify = function(script) {
- function hexlify(ar) {
- var result = '';
- for (var i = 0; i < ar.length; ++i) {
- if (ar[i] < 16) {
- result += '0';
- }
- result += ar[i].toString(16);
- }
- return result;
- }
- // add header, pad to multiple of 16 bytes
- data = new Uint8Array(4 + script.length + (16 - (4 + script.length) % 16));
- data[0] = 77; // 'M'
- data[1] = 80; // 'P'
- data[2] = script.length & 0xff;
- data[3] = (script.length >> 8) & 0xff;
- for (var i = 0; i < script.length; ++i) {
- data[4 + i] = script.charCodeAt(i);
- }
- // check data.length < 0x2000
- if(data.length > 8192) {
- throw new RangeError('Too long');
- }
- // convert to .hex format
- var addr = 0x3e000; // magic start address in flash
- var chunk = new Uint8Array(5 + 16);
- var output = [];
- for (var i = 0; i < data.length; i += 16, addr += 16) {
- chunk[0] = 16; // length of data section
- chunk[1] = (addr >> 8) & 0xff; // high byte of 16-bit addr
- chunk[2] = addr & 0xff; // low byte of 16-bit addr
- chunk[3] = 0; // type (data)
- for (var j = 0; j < 16; ++j) {
- chunk[4 + j] = data[i + j];
- }
- var checksum = 0;
- for (var j = 0; j < 4 + 16; ++j) {
- checksum += chunk[j];
- }
- chunk[4 + 16] = (-checksum) & 0xff;
- output.push(':' + hexlify(chunk).toUpperCase())
- }
- return output.join('\n');
- };
-
// Generates a hex file containing the user's Python from the firmware.
editor.getHexFile = function(firmware) {
- var hexlified_python = this.hexlify(this.getCode());
- var insertion_point = ":::::::::::::::::::::::::::::::::::::::::::";
- return firmware.replace(insertion_point, hexlified_python);
- }
-
- // Takes a hex blob and turns it into a decoded string.
- editor.unhexlify = function(data) {
-
- var hex2str = function(str) {
- var result = '';
- for (var i=0, l=str.length; i 0) {
- var output = [];
- for (var i=0; i 0) {
- var lines = hex_lines.slice(start_line + 1, -5);
- var blob = lines.join('\n');
- if (blob=='') {
- return '';
- } else {
- return this.unhexlify(blob);
- }
- } else {
- return '';
- }
- }
+ return upyhex.injectPyStrIntoIntelHex(firmware, this.getCode());
+ };
// Given a password and some plaintext, will return an encrypted version.
editor.encrypt = function(password, plaintext) {
@@ -185,7 +85,7 @@ function pythonEditor(id) {
output.putBytes(salt);
output.putBuffer(cipher.output);
return encodeURIComponent(btoa(output.getBytes()));
- }
+ };
// Given a password and cyphertext will return the decrypted plaintext.
editor.decrypt = function(password, cyphertext) {
@@ -204,10 +104,10 @@ function pythonEditor(id) {
decipher.update(input);
var result = decipher.finish();
return decipher.output.getBytes();
- }
+ };
return editor;
-};
+}
/*
The following code contains the various functions that connect the behaviour of
@@ -267,7 +167,7 @@ function web_editor(config) {
if (workspace && continueZooming) {
Blockly.getMainWorkspace().zoomCenter(1);
}
- };
+ }
// Sets up the zoom-out functionality.
function zoomOut() {
@@ -286,7 +186,7 @@ function web_editor(config) {
if (workspace && continueZooming) {
Blockly.getMainWorkspace().zoomCenter(-1);
}
- };
+ }
// Checks for feature flags in the config object and shows/hides UI
// elements as required.
@@ -300,7 +200,7 @@ function web_editor(config) {
if(config.flags.share) {
$("#command-share").removeClass('hidden');
}
- };
+ }
// This function is called to initialise the editor. It sets things up so
// the user sees their code or, in the case of a new program, uses some
@@ -319,7 +219,7 @@ function web_editor(config) {
}
vex.open({
content: Mustache.render(template, context)
- })
+ });
$('#button-decrypt-link').click(function() {
var password = $('#passphrase').val();
setName(EDITOR.decrypt(password, message.n));
@@ -335,7 +235,7 @@ function web_editor(config) {
EDITOR.focus();
} else {
// If there's no name, default to something sensible.
- setName("microbit")
+ setName("microbit");
// If there's no description, default to something sensible.
setDescription("A MicroPython script");
// A sane default starting point for a new script.
@@ -344,7 +244,7 @@ function web_editor(config) {
EDITOR.ACE.gotoLine(EDITOR.ACE.session.getLength());
// If configured as experimental update editor background to indicate it
if(config.flags.experimental) {
- EDITOR.ACE.renderer.scroller.style.backgroundImage = "url('static/img/experimental.png')"
+ EDITOR.ACE.renderer.scroller.style.backgroundImage = "url('static/img/experimental.png')";
}
// Configure the zoom related buttons.
$("#zoom-in").click(function (e) {
@@ -466,18 +366,19 @@ function web_editor(config) {
setDescription(config.translate.drop.python);
reader.onload = function(e) {
EDITOR.setCode(e.target.result);
- }
+ };
reader.readAsText(f);
EDITOR.ACE.gotoLine(EDITOR.ACE.session.getLength());
} else if (ext == 'hex') {
setName(f.name.replace('.hex', ''));
setDescription(config.translate.drop.hex);
reader.onload = function(e) {
- var code = EDITOR.extractScript(e.target.result);
+ var code = upyhex.extractPyStrFromIntelHex(
+ e.target.result);
if(code.length < 8192) {
EDITOR.setCode(code);
}
- }
+ };
reader.readAsText(f);
EDITOR.ACE.gotoLine(EDITOR.ACE.session.getLength());
}
@@ -487,7 +388,7 @@ function web_editor(config) {
return false;
});
}
- })
+ });
$('.load-toggle').on('click', function(e) {
$('.load-drag-target').toggle();
$('.load-form').toggle();
@@ -550,7 +451,7 @@ function web_editor(config) {
}
// Set editor to current state of blocks.
EDITOR.setCode(Blockly.Python.workspaceToCode(workspace));
- };
+ }
}
// This function describes what to do when the snippets button is clicked.
@@ -572,9 +473,9 @@ function web_editor(config) {
var name = render(text);
var trigger = name.split(' - ')[0];
return config.translate.code_snippets[trigger];
- }
+ };
}
- }
+ };
vex.open({
content: Mustache.render(template, context),
afterOpen: function(vexContent) {
@@ -594,7 +495,7 @@ function web_editor(config) {
Mustache.parse(template);
vex.open({
content: Mustache.render(template, config.translate.share)
- })
+ });
$('#passphrase').focus();
$('#button-create-link').click(function() {
var password = $('#passphrase').val();
@@ -642,18 +543,18 @@ function web_editor(config) {
setDescription(config.translate.drop.python);
reader.onload = function(e) {
EDITOR.setCode(e.target.result);
- }
+ };
reader.readAsText(file);
EDITOR.ACE.gotoLine(EDITOR.ACE.session.getLength());
} else if (ext == 'hex') {
setName(file.name.replace('.hex', ''));
setDescription(config.translate.drop.hex);
reader.onload = function(e) {
- var code = EDITOR.extractScript(e.target.result);
+ var code = upyhex.extractPyStrFromIntelHex(e.target.result);
if (code.length < 8192) {
EDITOR.setCode(code);
}
- }
+ };
reader.readAsText(file);
EDITOR.ACE.gotoLine(EDITOR.ACE.session.getLength());
}
@@ -735,7 +636,7 @@ function web_editor(config) {
Mustache.parse(template);
var context = config.translate.messagebar;
var messagebar = $('#messagebar');
- messagebar.html(Mustache.render(template, context))
+ messagebar.html(Mustache.render(template, context));
messagebar.show();
$('#messagebar-link').attr('href',
window.location.href.replace(VERSION, data.latest));
@@ -746,11 +647,10 @@ function web_editor(config) {
});
}
- var qs = get_qs_context()
+ var qs = get_qs_context();
var migration = get_migration();
setupFeatureFlags();
setupEditor(qs, migration);
checkVersion(qs);
setupButtons();
-};
-
+}
diff --git a/static/js/intel-hex.browser.js b/static/js/intel-hex.browser.js
new file mode 100644
index 00000000..b36d1d0b
--- /dev/null
+++ b/static/js/intel-hex.browser.js
@@ -0,0 +1,1249 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.MemoryMap = factory());
+}(this, (function () { 'use strict';
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+/**
+ * Parser/writer for the "Intel hex" format.
+ */
+
+/*
+ * A regexp that matches lines in a .hex file.
+ *
+ * One hexadecimal character is matched by "[0-9A-Fa-f]".
+ * Two hex characters are matched by "[0-9A-Fa-f]{2}"
+ * Eight or more hex characters are matched by "[0-9A-Fa-f]{8,}"
+ * A capture group of two hex characters is "([0-9A-Fa-f]{2})"
+ *
+ * Record mark :
+ * 8 or more hex chars ([0-9A-Fa-f]{8,})
+ * Checksum ([0-9A-Fa-f]{2})
+ * Optional newline (?:\r\n|\r|\n|)
+ */
+var hexLineRegexp = /:([0-9A-Fa-f]{8,})([0-9A-Fa-f]{2})(?:\r\n|\r|\n|)/g;
+
+// Takes a Uint8Array as input,
+// Returns an integer in the 0-255 range.
+function checksum(bytes) {
+ return -bytes.reduce(function (sum, v) {
+ return sum + v;
+ }, 0) & 0xFF;
+}
+
+// Takes two Uint8Arrays as input,
+// Returns an integer in the 0-255 range.
+function checksumTwo(array1, array2) {
+ var partial1 = array1.reduce(function (sum, v) {
+ return sum + v;
+ }, 0);
+ var partial2 = array2.reduce(function (sum, v) {
+ return sum + v;
+ }, 0);
+ return -(partial1 + partial2) & 0xFF;
+}
+
+// Trivial utility. Converts a number to hex and pads with zeroes up to 2 characters.
+function hexpad(number) {
+ return number.toString(16).toUpperCase().padStart(2, '0');
+}
+
+// Polyfill as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
+Number.isInteger = Number.isInteger || function (value) {
+ return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
+};
+
+/**
+ * @class MemoryMap
+ *
+ * Represents the contents of a memory layout, with main focus into (possibly sparse) blocks of data.
+ *
+ * A {@linkcode MemoryMap} acts as a subclass of
+ * {@linkcode https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map|Map}.
+ * In every entry of it, the key is the starting address of a data block (an integer number),
+ * and the value is the Uint8Array with the data for that block.
+ *
+ * The main rationale for this is that a .hex file can contain a single block of contiguous
+ * data starting at memory address 0 (and it's the common case for simple .hex files),
+ * but complex files with several non-contiguous data blocks are also possible, thus
+ * the need for a data structure on top of the Uint8Arrays.
+ *
+ * In order to parse .hex files, use the {@linkcode MemoryMap.fromHex} static factory
+ * method. In order to write .hex files, create a new {@linkcode MemoryMap} and call
+ * its {@linkcode MemoryMap.asHexString} method.
+ *
+ * @extends Map
+ * @example
+ * import MemoryMap from 'nrf-intel-hex';
+ *
+ * let memMap1 = new MemoryMap();
+ * let memMap2 = new MemoryMap([[0, new Uint8Array(1,2,3,4)]]);
+ * let memMap3 = new MemoryMap({0: new Uint8Array(1,2,3,4)});
+ * let memMap4 = new MemoryMap({0xCF0: new Uint8Array(1,2,3,4)});
+ */
+
+var MemoryMap = function () {
+ /**
+ * @param {Iterable} blocks The initial value for the memory blocks inside this
+ * MemoryMap. All keys must be numeric, and all values must be instances of
+ * Uint8Array. Optionally it can also be a plain Object with
+ * only numeric keys.
+ */
+ function MemoryMap(blocks) {
+ _classCallCheck(this, MemoryMap);
+
+ this._blocks = new Map();
+
+ if (blocks && typeof blocks[Symbol.iterator] === 'function') {
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = blocks[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var tuple = _step.value;
+
+ if (!(tuple instanceof Array) || tuple.length !== 2) {
+ throw new Error('First parameter to MemoryMap constructor must be an iterable of [addr, bytes] or undefined');
+ }
+ this.set(tuple[0], tuple[1]);
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+ } else if ((typeof blocks === 'undefined' ? 'undefined' : _typeof(blocks)) === 'object') {
+ // Try iterating through the object's keys
+ var addrs = Object.keys(blocks);
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = addrs[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var addr = _step2.value;
+
+ this.set(parseInt(addr), blocks[addr]);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+ } else if (blocks !== undefined && blocks !== null) {
+ throw new Error('First parameter to MemoryMap constructor must be an iterable of [addr, bytes] or undefined');
+ }
+ }
+
+ _createClass(MemoryMap, [{
+ key: 'set',
+ value: function set(addr, value) {
+ if (!Number.isInteger(addr)) {
+ throw new Error('Address passed to MemoryMap is not an integer');
+ }
+ if (addr < 0) {
+ throw new Error('Address passed to MemoryMap is negative');
+ }
+ if (!(value instanceof Uint8Array)) {
+ throw new Error('Bytes passed to MemoryMap are not an Uint8Array');
+ }
+ return this._blocks.set(addr, value);
+ }
+ // Delegate the following to the 'this._blocks' Map:
+
+ }, {
+ key: 'get',
+ value: function get(addr) {
+ return this._blocks.get(addr);
+ }
+ }, {
+ key: 'clear',
+ value: function clear() {
+ return this._blocks.clear();
+ }
+ }, {
+ key: 'delete',
+ value: function _delete(addr) {
+ return this._blocks.delete(addr);
+ }
+ }, {
+ key: 'entries',
+ value: function entries() {
+ return this._blocks.entries();
+ }
+ }, {
+ key: 'forEach',
+ value: function forEach(callback, that) {
+ return this._blocks.forEach(callback, that);
+ }
+ }, {
+ key: 'has',
+ value: function has(addr) {
+ return this._blocks.has(addr);
+ }
+ }, {
+ key: 'keys',
+ value: function keys() {
+ return this._blocks.keys();
+ }
+ }, {
+ key: 'values',
+ value: function values() {
+ return this._blocks.values();
+ }
+ }, {
+ key: Symbol.iterator,
+ value: function value() {
+ return this._blocks[Symbol.iterator]();
+ }
+
+ /**
+ * Parses a string containing data formatted in "Intel HEX" format, and
+ * returns an instance of {@linkcode MemoryMap}.
+ *
+ * The insertion order of keys in the {@linkcode MemoryMap} is guaranteed to be strictly
+ * ascending. In other words, when iterating through the {@linkcode MemoryMap}, the addresses
+ * will be ordered in ascending order.
+ *
+ * The parser has an opinionated behaviour, and will throw a descriptive error if it
+ * encounters some malformed input. Check the project's
+ * {@link https://github.com/NordicSemiconductor/nrf-intel-hex#Features|README file} for details.
+ *
+ * If maxBlockSize is given, any contiguous data block larger than that will
+ * be split in several blocks.
+ *
+ * @param {String} hexText The contents of a .hex file.
+ * @param {Number} [maxBlockSize=Infinity] Maximum size of the returned Uint8Arrays.
+ *
+ * @return {MemoryMap}
+ *
+ * @example
+ * import MemoryMap from 'nrf-intel-hex';
+ *
+ * let intelHexString =
+ * ":100000000102030405060708090A0B0C0D0E0F1068\n" +
+ * ":00000001FF";
+ *
+ * let memMap = MemoryMap.fromHex(intelHexString);
+ *
+ * for (let [address, dataBlock] of memMap) {
+ * console.log('Data block at ', address, ', bytes: ', dataBlock);
+ * }
+ */
+
+ }, {
+ key: 'join',
+
+
+ /**
+ * Returns a new instance of {@linkcode MemoryMap}, containing
+ * the same data, but concatenating together those memory blocks that are adjacent.
+ *
+ * The insertion order of keys in the {@linkcode MemoryMap} is guaranteed to be strictly
+ * ascending. In other words, when iterating through the {@linkcode MemoryMap}, the addresses
+ * will be ordered in ascending order.
+ *
+ * If maxBlockSize is given, blocks will be concatenated together only
+ * until the joined block reaches this size in bytes. This means that the output
+ * {@linkcode MemoryMap} might have more entries than the input one.
+ *
+ * If there is any overlap between blocks, an error will be thrown.
+ *
+ * The returned {@linkcode MemoryMap} will use newly allocated memory.
+ *
+ * @param {Number} [maxBlockSize=Infinity] Maximum size of the Uint8Arrays in the
+ * returned {@linkcode MemoryMap}.
+ *
+ * @return {MemoryMap}
+ */
+ value: function join() {
+ var maxBlockSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Infinity;
+
+
+ // First pass, create a Map of address→length of contiguous blocks
+ var sortedKeys = Array.from(this.keys()).sort(function (a, b) {
+ return a - b;
+ });
+ var blockSizes = new Map();
+ var lastBlockAddr = -1;
+ var lastBlockEndAddr = -1;
+
+ for (var i = 0, l = sortedKeys.length; i < l; i++) {
+ var blockAddr = sortedKeys[i];
+ var blockLength = this.get(sortedKeys[i]).length;
+
+ if (lastBlockEndAddr === blockAddr && lastBlockEndAddr - lastBlockAddr < maxBlockSize) {
+ // Grow when the previous end address equals the current,
+ // and we don't go over the maximum block size.
+ blockSizes.set(lastBlockAddr, blockSizes.get(lastBlockAddr) + blockLength);
+ lastBlockEndAddr += blockLength;
+ } else if (lastBlockEndAddr <= blockAddr) {
+ // Else mark a new block.
+ blockSizes.set(blockAddr, blockLength);
+ lastBlockAddr = blockAddr;
+ lastBlockEndAddr = blockAddr + blockLength;
+ } else {
+ throw new Error('Overlapping data around address 0x' + blockAddr.toString(16));
+ }
+ }
+
+ // Second pass: allocate memory for the contiguous blocks and copy data around.
+ var mergedBlocks = new MemoryMap();
+ var mergingBlock = void 0;
+ var mergingBlockAddr = -1;
+ for (var _i = 0, _l = sortedKeys.length; _i < _l; _i++) {
+ var _blockAddr = sortedKeys[_i];
+ if (blockSizes.has(_blockAddr)) {
+ mergingBlock = new Uint8Array(blockSizes.get(_blockAddr));
+ mergedBlocks.set(_blockAddr, mergingBlock);
+ mergingBlockAddr = _blockAddr;
+ }
+ mergingBlock.set(this.get(_blockAddr), _blockAddr - mergingBlockAddr);
+ }
+
+ return mergedBlocks;
+ }
+
+ /**
+ * Given a {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map|Map}
+ * of {@linkcode MemoryMap}s, indexed by a alphanumeric ID,
+ * returns a Map of address to tuples (Arrayss of length 2) of the form
+ * (id, Uint8Array)s.
+ *
+ * The scenario for using this is having several {@linkcode MemoryMap}s, from several calls to
+ * {@link module:nrf-intel-hex~hexToArrays|hexToArrays}, each having a different identifier.
+ * This function locates where those memory block sets overlap, and returns a Map
+ * containing addresses as keys, and arrays as values. Each array will contain 1 or more
+ * (id, Uint8Array) tuples: the identifier of the memory block set that has
+ * data in that region, and the data itself. When memory block sets overlap, there will
+ * be more than one tuple.
+ *
+ * The Uint8Arrays in the output are
+ * {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray|subarrays}
+ * of the input data; new memory is not allocated for them.
+ *
+ * The insertion order of keys in the output Map is guaranteed to be strictly
+ * ascending. In other words, when iterating through the Map, the addresses
+ * will be ordered in ascending order.
+ *
+ * When two blocks overlap, the corresponding array of tuples will have the tuples ordered
+ * in the insertion order of the input Map of block sets.
+ *
+ *
+ * @param {Map.MemoryMap} memoryMaps The input memory block sets
+ *
+ * @example
+ * import MemoryMap from 'nrf-intel-hex';
+ *
+ * let memMap1 = MemoryMap.fromHex( hexdata1 );
+ * let memMap2 = MemoryMap.fromHex( hexdata2 );
+ * let memMap3 = MemoryMap.fromHex( hexdata3 );
+ *
+ * let maps = new Map([
+ * ['file A', blocks1],
+ * ['file B', blocks2],
+ * ['file C', blocks3]
+ * ]);
+ *
+ * let overlappings = MemoryMap.overlapMemoryMaps(maps);
+ *
+ * for (let [address, tuples] of overlappings) {
+ * // if 'tuples' has length > 1, there is an overlap starting at 'address'
+ *
+ * for (let [address, tuples] of overlappings) {
+ * let [id, bytes] = tuple;
+ * // 'id' in this example is either 'file A', 'file B' or 'file C'
+ * }
+ * }
+ * @return {Map.Array} The map of possibly overlapping memory blocks
+ */
+
+ }, {
+ key: 'paginate',
+
+
+ /**
+ * Returns a new instance of {@linkcode MemoryMap}, where:
+ *
+ *
+ * - Each key (the start address of each Uint8Array) is a multiple of
+ * pageSize
+ * - The size of each Uint8Array is exactly pageSize
+ * - Bytes from the input map to bytes in the output
+ * - Bytes not in the input are replaced by a padding value
+ *
+ *
+ * The scenario is wanting to prepare pages of bytes for a write operation, where the write
+ * operation affects a whole page/sector at once.
+ *
+ * The insertion order of keys in the output {@linkcode MemoryMap} is guaranteed
+ * to be strictly ascending. In other words, when iterating through the
+ * {@linkcode MemoryMap}, the addresses will be ordered in ascending order.
+ *
+ * The Uint8Arrays in the output will be newly allocated.
+ *
+ *
+ * @param {Number} [pageSize=1024] The size of the output pages, in bytes
+ * @param {Number} [pad=0xFF] The byte value to use for padding
+ * @return {MemoryMap}
+ */
+ value: function paginate() {
+ var pageSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1024;
+ var pad = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0xFF;
+
+ if (pageSize <= 0) {
+ throw new Error('Page size must be greater than zero');
+ }
+ var outPages = new MemoryMap();
+ var page = void 0;
+
+ var sortedKeys = Array.from(this.keys()).sort(function (a, b) {
+ return a - b;
+ });
+
+ for (var i = 0, l = sortedKeys.length; i < l; i++) {
+ var blockAddr = sortedKeys[i];
+ var block = this.get(blockAddr);
+ var blockLength = block.length;
+ var blockEnd = blockAddr + blockLength;
+
+ for (var pageAddr = blockAddr - blockAddr % pageSize; pageAddr < blockEnd; pageAddr += pageSize) {
+ page = outPages.get(pageAddr);
+ if (!page) {
+ page = new Uint8Array(pageSize);
+ page.fill(pad);
+ outPages.set(pageAddr, page);
+ }
+
+ var offset = pageAddr - blockAddr;
+ var subBlock = void 0;
+ if (offset <= 0) {
+ // First page which intersects the block
+ subBlock = block.subarray(0, Math.min(pageSize + offset, blockLength));
+ page.set(subBlock, -offset);
+ } else {
+ // Any other page which intersects the block
+ subBlock = block.subarray(offset, offset + Math.min(pageSize, blockLength - offset));
+ page.set(subBlock, 0);
+ }
+ }
+ }
+
+ return outPages;
+ }
+
+ /**
+ * Locates the Uint8Array which contains the given offset,
+ * and returns the four bytes held at that offset, as a 32-bit unsigned integer.
+ *
+ *
+ * Behaviour is similar to {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32|DataView.prototype.getUint32},
+ * except that this operates over a {@linkcode MemoryMap} instead of
+ * over an ArrayBuffer, and that this may return undefined if
+ * the address is not entirely contained within one of the Uint8Arrays.
+ *
+ *
+ * @param {Number} offset The memory offset to read the data
+ * @param {Boolean} [littleEndian=false] Whether to fetch the 4 bytes as a little- or big-endian integer
+ * @return {Number|undefined} An unsigned 32-bit integer number
+ */
+
+ }, {
+ key: 'getUint32',
+ value: function getUint32(offset, littleEndian) {
+ var keys = Array.from(this.keys());
+
+ for (var i = 0, l = keys.length; i < l; i++) {
+ var blockAddr = keys[i];
+ var block = this.get(blockAddr);
+ var blockLength = block.length;
+ var blockEnd = blockAddr + blockLength;
+
+ if (blockAddr <= offset && offset + 4 <= blockEnd) {
+ return new DataView(block.buffer, offset - blockAddr, 4).getUint32(0, littleEndian);
+ }
+ }
+ return;
+ }
+
+ /**
+ * Returns a String of text representing a .hex file.
+ *
+ * The writer has an opinionated behaviour. Check the project's
+ * {@link https://github.com/NordicSemiconductor/nrf-intel-hex#Features|README file} for details.
+ *
+ * @param {Number} [lineSize=16] Maximum number of bytes to be encoded in each data record.
+ * Must have a value between 1 and 255, as per the specification.
+ *
+ * @return {String} String of text with the .hex representation of the input binary data
+ *
+ * @example
+ * import MemoryMap from 'nrf-intel-hex';
+ *
+ * let memMap = new MemoryMap();
+ * let bytes = new Uint8Array(....);
+ * memMap.set(0x0FF80000, bytes); // The block with 'bytes' will start at offset 0x0FF80000
+ *
+ * let string = memMap.asHexString();
+ */
+
+ }, {
+ key: 'asHexString',
+ value: function asHexString() {
+ var lineSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 16;
+
+ var lowAddress = 0; // 16 least significant bits of the current addr
+ var highAddress = -1 << 16; // 16 most significant bits of the current addr
+ var records = [];
+ if (lineSize <= 0) {
+ throw new Error('Size of record must be greater than zero');
+ } else if (lineSize > 255) {
+ throw new Error('Size of record must be less than 256');
+ }
+
+ // Placeholders
+ var offsetRecord = new Uint8Array(6);
+ var recordHeader = new Uint8Array(4);
+
+ var sortedKeys = Array.from(this.keys()).sort(function (a, b) {
+ return a - b;
+ });
+ for (var i = 0, l = sortedKeys.length; i < l; i++) {
+ var blockAddr = sortedKeys[i];
+ var block = this.get(blockAddr);
+
+ // Sanity checks
+ if (!(block instanceof Uint8Array)) {
+ throw new Error('Block at offset ' + blockAddr + ' is not an Uint8Array');
+ }
+ if (blockAddr < 0) {
+ throw new Error('Block at offset ' + blockAddr + ' has a negative thus invalid address');
+ }
+ var blockSize = block.length;
+ if (!blockSize) {
+ continue;
+ } // Skip zero-length blocks
+
+
+ if (blockAddr > highAddress + 0xFFFF) {
+ // Insert a new 0x04 record to jump to a new 64KiB block
+
+ // Round up the least significant 16 bits - no bitmasks because they trigger
+ // base-2 negative numbers, whereas subtracting the modulo maintains precision
+ highAddress = blockAddr - blockAddr % 0x10000;
+ lowAddress = 0;
+
+ offsetRecord[0] = 2; // Length
+ offsetRecord[1] = 0; // Load offset, high byte
+ offsetRecord[2] = 0; // Load offset, low byte
+ offsetRecord[3] = 4; // Record type
+ offsetRecord[4] = highAddress >> 24; // new address offset, high byte
+ offsetRecord[5] = highAddress >> 16; // new address offset, low byte
+
+ records.push(':' + Array.prototype.map.call(offsetRecord, hexpad).join('') + hexpad(checksum(offsetRecord)));
+ }
+
+ if (blockAddr < highAddress + lowAddress) {
+ throw new Error('Block starting at 0x' + blockAddr.toString(16) + ' overlaps with a previous block.');
+ }
+
+ lowAddress = blockAddr % 0x10000;
+ var blockOffset = 0;
+ var blockEnd = blockAddr + blockSize;
+ if (blockEnd > 0xFFFFFFFF) {
+ throw new Error('Data cannot be over 0xFFFFFFFF');
+ }
+
+ // Loop for every 64KiB memory segment that spans this block
+ while (highAddress + lowAddress < blockEnd) {
+
+ if (lowAddress > 0xFFFF) {
+ // Insert a new 0x04 record to jump to a new 64KiB block
+ highAddress += 1 << 16; // Increase by one
+ lowAddress = 0;
+
+ offsetRecord[0] = 2; // Length
+ offsetRecord[1] = 0; // Load offset, high byte
+ offsetRecord[2] = 0; // Load offset, low byte
+ offsetRecord[3] = 4; // Record type
+ offsetRecord[4] = highAddress >> 24; // new address offset, high byte
+ offsetRecord[5] = highAddress >> 16; // new address offset, low byte
+
+ records.push(':' + Array.prototype.map.call(offsetRecord, hexpad).join('') + hexpad(checksum(offsetRecord)));
+ }
+
+ var recordSize = -1;
+ // Loop for every record for that spans the current 64KiB memory segment
+ while (lowAddress < 0x10000 && recordSize) {
+ recordSize = Math.min(lineSize, // Normal case
+ blockEnd - highAddress - lowAddress, // End of block
+ 0x10000 - lowAddress // End of low addresses
+ );
+
+ if (recordSize) {
+
+ recordHeader[0] = recordSize; // Length
+ recordHeader[1] = lowAddress >> 8; // Load offset, high byte
+ recordHeader[2] = lowAddress; // Load offset, low byte
+ recordHeader[3] = 0; // Record type
+
+ var subBlock = block.subarray(blockOffset, blockOffset + recordSize); // Data bytes for this record
+
+ records.push(':' + Array.prototype.map.call(recordHeader, hexpad).join('') + Array.prototype.map.call(subBlock, hexpad).join('') + hexpad(checksumTwo(recordHeader, subBlock)));
+
+ blockOffset += recordSize;
+ lowAddress += recordSize;
+ }
+ }
+ }
+ }
+
+ records.push(':00000001FF'); // EOF record
+
+ return records.join('\n');
+ }
+
+ /**
+ * Performs a deep copy of the current {@linkcode MemoryMap}, returning a new one
+ * with exactly the same contents, but allocating new memory for each of its
+ * Uint8Arrays.
+ *
+ * @return {MemoryMap}
+ */
+
+ }, {
+ key: 'clone',
+ value: function clone() {
+ var cloned = new MemoryMap();
+
+ var _iteratorNormalCompletion3 = true;
+ var _didIteratorError3 = false;
+ var _iteratorError3 = undefined;
+
+ try {
+ for (var _iterator3 = this[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+ var _step3$value = _slicedToArray(_step3.value, 2),
+ addr = _step3$value[0],
+ value = _step3$value[1];
+
+ cloned.set(addr, new Uint8Array(value));
+ }
+ } catch (err) {
+ _didIteratorError3 = true;
+ _iteratorError3 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion3 && _iterator3.return) {
+ _iterator3.return();
+ }
+ } finally {
+ if (_didIteratorError3) {
+ throw _iteratorError3;
+ }
+ }
+ }
+
+ return cloned;
+ }
+
+ /**
+ * Given one Uint8Array, looks through its contents and returns a new
+ * {@linkcode MemoryMap}, stripping away those regions where there are only
+ * padding bytes.
+ *
+ * The start of the input Uint8Array is assumed to be offset zero for the output.
+ *
+ * The use case here is dumping memory from a working device and try to see the
+ * "interesting" memory regions it has. This assumes that there is a constant,
+ * predefined padding byte value being used in the "non-interesting" regions.
+ * In other words: this will work as long as the dump comes from a flash memory
+ * which has been previously erased (thus 0xFFs for padding), or from a
+ * previously blanked HDD (thus 0x00s for padding).
+ *
+ * This method uses subarray on the input data, and thus does not allocate memory
+ * for the Uint8Arrays.
+ *
+ * @param {Uint8Array} bytes The input data
+ * @param {Number} [padByte=0xFF] The value of the byte assumed to be used as padding
+ * @param {Number} [minPadLength=64] The minimum number of consecutive pad bytes to
+ * be considered actual padding
+ *
+ * @return {MemoryMap}
+ */
+
+ }, {
+ key: 'slice',
+
+
+ /**
+ * Returns a new instance of {@linkcode MemoryMap}, containing only data between
+ * the addresses address and address + length.
+ * Behaviour is similar to {@linkcode https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/slice|Array.prototype.slice},
+ * in that the return value is a portion of the current {@linkcode MemoryMap}.
+ *
+ *
+ * The returned {@linkcode MemoryMap} might be empty.
+ *
+ *
+ * Internally, this uses subarray, so new memory is not allocated.
+ *
+ * @param {Number} address The start address of the slice
+ * @param {Number} length The length of memory map to slice out
+ * @return {MemoryMap}
+ */
+ value: function slice(address) {
+ var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Infinity;
+
+ if (length < 0) {
+ throw new Error('Length of the slice cannot be negative');
+ }
+
+ var sliced = new MemoryMap();
+
+ var _iteratorNormalCompletion4 = true;
+ var _didIteratorError4 = false;
+ var _iteratorError4 = undefined;
+
+ try {
+ for (var _iterator4 = this[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+ var _step4$value = _slicedToArray(_step4.value, 2),
+ blockAddr = _step4$value[0],
+ block = _step4$value[1];
+
+ var blockLength = block.length;
+
+ if (blockAddr + blockLength >= address && blockAddr < address + length) {
+ var sliceStart = Math.max(address, blockAddr);
+ var sliceEnd = Math.min(address + length, blockAddr + blockLength);
+ var sliceLength = sliceEnd - sliceStart;
+ var relativeSliceStart = sliceStart - blockAddr;
+
+ if (sliceLength > 0) {
+ sliced.set(sliceStart, block.subarray(relativeSliceStart, relativeSliceStart + sliceLength));
+ }
+ }
+ }
+ } catch (err) {
+ _didIteratorError4 = true;
+ _iteratorError4 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion4 && _iterator4.return) {
+ _iterator4.return();
+ }
+ } finally {
+ if (_didIteratorError4) {
+ throw _iteratorError4;
+ }
+ }
+ }
+
+ return sliced;
+ }
+
+ /**
+ * Returns a new instance of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32|Uint8Array}, containing only data between
+ * the addresses address and address + length. Any byte without a value
+ * in the input {@linkcode MemoryMap} will have a value of padByte.
+ *
+ *
+ * This method allocates new memory.
+ *
+ * @param {Number} address The start address of the slice
+ * @param {Number} length The length of memory map to slice out
+ * @param {Number} [padByte=0xFF] The value of the byte assumed to be used as padding
+ * @return {MemoryMap}
+ */
+
+ }, {
+ key: 'slicePad',
+ value: function slicePad(address, length) {
+ var padByte = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0xFF;
+
+ if (length < 0) {
+ throw new Error('Length of the slice cannot be negative');
+ }
+
+ var out = new Uint8Array(length).fill(padByte);
+
+ var _iteratorNormalCompletion5 = true;
+ var _didIteratorError5 = false;
+ var _iteratorError5 = undefined;
+
+ try {
+ for (var _iterator5 = this[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+ var _step5$value = _slicedToArray(_step5.value, 2),
+ blockAddr = _step5$value[0],
+ block = _step5$value[1];
+
+ var blockLength = block.length;
+
+ if (blockAddr + blockLength >= address && blockAddr < address + length) {
+ var sliceStart = Math.max(address, blockAddr);
+ var sliceEnd = Math.min(address + length, blockAddr + blockLength);
+ var sliceLength = sliceEnd - sliceStart;
+ var relativeSliceStart = sliceStart - blockAddr;
+
+ if (sliceLength > 0) {
+ out.set(block.subarray(relativeSliceStart, relativeSliceStart + sliceLength), sliceStart - address);
+ }
+ }
+ }
+ } catch (err) {
+ _didIteratorError5 = true;
+ _iteratorError5 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion5 && _iterator5.return) {
+ _iterator5.return();
+ }
+ } finally {
+ if (_didIteratorError5) {
+ throw _iteratorError5;
+ }
+ }
+ }
+
+ return out;
+ }
+
+ /**
+ * Checks whether the current memory map contains the one given as a parameter.
+ *
+ *
+ * "Contains" means that all the offsets that have a byte value in the given
+ * memory map have a value in the current memory map, and that the byte values
+ * are the same.
+ *
+ *
+ * An empty memory map is always contained in any other memory map.
+ *
+ *
+ * Returns boolean true if the memory map is contained, false
+ * otherwise.
+ *
+ * @param {MemoryMap} memMap The memory map to check
+ * @return {Boolean}
+ */
+
+ }, {
+ key: 'contains',
+ value: function contains(memMap) {
+ var _iteratorNormalCompletion6 = true;
+ var _didIteratorError6 = false;
+ var _iteratorError6 = undefined;
+
+ try {
+ for (var _iterator6 = memMap[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
+ var _step6$value = _slicedToArray(_step6.value, 2),
+ blockAddr = _step6$value[0],
+ block = _step6$value[1];
+
+ var blockLength = block.length;
+
+ var slice = this.slice(blockAddr, blockLength).join().get(blockAddr);
+
+ if (!slice || slice.length !== blockLength) {
+ return false;
+ }
+
+ for (var i in block) {
+ if (block[i] !== slice[i]) {
+ return false;
+ }
+ }
+ }
+ } catch (err) {
+ _didIteratorError6 = true;
+ _iteratorError6 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion6 && _iterator6.return) {
+ _iterator6.return();
+ }
+ } finally {
+ if (_didIteratorError6) {
+ throw _iteratorError6;
+ }
+ }
+ }
+
+ return true;
+ }
+ }, {
+ key: 'size',
+ get: function get() {
+ return this._blocks.size;
+ }
+ }], [{
+ key: 'fromHex',
+ value: function fromHex(hexText) {
+ var maxBlockSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Infinity;
+
+ var blocks = new MemoryMap();
+
+ var lastCharacterParsed = 0;
+ var matchResult = void 0;
+ var recordCount = 0;
+
+ // Upper Linear Base Address, the 16 most significant bits (2 bytes) of
+ // the current 32-bit (4-byte) address
+ // In practice this is a offset that is summed to the "load offset" of the
+ // data records
+ var ulba = 0;
+
+ hexLineRegexp.lastIndex = 0; // Reset the regexp, if not it would skip content when called twice
+
+ while ((matchResult = hexLineRegexp.exec(hexText)) !== null) {
+ recordCount++;
+
+ // By default, a regexp loop ignores gaps between matches, but
+ // we want to be aware of them.
+ if (lastCharacterParsed !== matchResult.index) {
+ throw new Error('Malformed hex file: Could not parse between characters ' + lastCharacterParsed + ' and ' + matchResult.index + ' ("' + hexText.substring(lastCharacterParsed, Math.min(matchResult.index, lastCharacterParsed + 16)).trim() + '")');
+ }
+ lastCharacterParsed = hexLineRegexp.lastIndex;
+
+ // Give pretty names to the match's capture groups
+
+ var _matchResult = matchResult,
+ _matchResult2 = _slicedToArray(_matchResult, 3),
+ recordStr = _matchResult2[1],
+ recordChecksum = _matchResult2[2];
+
+ // String to Uint8Array - https://stackoverflow.com/questions/43131242/how-to-convert-a-hexademical-string-of-data-to-an-arraybuffer-in-javascript
+
+
+ var recordBytes = new Uint8Array(recordStr.match(/[\da-f]{2}/gi).map(function (h) {
+ return parseInt(h, 16);
+ }));
+
+ var recordLength = recordBytes[0];
+ if (recordLength + 4 !== recordBytes.length) {
+ throw new Error('Mismatched record length at record ' + recordCount + ' (' + matchResult[0].trim() + '), expected ' + recordLength + ' data bytes but actual length is ' + (recordBytes.length - 4));
+ }
+
+ var cs = checksum(recordBytes);
+ if (parseInt(recordChecksum, 16) !== cs) {
+ throw new Error('Checksum failed at record ' + recordCount + ' (' + matchResult[0].trim() + '), should be ' + cs.toString(16));
+ }
+
+ var offset = (recordBytes[1] << 8) + recordBytes[2];
+ var recordType = recordBytes[3];
+ var data = recordBytes.subarray(4);
+
+ if (recordType === 0) {
+ // Data record, contains data
+ // Create a new block, at (upper linear base address + offset)
+ if (blocks.has(ulba + offset)) {
+ throw new Error('Duplicated data at record ' + recordCount + ' (' + matchResult[0].trim() + ')');
+ }
+ if (offset + data.length > 0x10000) {
+ throw new Error('Data at record ' + recordCount + ' (' + matchResult[0].trim() + ') wraps over 0xFFFF. This would trigger ambiguous behaviour. Please restructure your data so that for every record the data offset plus the data length do not exceed 0xFFFF.');
+ }
+
+ blocks.set(ulba + offset, data);
+ } else {
+
+ // All non-data records must have a data offset of zero
+ if (offset !== 0) {
+ throw new Error('Record ' + recordCount + ' (' + matchResult[0].trim() + ') must have 0000 as data offset.');
+ }
+
+ switch (recordType) {
+ case 1:
+ // EOF
+ if (lastCharacterParsed !== hexText.length) {
+ // This record should be at the very end of the string
+ throw new Error('There is data after an EOF record at record ' + recordCount);
+ }
+
+ return blocks.join(maxBlockSize);
+
+ case 2:
+ // Extended Segment Address Record
+ // Sets the 16 most significant bits of the 20-bit Segment Base
+ // Address for the subsequent data.
+ ulba = (data[0] << 8) + data[1] << 4;
+ break;
+
+ case 3:
+ // Start Segment Address Record
+ // Do nothing. Record type 3 only applies to 16-bit Intel CPUs,
+ // where it should reset the program counter (CS+IP CPU registers)
+ break;
+
+ case 4:
+ // Extended Linear Address Record
+ // Sets the 16 most significant (upper) bits of the 32-bit Linear Address
+ // for the subsequent data
+ ulba = (data[0] << 8) + data[1] << 16;
+ break;
+
+ case 5:
+ // Start Linear Address Record
+ // Do nothing. Record type 5 only applies to 32-bit Intel CPUs,
+ // where it should reset the program counter (EIP CPU register)
+ // It might have meaning for other CPU architectures
+ // (see http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka9903.html )
+ // but will be ignored nonetheless.
+ break;
+ default:
+ throw new Error('Invalid record type 0x' + hexpad(recordType) + ' at record ' + recordCount + ' (should be between 0x00 and 0x05)');
+ }
+ }
+ }
+
+ if (recordCount) {
+ throw new Error('No EOF record at end of file');
+ } else {
+ throw new Error('Malformed .hex file, could not parse any registers');
+ }
+ }
+ }, {
+ key: 'overlapMemoryMaps',
+ value: function overlapMemoryMaps(memoryMaps) {
+ // First pass: create a list of addresses where any block starts or ends.
+ var cuts = new Set();
+ var _iteratorNormalCompletion7 = true;
+ var _didIteratorError7 = false;
+ var _iteratorError7 = undefined;
+
+ try {
+ for (var _iterator7 = memoryMaps[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
+ var _step7$value = _slicedToArray(_step7.value, 2),
+ blocks = _step7$value[1];
+
+ var _iteratorNormalCompletion8 = true;
+ var _didIteratorError8 = false;
+ var _iteratorError8 = undefined;
+
+ try {
+ for (var _iterator8 = blocks[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
+ var _step8$value = _slicedToArray(_step8.value, 2),
+ address = _step8$value[0],
+ block = _step8$value[1];
+
+ cuts.add(address);
+ cuts.add(address + block.length);
+ }
+ } catch (err) {
+ _didIteratorError8 = true;
+ _iteratorError8 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion8 && _iterator8.return) {
+ _iterator8.return();
+ }
+ } finally {
+ if (_didIteratorError8) {
+ throw _iteratorError8;
+ }
+ }
+ }
+ }
+ } catch (err) {
+ _didIteratorError7 = true;
+ _iteratorError7 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion7 && _iterator7.return) {
+ _iterator7.return();
+ }
+ } finally {
+ if (_didIteratorError7) {
+ throw _iteratorError7;
+ }
+ }
+ }
+
+ var orderedCuts = Array.from(cuts.values()).sort(function (a, b) {
+ return a - b;
+ });
+ var overlaps = new Map();
+
+ // Second pass: iterate through the cuts, get slices of every intersecting blockset
+
+ var _loop = function _loop(i, l) {
+ var cut = orderedCuts[i];
+ var nextCut = orderedCuts[i + 1];
+ var tuples = [];
+
+ var _iteratorNormalCompletion9 = true;
+ var _didIteratorError9 = false;
+ var _iteratorError9 = undefined;
+
+ try {
+ for (var _iterator9 = memoryMaps[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {
+ var _step9$value = _slicedToArray(_step9.value, 2),
+ setId = _step9$value[0],
+ blocks = _step9$value[1];
+
+ // Find the block with the highest address that is equal or lower to
+ // the current cut (if any)
+ var blockAddr = Array.from(blocks.keys()).reduce(function (acc, val) {
+ if (val > cut) {
+ return acc;
+ }
+ return Math.max(acc, val);
+ }, -1);
+
+ if (blockAddr !== -1) {
+ var block = blocks.get(blockAddr);
+ var subBlockStart = cut - blockAddr;
+ var subBlockEnd = nextCut - blockAddr;
+
+ if (subBlockStart < block.length) {
+ tuples.push([setId, block.subarray(subBlockStart, subBlockEnd)]);
+ }
+ }
+ }
+ } catch (err) {
+ _didIteratorError9 = true;
+ _iteratorError9 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion9 && _iterator9.return) {
+ _iterator9.return();
+ }
+ } finally {
+ if (_didIteratorError9) {
+ throw _iteratorError9;
+ }
+ }
+ }
+
+ if (tuples.length) {
+ overlaps.set(cut, tuples);
+ }
+ };
+
+ for (var i = 0, l = orderedCuts.length - 1; i < l; i++) {
+ _loop(i, l);
+ }
+
+ return overlaps;
+ }
+
+ /**
+ * Given the output of the {@linkcode MemoryMap.overlapMemoryMaps|overlapMemoryMaps}
+ * (a Map of address to an Array of (id, Uint8Array) tuples),
+ * returns a {@linkcode MemoryMap}. This discards the IDs in the process.
+ *
+ * The output Map contains as many entries as the input one (using the same addresses
+ * as keys), but the value for each entry will be the Uint8Array of the last
+ * tuple for each address in the input data.
+ *
+ * The scenario is wanting to join together several parsed .hex files, not worrying about
+ * their overlaps.
+ *
+ *
+ * @param {Map.Array} overlaps The (possibly overlapping) input memory blocks
+ * @return {MemoryMap} The flattened memory blocks
+ */
+
+ }, {
+ key: 'flattenOverlaps',
+ value: function flattenOverlaps(overlaps) {
+ return new MemoryMap(Array.from(overlaps.entries()).map(function (_ref) {
+ var _ref2 = _slicedToArray(_ref, 2),
+ address = _ref2[0],
+ tuples = _ref2[1];
+
+ return [address, tuples[tuples.length - 1][1]];
+ }));
+ }
+ }, {
+ key: 'fromPaddedUint8Array',
+ value: function fromPaddedUint8Array(bytes) {
+ var padByte = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0xFF;
+ var minPadLength = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 64;
+
+
+ if (!(bytes instanceof Uint8Array)) {
+ throw new Error('Bytes passed to fromPaddedUint8Array are not an Uint8Array');
+ }
+
+ // The algorithm used is naïve and checks every byte.
+ // An obvious optimization would be to implement Boyer-Moore
+ // (see https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm )
+ // or otherwise start skipping up to minPadLength bytes when going through a non-pad
+ // byte.
+ // Anyway, we could expect a lot of cases where there is a majority of pad bytes,
+ // and the algorithm should check most of them anyway, so the perf gain is questionable.
+
+ var memMap = new MemoryMap();
+ var consecutivePads = 0;
+ var lastNonPad = -1;
+ var firstNonPad = 0;
+ var skippingBytes = false;
+ var l = bytes.length;
+
+ for (var addr = 0; addr < l; addr++) {
+ var byte = bytes[addr];
+
+ if (byte === padByte) {
+ consecutivePads++;
+ if (consecutivePads >= minPadLength) {
+ // Edge case: ignore writing a zero-length block when skipping
+ // bytes at the beginning of the input
+ if (lastNonPad !== -1) {
+ /// Add the previous block to the result memMap
+ memMap.set(firstNonPad, bytes.subarray(firstNonPad, lastNonPad + 1));
+ }
+
+ skippingBytes = true;
+ }
+ } else {
+ if (skippingBytes) {
+ skippingBytes = false;
+ firstNonPad = addr;
+ }
+ lastNonPad = addr;
+ consecutivePads = 0;
+ }
+ }
+
+ // At EOF, add the last block if not skipping bytes already (and input not empty)
+ if (!skippingBytes && lastNonPad !== -1) {
+ memMap.set(firstNonPad, bytes.subarray(firstNonPad, l));
+ }
+
+ return memMap;
+ }
+ }]);
+
+ return MemoryMap;
+}();
+
+return MemoryMap;
+
+})));
+//# sourceMappingURL=intel-hex.browser.js.map
diff --git a/tests.html b/tests.html
index aab7ca77..d9ed66d7 100644
--- a/tests.html
+++ b/tests.html
@@ -16,6 +16,8 @@
+
+
diff --git a/tests/spec/python-spec.js b/tests/spec/python-spec.js
index 9f3c561b..1d856e29 100644
--- a/tests/spec/python-spec.js
+++ b/tests/spec/python-spec.js
@@ -136,43 +136,40 @@ describe("An editor for MicroPython on the BBC micro:bit:", function() {
describe("It's possible to generate a hex file.", function() {
var editor;
+ var template_hex = ":1000000000400020ED530100295401002B54010051\n" +
+ ":::::::::::::::::::::::::::::::::::::::::::\n" +
+ ":00000001FF\n";
beforeEach(function() {
affix("#editor");
editor = pythonEditor('editor');
});
- it("The editor converts text into Intel's hex format.", function() {
- var hexified = editor.hexlify('display.scroll("Hello")');
- var expected = ':10E000004D501700646973706C61792E7363726F81\n' +
- ':10E010006C6C282248656C6C6F222900000000009F';
- expect(hexified).toEqual(expected);
- });
-
it("The editor complains if the Python script is greater than 8k in length.", function() {
var hex_fail = function() {
- var result = editor.hexlify(new Array(8189).join('a'));
- }
+ // Keep in mind the 4 Bytes header
+ var codeLen = (8 * 1024) - 4 + 1;
+ var result = upyhex.injectPyStrIntoIntelHex(template_hex, new Array(codeLen + 1).join('a'));
+ };
expect(hex_fail).toThrowError(RangeError, 'Too long');
});
it("The editor is fine if the Python script is 8k in length.", function() {
- var hexified = editor.hexlify(new Array(8188).join('a'));
+ var codeLen = (8 * 1024) - 4;
+ var hexified = upyhex.injectPyStrIntoIntelHex(template_hex, new Array(codeLen + 1).join('a'));
expect(hexified).not.toBe(null);
});
-
it("A hex file is generated from the script and template firmware.",
function() {
- var template_hex = ":10E000004D500B004D6963726F507974686F6E00EC\n" +
- ":::::::::::::::::::::::::::::::::::::::::::\n" +
- ":10E000004D500B004D6963726F507974686F6E00EC";
editor.setCode('display.scroll("Hello")');
var result = editor.getHexFile(template_hex);
- var expected = ":10E000004D500B004D6963726F507974686F6E00EC\n" +
+ var expected = ":020000040000FA\n" +
+ ":1000000000400020ED530100295401002B54010051\n" +
+ ":020000040003F7\n" +
":10E000004D501700646973706C61792E7363726F81\n" +
":10E010006C6C282248656C6C6F222900000000009F\n" +
- ":10E000004D500B004D6963726F507974686F6E00EC";
+ ":00000001FF\n";
expect(result).toEqual(expected);
});
});
@@ -187,9 +184,11 @@ describe("An editor for MicroPython on the BBC micro:bit:", function() {
});
it("The editor converts from Intel's hex format to text", function() {
- var raw_hex = ":10E000004D501700646973706C61792E7363726F81\n" +
- ":10E010006C6C282248656C6C6F222900000000009F\n";
- var result = editor.unhexlify(raw_hex);
+ var raw_hex = ":020000040003F7\n" +
+ ":10E000004D501700646973706C61792E7363726F81\n" +
+ ":10E010006C6C282248656C6C6F222900000000009F\n" +
+ ":00000001FF\n";
+ var result = upyhex.extractPyStrFromIntelHex(raw_hex);
var expected = 'display.scroll("Hello")';
expect(result).toEqual(expected);
});
@@ -204,7 +203,7 @@ describe("An editor for MicroPython on the BBC micro:bit:", function() {
":10E010006C6C282248656C6C6F222900000000009F\n" +
":04000005000153EDB6\n" +
":00000001FF";
- var result = editor.extractScript(raw_hex);
+ var result = upyhex.extractPyStrFromIntelHex(raw_hex);
var expected = 'display.scroll("Hello")';
expect(result).toEqual(expected);
});
@@ -213,11 +212,9 @@ describe("An editor for MicroPython on the BBC micro:bit:", function() {
var raw_hex = ":10E000004D500B004D6963726F507974686F6E00EC\n" +
":10B2C00021620100E1780100198001000190010074\n" +
":04B2D0000D0100006C\n" +
- ":10E000004D501700646973706C61792E7363726F81\n" +
- ":10E010006C6C282248656C6C6F222900000000009F\n" +
":04000005000153EDB6\n" +
":00000001FF";
- var result = editor.extractScript(raw_hex);
+ var result = upyhex.extractPyStrFromIntelHex(raw_hex);
var expected = '';
expect(result).toEqual(expected);
});