Compare commits

..

8 Commits

Author SHA1 Message Date
4afdcd1660 Cleanup the project
* Add .cache to gitignore (for the nlohmann::json library)
* Fix the project name
* Update the readme
2026-06-03 10:17:03 +02:00
214b3e4d9e Add symbol support 2026-06-02 15:59:28 +02:00
a53125fa6e asm-samples: remove heavy .hack files
This was tested for comparison and should be later generated on the fly for unit testing
2026-06-02 11:21:46 +02:00
3e3330b046 Load symbols from the json 2026-05-19 19:35:52 +02:00
7eee6a08c8 Add predefined symbols in a json 2026-05-19 19:25:49 +02:00
51de990690 Switch to cmake and fetch nlohmann::json
This will be used to dynamically set a label table
2026-05-19 19:11:53 +02:00
ba4d9afda9 Adding asm & hack samples
Those are the examples and the asm & hack (assembled) from the project 6 of
nand2tetris
2026-05-19 18:57:20 +02:00
0ace0f424a Adding gitignore 2026-05-19 18:55:58 +02:00
12 changed files with 56164 additions and 12 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
out
hack-assembler
build
.cache

26
CMakeLists.txt Normal file
View File

@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.18)
set(NAME "hack-assembler")
project(${NAME} CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(FetchContent)
# nlohmann::json
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
set(SRC_CXX_FILES "./main.cpp")
add_executable(${NAME} ${SRC_CXX_FILES})
target_link_libraries(${NAME} PUBLIC nlohmann_json::nlohmann_json)

View File

@@ -1,13 +1,26 @@
# hack-assembler
An implementation of a hack assembler in C++.
An implementation of a hack assembler in C++, based on the project 6 of the [nand2tetris course](https://www.nand2tetris.org/https://b1391bd6-da3d-477d-8c01-38cdf774495a.filesusr.com/ugd/44046b_89a8e226476741a3b7c5204575b8a0b2.pdf).
## Compilation
```bash
cmake -S . -B build
cmake --build build
```
## Usage
```bash
g++ -o hack-assembler main.cpp
cat <input_file.asm> | ./hack-assembler > <output_file.hack>
cat <input_file.asm> | ./hack-assembler > ./out/<output_file.hack>
```
## Testing
In order to check the assembler, you can generate the .hack files from the asm-samples directory. And test those within [nand2tetris CPU emulator](https://nand2tetris.github.io/web-ide/cpu/).
## References
- [nand2tetris course](https://www.nand2tetris.org/)
## License
This project is under the WTFPL License. See [LICENSE](LICENSE) for details.

12
asm-samples/Add.asm Normal file
View File

@@ -0,0 +1,12 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// Computes R0 = 2 + 3 (R0 refers to RAM[0])
@2
D=A
@3
D=D+A
@0
M=D

30
asm-samples/Max.asm Normal file
View File

@@ -0,0 +1,30 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/6/max/Max.asm
// Computes R2 = max(R0, R1) (R0,R1,R2 refer to RAM[0],RAM[1],RAM[2])
// Usage: Before executing, put two values in R0 and R1.
// D = R0 - R1
@R0
D=M
@R1
D=D-M
// If (D > 0) goto ITSR0
@ITSR0
D;JGT
// Its R1
@R1
D=M
@OUTPUT_D
0;JMP
(ITSR0)
@R0
D=M
(OUTPUT_D)
@R2
M=D
(END)
@END
0;JMP

24
asm-samples/MaxL.asm Normal file
View File

@@ -0,0 +1,24 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/6/max/MaxL.asm
// Symbol-less version of the Max.asm program.
// Designed for testing the basic version of the assembler.
@0
D=M
@1
D=D-M
@10
D;JGT
@1
D=M
@12
0;JMP
@0
D=M
@2
M=D
@14
0;JMP

28375
asm-samples/Pong.asm Normal file

File diff suppressed because it is too large Load Diff

27491
asm-samples/PongL.asm Normal file

File diff suppressed because it is too large Load Diff

41
asm-samples/Rect.asm Normal file
View File

@@ -0,0 +1,41 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/6/rect/Rect.asm
// Draws a rectangle at the top-left corner of the screen.
// The rectangle is 16 pixels wide and R0 pixels high.
// Usage: Before executing, put a value in R0.
// If (R0 <= 0) goto END else n = R0
@R0
D=M
@END
D;JLE
@n
M=D
// addr = base address of first screen row
@SCREEN
D=A
@addr
M=D
(LOOP)
// RAM[addr] = -1
@addr
A=M
M=-1
// addr = base address of next screen row
@addr
D=M
@32
D=D+A
@addr
M=D
// decrements n and loops
@n
MD=M-1
@LOOP
D;JGT
(END)
@END
0;JMP

33
asm-samples/RectL.asm Normal file
View File

@@ -0,0 +1,33 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/6/rect/RectL.asm
// Symbol-less version of the Rect.asm program.
// Designed for testing the basic version of the assembler.
@0
D=M
@23
D;JLE
@16
M=D
@16384
D=A
@17
M=D
@17
A=M
M=-1
@17
D=M
@32
D=D+A
@17
M=D
@16
MD=M-1
@10
D;JGT
@23
0;JMP

View File

@@ -3,6 +3,9 @@
#include <string>
#include <cctype>
#include <map>
#include <fstream>
#include <nlohmann/json.hpp>
// Comp map
const std::map<std::string, std::string> compMap = {
@@ -92,24 +95,96 @@ static inline std::string strip(const std::string &s) {
return result;
}
using symbolsMap = std::map<std::string, int>;
using labelsMap = std::map<std::string, int>;
int find_available_address(const symbolsMap &symbols) {
int address = 16; // Start from 16 for variables
while (true) {
bool found = false;
for (const auto &pair : symbols) {
if (pair.second == address) {
found = true;
break;
}
}
if (!found) return address;
++address;
}
}
int main() {
// Load predefined symbols
std::ifstream file("./symbols.json");
nlohmann::json symbols_json = nlohmann::json::parse(file);
symbolsMap symbols_map = {};
labelsMap labels_map = symbols_json.get<symbolsMap>();
std::string line;
while (std::getline(std::cin, line)) {
int line_number = 0;
std::vector<std::string> lines;
while (std::getline(std::cin, line)) { // Store lines for first pass
line = trim(line);
if (line.empty()) continue;
// skip comments
if (line[0] == '#' || (line.size() > 1 && line[0] == '/' && line[1] == '/')) continue;
lines.push_back(line);
}
auto line_it = lines.begin();
while (line_it != lines.end()) { // First pass to handle labels
if ((*line_it)[0] == '(' && line_it->back() == ')') {
std::string label = line_it->substr(1, line_it->size() - 2);
labels_map[label] = line_number;
line_it = lines.erase(line_it); // Remove label lines
} else {
++line_it;
++line_number; // Only count non-label lines
}
}
for (const std::string &line : lines) { // Second pass to handle variables
if (line[0] == '@') {
if (std::isalpha(line[1]) || line[1] == '_') {
std::string symbol = line.substr(1);
if (labels_map.find(symbol) != labels_map.end()) {
continue; // Skip labels, they are already handled
}
auto it = symbols_map.find(symbol);
if (it == symbols_map.end()) {
int address = find_available_address(symbols_map);
symbols_map[symbol] = address;
}
}
}
}
for (const std::string &line : lines) { // Second pass to generate
// A-instruction
if (line[0] == '@') {
std::string value = line.substr(1);
if (value.empty()) continue;
try {
int intValue = std::stoi(value);
std::bitset<16> binaryValue(intValue);
// Check if it's a symbol or a number
if (std::isalpha(line[1]) || line[1] == '_') {
std::string symbol = line.substr(1);
int address;
// search labels first
auto labelIt = labels_map.find(symbol);
if (labelIt != labels_map.end()) {
address = labelIt->second;
} else {
// Search variables.
auto it = symbols_map.find(symbol);
if (it != symbols_map.end()) {
address = it->second;
}
}
std::bitset<16> binaryValue(address);
std::cout << binaryValue.to_string() << '\n';
} catch (...) { continue; }
} else {
std::string value = line.substr(1);
if (value.empty()) continue;
try {
int intValue = std::stoi(value);
std::bitset<16> binaryValue(intValue);
std::cout << binaryValue.to_string() << '\n';
} catch (...) { continue; }
}
} else { // C-instruction
std::string instruction = "111";
size_t eqPos = line.find('=');
@@ -138,6 +213,7 @@ int main() {
auto jumpIt = jumpMap.find(jump);
instruction += (jumpIt != jumpMap.end()) ? jumpIt->second : jumpMap.at("null");
std::cout << instruction << '\n';
line_number++;
}
}
return 0;

27
symbols.json Normal file
View File

@@ -0,0 +1,27 @@
{
"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,
"KBD": 24576
}