const JSZM = require('./jszm-async') const defer = () => { const deferred = { resolve: value => (deferred._resolved = value), reject: err => (deferred._rejected = err) } const promise = new Promise((resolve, reject) => { if (deferred.hasOwnProperty('_resolved')) return resolve(deferred._resolved) if (deferred.hasOwnProperty('_rejected')) return reject(deferred._rejected) deferred.resolve = resolve deferred.reject = reject }) deferred.promise = promise return deferred } class VM { constructor(data, save) { this.zm = new JSZM(data) const vm = this let printBuffer = [] const transcript = [] this.inputBuffer = [] this.outputBuffer = [] this.saveData = save this.zm.save = function*(buf) { vm.saveData = buf return true } this.zm.restore = function*() { return vm.saveData } this.zm.highlight = function*(a) { //printBuffer.push(`[${a}]`) } this.zm.print = function*(text, scripting) { printBuffer.push(text) if (scripting) { transcript.push(text) } } const flush = () => { const output = printBuffer.join('') printBuffer = [] if (this.outputBuffer.length) { const deferred = this.outputBuffer.shift() deferred.resolve(output) } else { this.initialOutput = output } } vm.reader = null this.zm.read = async function*(maxlen) { flush() // Should only read after done writing, so flush what it's written. if (vm.reader) throw new Error('Simultaneous reads') if (vm.inputBuffer.length) { const text = this.inputBuffer.shift() return text } else { const deferred = defer() vm.reader = deferred const text = await deferred.promise vm.reader = null return text } } } start() { if (this._started) throw new Error('Already started') const deferred = defer() this.outputBuffer.push(deferred) this.zm.run().next() return deferred.promise } play(text) { const deferred = defer() this.outputBuffer.push(deferred) if (this.reader) { this.reader.resolve(text) } else { this.inputBuffer.push(text) } return deferred.promise } save() { const ret = [Buffer.from(this.zm.serialize([], [])).toString('base64')] if (this.saveData) { ret.push(Buffer.from(this.saveData).toString('base64')) } return ret.join('.') } restore(save) { if (typeof save === 'string') { const datas = save.split('.') this.zm.deserialize(Buffer.from(datas[0], 'base64')) if (datas.length > 1) { this.saveData = Buffer.from(datas[1], 'base64') } } } } VM.play = async (data, save, input) => { const vm = new VM(data) const ret = {} ret.output = await vm.start() if (save) { vm.restore(save) } if (input) { ret.output = await vm.play(input) } ret.save = vm.save() return ret } module.exports = VM