UNPKG

microvium

Version:

A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.

314 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Future = exports.BinaryRegion = void 0; const utils_1 = require("./utils"); const visual_buffer_1 = require("./visual-buffer"); const events_1 = require("events"); const trace_file_1 = require("./trace-file"); const general_1 = require("./general"); // A class roughly like VisualBuffer for writing buffers, except that you are // able to write placeholder values that will only get their final value later // (`Future` values) class BinaryRegion { constructor(htmlTemplate, traceFilename) { this.htmlTemplate = htmlTemplate; this._segments = new Array(); this.writeToBuffer = (buffer) => { const cleanups = this._segments.map(segment => segment(buffer)); const cleanup = checkFinalized => { cleanups.forEach(cleanup => cleanup && cleanup(checkFinalized)); }; return cleanup; }; this._traceFile = traceFilename !== undefined ? new trace_file_1.TraceFile(traceFilename) : undefined; this.traceDump(); } append(value, label, format) { if (value instanceof Future) { this.appendFuture(value, label, format); } else { this.appendDirect(value, label, format); } } // Add padding to an even boundary padToEven(format) { this.appendSegment(b => { if (b.writeOffset % 2 !== 0) { const count = 1; b.append({ value: count }, format); } }); } // Add pad so that an allocation excluding the header will be 4-byte aligned padToQuad(format, headerSize) { this.appendSegment(b => { if ((b.writeOffset + headerSize) % 4 !== 0) { const count = 4 - (b.writeOffset + headerSize) % 4; b.append({ value: count }, format); } }); } appendBuffer(buffer, label) { (0, utils_1.hardAssert)(buffer instanceof BinaryRegion); this.appendSegment(buffer.writeToBuffer); } toBuffer(enforceFinalized = true) { return this.toVisualBuffer(enforceFinalized).toBuffer(); } toHTML() { return this.toVisualBuffer(false).toHTML(); } get currentOffset() { const address = new Future(false); this.appendSegment(b => { address.resolve(b.writeOffset); return () => address.unresolve(); }); return address; } // Post processing is for things like CRC values. Post processing steps only // get evaluated at the end. postProcess(start, end, process) { const result = new Future(); // Insert a segment just to hook into the "cleanup" phase at the end, which // is where the post processing will be done. this.appendSegment(buffer => { const cleanup = enforceFinalized => { if (!start.isResolved || !end.isResolved) { if (enforceFinalized) { throw new Error('Post processing cannot be done with unresolved range'); } return; } const data = buffer.toBuffer().slice(start.value, end.value); result.resolve(process(data)); // Since we're in the cleanup phase, we need to unresolve immediately. // This is valid since post-processing occurs after all appends to the // buffer, so there is no "future" beyond this point at which the result // may still be used. result.unresolve(); }; return cleanup; }); return result; } appendSegment(item) { this._segments.push(item); this.traceDump(); } traceDump() { this._traceFile && this._traceFile.dump(() => (0, general_1.htmlPageTemplate)(this.toHTML())); } appendDirect(value, label, format) { const labelledValue = { value, label }; this.appendSegment((b => (b.append(labelledValue, format), noCleanupRequired))); } appendFuture(value, label, format) { this.appendSegment((buffer) => { // If it's already resolved, we can just write the value itself if (value.isResolved) { buffer.append({ value: value.value, label }, format); return noCleanupRequired; } else { // If it's not yet resolved, then we write a placeholder and then // subscribe to be notified when we have the final value, so we can // overwrite the placeholder with the actual value. // The placeholder renders with a value of "undefined" but still has the label const bufferOnWhichToOverwrite = buffer; const whereToOverwrite = buffer.writeOffset; let isWriteFinalized = false; buffer.append({ value: undefined, label }, format); value.once('resolve', resolve); return cleanup; function resolve(value) { (0, utils_1.hardAssert)(!isWriteFinalized); isWriteFinalized = true; bufferOnWhichToOverwrite.overwrite({ value, label }, format, whereToOverwrite); } function cleanup(checkFinalized) { // Cleanup is called after a buffer is produced. We need to unsubscribe // from the source value because otherwise we'll mutate this buffer on // future calls to `toBuffer` value.off('resolve', resolve); if (checkFinalized && !isWriteFinalized) { return (0, utils_1.invalidOperation)('Expected future value to be resolved, but it is not.'); } } } }); } toVisualBuffer(enforceFinalized) { const buffer = new visual_buffer_1.VisualBuffer(this.htmlTemplate); const cleanup = this.writeToBuffer(buffer); cleanup(enforceFinalized); return buffer; } } exports.BinaryRegion = BinaryRegion; const noCleanupRequired = () => { }; let futureIdCounter = 1; //const logFileName = `future.${Date.now()}.log`; // Value to be calculated later class Future extends events_1.EventEmitter { constructor(assignable = true) { super(); this._resolved = false; this._assigned = false; this._id = futureIdCounter++; this.setMaxListeners(5000); // this.log('Created'); this._assignable = assignable; } // log(message: string) { // fs.appendFileSync(logFileName, `Future ${this._id}: ${message}\n`); // } toString() { return 'Future ' + this._id; } assign(value) { // this.log('Assign: ' + value); // Futures created with `new Future(true)` are assignable, but those created // with `map` or `bind` are not since they already have a source. In the // case of a BinaryRegion, the cursor locations are not assignable. if (!this._assignable) { return (0, utils_1.invalidOperation)('Value not assignable.'); } if (this._assigned) { return (0, utils_1.invalidOperation)('Cannot assign multiple times'); } if (this._resolved) { return (0, utils_1.invalidOperation)('Future has already been resolved'); } this._assigned = true; if (value instanceof Future) { value.on('resolve', v => this.resolve(v)); value.on('unresolve', () => this.unresolve()); if (value.isResolved) this.resolve(value.value); else this.unresolve(); } else { this.resolve(value); } } map(f) { const result = new Future(false); if (this.isResolved) { result.resolve(f(this.value)); } this.on('resolve', v => result.resolve(f(v))); this.on('unresolve', () => result.unresolve()); // this.log('Map result: ' + result); return result; } bind(f) { let state = 'not-resolved'; const result = new Future(false); let inner; this.on('resolve', outerResolve); this.on('unresolve', outerUnresolve); if (this.isResolved) { outerResolve(this.value); } // this.log('Bind result: ' + result); return result; function outerResolve(value) { if (state !== 'not-resolved') return (0, utils_1.unexpected)(); inner = f(value); inner.on('resolve', innerResolve); inner.on('unresolve', innerUnresolve); if (inner.isResolved) { state = 'resolved'; result.resolve(inner.value); } else { state = 'outer-resolved'; } } function outerUnresolve() { if (state === 'not-resolved') return (0, utils_1.unexpected)(); if (!inner) return (0, utils_1.unexpected)(); inner.off('resolve', innerResolve); inner.off('unresolve', innerUnresolve); inner = undefined; if (state === 'resolved') { result.unresolve(); } state = 'not-resolved'; } function innerResolve(value) { if (state !== 'outer-resolved') return (0, utils_1.unexpected)(); state = 'resolved'; result.resolve(value); } function innerUnresolve() { if (state !== 'resolved') return (0, utils_1.unexpected)(); state = 'outer-resolved'; result.unresolve(); } } subtract(that) { return this.bind(a => that.map(b => a - b)); } static create(value) { if (value instanceof Future) return value; const result = new Future(false); result.resolve(value); return result; } static isFuture(value) { return value instanceof Future; } static map(value, f) { if (Future.isFuture(value)) return value.map(f); else return f(value); } static bind(value, f) { if (Future.isFuture(value)) { return value.bind(v => Future.create(f(v))); } else return f(value); } static lift(operation) { return (v) => Future.bind(v, operation); } get isResolved() { return this._resolved; } get value() { if (!this._resolved) { return (0, utils_1.invalidOperation)('Value not resolved'); } return this._value; } resolve(value) { // this.log('Resolve with ' + value); if (this._resolved) { return (0, utils_1.invalidOperation)('Cannot resolve a future multiple times in the same context'); } this._value = value; this.lastValue = value; this._resolved = true; this.emit('resolve', value); } unresolve() { // this.log('Unresolve'); if (!this._resolved) return; this._value = undefined; this._resolved = false; this.emit('unresolve'); } } exports.Future = Future; //# sourceMappingURL=binary-region.js.map