1
\$\begingroup\$

Now I know the same type of question (same task) has been asked on this site before, but I recently wrote my C++ implementation of the Hack Assembler from the Nand2Tetris Chapter 6 course (defined here). I'm not really looking for tips on solving the task itself, although these are appreciated as well. Basically, if I use bad style, inefficient structures, or the like, it would be nice of you to highlight these for me. Also, if there is a language much better suited for a task like this, please give me a hint on that.

I'll give a quick résumé on the task at hand: Given a valid source code file written in the Hack Assembly language (.asm), translate it into its corresponding 16-bit CPU instructions and store these in a separate text file with the .hack extension. There are 2 different types of instructions (A and C) and a pseudo-label-command. These are translated according to page 7 of the above PDF.

Example file

// Computes R2 = max(R0, R1)  (R0,R1,R2 refer to RAM[0],RAM[1],RAM[2])

@R0
D=M              // D = first number
@R1
D=D-M            // D = first number - second number
@OUTPUT_FIRST
D;JGT            // if D>0 (first is greater) goto output_first
@R1
D=M              // D = second number
@OUTPUT_D
0;JMP            // goto output_d
(OUTPUT_FIRST)
@R0             
D=M              // D = first number
(OUTPUT_D)
@R2
M=D              // M[2] = D (greatest number)
(INFINITE_LOOP)
@INFINITE_LOOP
0;JMP            // infinite loop

Example result

0000000000000000
1111110000010000
0000000000000001
1111010011010000
0000000000001010
1110001100000001
0000000000000001
1111110000010000
0000000000001100
1110101010000111
0000000000000000
1111110000010000
0000000000000010
1110001100001000
0000000000001110
1110101010000111

Code

//Hack Assembler for translating .asm files in the Hack assembly language
//into .hack files with the corresponding binary code for the Hack computer.

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <experimental\filesystem>
#include <string>
#include <sstream>
#include <algorithm>
#include <bitset>
#include <unordered_map>

void RComWS(std::istream&);
void SymLN(std::istream&);
void Vars(std::istream&);
void Translate(std::istream&);
unsigned short int A_Inst(std::string);
unsigned short int C_Inst(std::string);
std::string* GetDCJ(std::string);

//Buffer string, for all file buffering purposes in main.
std::string buf;

//Symbol table/dictionary/hashmap with predefined symbols
std::unordered_map<std::string, int> symbols
({ {"SP", 0}, { "LCL", 1 }, { "ARG", 2 }, { "THIS", 3 }, { "THAT", 4 },
{ "R0", 0 }, { "R1", 1 }, { "R2", 2 }, { "R3", 3 }, { "R4", 4 }, 
{ "R5", 5 }, { "R6", 6 }, { "R7", 7 }, { "R8", 8 }, { "R9", 9 }, 
{ "R10", 10 }, { "R11", 11 }, { "R12", 12 }, { "R13", 13 }, { "R14", 14 }, 
{ "R15", 15 }, { "SCREEN", 16384 }, { "KEYBOARD", 24576 } });

//Hardcoded comp binary mnemonics for C-instructions
std::unordered_map<std::string, unsigned short int> comp
({ { "0", 0b0101010}, { "1", 0b0111111}, { "-1", 0b0111010}, { "D", 0b0001100},
{ "A", 0b0110000}, { "M", 0b1110000}, { "!D", 0b0001101}, { "!A", 0b0110001},
{ "!M", 0b1110001}, { "-D", 0b0001111}, { "-A", 0b0110001}, { "-M", 0b1110011},
{ "D+1", 0b0011111}, { "A+1", 0b0110111}, { "M+1", 0b1110111}, { "D-1", 0b0001110},
{ "A-1", 0b0110010}, { "M-1", 0b1110010}, { "D+A", 0b0000010}, { "D+M", 0b1000010},
{ "D-A", 0b0010011}, { "D-M", 0b1010011}, { "A-D", 0b0000111}, { "M-D", 0b1000111},
{ "D&A", 0b0000000}, { "D&M", 0b1000000}, { "D|A", 0b0010101}, { "D|M", 0b1010101}});

//Hardcoded dest binary mnemonics for C-instructions
std::unordered_map<std::string, unsigned short int> dest
({ { "M", 0b001 }, { "D", 0b010 }, { "MD", 0b011 }, { "A", 0b100 },
{ "AM", 0b101 }, { "AD", 0b110 }, { "AMD", 0b111 } });

//Hardcoded jump binary mnemonics for C-instructions
std::unordered_map<std::string, unsigned short int> jump
({ { "JGT", 0b001 }, { "JEQ", 0b010 }, { "JGE", 0b011 }, { "JLT", 0b100 },
{ "JNE", 0b101 }, { "JLE", 0b110 }, { "JMP", 0b111 }});

int main(int argc, char* argv[])
{
    if(argc < 2)
    {
        std::cout << "Not enough arguments, exiting process.";
        exit(0);
    }
    //Check for and open file
    if(!std::tr2::sys::exists(argv[1]))
    {
        std::cout << "Nonexistant file. Aborting.";
        exit(0);
    }
    std::ifstream stream;
    stream.open(argv[1]);
    buf.clear();
    //"Cleaning & Pruning" the text
    RComWS(stream);
    //Internal stream, for usage of getline()
    std::istringstream streamer(buf);
    //Get all labels into symbol table
    SymLN(streamer);
    //Resetting stream
    streamer.str(buf);
    streamer.clear();
    //Creating more symbols for variables
    Vars(streamer);
    //Resetting stream
    streamer.str(buf);
    streamer.clear();
    //Translate the text file
    Translate(streamer);
    //Get the fie name and directory without the extension.
    std::string file(argv[1]);
    std::string noextension = file.substr(0, file.rfind('.'));
    //Stream the buffer into the file.
    std::ofstream out;
    out.open(noextension + ".hack", std::ofstream::out);
    out << buf;
    out.close();
}

//Actual translation happens here
void Translate(std::istream& stream)
{
    std::ostringstream os;
    for(std::string line; std::getline(stream, line); )
    {
        //Switch depending on first character in line
        switch(line[0])
        {
            case '(':
                continue;
            case '@':
                os << std::bitset<16>(A_Inst(line)).to_string() + '\n';
                break;
            default:
                os << std::bitset<16>(C_Inst(line)).to_string() + '\n';
                break;
        }
    }
    buf = os.str();
}

// Translate an A-instruction:
// 0vvv vvvv vvvv vvvv
// v = memory address (15 bits)
unsigned short int A_Inst(std::string line)
{
    unsigned short int result = 0;
    //Check for symbol or literal
    if(isdigit(line[1]))
        result += std::stoi(line.substr(1, line.length() - 1));
    else
        result += symbols[line.substr(1, line.length() - 1)];
    return result;
}

// Translate a C-Instruction:
// 111a c1c2c3c4 c5c6d1d2 d3j1j2j3
// a = A || M
// c1 = zero D; c2 = negate D; c3 = zero a;
// c4 = negate a; c5 = + || &; c6 = negate result
// d1 = store in A; d2 = store in D; d3 = store in M
// j1 = jump if < 0; j2 = jump if = 0; j3 = jump if > 0
unsigned short int C_Inst(std::string line)
{
    unsigned short int result = 0;
    //C-Insts always begin with 3 1-bits.
    result = 7 << 13;
    std::string* dCJ= GetDCJ(line);
    //Retrieve comp component.
    result += comp[dCJ[1]] << 6;
    //Retrieve dest component.
    result += dest[dCJ[0]] << 3;
    //Retrieve jump component.
    result += jump[dCJ[2]];
    delete[] dCJ;
    return result;
}

//Get the three components of a C-instruction:
// dest=comp;jump
std::string* GetDCJ(std::string line)
{
    //result[0] = dest, result[1] = comp, result[2] = jump
    std::string* result = new std::string[3];
    std::string temp;
    size_t i, j;
    //If there is no dest component, the '=' is omitted
    if((temp = line.substr(0, (i = line.find('=')))) != line)
    {
        result[0] = temp;
        i++;
    }
    else
        i = 0;
    //There is always a comp component
    result[1] = line.substr(i, (j = line.find(';')));
    //If there is no ';', the jump component is omitted
    if(j != std::string::npos)
    {
        result[2] = line.substr(j+1);
    }
    return result;
}

//Add variables to the symbol table and assign RAM addresses to them
// @VARIABLE
void Vars(std::istream& stream)
{
    //Predefined symbols for RAM[0]-RAM[15]
    size_t i = 16;
    for(std::string line; std::getline(stream, line); )
    {
        //Signals an A-Inst
        if(line[0] == '@')
        {
            std::string symbol = line.substr(1, line.length() - 1);
            //Literal or symbol
            if(isdigit(line[1]))
                continue;
            //Only add if nonexistant in table
            if(symbols.find(symbol) == symbols.end())
                symbols[symbol] = i++;
        }
    }
}

//Find pseudo-label-commands and add them to the symbol table with their line number:
// (SYMBOL)
void SymLN(std::istream& stream)
{
    size_t i = 0;
    for(std::string line; std::getline(stream, line); )
    {
        //'(' signals a label-pseudo-command
        if(line.find('(') != std::string::npos)
        {
            std::string symbol = line.substr(1, line.length() - 2);
            //Labels refer to the line below, set it's value to that.
            //Label itself is removed and does not count as a line.
            symbols[symbol] = i;
        }
        else i++;
    }
}

//Remove comments and whitespace. Formats to prepare for other functions.
void RComWS(std::istream& stream)
{
    //ostream for buffering
    std::ostringstream os;
    for(std::string line; std::getline(stream, line); )
    {
        //'//' is the beginning of a comment
        size_t pos = line.find('/');
        //Erase comment at end of line
        //Cannot erase whole line, comments could be behind code
        if(pos != std::string::npos)
            line.erase(pos);
        //Find and remove all whitespace from Assembly code
        line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end());
        //Format as line for later streaming
        line += '\n';
        //Add to buffer only if more than the carriage return is left
        if(line.length() > 1)
            os << line;
    }
    //Set buffer stream to ostream content
    buf = os.str();
}

As is the point of the site, you do not have to rewrite my code. I can do that myself. I just want some tips on how to improve before I make others go completely insane on team projects ;).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.