hdl-js
Version:
Hardware definition language (HDL) and Hardware simulator
553 lines (472 loc) • 15.1 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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
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;
var _require3 = require('../../../util/string-util'),
centerString = _require3.centerString;
/**
* 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,
workingDirectory = _ref.workingDirectory;
_classCallCheck(this, ScriptInterpreter);
/**
* Working directory to load files.
*/
this._workingDirectory = workingDirectory;
this._isVirtualDirectory = typeof workingDirectory === 'object';
/**
* Script from a file.
*/
if (file) {
this._script = fs.readFileSync(file, 'utf-8');
if (!this._workingDirectory) {
this._workingDirectory = path.dirname(file);
}
}
if (!this._workingDirectory) {
this._workingDirectory = __dirname;
}
/**
* Script source.
*/
if (script) {
this._script = script;
}
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;
this._compareToLines = null;
this._actualLines = [];
/**
* Collected error list.
*/
this._errorList = [];
/**
* 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;
// Reset System clock on each script run:
SystemClock.reset();
}
/**
* 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++]);
var allExecuted = this._container === this._ast && !this._hasMoreCommands();
if (allExecuted && this._errorList.length > 0) {
throw new ScriptError({
errorList: this._errorList,
compareTo: this._compareTo,
header: this._compareToLines[0]
});
}
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._loadGate(node);
break;
case 'output-file':
this._initOutputFile(node);
break;
case 'compare-to':
this._createCompareTo(node);
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: '_loadGate',
value: function _loadGate(node) {
this._gate = HDLClassFactory.loadGate(node.arguments[0].replace('.hdl', ''), this._workingDirectory).defaultFromSpec();
}
}, {
key: '_createCompareTo',
value: function _createCompareTo(node) {
if (this._isVirtualDirectory) {
this._compareTo = node.arguments[0];
} else {
this._compareTo = this._workingDirectory + '/' + node.arguments[0];
}
this._compareToLines = this._readFileContents(this._compareTo).split(/\r?\n/);
}
}, {
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();
if (content.length > middle) {
content = content.slice(-middle);
}
}
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;
return centerString(column, totalLength);
}
}, {
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._compareTo) {
this._actualLines.push(line);
var compareIdx = this._actualLines.length - 1;
var expected = this._compareToLines[compareIdx];
var actual = this._actualLines[compareIdx];
if (expected !== actual) {
this._errorList.push({
actual: actual,
expected: expected,
line: compareIdx + 1
});
}
}
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: '_readFileContents',
value: function _readFileContents(file) {
if (this._isVirtualDirectory) {
return this._workingDirectory[file];
}
return fs.readFileSync(file, '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.hasOwnProperty('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.hasOwnProperty('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;
}();
var ScriptError = function (_Error) {
_inherits(ScriptError, _Error);
function ScriptError(errorData) {
_classCallCheck(this, ScriptError);
var _this2 = _possibleConstructorReturn(this, (ScriptError.__proto__ || Object.getPrototypeOf(ScriptError)).call(this, 'Script comparison error.'));
_this2.errorData = errorData;
return _this2;
}
return ScriptError;
}(Error);
module.exports = ScriptInterpreter;