/*
* MIPS-Simulator : parser.js [Parsing string and extracting tokens]
* Copyright (C) 2017 Progyan Bhattacharya, Bytes Club
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const isInstruction = require("./instructions"),
checkType = require("./types"),
mapToEncoding = require("./encoding");
// Remove Escape Sequences from a String
const escapeString = string =>
string.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\"/g, "\"")
.replace(/\\r/g, "\r")
.replace(/\\f/g, "\f");
/** class: Parser
* @desc Parses Tokens Generated by Lexer and Creates Parse Tree
* @prop {Array} AST - Abstract Syntax Tree (Parse Tree)
* @prop {Array} symTable - Symbol Table for Lookup
*/
class Parser extends Object {
constructor() {
super();
this.AST = null;
this.symTable = null;
this.parseTokens = this.parseTokens.bind(this);
this.getParseTree = this.getParseTree.bind(this);
}
/** method: parseTokens
* @desc Parse Each Token of Each Instruction and Generate AST
* @param {Array} instructions - List of Tokens for Each Instructions
*/
parseTokens(instructions) {
// Check if instructions are valid
if (typeof instructions === "undefined" || instructions === null) {
const err = "No instructions recieved for parsing.";
throw err;
}
const AST = [],
symTable = [];
let type = null,
startPtr = 0;
instructions.forEach((item, index) => {
const { line, tokens: lexer } = item;
const token = lexer.shift();
// Invalid token recieved
if (typeof token === "undefined" || token === null) {
console.error(`Unexpected token found at ${line}.` +
" Please check the correct syntax.");
process.exit(3);
}
// If it's a declaration
if (token === ".data" || token === ".text") {
if (lexer.length !== 0) {
console.error(`Unexpected token found at ${line}.` +
" Please check the correct syntax.");
process.exit(3);
}
if (token === ".data") {
type = "Data Declaration";
} else {
type = "Program Instructions";
startPtr = index + 1;
}
} else {
// If type not found
if (type === null) {
console.error("Missing type specification for" +
` instruction at ${line}. Check mannual for details.`);
process.exit(3);
}
// If it is a label
if (checkType(token, "label")) {
const label = token.replace(":", "");
const symEntry = symTable.find((v) => v.name === label);
const value = type === "Data Declaration" ?
{
encoding: lexer.length === 2 ?
mapToEncoding(lexer.shift()) :
"ASCII",
data: escapeString(lexer.shift())
} :
index - startPtr;
const labelDef = {
type,
value,
name: label
};
if (type === "Data Declaration") {
labelDef.readOnly = true;
}
// Already exists
if (typeof symEntry !== "undefined" &&
symEntry.value === "undefined") {
Object.assign(symEntry, labelDef);
} else {
// Simply push the name
symTable.push(labelDef);
}
} else {
// If it is an instruction
const type = isInstruction(token);
if (typeof type !== "undefined" && type !== null) {
const argumentType = type.argumentType.slice(0);
const args = [];
if (lexer.length !== type.arguments) {
console.error("Invalid number of arguments" +
` provided with ${type.action} instruction` +
` at line ${line}.`);
process.exit(3);
}
// Recieve necessary arguments
for (let i = 0; i < type.arguments; i++) {
const arg = lexer.shift();
const argType = argumentType.shift();
if (checkType(arg, argType)) {
args.push(arg);
} else {
// Invalid argument
console.error("Invalid argument provided with" +
` ${type.action} instruction; Expected` +
` ${argType}, found ${arg} at` +
` line ${line}`);
process.exit(3);
}
}
type.argument = args;
AST.push({ index: line, item: type });
} else {
// Invalid otherwise
console.error(`Cannot understand token at ${line}.` +
" Please check the mannual for details.");
process.exit(3);
}
}
}
});
this.AST = AST;
this.symTable = symTable;
}
/** method: getParseTree
* @desc Create Syntax Tree and Symbol Table and return to Caller Method
* @prop {Array} AST - Abstract Syntax Tree
* @prop {Array} symTable - Symbol Table
* @return {Object} Parse Tree Object
*/
getParseTree() {
const { AST, symTable } = this;
if (AST !== null && symTable !== null) {
const retVal = Object.assign({}, {
SyntaxTree: AST,
SymbolTable: symTable
});
return retVal;
}
console.error("The content failed or haven't recieved for parsing.");
process.exit(3);
}
}
module.exports = Parser;