hdl-js
Version:
Hardware definition language (HDL) and Hardware simulator
478 lines (410 loc) • 12.4 kB
JavaScript
/**
* The MIT License (MIT)
* Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var fs = require('fs');
var HDLClassFactory = require('../HDLClassFactory');
var path = require('path');
var Pin = require('../Pin');
var scriptParser = require('./script-parser');
var _require = require('../Clock'),
SystemClock = _require.SystemClock;
var _require2 = require('../../../util/numbers'),
toSignedString = _require2.toSignedString;
/**
* Number format.
*/
var formatRadix = {
B: 2,
X: 16,
D: 10
};
/**
* Evaluates a script code, testing gates logic.
*/
var ScriptInterpreter = function () {
function ScriptInterpreter(_ref) {
var script = _ref.script,
file = _ref.file,
_ref$workingDirectory = _ref.workingDirectory,
workingDirectory = _ref$workingDirectory === undefined ? __dirname : _ref$workingDirectory;
_classCallCheck(this, ScriptInterpreter);
/**
* Script from a file.
*/
if (file) {
this._script = fs.readFileSync(file, 'utf-8');
this._workingDirectory = path.dirname(file);
}
/**
* Script source.
*/
if (script) {
this._script = script;
}
/**
* Working directory to load files.
*/
if (workingDirectory) {
this._workingDirectory = workingDirectory;
}
/**
* Whether the working directory is virtual.
*/
this._isVirtualDirectory = typeof workingDirectory === 'object';
if (this._isVirtualDirectory) {
HDLClassFactory.setVirtualDirectory(this._workingDirectory);
}
/**
* Parsed AST to evaluate.
*/
this._ast = scriptParser.parse(this._script);
/**
* Program counter (current command to eval).
*/
this._pc = 0;
/**
* Loaded gate instance.
*/
this._gate = null;
/**
* Output file. By default to the console.
*/
this._outputFile = 'console';
/**
* Compare file.
*/
this._compareTo = null;
/**
* Format of the output columns.
*/
this._outputListMap = null;
/**
* Current container to evaluate commands.
* Containainers are: Script, bodies of the while, and repeat loops.
*/
this._container = this._ast;
}
/**
* Returns gate.
*/
_createClass(ScriptInterpreter, [{
key: 'getGate',
value: function getGate() {
return this._gate;
}
/**
* Executes the full script.
*/
}, {
key: 'exec',
value: function exec() {
this.eval(this._ast);
return this;
}
/**
* Executes next full step (until the `;` terminator).
*/
}, {
key: 'nextStep',
value: function nextStep() {
var shouldBreak = false;
while (this._hasMoreCommands(this._container)) {
this.nextCommand();
if (shouldBreak) {
break;
}
if (this._isLastCommandInStep()) {
shouldBreak = true;
}
}
return this;
}
/**
* Executes next command within step (until `,` terminator).
*/
}, {
key: 'nextCommand',
value: function nextCommand() {
this.eval(this._container.commands[this._pc++]);
return this;
}
}, {
key: 'eval',
value: function _eval(node) {
return node ? this[node.type](node) : null;
}
}, {
key: 'Script',
value: function Script(node) {
this._container = node;
this._pc = 0;
while (this._hasMoreCommands()) {
this.nextStep();
}
}
}, {
key: '_hasMoreCommands',
value: function _hasMoreCommands() {
return this._pc < this._container.commands.length;
}
}, {
key: '_isLastCommandInStep',
value: function _isLastCommandInStep() {
if (!this._hasMoreCommands()) {
return true;
}
return this._container.commands[this._pc].terminator === ';';
}
}, {
key: 'ControllerCommand',
value: function ControllerCommand(node) {
switch (node.name) {
case 'load':
this._gate = HDLClassFactory.loadGate(node.arguments[0].replace('.hdl', '')).defaultFromSpec();
break;
case 'output-file':
this._initOutputFile(node);
break;
case 'compare-to':
this._compareTo = node.arguments[0];
break;
case 'output-list':
this._createOutputListMap(node.arguments);
break;
case 'output':
this._evalOutput();
break;
case 'echo':
console.info(node.arguments[0]);
break;
case 'clear-echo':
// Unimplemented, ignore.
break;
case 'while':
this._evalWhile(node);
break;
case 'repeat':
this._evalRepeat(node);
break;
default:
throw TypeError('Unrecognized controller command: "' + node.name + '".');
}
}
}, {
key: 'SimulatorCommand',
value: function SimulatorCommand(node) {
switch (node.name) {
case 'set':
this._setPinValue(node.arguments[0], node.arguments[1]);
break;
case 'eval':
this._gate.eval();
break;
case 'tick':
SystemClock.tick();
break;
case 'tock':
SystemClock.tock();
break;
case 'ticktock':
SystemClock.cycle();
break;
default:
throw TypeError('Unrecognized simulator command: "' + node.name + '".');
}
}
}, {
key: '_initOutputFile',
value: function _initOutputFile(node) {
var outputFile = node.arguments[0];
if (outputFile === 'console') {
this._outputFile = outputFile;
return;
}
if (this._isVirtualDirectory) {
this._outputFile = outputFile;
this._workingDirectory[outputFile] = '';
return;
}
this._outputFile = this._workingDirectory + '/' + outputFile;
fs.writeFileSync(this._outputFile, '', 'utf-8');
}
}, {
key: '_createOutputListMap',
value: function _createOutputListMap(rawList) {
var _this = this;
this._outputListMap = {};
rawList.forEach(function (column, idx) {
_this._outputListMap[column.column] = column;
_this._outputListMap[column.column].index = idx;
});
}
}, {
key: '_evalOutput',
value: function _evalOutput() {
this._printHeader();
var line = ['|'];
for (var column in this._outputListMap) {
var _outputListMap$column = this._outputListMap[column],
right = _outputListMap$column.right,
middle = _outputListMap$column.middle,
left = _outputListMap$column.left,
format = _outputListMap$column.format;
var radix = formatRadix[format];
var actualPinColumn = column === 'time' ? Pin.CLOCK : column;
var pinInfo = this._gate.getClass().getPinInfo(actualPinColumn);
var value = this._gate.getPin(actualPinColumn).getValue();
var content = void 0;
// Special case for `time` column, which is an alias
// for the `$clock`. The `time` uses string representation,
// e.g. '1' string for negative, and '1+' for positive, while
// `$clock` uses -1 for negative, and +1 for positive.
if (column === 'time') {
content = value >= 0 ? value + '+' : '' + Math.abs(value);
content = content.padEnd(middle, ' ');
} else if (column === Pin.CLOCK) {
content = toSignedString(value).padEnd(middle, ' ');
} else if (format === 'S') {
// Explicit string format.
content = ('' + value).padEnd(middle, ' ');
} else {
content = (radix !== 10 ? value >>> 0 : value).toString(radix).padStart(radix !== 10 ? pinInfo.size : 0, '0').padStart(radix === 10 ? middle : 0, ' ').toUpperCase();
}
line.push(' '.repeat(right), content, ' '.repeat(left), '|');
}
this._printLine(line.join(''));
}
}, {
key: '_centerHeaderColumn',
value: function _centerHeaderColumn(columnInfo) {
var right = columnInfo.right,
middle = columnInfo.middle,
left = columnInfo.left,
column = columnInfo.column;
var totalLength = right + middle + left;
if (column.length < totalLength) {
var len = totalLength - column.length;
var remain = len % 2 == 0 ? '' : ' ';
var pads = ' '.repeat(parseInt(len / 2));
return pads + column + pads + remain;
}
return column;
}
}, {
key: '_printHeader',
value: function _printHeader() {
// Header is printed only once.
if (this._headerPrinted) {
return;
}
var line = ['|'];
for (var column in this._outputListMap) {
var centerColumn = this._centerHeaderColumn(this._outputListMap[column]);
line.push(centerColumn, '|');
}
this._printLine(line.join(''));
this._headerPrinted = true;
}
}, {
key: '_printLine',
value: function _printLine(line) {
if (this._outputFile === 'console') {
console.info(line);
return;
}
if (this._isVirtualDirectory) {
this._workingDirectory[this._outputFile] += line + '\n';
return;
}
fs.appendFileSync(this._outputFile, line + '\n', 'utf-8');
}
}, {
key: '_evalWhile',
value: function _evalWhile(node) {
var savedContainer = this._container;
var savedPC = this._pc;
this._container = node;
this._pc = 0;
while (this.eval(node.condition)) {
while (this._hasMoreCommands()) {
this.nextStep();
}
}
this._container = savedContainer;
this._pc = savedPC;
}
}, {
key: 'RelationalExpression',
value: function RelationalExpression(node) {
var left = this._getPinValue(node.left);
var right = this._getConditionValue(node.right);
switch (node.operator) {
case '=':
return left === right;
case '<>':
return left !== right;
case '<=':
return left <= right;
case '>=':
return left >= right;
case '<':
return left < right;
case '>':
return left > right;
default:
throw TypeError('Unrecognized condition operator: "' + node.operator + '".');
}
}
}, {
key: '_getPinValue',
value: function _getPinValue(node) {
var pin = this._gate.getPin(node.value);
// a[1]
if (node.index) {
return pin.getValueAt(node.index);
}
// a
return pin.getValue();
}
}, {
key: '_setPinValue',
value: function _setPinValue(node, value) {
var pin = this._gate.getPin(node.value);
// a[1]
if (node.index) {
return pin.setValueAt(node.index, value.value);
}
// a
return pin.setValue(value.value);
}
}, {
key: '_getConditionValue',
value: function _getConditionValue(node) {
if (node.type === 'Name') {
return this._getPinValue(node);
}
return node.value;
}
}, {
key: '_evalRepeat',
value: function _evalRepeat(node) {
var savedContainer = this._container;
var savedPC = this._pc;
this._container = node;
for (var i = 0; i < node.times.value; i++) {
this._pc = 0;
while (this._hasMoreCommands()) {
this.nextStep();
}
}
this._container = savedContainer;
this._pc = savedPC;
}
}]);
return ScriptInterpreter;
}();
module.exports = ScriptInterpreter;