UNPKG

bali-type-compiler

Version:

This library provides a JavaScript based implementation for the compiler for the Bali Virtual Processor.

453 lines (413 loc) 13.2 kB
/************************************************************************ * Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. * ************************************************************************ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * * * This code is free software; you can redistribute it and/or modify it * * under the terms of The MIT License (MIT), as published by the Open * * Source Initiative. (See http://opensource.org/licenses/MIT) * ************************************************************************/ 'use strict'; /** * This module defines a class that assembles compiled methods into bytecode that * can run on the Bali Virtual Machine™. */ const bali = require('bali-component-framework').api(this.debug); const Decoder = require('./Decoder').Decoder; const types = require('./Types'); const Parser = require('./Parser').Parser; const EOL = '\n'; // POSIX end of line character /** * This constructor returns an assembler that assembles a compiled method into the * corresponding bytecode to be run on the Bali Nebulavirtual processor. * * An optional debug argument may be specified that controls the level of debugging that * should be applied during execution. The allowed levels are as follows: * <pre> * 0: no debugging is applied (this is the default value and has the best performance) * 1: log any exceptions to console.error before throwing them * 2: perform argument validation checks on each call (poor performance) * 3: log interesting arguments, states and results to console.log * </pre> * * @returns {Assembler} The new instruction assembler. */ function Assembler(debug) { this.debug = debug || 0; // default is off this.decoder = new Decoder(this.debug); return this; } Assembler.prototype.constructor = Assembler; exports.Assembler = Assembler; /** * This method assembles the instructions for a procedure in a compiled method into the * corresponding bytecode to be run on the Bali Virtual Machine™. * * @param {Catalog} type The type context for the method being assembled. * @param {Catalog} method The method being assembled. */ Assembler.prototype.assembleMethod = function(type, method) { // assemble the instructions into bytecode var instructions = method.getAttribute('$instructions'); const parser = new Parser(this.debug); instructions = parser.parseInstructions(instructions.getValue()); const visitor = new AssemblingVisitor(type, method, this.debug); instructions.acceptVisitor(visitor); // format the bytecode and add to the method context var bytecode = visitor.getBytecode(); const base16 = bali.decoder(2).base16Encode(this.decoder.bytecodeToBytes(bytecode)); bytecode = bali.component("'" + base16 + EOL + " '" + '($encoding: $base16, $mediaType: "application/bcod")'); method.setAttribute('$bytecode', bytecode); }; // PRIVATE CLASSES function AssemblingVisitor(type, method, debug) { this.debug = debug || 0; this.decoder = new Decoder(this.debug); this.literals = type.getAttribute('$literals'); this.constants = type.getAttribute('$constants'); this.argumentz = method.getAttribute('$arguments'); this.variables = method.getAttribute('$variables'); this.messages = method.getAttribute('$messages'); this.addresses = method.getAttribute('$addresses'); this.bytecode = []; return this; } AssemblingVisitor.prototype.constructor = AssemblingVisitor; AssemblingVisitor.prototype.getBytecode = function() { return this.bytecode; }; AssemblingVisitor.prototype.visitCollection = function(collection) { if (collection.isType('/bali/collections/List')) { this.visitList(collection); } else { this.visitCatalog(collection); } }; // component: value parameters? note? AssemblingVisitor.prototype.visitComponent = function(component) { const functionName = 'visit' + component.getType().split('/')[3]; if (this[functionName]) { // dispatch to the actual type handler this[functionName](component); } else { // dispatch to typed catalog handler this.visitCatalog(component); } }; // document: EOL* instructions EOL* EOF // instructions: (instruction EOL)* AssemblingVisitor.prototype.visitList = function(instructions) { const iterator = instructions.getIterator(); while (iterator.hasNext()) { var instruction = iterator.getNext(); instruction.acceptVisitor(this); } }; // instruction: label? action // label: EOL LABEL ':' EOL AssemblingVisitor.prototype.visitCatalog = function(instruction) { // can ignore the label at this stage since they don't show up in the bytecode const operation = instruction.getAttribute('$operation').toInteger(); switch (operation) { case types.NOTE: this.visitNote(instruction); break; case types.JUMP: this.visitJump(instruction); break; case types.PUSH: this.visitPush(instruction); break; case types.PULL: this.visitPull(instruction); break; case types.LOAD: this.visitLoad(instruction); break; case types.SAVE: this.visitSave(instruction); break; case types.DROP: this.visitDrop(instruction); break; case types.CALL: this.visitCall(instruction); break; case types.SEND: this.visitSend(instruction); break; default: const exception = bali.exception({ $module: '/bali/compiler/Assembler', $procedure: '$visitCatalog', $exception: '$invalidOperation', $expected: bali.range(0, 7), $actual: operation, $instruction: instruction, $text: 'An invalid operation was found in a procedure instruction.' }); if (this.debug) console.error(exception.toString()); throw exception; } }; // note: COMMENT AssemblingVisitor.prototype.visitNote = function(instruction) { // ignore notes }; // jump: // 'JUMP' 'TO' 'NEXT' 'INSTRUCTION' | // 'JUMP' 'TO' LABEL | // 'JUMP' 'TO' LABEL 'ON' 'EMPTY' | // 'JUMP' 'TO' LABEL 'ON' 'NONE' | // 'JUMP' 'TO' LABEL 'ON' 'FALSE' AssemblingVisitor.prototype.visitJump = function(instruction) { var word; var modifier = instruction.getAttribute('$modifier'); if (!modifier) { word = this.decoder.encodeInstruction(types.JUMP, 0, 0); // JUMP TO NEXT INSTRUCTION } else { modifier = modifier.toInteger(); const label = instruction.getAttribute('$operand'); const address = this.addresses.getAttribute(label); word = this.decoder.encodeInstruction(types.JUMP, modifier, address); } this.bytecode.push(word); }; // push: // 'PUSH' 'HANDLER' LABEL | // 'PUSH' 'LITERAL' LITERAL | // 'PUSH' 'CONSTANT' SYMBOL | // 'PUSH' 'ARGUMENT' SYMBOL AssemblingVisitor.prototype.visitPush = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); var value = instruction.getAttribute('$operand'); switch(modifier) { case types.HANDLER: value = this.addresses.getAttribute(value); break; case types.LITERAL: value = this.literals.getIndex(value); break; case types.CONSTANT: value = this.constants.getKeys().getIndex(value); break; case types.ARGUMENT: value = this.argumentz.getKeys().getIndex(value); break; } const word = this.decoder.encodeInstruction(types.PUSH, modifier, value); this.bytecode.push(word); }; // pull: // 'PULL' 'HANDLER' | // 'PULL' 'COMPONENT' | // 'PULL' 'RESULT' | // 'PULL' 'EXCEPTION' AssemblingVisitor.prototype.visitPull = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); const word = this.decoder.encodeInstruction(types.PULL, modifier); this.bytecode.push(word); }; // load: // 'LOAD' 'VARIABLE' SYMBOL | // 'LOAD' 'DOCUMENT' SYMBOL | // 'LOAD' 'CONTRACT' SYMBOL | // 'LOAD' 'MESSAGE' SYMBOL AssemblingVisitor.prototype.visitLoad = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); const symbol = instruction.getAttribute('$operand'); const index = this.variables.getIndex(symbol); const word = this.decoder.encodeInstruction(types.LOAD, modifier, index); this.bytecode.push(word); }; // save: // 'SAVE' 'VARIABLE' SYMBOL | // 'LOAD' 'DOCUMENT' SYMBOL | // 'LOAD' 'CONTRACT' SYMBOL | // 'LOAD' 'MESSAGE' SYMBOL AssemblingVisitor.prototype.visitSave = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); const symbol = instruction.getAttribute('$operand'); const index = this.variables.getIndex(symbol); const word = this.decoder.encodeInstruction(types.SAVE, modifier, index); this.bytecode.push(word); }; // drop: // 'DROP' 'VARIABLE' SYMBOL | // 'LOAD' 'DOCUMENT' SYMBOL | // 'LOAD' 'CONTRACT' SYMBOL | // 'LOAD' 'MESSAGE' SYMBOL AssemblingVisitor.prototype.visitDrop = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); const symbol = instruction.getAttribute('$operand'); const index = this.variables.getIndex(symbol); const word = this.decoder.encodeInstruction(types.DROP, modifier, index); this.bytecode.push(word); }; // call: // 'CALL' SYMBOL | // 'CALL' SYMBOL 'WITH' '1' 'ARGUMENT' | // 'CALL' SYMBOL 'WITH' NUMBER 'ARGUMENTS' AssemblingVisitor.prototype.visitCall = function(instruction) { const count = instruction.getAttribute('$modifier').toInteger(); const symbol = instruction.getAttribute('$operand'); const index = intrinsics.indexOf(symbol.toString()); const word = this.decoder.encodeInstruction(types.CALL, count, index); this.bytecode.push(word); }; // send: // 'SEND' SYMBOL 'TO' 'COMPONENT' | // 'SEND' SYMBOL 'TO' 'COMPONENT' 'WITH' 'ARGUMENTS' | // 'SEND' SYMBOL 'TO' 'DOCUMENT' | // 'SEND' SYMBOL 'TO' 'DOCUMENT' 'WITH' 'ARGUMENTS' AssemblingVisitor.prototype.visitSend = function(instruction) { const modifier = instruction.getAttribute('$modifier').toInteger(); const symbol = instruction.getAttribute('$operand'); const index = this.messages.getIndex(symbol); const word = this.decoder.encodeInstruction(types.SEND, modifier, index); this.bytecode.push(word); }; const intrinsics = [ '$invalid', '$addItem', '$ancestry', '$and', '$arccosine', '$arcsine', '$arctangent', '$areEqual', '$areSame', '$association', '$attribute', '$authority', '$base02', '$base16', '$base32', '$base64', '$binary', '$bytes', '$catalog', '$chain', '$citation', '$code', '$coinToss', '$comparator', '$complement', '$component', '$conjugate', '$connector', '$cosine', '$day', '$days', '$default', '$degrees', '$difference', '$document', '$doesMatch', '$duplicate', '$duration', '$earlier', '$effective', '$emptyCollection', '$exponential', '$factorial', '$first', '$format', '$fragment', '$hasNext', '$hasPrevious', '$hash', '$head', '$hour', '$hours', '$html', '$imaginary', '$insertItem', '$insertItems', '$integer', '$interfaces', '$inverse', '$isEnumerable', '$isLess', '$isMore', '$isNegative', '$isSignificant', '$item', '$iterator', '$key', '$keys', '$last', '$later', '$levels', '$list', '$logarithm', '$magnitude', '$matchesText', '$millisecond', '$milliseconds', '$minute', '$minutes', '$month', '$months', '$nextItem', '$nextVersion', '$node', '$not', '$now', '$or', '$parameters', '$path', '$phase', '$previousItem', '$procedure', '$product', '$query', '$queue', '$quotient', '$radians', '$random', '$range', '$ranking', '$real', '$reciprocal', '$remainder', '$removeAttribute', '$removeHead', '$removeIndex', '$removeIndices', '$removeItem', '$removeTop', '$reverseItems', '$sans', '$scaled', '$scheme', '$second', '$seconds', '$set', '$setAttribute', '$setFirst', '$setItem', '$setLast', '$setParameter', '$setValue', '$shuffleItems', '$sine', '$size', '$sortItems', '$sorter', '$source', '$stack', '$sum', '$supplement', '$tag', '$tangent', '$toEnd', '$toSlot', '$toStart', '$top', '$value', '$weeks', '$xor', '$year', '$years' ];