Back to a yielding compiler

This commit is contained in:
Thomas Weber committed 2020-07-06 19:34:38 -05:00
1 parent 5bec779b4a
commit bf32e2e468
5 files changed
+170 -257

No files matched your search

@@ -32,20 +32,28 @@ const repeat = /** @param {StatementUtil} util */ (util) => {
const TIMES = util.input('TIMES');
const SUBSTACK = util.substack('SUBSTACK');
util.enterState(TIMES.asNumber());
const label = util.putLabel();
util.writeLn(`if (thread.state >= 0.5) {`);
util.writeLn(` thread.state--;`);
util.writeLn(`while (thread.state >= 0.5) { thread.state--;`)
util.write(SUBSTACK);
util.jumpLazy(label);
util.writeLn('}');
util.yieldLoop();
util.writeLn(`}`)
// const label = util.putLabel();
// util.writeLn(`if (thread.state >= 0.5) {`);
// util.writeLn(` thread.state--;`);
// util.write(SUBSTACK);
// util.jumpLazy(label);
// util.writeLn('}');
util.restoreState();
};
const forever = /** @param {StatementUtil} util */ (util) => {
const SUBSTACK = util.substack('SUBSTACK');
const label = util.putLabel();
util.writeLn('while (true) {');
util.write(SUBSTACK);
util.jumpLazy(label);
util.yieldLoop();
util.writeLn('}');
// const label = util.putLabel();
// util.write(SUBSTACK);
// util.jumpLazy(label);
};
const if_ = /** @param {StatementUtil} util */ (util) => {
@@ -70,21 +78,29 @@ const ifElse = /** @param {StatementUtil} util */ (util) => {
const repeatUntil = /** @param {StatementUtil} util */ (util) => {
const CONDITION = util.input('CONDITION');
const SUBSTACK = util.substack('SUBSTACK');
const label = util.putLabel();
util.writeLn(`if (!${CONDITION.asBoolean()}) {`);
util.writeLn(`while (!${CONDITION.asBoolean()}) {`);
util.write(SUBSTACK);
util.jumpLazy(label);
util.writeLn(`}`);
util.yieldLoop();
util.writeLn('}');
// const label = util.putLabel();
// util.writeLn(`if (!${CONDITION.asBoolean()}) {`);
// util.write(SUBSTACK);
// util.jumpLazy(label);
// util.writeLn(`}`);
};
const while_ = /** @param {StatementUtil} util */ (util) => {
const CONDITION = util.input('CONDITION');
const SUBSTACK = util.substack('SUBSTACK');
const label = util.putLabel();
util.writeLn(`if (${CONDITION.asBoolean()}) {`);
util.writeLn(`while (${CONDITION.asBoolean()}) {`);
util.write(SUBSTACK);
util.jumpLazy(label);
util.writeLn(`}`);
util.yieldLoop();
util.writeLn('}');
// const label = util.putLabel();
// util.writeLn(`if (${CONDITION.asBoolean()}) {`);
// util.write(SUBSTACK);
// util.jumpLazy(label);
// util.writeLn(`}`);
};
const wait = /** @param {StatementUtil} util */ (util) => {
@@ -124,7 +140,7 @@ const stop = /** @param {StatementUtil} util */ (util) => {
} else if (STOP_OPTION === 'other scripts in sprite' || STOP_OPTION === 'other scripts in stage') {
util.writeLn('target.runtime.stopForTarget(target, thread);');
} else if (STOP_OPTION === 'this script') {
util.writeLn('end(); return;');
util.writeLn('return;');
}
};
@@ -34,11 +34,10 @@ const call = /** @param {StatementUtil} util */ (util) => {
}
const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;
const labelId = util.nextLabel();
util.compiler.dependProcedure(procedureCode);
util.write(`call("${util.safe(procedureCode)}", {`);
util.write(`yield* thread.procedures["${util.safe(procedureCode)}"].fn({`);
for (let i = 0; i < paramIds.length; i++) {
let value;
@@ -55,16 +54,15 @@ const call = /** @param {StatementUtil} util */ (util) => {
util.write(`"${util.safe(paramNames[i])}": ${value},`);
}
util.writeLn(`}, ${labelId}); return;`);
util.putLabel(labelId);
util.writeLn(`});`);
};
const getStringArgument = /** @param {InputUtil} util */ (util) => {
const VALUE = util.fieldValueUnsafe('VALUE');
return util.unknown(`thread.call.args["${util.safe(VALUE)}"]`);
return util.unknown(`C["${util.safe(VALUE)}"]`);
};
const getBooleanArgument = /** @param {InputUtil} util */ (util) => {
const VALUE = util.fieldValueUnsafe('VALUE');
return util.boolean(`toBoolean(thread.call.args["${util.safe(VALUE)}"])`);
return util.boolean(`toBoolean(C["${util.safe(VALUE)}"])`);
};
+61 -102
View File
@@ -173,53 +173,38 @@ class StatementUtil extends BlockUtil {
this.source = '';
}
/**
* @returns {number}
*/
nextLabel() {
return this.compiler.nextLabel();
yieldLoop() {
this.writeLn('if (thread.warp === 0) { debugger; yield; }');
}
/**
* @param {number} [label]
* @returns {number}
*/
putLabel(label) {
if (label === undefined) {
label = this.nextLabel();
}
this.write(`{{${label}}}`);
return label;
}
// /**
// * Immediately jump to a label.
// * @param {number} label
// */
// jump(label) {
// this.writeLn(`jump(${label}); return;`);
// }
/**
* Immediately jump to a label.
* @param {number} label
*/
jump(label) {
this.writeLn(`jump(${label}); return;`);
}
// /**
// * Lazily jump to a label.
// * If running in warp mode, this will be instant. Otherwise, it will run the next tick.
// * @param {number} label
// */
// jumpLazy(label) {
// this.writeLn(`jumpLazy(${label}); return;`);
// }
/**
* Lazily jump to a label.
* If running in warp mode, this will be instant. Otherwise, it will run the next tick.
* @param {number} label
*/
jumpLazy(label) {
this.writeLn(`jumpLazy(${label}); return;`);
}
/**
* Writes the necessary JS to yield the current thread until all given threads have finished executing.
* @param {string} threads The threads to wait for (eg. "thread.state")
*/
waitUntilThreadsComplete(threads) {
this.enterState(threads);
const label = this.putLabel();
this.writeLn(`if (waitThreads(thread.state)) {`);
this.jumpLazy(label);
this.writeLn(`}`);
}
// /**
// * Writes the necessary JS to yield the current thread until all given threads have finished executing.
// * @param {string} threads The threads to wait for (eg. "thread.state")
// */
// waitUntilThreadsComplete(threads) {
// this.enterState(threads);
// const label = this.putLabel();
// this.writeLn(`if (waitThreads(thread.state)) {`);
// this.jumpLazy(label);
// this.writeLn(`}`);
// }
/**
* Write JS to this statement, followed by a newline.
@@ -341,13 +326,12 @@ disableToString(CompiledInput.prototype.asBoolean);
/**
* @typedef {Object} CompiledProcedure
* @property {boolean} warp
* @property {number} label
* @property {Function} fn
*/
/**
* @typedef {Object} CompilationResult
* @property {Function} startingFunction
* @property {Jump[]} jumps
* @property {Object.<string, CompiledProcedure>} procedures
*/
@@ -371,15 +355,6 @@ class Compiler {
/** @type {string} */
this.topBlock = thread.topBlock;
/** @type {number} */
this.labelCount = 0;
/**
* Function jump points.
* @type {Jump[]}
*/
this.jumps = [];
/**
* Compiled procedures.
* @type {Object.<string, CompiledProcedure>}
@@ -474,54 +449,36 @@ class Compiler {
return source;
}
nextLabel() {
return this.labelCount++;
}
parseContinuations(script) {
const labels = {};
let index = 0;
let accumulator = 0;
while (true) {
const labelStart = script.indexOf('{{', index);
if (labelStart === -1) {
break;
}
const labelEnd = script.indexOf('}}', index);
const id = script.substring(labelStart + 2, labelEnd);
const length = labelEnd + 2 - labelStart;
accumulator += length;
labels[id] = labelEnd + 2 - accumulator;
index = labelEnd + 2;
}
const modifiedScript = script.replace(/{{\d+}}/g, '');
return {
labels,
script: modifiedScript,
};
}
compileHat(topBlock) {
let script = '';
script += '{{' + this.nextLabel() + '}}';
script += this.compileStack(topBlock);
script += 'end();';
const parseResult = this.parseContinuations(script);
const parsedScript = parseResult.script;
const startingLabelCount = this.jumps.length;
for (const label of Object.keys(parseResult.labels)) {
this.jumps[label] = execute.createContinuation(this, parsedScript.slice(parseResult.labels[label]));
let script = '(function*(C) {\n';
if (this.isWarp) {
script += 'thread.warp++;\n';
} else {
script += 'if (thread.warp) thread.warp++;\n';
}
script += this.compileStack(topBlock);
if (this.isWarp) {
script += 'thread.warp--;\n';
} else {
script += 'debugger;\n';
script += 'if (thread.warp) thread.warp--; if (thread.warp === 0) yield;\n';
}
script += '\n});';
// script += 'end();';
// const parseResult = this.parseContinuations(script);
// const parsedScript = parseResult.script;
// const startingLabelCount = this.jumps.length;
// for (const label of Object.keys(parseResult.labels)) {
// this.jumps[label] = execute.createContinuation(this, parsedScript.slice(parseResult.labels[label]));
// }
const fn = execute.evalCompiledScript(this, script);
log.info(`[${this.target.getName()}] compiled script`, script);
return startingLabelCount;
return fn;
}
/**
@@ -568,17 +525,19 @@ class Compiler {
}
}
const procedureLabel = this.compileHat(bodyStart);
this.isWarp = isWarp;
const procedureFn = this.compileHat(bodyStart);
this.procedures[procedureCode] = {
warp: isWarp,
label: procedureLabel,
fn: procedureFn,
};
}
}
return {
jumps: this.jumps,
startingFunction: this.jumps[startingFunction],
startingFunction: startingFunction,
procedures: this.procedures,
};
}
+71 -132
View File
@@ -5,62 +5,62 @@ const Timer = require('../util/timer');
// The JSDoc annotations define the function's contract.
// Most of these functions are only used at runtime by generated scripts. Despite what your editor may say, they are not unused.
/**
* Immediately jump to a label.
* @param {number} id The label to jump to.
*/
const jump = (id) => {
immediate = thread.jumps[id];
};
// /**
// * Immediately jump to a label.
// * @param {number} id The label to jump to.
// */
// const jump = (id) => {
// immediate = thread.jumps[id];
// };
/**
* Jump to a label.
* If in warp mode, this will be instant (like jump())
* Otherwise, this jump will occur in the next tick loop.
* @param {number} id The label to jump to.
*/
const jumpLazy = (id) => {
if (thread.warp) {
jump(id);
} else {
thread.fn = thread.jumps[id];
}
};
// /**
// * Jump to a label.
// * If in warp mode, this will be instant (like jump())
// * Otherwise, this jump will occur in the next tick loop.
// * @param {number} id The label to jump to.
// */
// const jumpLazy = (id) => {
// if (thread.warp) {
// jump(id);
// } else {
// thread.fn = thread.jumps[id];
// }
// };
/**
* Call into a procedure.
* @param {string} procedureCode The procedure's name
* @param {*} args The arguments to pass to the procedure.
* @param {number} resume The label to return to when the procedure completes.
*/
const call = (procedureCode, args, resume) => {
thread.callStack.push(thread.call);
thread.call = {
args,
resume,
};
// TODO: check recursion
const procedure = thread.procedures[procedureCode];
if (procedure.warp || thread.warp) {
thread.warp++;
}
jump(procedure.label);
};
// /**
// * Call into a procedure.
// * @param {string} procedureCode The procedure's name
// * @param {*} args The arguments to pass to the procedure.
// * @param {number} resume The label to return to when the procedure completes.
// */
// const call = (procedureCode, args, resume) => {
// thread.callStack.push(thread.call);
// thread.call = {
// args,
// resume,
// };
// // TODO: check recursion
// const procedure = thread.procedures[procedureCode];
// if (procedure.warp || thread.warp) {
// thread.warp++;
// }
// jump(procedure.label);
// };
/**
* End a script or procedure call.
*/
const end = () => {
if (thread.callStack.length) {
jump(thread.call.resume);
if (thread.warp) {
thread.warp--;
}
thread.call = thread.callStack.pop();
} else {
retire();
}
};
// /**
// * End a script or procedure call.
// */
// const end = () => {
// if (thread.callStack.length) {
// jump(thread.call.resume);
// if (thread.warp) {
// thread.warp--;
// }
// thread.call = thread.callStack.pop();
// } else {
// retire();
// }
// };
/**
* Start hats by opcode.
@@ -353,11 +353,11 @@ const mod = (n, modulus) => {
return result;
};
/**
* If set, the executor will immediately start executing this function when the current function returns.
* @type {Function}
*/
var immediate;
// /**
// * If set, the executor will immediately start executing this function when the current function returns.
// * @type {Function}
// */
// var immediate;
/**
* The currently running thread.
* @type {Thread}
@@ -377,13 +377,19 @@ const execute = (_thread) => {
thread = _thread;
target = thread.target;
_thread.fn();
_thread.generator.next();
// while (_thread.warp) {
// _thread.generator.next();
// }
while (immediate) {
var fn = immediate;
immediate = null;
fn();
}
// _thread.fn();
// while (immediate) {
// var fn = immediate;
// immediate = null;
// fn();
// }
};
/**
@@ -404,73 +410,6 @@ const evalCompiledScript = (compiler, _source) => {
return eval(_source);
};
const createContinuation = (compiler, source) => {
// TODO: optimize, refactor
// TODO: support more than just "} else {"
// TODO: this is something that definitely deserves unit tests
var result = '(function continuation() {\n';
var brackets = 0;
var delBrackets = 0;
var shouldDelete = false;
var here = 0;
var length = source.length;
while (here < length) {
var i = source.indexOf('{', here);
var j = source.indexOf('}', here);
var k = source.indexOf('return;', here);
if (k === -1) k = length;
if (i === -1 && j === -1) {
if (!shouldDelete) {
result += source.slice(here, k);
}
break;
}
if (i === -1) i = length;
if (j === -1) j = length;
if (shouldDelete) {
if (i < j) {
delBrackets++;
here = i + 1;
} else {
delBrackets--;
if (!delBrackets) {
shouldDelete = false;
}
here = j + 1;
}
} else {
if (brackets === 0 && k < i && k < j) {
result += source.slice(here, k);
break;
}
if (i < j) {
result += source.slice(here, i + 1);
brackets++;
here = i + 1;
} else {
result += source.slice(here, j);
here = j + 1;
if (source.substr(j, 8) === '} else {') {
if (brackets > 0) {
result += '} else {';
here = j + 8;
} else {
shouldDelete = true;
delBrackets = 0;
}
} else {
if (brackets > 0) {
result += '}';
brackets--;
}
}
}
}
}
result += '})';
return evalCompiledScript(compiler, result);
};
execute.createContinuation = createContinuation;
execute.evalCompiledScript = evalCompiledScript;
module.exports = execute;
+2 -1
View File
@@ -198,6 +198,7 @@ class Thread {
this.warp = 0;
this.jumps = [];
this.fn = null;
this.generator = null;
this.state = null;
this.stateStack = [];
this.call = null;
@@ -452,10 +453,10 @@ class Thread {
}
}
this.jumps = result.jumps;
this.procedures = result.procedures;
this.fn = result.startingFunction;
this.isCompiled = true;
this.generator = this.fn();
}
}