Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/vm/src/compiler/compile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const IRGenerator = require('./ir-generator');
const JSGenerator = require('./js-generator');
const Cast = require('../util/cast');

/** @import Thread from '../engine/thread.js' */

/**
* Compile an existing thread to executable object.
* @param {Thread} thread thread to compile
* @return {?Generator} compiled result, null if failed
*/
const compile = function (thread) {
try {
const irGenerator = new IRGenerator(thread);
const ir = irGenerator.generateScript(thread.topBlock);
console.log(ir);

const jsGenerator = new JSGenerator();
const fn = jsGenerator.compileForThread(ir).call(globalThis);

console.log(fn.toString());

thread.compileResult = fn.call({Cast}, thread)();
} catch (e) {
console.error(e);
thread.compileResult = null;
}
return thread.compileResult;
};

module.exports = compile;
33 changes: 33 additions & 0 deletions packages/vm/src/compiler/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const TYPE_NUMBER = 1;
const TYPE_STRING = 2;
const TYPE_STATEMENT = 8;
const TYPE_UNKNOWN = 99;

const IR_CONSTANT = 'constant';
const IR_IDENTIFIER = 'identifier';
const IR_WHILE = 'control.while';
const IR_REPEAT = 'control.repeat';
const IR_IFELSE = 'control.ifelse';
const IR_WAIT = 'control.wait';
const IR_ADD = 'op.add';
const IR_SUB = 'op.sub';
const IR_LOAD = 'var.load';
const IR_STORE = 'var.store';

module.exports = {
TYPE_NUMBER,
TYPE_STRING,
TYPE_STATEMENT,
TYPE_UNKNOWN,

IR_CONSTANT,
IR_IDENTIFIER,
IR_WHILE,
IR_REPEAT,
IR_IFELSE,
IR_WAIT,
IR_ADD,
IR_SUB,
IR_LOAD,
IR_STORE
};
205 changes: 205 additions & 0 deletions packages/vm/src/compiler/ir-generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
const { IR_IDENTIFIER } = require('./constants');
const ScratchIRGenerator = require('./ir-scratch');

/** @import Blocks from '../engine/blocks' */
/** @import Target from '../engine/target' */
/** @import Thread from '../engine/thread' */

class IRGenerator {
/**
* @param {Thread} thread
*/
constructor (thread) {
this.thread = thread;
this.blockContainer = /** @type {Blocks} */ (thread.blockContainer);
this.generator = ScratchIRGenerator;

this.clear();
}

/**
* Clear compiler context.
*/
clear () {
this.variables = {};
}

/**
* Generate IR for a list of blocks.
* @param {?string} blockId top block id
* @return {IRBaseInst[]} list of ir instructions
*/
generateScript (blockId) {
const result = [];

while (blockId) {
const block = this.blockContainer.getBlock(blockId);
if (!block) {
break;
}

result.push(this.generateBlock(blockId));
blockId = block.next; // next block
}

return result;
}

/**
* Generate IR for a block.
* @param {?string} blockId block id
* @return {IRBaseInst} ir instruction
*/
generateBlock (blockId) {
const block = blockId && this.blockContainer.getBlock(blockId);
if (!block) {
return null;
}

const opcode = block.opcode;

const generate = this.generator[opcode];
if (generate) {
return generate(block, this);
} else {
console.warn(`no ir generator for opcode ${opcode}`);
return IRGenerator.defaultGenerator(block, this);
}
}

static defaultGenerator (block, generator) {
// TODO: compatible mode
return null;
}

/**
* Generate IR from field.
* @param {Object} block block to generate ir
* @param {string} name field name
* @return {IRBaseInst}
*/
fromField (block, name) {
if (block.fields.hasOwnProperty(name)) {
return block.fields[name].value;
} else {
throw `no field ${name} in block ${block.opcode}`;
}
}

/**
* Generate IR from field.
* @param {Object} block block to generate ir
* @param {string} name value name
* @return {IRBaseInst}
*/
fromValue (block, name) {
if (block.inputs.hasOwnProperty(name)) {
const blockId = block.inputs[name].block;
return this.generateBlock(blockId);
} else {
throw `no input ${name} in block ${block.opcode}`;
}
}

/**
* Generate IR from statement.
* @param {Object} block block to generate ir
* @param {string} name statement name
* @return {IRBaseInst[]}
*/
fromStatement (block, name) {
if (block.inputs.hasOwnProperty(name)) {
const blockId = block.inputs[name].block;
return this.generateScript(blockId);
} else {
// input undefined when no block is connected to substack
return [];
}
}

/**
* Generate IR from variable field.
* @param {Object} block block to generate ir
* @param {string} name field name
* @return {IRBaseInst[]}
*/
fromVariable (block, name) {
if (block.fields.hasOwnProperty(name)) {
const field = block.fields[name];
return this.lookupVariable(field.name, field.id);
} else {
throw `no variable field ${name} in block ${block.opcode}`;
}
}

/**
* Generate IR from variable.
* @param {string} name name of data
* @param {string} id id of data
* @return {IRBaseInst}
*/
lookupVariable (name, id) {
const target = /** @type {Target} */ (this.thread.target);
const stage = /** @type {?Target} */ (target.runtime.getTargetForStage());

// lookup by id
if (target.variables.hasOwnProperty(id)) {
return {
opcode: IR_IDENTIFIER,
name: name,
id: id,
scope: 'target'
};
}

// lookup in stage by id
if (!target.isStage && stage) {
if (stage.variables.hasOwnProperty(id)) {
return {
opcode: IR_IDENTIFIER,
name: name,
id: id,
scope: 'stage'
};
}
}

// lookup by name
for (const varId in this.variables) {
const currVar = this.variables[varId];
if (currVar.name === name && currVar.type === '') {
return {
opcode: IR_IDENTIFIER,
name: name,
id: varId,
scope: 'target'
};
}
}

// lookup in stage by name
if (!target.isStage && stage) {
for (const varId in stage.variables) {
const currVar = stage.variables[varId];
if (currVar.name === name && currVar.type === '') {
return {
opcode: IR_IDENTIFIER,
name: name,
id: varId,
scope: 'stage'
};
}
}
}

// create a new variable in current target
return {
opcode: IR_IDENTIFIER,
name: name,
id: varId,
scope: 'target'
};
}
}

module.exports = IRGenerator;
Loading