microvium
Version:
A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.
314 lines • 11.9 kB
JavaScript
"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