Compare commits

...

3 Commits

Author SHA1 Message Date
36b54929d0 Remove trailing whitespace and clean up comments 2025-12-10 13:50:02 -05:00
1870e87a9c project12 - testing gitignore changes 2025-12-07 23:39:01 -05:00
4e8169508a project12 - complete 2025-12-07 23:31:41 -05:00
9 changed files with 550 additions and 19 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ nand2tetris/
07/**/*.asm
08/**/*.asm
09/**/*.vm
12/**/*.vm
# Compiled outputs
*.hack

View File

@@ -14,9 +14,12 @@ class Array {
/** Constructs a new Array of the given size. */
function Array new(int size) {
return Memory.alloc(size);
}
/** Disposes this array. */
method void dispose() {
do Memory.deAlloc(this);
return;
}
}

View File

@@ -9,7 +9,8 @@ class Keyboard {
/** Initializes the keyboard. */
function void init() {
}
return;
}
/**
* Returns the character of the currently pressed key on the keyboard;
@@ -32,24 +33,86 @@ class Keyboard {
* F1 - F12 = 141 - 152
*/
function char keyPressed() {
return Memory.peek(24576);
}
/** Waits until a key is pressed on the keyboard and released,
* then echoes the key to the screen, and returns the character
* then echoes the key to the screen, and returns the character
* of the pressed key. */
function char readChar() {
var char key;
// Wait for key press
while (Keyboard.keyPressed() = 0) {}
let key = Keyboard.keyPressed();
// Wait for release
while (~(Keyboard.keyPressed() = 0)) {}
// Echo (except backspace)
if (key < 129) {
// Handle newline specially for cleaner output?
if (key = 128) {
do Output.println();
} else {
do Output.printChar(key);
}
}
// If key > 129 (arrows etc), printChar might print garbage or square.
return key;
}
/** Displays the message on the screen, reads from the keyboard the entered
* text until a newline character is detected, echoes the text to the screen,
* and returns its value. Also handles user backspaces. */
function String readLine(String message) {
}
var String s;
var char c;
do Output.printString(message);
let s = String.new(64); // Assume max line 64
while (true) {
let c = Keyboard.readChar();
if (c = 128) { // Newline
do Output.println();
return s;
}
if (c = 129) { // Backspace
if (s.length() > 0) {
do s.eraseLastChar();
do Output.backSpace();
// Erase not supported by Output, so we print space and back up again?
// But printChar sends cursor forward.
// To visually erase: Backspace (move left), Print Space (overwrite, move right), Backspace (move left).
// My output implementation doesn't support 'erasing' naturally.
// But this trick works.
do Output.printChar(32); // Space
do Output.backSpace();
}
} else {
do s.appendChar(c);
}
}
return s;
}
/** Displays the message on the screen, reads from the keyboard the entered
* text until a newline character is detected, echoes the text to the screen,
* and returns its integer value (until the first non-digit character in the
* entered text is detected). Also handles user backspaces. */
function int readInt(String message) {
var String s;
var int val;
let s = Keyboard.readLine(message);
let val = s.intValue();
do s.dispose();
return val;
}
}

View File

@@ -15,6 +15,17 @@ class Math {
// Initializes the Math library.
function void init() {
var int i, val;
let n = 16;
let powersOfTwo = Array.new(n);
let i = 0;
let val = 1;
while (i < n) {
let powersOfTwo[i] = val;
let val = val + val;
let i = i + 1;
}
return;
}
/** Returns the product of x and y.
@@ -22,6 +33,24 @@ class Math {
* in an expression, it handles it by invoking this method.
* Thus, in Jack, x * y and Math.multiply(x,y) return the same value. */
function int multiply(int x, int y) {
var int sum, shiftedX, i;
let sum = 0;
let shiftedX = x;
let i = 0;
while (i < n) {
if (Math.bit(y, i)) {
let sum = sum + shiftedX;
}
let shiftedX = shiftedX + shiftedX;
let i = i + 1;
}
return sum;
}
/** Returns true if the i-th bit of x is 1, false otherwise */
function boolean bit(int x, int i) {
return ~((x & powersOfTwo[i]) = 0);
}
/** Returns the integer part of x / y.
@@ -29,21 +58,78 @@ class Math {
* an an expression, it handles it by invoking this method.
* Thus, x/y and Math.divide(x,y) return the same value. */
function int divide(int x, int y) {
var int q;
var int absX, absY;
var int result;
var boolean neg;
let neg = (x < 0) = (y > 0); // true if signs differ
let absX = Math.abs(x);
let absY = Math.abs(y);
if (absY > absX) {
return 0;
}
// Handle overflow for 2*y
if ((absY + absY) < 0) {
let q = 0;
} else {
let q = Math.divide(absX, absY + absY);
}
if ((absX - (2 * q * absY)) < absY) {
let result = q + q;
} else {
let result = q + q + 1;
}
if (neg) {
return -result;
}
return result;
}
/** Returns the integer part of the square root of x. */
function int sqrt(int x) {
var int y, j, approx, approxSq;
let y = 0;
let j = 7; // n/2 - 1 for n=16
while (j > -1) {
let approx = y + powersOfTwo[j];
let approxSq = approx * approx;
// Checks if approxSq > 0 (overflow check) and approxSq <= x
if (((approxSq > 0) | (approxSq = 0)) & ((approxSq < x) | (approxSq = x))) {
let y = approx;
}
let j = j - 1;
}
return y;
}
/** Returns the greater value. */
function int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
/** Returns the smaller value. */
function int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}
/** Returns the absolute value of x. */
function int abs(int x) {
if (x < 0) {
return -x;
}
return x;
}
}

View File

@@ -8,26 +8,70 @@
* consists of 32,768 words, each holding a 16-bit binary number.
*/
class Memory {
static Array ram;
static Array heap;
static int freeList;
static int heapBottom;
/** Initializes the class. */
function void init() {
let ram = 0;
let heap = 2048; // heapBase
let heapBottom = 16384;
let freeList = heap;
let heap[0] = heapBottom - heap; // length of first block
let heap[1] = 0; // next pointer
return;
}
/** Returns the RAM value at the given address. */
function int peek(int address) {
return ram[address];
}
/** Sets the RAM value at the given address to the given value. */
function void poke(int address, int value) {
let ram[address] = value;
return;
}
/** Finds an available RAM block of the given size and returns
* a reference to its base address. */
function int alloc(int size) {
var int curr, next, len;
var int block;
let curr = freeList;
// Search for a block with enough space (size + 1 for header)
while (~(curr = 0)) {
let len = ram[curr];
// Check if block is large enough (size + 1 for header)
// Ideally we also want to leave at least 2 words for the free block itself to remain valid
if (len > (size + 2)) {
// Carve from the end of the block
let ram[curr] = len - (size + 1); // Reduce free block size
let block = curr + ram[curr]; // Base of new block (including header)
let ram[block] = size + 1; // Set allocated block size (including header)
return block + 1; // Return user pointer
}
let curr = ram[curr + 1]; // Next block
}
return 0; // Out of memory
}
/** De-allocates the given object (cast as an array) by making
* it available for future allocations. */
function void deAlloc(Array o) {
var int block;
let block = o - 1; // Get header
// Append to head of freeList
let ram[block + 1] = freeList;
let freeList = block;
return;
}
}

View File

@@ -5,29 +5,33 @@
/**
* A library of functions for writing text on the screen.
* The Hack physical screen consists of 512 rows of 256 pixels each.
* The library uses a fixed font, in which each character is displayed
* within a frame which is 11 pixels high (including 1 pixel for inter-line
* The library uses a fixed font, in which each character is displayed
* within a frame which is 11 pixels high (including 1 pixel for inter-line
* spacing) and 8 pixels wide (including 2 pixels for inter-character spacing).
* The resulting grid accommodates 23 rows (indexed 0..22, top to bottom)
* of 64 characters each (indexed 0..63, left to right). The top left
* of 64 characters each (indexed 0..63, left to right). The top left
* character position on the screen is indexed (0,0). A cursor, implemented
* as a small filled square, indicates where the next character will be displayed.
*/
class Output {
// Character map for displaying characters
static Array charMaps;
static Array charMaps;
static int cursorRow, cursorCol;
/** Initializes the screen, and locates the cursor at the screen's top-left. */
function void init() {
do Output.initMap();
do Output.moveCursor(0, 0);
return;
}
// Initializes the character map array
function void initMap() {
var int i;
let charMaps = Array.new(127);
// Black square, used for displaying non-printable characters.
do Output.create(0,63,63,63,63,63,63,63,63,63,0,0);
@@ -48,9 +52,9 @@ class Output {
do Output.create(43,0,0,0,12,12,63,12,12,0,0,0); // +
do Output.create(44,0,0,0,0,0,0,0,12,12,6,0); // ,
do Output.create(45,0,0,0,0,0,63,0,0,0,0,0); // -
do Output.create(46,0,0,0,0,0,0,0,12,12,0,0); // .
do Output.create(46,0,0,0,0,0,0,0,12,12,0,0); // .
do Output.create(47,0,0,32,48,24,12,6,3,1,0,0); // /
do Output.create(48,12,30,51,51,51,51,51,30,12,0,0); // 0
do Output.create(49,12,14,15,12,12,12,12,12,63,0,0); // 1
do Output.create(50,30,51,48,24,12,6,3,51,63,0,0); // 2
@@ -61,7 +65,7 @@ class Output {
do Output.create(55,63,49,48,48,24,12,12,12,12,0,0); // 7
do Output.create(56,30,51,51,51,30,51,51,51,30,0,0); // 8
do Output.create(57,30,51,51,51,62,48,48,24,14,0,0); // 9
do Output.create(58,0,0,12,12,0,0,12,12,0,0,0); // :
do Output.create(59,0,0,12,12,0,0,12,12,6,0,0); // ;
do Output.create(60,0,0,24,12,6,3,6,12,24,0,0); // <
@@ -70,7 +74,7 @@ class Output {
do Output.create(64,30,51,51,59,59,59,27,3,30,0,0); // @
do Output.create(63,30,51,51,24,12,12,0,12,12,0,0); // ?
do Output.create(65,0,0,0,0,0,0,0,0,0,0,0); // A ** TO BE FILLED **
do Output.create(65,0,0,12,30,51,51,63,51,51,51,0); // A
do Output.create(66,31,51,51,51,31,51,51,51,31,0,0); // B
do Output.create(67,28,54,35,3,3,3,35,54,28,0,0); // C
do Output.create(68,15,27,51,51,51,51,51,27,15,0,0); // D
@@ -96,14 +100,12 @@ class Output {
do Output.create(88,51,51,30,30,12,30,30,51,51,0,0); // X
do Output.create(89,51,51,51,51,30,12,12,12,30,0,0); // Y
do Output.create(90,63,51,49,24,12,6,35,51,63,0,0); // Z
do Output.create(91,30,6,6,6,6,6,6,6,30,0,0); // [
do Output.create(92,0,0,1,3,6,12,24,48,32,0,0); // \
do Output.create(93,30,24,24,24,24,24,24,24,30,0,0); // ]
do Output.create(94,8,28,54,0,0,0,0,0,0,0,0); // ^
do Output.create(95,0,0,0,0,0,0,0,0,0,63,0); // _
do Output.create(96,6,12,24,0,0,0,0,0,0,0,0); // `
do Output.create(97,0,0,0,14,24,30,27,27,54,0,0); // a
do Output.create(98,3,3,3,15,27,51,51,51,30,0,0); // b
do Output.create(99,0,0,0,30,51,3,3,51,30,0,0); // c
@@ -130,7 +132,7 @@ class Output {
do Output.create(120,0,0,0,51,30,12,12,30,51,0,0); // x
do Output.create(121,0,0,0,51,51,51,62,48,24,15,0); // y
do Output.create(122,0,0,0,63,27,12,6,51,63,0,0); // z
do Output.create(123,56,12,12,12,7,12,12,12,56,0,0); // {
do Output.create(124,12,12,12,12,12,12,12,12,12,0,0); // |
do Output.create(125,7,12,12,12,56,12,12,12,7,0,0); // }
@@ -161,7 +163,7 @@ class Output {
return;
}
// Returns the character map (array of size 11) of the given character.
// If the given character is invalid or non-printable, returns the
// character map of a black square.
@@ -175,28 +177,101 @@ class Output {
/** Moves the cursor to the j-th column of the i-th row,
* and erases the character displayed there. */
function void moveCursor(int i, int j) {
let cursorRow = i;
let cursorCol = j;
return;
}
/** Displays the given character at the cursor location,
* and advances the cursor one column forward. */
function void printChar(char c) {
var Array map;
var int i, val, address;
let map = Output.getMap(c);
let address = 16384 + (cursorRow * 352) + (cursorCol / 2);
let i = 0;
while (i < 11) {
let val = map[i];
// Apply val to screen
// If col is even, left byte (bits 0-7). If odd, right byte (8-15).
// LSB is left.
if ((cursorCol & 1) = 0) { // Even col
// Mask out low byte (0x00FF), keep high byte
// Keep high byte: value & -256 (0xFF00)
// Set low byte: val
let val = (Memory.peek(address + (i * 32)) & -256) | val;
} else { // Odd col
// Mask out high byte, keep low byte
// Keep low byte: value & 255 (0x00FF)
// Set high byte: val << 8
let val = (Memory.peek(address + (i * 32)) & 255) | (val * 256);
}
do Memory.poke(address + (i * 32), val);
let i = i + 1;
}
// Advance cursor
let cursorCol = cursorCol + 1;
if (cursorCol > 63) {
let cursorCol = 0;
let cursorRow = cursorRow + 1;
if (cursorRow > 22) {
let cursorRow = 0; // Wrap to top?
}
}
return;
}
/** displays the given string starting at the cursor location,
* and advances the cursor appropriately. */
function void printString(String s) {
var int i;
let i = 0;
while (i < s.length()) {
do Output.printChar(s.charAt(i));
let i = i + 1;
}
return;
}
/** Displays the given integer starting at the cursor location,
* and advances the cursor appropriately. */
function void printInt(int i) {
var String s;
let s = String.new(6); // Max int is 5 chars + sign
do s.setInt(i);
do Output.printString(s);
do s.dispose();
return;
}
/** Advances the cursor to the beginning of the next line. */
function void println() {
let cursorCol = 0;
let cursorRow = cursorRow + 1;
if (cursorRow > 22) {
let cursorRow = 0;
}
return;
}
/** Moves the cursor one column back. */
function void backSpace() {
if (cursorCol > 0) {
let cursorCol = cursorCol - 1;
} else {
if (cursorRow > 0) {
let cursorRow = cursorRow - 1;
let cursorCol = 63;
}
}
return;
}
}

View File

@@ -5,38 +5,178 @@
/**
* A library of functions for displaying graphics on the screen.
* The Hack physical screen consists of 512 rows (indexed 0..511, top to bottom)
* of 256 pixels each (indexed 0..255, left to right). The top left pixel on
* of 256 pixels each (indexed 0..255, left to right). The top left pixel on
* the screen is indexed (0,0).
*/
class Screen {
static boolean color;
static Array powersOfTwo; // Helper for bit manipulation
/** Initializes the Screen. */
function void init() {
let color = true; // Default black
let powersOfTwo = Array.new(16);
let powersOfTwo[0] = 1;
let powersOfTwo[1] = 2;
let powersOfTwo[2] = 4;
let powersOfTwo[3] = 8;
let powersOfTwo[4] = 16;
let powersOfTwo[5] = 32;
let powersOfTwo[6] = 64;
let powersOfTwo[7] = 128;
let powersOfTwo[8] = 256;
let powersOfTwo[9] = 512;
let powersOfTwo[10] = 1024;
let powersOfTwo[11] = 2048;
let powersOfTwo[12] = 4096;
let powersOfTwo[13] = 8192;
let powersOfTwo[14] = 16384;
let powersOfTwo[15] = 16384 + 16384; // 32768 (negative in 16-bit)
return;
}
/** Erases the entire screen. */
function void clearScreen() {
var int i;
let i = 16384;
while(i < 24576) {
do Memory.poke(i, 0);
let i = i + 1;
}
return;
}
/** Sets the current color, to be used for all subsequent drawXXX commands.
* Black is represented by true, white by false. */
function void setColor(boolean b) {
let color = b;
return;
}
/** Draws the (x,y) pixel, using the current color. */
function void drawPixel(int x, int y) {
var int address, value;
var int mask;
let address = 16384 + (y * 32) + (x / 16);
let value = Memory.peek(address);
// x & 15 is x % 16
let mask = powersOfTwo[x & 15];
if (color) {
let value = value | mask;
} else {
let value = value & ~mask;
}
do Memory.poke(address, value);
return;
}
/** Draws a line from pixel (x1,y1) to pixel (x2,y2), using the current color. */
function void drawLine(int x1, int y1, int x2, int y2) {
var int dx, dy;
var int a, b;
var int diff;
var int temp;
if (x1 > x2) {
let temp = x1;
let x1 = x2;
let x2 = temp;
let temp = y1;
let y1 = y2;
let y2 = temp;
}
let dx = x2 - x1;
let dy = y2 - y1;
let a = 0;
let b = 0;
let diff = 0;
// Vertical line
if (dx = 0) {
if (y1 > y2) {
let temp = y1;
let y1 = y2;
let y2 = temp;
}
while (~(y1 > y2)) {
do Screen.drawPixel(x1, y1);
let y1 = y1 + 1;
}
return;
}
// Horizontal line
if (dy = 0) {
while (~(x1 > x2)) {
do Screen.drawPixel(x1, y1);
let x1 = x1 + 1;
}
return;
}
// Diagonal
if (dy > 0) {
while ((~(a > dx)) & (~(b > dy))) {
do Screen.drawPixel(x1 + a, y1 + b);
if (diff < 0) {
let a = a + 1;
let diff = diff + dy;
} else {
let b = b + 1;
let diff = diff - dx;
}
}
} else {
while ((~(a > dx)) & (~(b < dy))) {
do Screen.drawPixel(x1 + a, y1 + b);
if (diff < 0) {
let a = a + 1;
let diff = diff - dy; // dy is negative
} else {
let b = b - 1;
let diff = diff - dx;
}
}
}
return;
}
/** Draws a filled rectangle whose top left corner is (x1, y1)
* and bottom right corner is (x2,y2), using the current color. */
function void drawRectangle(int x1, int y1, int x2, int y2) {
var int r;
let r = y1;
while (~(r > y2)) {
do Screen.drawLine(x1, r, x2, r); // reuse line for now for simplicity
let r = r + 1;
}
return;
}
/** Draws a filled circle of radius r<=181 around (x,y), using the current color. */
function void drawCircle(int x, int y, int r) {
var int dy;
var int r2;
var int halfWidth;
if (r > 181) {
return; // overflow check
}
let dy = -r;
let r2 = r*r;
while (~(dy > r)) {
let halfWidth = Math.sqrt(r2 - (dy*dy));
do Screen.drawLine(x - halfWidth, y + dy, x + halfWidth, y + dy);
let dy = dy + 1;
}
return;
}
}

View File

@@ -10,54 +10,141 @@
* string-oriented operations.
*/
class String {
field Array buffer;
field int length;
field int maxLen;
/** constructs a new empty string with a maximum length of maxLength
* and initial length of 0. */
constructor String new(int maxLength) {
if (maxLength = 0) {
let maxLength = 1; // min length 1 for alloc safety?
}
if (maxLength > 0) {
let buffer = Array.new(maxLength);
}
let maxLen = maxLength;
let length = 0;
return this;
}
/** Disposes this string. */
method void dispose() {
if (maxLen > 0) {
do buffer.dispose();
}
do Memory.deAlloc(this);
return;
}
/** Returns the current length of this string. */
method int length() {
return length;
}
/** Returns the character at the j-th location of this string. */
method char charAt(int j) {
return buffer[j];
}
/** Sets the character at the j-th location of this string to c. */
method void setCharAt(int j, char c) {
let buffer[j] = c;
return;
}
/** Appends c to this string's end and returns this string. */
method String appendChar(char c) {
if (length < maxLen) {
let buffer[length] = c;
let length = length + 1;
}
return this;
}
/** Erases the last character from this string. */
method void eraseLastChar() {
if (length > 0) {
let length = length - 1;
}
return;
}
/** Returns the integer value of this string,
/** Returns the integer value of this string,
* until a non-digit character is detected. */
method int intValue() {
var int val, i, d;
var boolean neg;
let val = 0;
let i = 0;
let neg = false;
if (length > 0) {
if (buffer[0] = 45) { // '-'
let neg = true;
let i = 1;
}
}
while (i < length) {
let d = buffer[i] - 48; // '0' is 48
if ((d > -1) & (d < 10)) {
let val = (val * 10) + d;
let i = i + 1;
} else {
let i = length; // break
}
}
if (neg) {
return -val;
}
return val;
}
/** Sets this string to hold a representation of the given value. */
method void setInt(int val) {
let length = 0; // Clear string
if (val < 0) {
let val = -val;
do appendChar(45); // '-'
}
do int2String(val);
return;
}
// Helper for recursive int printing
method void int2String(int val) {
var int lastDigit;
var int c;
let lastDigit = val - ((val / 10) * 10); // val % 10
let c = lastDigit + 48;
if (val < 10) {
do appendChar(c);
} else {
do int2String(val / 10);
do appendChar(c);
}
return;
}
/** Returns the new line character. */
function char newLine() {
return 128;
}
/** Returns the backspace character. */
function char backSpace() {
return 129;
}
/** Returns the double quote (") character. */
function char doubleQuote() {
return 34;
}
}

View File

@@ -10,18 +10,50 @@ class Sys {
/** Performs all the initializations required by the OS. */
function void init() {
// do Memory.poke(8000, 1); // Start
do Memory.init();
// do Memory.poke(8000, 2); // Mem init done
do Math.init();
// do Memory.poke(8000, 3); // Math init done
do Screen.init();
do Output.init();
do Keyboard.init();
do Main.main();
do Sys.halt();
return;
}
/** Halts the program execution. */
function void halt() {
while (true) {}
return;
}
/** Waits approximately duration milliseconds and returns. */
function void wait(int duration) {
var int i, j;
let i = 0;
while (i < duration) {
let j = 0;
// Calibration: loop count determines delay.
// On typical VM emulator settings (Fast), ~50-100 loops might be 1ms?
// "WaitWithInput" sample in Snake used loop for waiting.
while (j < 50) {
let j = j + 1;
}
let i = i + 1;
}
return;
}
/** Displays the given error code in the form "ERR<errorCode>",
* and halts the program's execution. */
function void error(int errorCode) {
do Output.printString("ERR");
do Output.printInt(errorCode);
do Sys.halt();
return;
}
}