diff --git a/12/Array.jack b/12/Array.jack index efc7e9c..ac68ebe 100644 --- a/12/Array.jack +++ b/12/Array.jack @@ -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; } } diff --git a/12/Keyboard.jack b/12/Keyboard.jack index d90e547..9141b5f 100644 --- a/12/Keyboard.jack +++ b/12/Keyboard.jack @@ -9,6 +9,7 @@ class Keyboard { /** Initializes the keyboard. */ function void init() { + return; } /** @@ -32,18 +33,77 @@ 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 * 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? + // Output.printChar(128) might not do NewLine with my Output implementation unless I updated it? + // My Output implementation (Step 240) does NOT check for 128. + // So I should call println() if 128. + if (key = 128) { + do Output.println(); + } else { + do Output.printChar(key); + } + } + // If key > 129 (arrows etc), printChar might print garbage or square. + // Spec usually implies echoing everything or being selective. + // I'll stick to echoing only standard chars + newline. + + 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 @@ -51,5 +111,13 @@ class Keyboard { * 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; } } diff --git a/12/Math.jack b/12/Math.jack index 446df75..64acca2 100644 --- a/12/Math.jack +++ b/12/Math.jack @@ -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; } } diff --git a/12/Memory.jack b/12/Memory.jack index 8a853fa..156a2ef 100644 --- a/12/Memory.jack +++ b/12/Memory.jack @@ -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; } } diff --git a/12/Output.jack b/12/Output.jack index 79534e5..83c8251 100644 --- a/12/Output.jack +++ b/12/Output.jack @@ -17,9 +17,13 @@ class Output { // Character map for displaying characters 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 @@ -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 @@ -175,28 +177,109 @@ 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; + // Spec says "and erases the character displayed there"? + // Usually moveCursor just moves it. The cursor 'character' itself (black square) + // is typically implemented by blinking or drawing. + // Project 12 spec: "A cursor, implemented as a small filled square, indicates where the next character will be displayed." + // And "erases the character displayed there"? Maybe erases the *cursor* from old location? + // Or erases the *content*? "Erases the character displayed there" usually implies drawing black square (cursor) or clearing it? + // Wait, standard implementation usually just updates coordinates. + // I will assume simple update. } /** 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; } } diff --git a/12/Screen.jack b/12/Screen.jack index 679b349..4516e08 100644 --- a/12/Screen.jack +++ b/12/Screen.jack @@ -9,34 +9,175 @@ * 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); + + // Calculate 2^(x%16) logic inline or helper? + // 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; } } diff --git a/12/String.jack b/12/String.jack index e829ec2..9e6b2c7 100644 --- a/12/String.jack +++ b/12/String.jack @@ -10,54 +10,145 @@ * 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? + // Actually spec says allocation of 0 size might happen. + // My Array.new -> Memory.alloc handles 0 size by returning a 1 word block (header-1?? No). + // Let's alloc 0 if needed. + // If maxLength is 0, we can't really store anything. + } + 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, * 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; } } diff --git a/12/Sys.jack b/12/Sys.jack index bee6f67..675997e 100644 --- a/12/Sys.jack +++ b/12/Sys.jack @@ -10,18 +10,49 @@ class Sys { /** Performs all the initializations required by the OS. */ function void init() { + do Memory.init(); + do Math.init(); + 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? + // User requested 2 seconds wait in test. + // "WaitWithInput" sample in Snake used loop for waiting. + + // Standard N2T value is roughly 50-100. + while (j < 50) { + let j = j + 1; + } + let i = i + 1; + } + return; } /** Displays the given error code in the form "ERR", * and halts the program's execution. */ function void error(int errorCode) { + do Output.printString("ERR"); + do Output.printInt(errorCode); + do Sys.halt(); + return; } }