Back to a yielding compiler
This commit is contained in:
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
@@ -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
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user