diff --git a/src/compiler/blocks/compiler_natives.js b/src/compiler/blocks/compiler_natives.js index 92c7bce0..a76eecf8 100644 --- a/src/compiler/blocks/compiler_natives.js +++ b/src/compiler/blocks/compiler_natives.js @@ -1,18 +1,29 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. 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); }; diff --git a/src/compiler/blocks/compiler_scratch3_control.js b/src/compiler/blocks/compiler_scratch3_control.js index c007a180..6226f644 100644 --- a/src/compiler/blocks/compiler_scratch3_control.js +++ b/src/compiler/blocks/compiler_scratch3_control.js @@ -1,3 +1,8 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { control_repeat: repeat, @@ -5,6 +10,9 @@ module.exports.getStatements = () => { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_data.js b/src/compiler/blocks/compiler_scratch3_data.js index d312919c..fe5fd6c6 100644 --- a/src/compiler/blocks/compiler_scratch3_data.js +++ b/src/compiler/blocks/compiler_scratch3_data.js @@ -1,11 +1,25 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. 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}")`); +}; diff --git a/src/compiler/blocks/compiler_scratch3_events.js b/src/compiler/blocks/compiler_scratch3_events.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_events.js +++ b/src/compiler/blocks/compiler_scratch3_events.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_looks.js b/src/compiler/blocks/compiler_scratch3_looks.js index c37a5c1f..d0eca509 100644 --- a/src/compiler/blocks/compiler_scratch3_looks.js +++ b/src/compiler/blocks/compiler_scratch3_looks.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { looks_changeeffectby: changeEffect, }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_motion.js b/src/compiler/blocks/compiler_scratch3_motion.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_motion.js +++ b/src/compiler/blocks/compiler_scratch3_motion.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_operators.js b/src/compiler/blocks/compiler_scratch3_operators.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_operators.js +++ b/src/compiler/blocks/compiler_scratch3_operators.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_procedures.js b/src/compiler/blocks/compiler_scratch3_procedures.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_procedures.js +++ b/src/compiler/blocks/compiler_scratch3_procedures.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_sensing.js b/src/compiler/blocks/compiler_scratch3_sensing.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_sensing.js +++ b/src/compiler/blocks/compiler_scratch3_sensing.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/blocks/compiler_scratch3_sounds.js b/src/compiler/blocks/compiler_scratch3_sounds.js index d312919c..ae4af813 100644 --- a/src/compiler/blocks/compiler_scratch3_sounds.js +++ b/src/compiler/blocks/compiler_scratch3_sounds.js @@ -1,9 +1,17 @@ +const { InputUtil, StatementUtil, CompiledInput } = require('../compiler'); + +/** + * @returns {Object. void>} + */ module.exports.getStatements = () => { return { }; }; +/** + * @returns {Object. CompiledInput>} + */ module.exports.getInputs = () => { return { diff --git a/src/compiler/compiler.js b/src/compiler/compiler.js index 6b9cd757..25a5dc08 100644 --- a/src/compiler/compiler.js +++ b/src/compiler/compiler.js @@ -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; diff --git a/src/compiler/eval.js b/src/compiler/eval.js new file mode 100644 index 00000000..9a8c410b --- /dev/null +++ b/src/compiler/eval.js @@ -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; diff --git a/src/compiler/execute.js b/src/compiler/execute.js new file mode 100644 index 00000000..25465122 --- /dev/null +++ b/src/compiler/execute.js @@ -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; diff --git a/src/compiler/executor.js b/src/compiler/executor.js deleted file mode 100644 index 8465c343..00000000 --- a/src/compiler/executor.js +++ /dev/null @@ -1,9 +0,0 @@ -const execute = function (sequencer, thread) { - const result = thread.generator.next(); - - if (result.done) { - sequencer.retireThread(thread); - } -}; - -module.exports = execute; diff --git a/src/engine/execute.js b/src/engine/execute.js index df802d69..1b923769 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -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. diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 2b6244f8..4d42a2c6 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -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.