Lay out more of the compiler groundwork
- add CompiledInput, will allow more complex operations later - add eval.js to handle runtime data & methods - move compiler executor to own file - simplify compiler execute, scripts are now responsible for all thread status on their own - add type annotations for compilers - add math_angle - add math_integer - add math_positive_number - add data_variable
This commit is contained in:
16 files changed
+176
-24
No files matched your search
@@ -1,18 +1,29 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
math_angle: number,
|
||||
math_number: number,
|
||||
math_integer: number,
|
||||
math_positive_number: number,
|
||||
math_whole_number: number,
|
||||
};
|
||||
};
|
||||
|
||||
const number = (util) => {
|
||||
const number = /** @param {InputUtil} util */ (util) => {
|
||||
const NUM = util.getFieldUnsafe('NUM');
|
||||
const number = Number(NUM);
|
||||
return number;
|
||||
return util.number(number);
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
control_repeat: repeat,
|
||||
@@ -5,6 +10,9 @@ module.exports.getStatements = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
data_variable: getVariable,
|
||||
};
|
||||
};
|
||||
|
||||
const getVariable = /** @param {InputUtil} util */ (util) => {
|
||||
// TODO: this is unsafe.
|
||||
// TODO: lookupOrCreateVariable has overhead, use target.variables directly instead if possible
|
||||
return util.number(`target.lookupOrCreateVariable("${util.block.fields.VARIABLE.id}", "${util.block.fields.VARIABLE.value}")`);
|
||||
};
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
looks_changeeffectby: changeEffect,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const { InputUtil, StatementUtil, CompiledInput } = require('../compiler');
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: StatementUtil) => void>}
|
||||
*/
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Object.<string, (util: InputUtil) => CompiledInput>}
|
||||
*/
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
|
||||
+54
-10
@@ -1,5 +1,6 @@
|
||||
const log = require('../util/log');
|
||||
const Thread = require('../engine/thread');
|
||||
const _eval = require('./eval');
|
||||
|
||||
const statements = {};
|
||||
const inputs = {};
|
||||
@@ -34,6 +35,9 @@ defaultExtensions.forEach((ext) => {
|
||||
});
|
||||
|
||||
class BlockUtil {
|
||||
/**
|
||||
* @param {Compiler} compiler
|
||||
*/
|
||||
constructor(compiler, block) {
|
||||
this.compiler = compiler;
|
||||
this.block = block;
|
||||
@@ -49,8 +53,8 @@ class BlockUtil {
|
||||
}
|
||||
|
||||
class InputUtil extends BlockUtil {
|
||||
constructor(compiler, block) {
|
||||
super(compiler, block);
|
||||
number(source) {
|
||||
return new CompiledInput(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +65,7 @@ class StatementUtil extends BlockUtil {
|
||||
}
|
||||
|
||||
yieldLoop() {
|
||||
this.writeLn('yield;');
|
||||
this.writeLn(`yield;`);
|
||||
}
|
||||
|
||||
writeLn(s) {
|
||||
@@ -88,28 +92,53 @@ class StatementUtil extends BlockUtil {
|
||||
}
|
||||
}
|
||||
|
||||
class CompiledInput {
|
||||
/**
|
||||
* @param {string} source The input's source code.
|
||||
*/
|
||||
constructor(source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
|
||||
class Compiler {
|
||||
/**
|
||||
* @param {Thread} thread
|
||||
*/
|
||||
constructor(thread) {
|
||||
this.thread = thread;
|
||||
this.target = thread.target;
|
||||
this.variables = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CompiledInput}
|
||||
*/
|
||||
compileInput(parentBlock, inputName) {
|
||||
const input = parentBlock.inputs[inputName];
|
||||
const inputId = input.block;
|
||||
const block = this.thread.target.blocks.getBlock(inputId);
|
||||
|
||||
const util = new InputUtil(this, block);
|
||||
let compiler = inputs[block.opcode];
|
||||
if (!compiler) {
|
||||
log.error('unknown opcode', block);
|
||||
throw new Error('unknown opcode: ' + block.opcode);
|
||||
}
|
||||
|
||||
const util = new InputUtil(this, block);
|
||||
const result = compiler(util);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} startingId The ID of the first block in the stack.
|
||||
* @returns {string}
|
||||
*/
|
||||
compileStack(startingId) {
|
||||
let blockId = startingId;
|
||||
let source = '';
|
||||
@@ -120,36 +149,51 @@ class Compiler {
|
||||
throw new Error('no block');
|
||||
}
|
||||
|
||||
const util = new StatementUtil(this, block);
|
||||
let compiler = statements[block.opcode];
|
||||
if (!compiler) {
|
||||
log.error('unknown opcode', block);
|
||||
throw new Error('unknown opcode: ' + block.opcode);
|
||||
}
|
||||
|
||||
const util = new StatementUtil(this, block);
|
||||
compiler(util);
|
||||
source += util.source;
|
||||
blockId = block.next;
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
compile() {
|
||||
const target = this.thread.target;
|
||||
const target = this.target;
|
||||
if (!target) throw new Error('no target');
|
||||
|
||||
const topBlockId = this.thread.topBlock;
|
||||
const topBlock = this.thread.target.blocks.getBlock(topBlockId);
|
||||
const topBlock = this.target.blocks.getBlock(topBlockId);
|
||||
if (!topBlock) throw new Error('not a hat');
|
||||
|
||||
const script = this.compileStack(topBlock.next);
|
||||
if (script.length === 0) {
|
||||
throw new Error('generated script was empty');
|
||||
}
|
||||
|
||||
log.info('compiled script', script);
|
||||
|
||||
// TODO: move this to execute.js
|
||||
var fn = eval('(function* compiled_script() { ' + script + ' })');
|
||||
return fn;
|
||||
try {
|
||||
const fn = _eval(this, `(function* compiled_script() {\n${script}\nthread.status = 4;\n})`);
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error('fn is not a function');
|
||||
}
|
||||
return fn;
|
||||
} catch (e) {
|
||||
log.error('error evaling', e, script);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Compiler.InputUtil = InputUtil;
|
||||
Compiler.StatementUtil = StatementUtil;
|
||||
Compiler.CompiledInput = CompiledInput;
|
||||
|
||||
module.exports = Compiler;
|
||||
@@ -0,0 +1,16 @@
|
||||
// eval.js is responsible for eval()ing the generated JS and providing the proper runtime methods and data.
|
||||
|
||||
// `eval` is reserved in strict mode
|
||||
const _eval = (compiler, _source) => {
|
||||
// init data
|
||||
const thread = compiler.thread;
|
||||
const target = compiler.target;
|
||||
|
||||
// no reason to access compiler
|
||||
compiler = null;
|
||||
|
||||
// eval will grab references to all variables in this context
|
||||
return eval(_source);
|
||||
};
|
||||
|
||||
module.exports = _eval;
|
||||
@@ -0,0 +1,12 @@
|
||||
const Thread = require('../engine/thread');
|
||||
const Sequencer = require('../engine/thread');
|
||||
|
||||
/**
|
||||
* @param {Sequencer} sequencer
|
||||
* @param {Thread} thread
|
||||
*/
|
||||
const execute = function (sequencer, thread) {
|
||||
thread.generator.next();
|
||||
};
|
||||
|
||||
module.exports = execute;
|
||||
@@ -1,9 +0,0 @@
|
||||
const execute = function (sequencer, thread) {
|
||||
const result = thread.generator.next();
|
||||
|
||||
if (result.done) {
|
||||
sequencer.retireThread(thread);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = execute;
|
||||
@@ -4,7 +4,7 @@ const log = require('../util/log');
|
||||
const Thread = require('./thread');
|
||||
const {Map} = require('immutable');
|
||||
const cast = require('../util/cast');
|
||||
const compilerExecute = require('../compiler/executor');
|
||||
const compilerExecute = require('../compiler/execute');
|
||||
|
||||
/**
|
||||
* Single BlockUtility instance reused by execute for every pritimive ran.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Timer = require('../util/timer');
|
||||
const Thread = require('./thread');
|
||||
const execute = require('./execute.js');
|
||||
const compilerExecute = require('../compiler/executor');
|
||||
const compilerExecute = require('../compiler/execute');
|
||||
|
||||
/**
|
||||
* Profiler frame name for stepping a single thread.
|
||||
|
||||
Reference in new issue
Block a user