2020-06-01 21:49:28 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace captn3m0\NandToTetris;
|
|
|
|
|
|
|
|
class CodeWriter {
|
|
|
|
function __construct($outputFile) {
|
|
|
|
$this->ic = 0;
|
|
|
|
$this->sourceLine = 0;
|
|
|
|
$this->file = fopen($outputFile, "w");
|
2020-06-03 12:59:50 +00:00
|
|
|
|
|
|
|
// We aren't inside a function by default
|
|
|
|
$this->fn = null;
|
2020-06-03 19:55:06 +00:00
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
$this->comments = true;
|
|
|
|
|
2020-06-03 19:55:06 +00:00
|
|
|
// Write the preamble for the assembler
|
|
|
|
$this->writeInit();
|
2020-06-01 21:49:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
private function push($kind, $what, $direct = false) {
|
|
|
|
if ($kind === 'register') {
|
|
|
|
assert(in_array($what, [
|
|
|
|
'SP','LCL','ARG','THIS','THAT',
|
|
|
|
'R0','R1','R2','R3','R4','R5',
|
|
|
|
'R6','R7','R8','R9','R10','R11'
|
|
|
|
,'R12','R13','R14','R15' , 'D', 'A']
|
|
|
|
));
|
|
|
|
|
|
|
|
switch ($what) {
|
|
|
|
// A Can be optimized
|
|
|
|
case 'A':
|
|
|
|
$direct = true;
|
|
|
|
$this->write(['D=A']);
|
|
|
|
break;
|
|
|
|
case 'D':
|
|
|
|
$direct = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
$this->write([
|
|
|
|
"@$what",
|
|
|
|
"AD=M",
|
|
|
|
]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($direct === false) {
|
|
|
|
$this->write(["D=M"]);
|
|
|
|
}
|
|
|
|
} else if ($kind === 'label') {
|
|
|
|
$this->write([
|
|
|
|
"@$what",
|
|
|
|
"D=A"
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->writeDtoSPAndBump();
|
|
|
|
}
|
|
|
|
|
|
|
|
private function writeDtoSPAndBump() {
|
|
|
|
$this->write([
|
|
|
|
"@SP",
|
|
|
|
"A=M",
|
|
|
|
"M=D",
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->increaseSP();
|
|
|
|
}
|
|
|
|
|
|
|
|
private function increaseSP() {
|
|
|
|
$this->write([
|
|
|
|
"@SP",
|
|
|
|
"M=M+1",
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 18:20:34 +00:00
|
|
|
public function writeReturn() {
|
|
|
|
$this->write([
|
2020-06-04 15:35:02 +00:00
|
|
|
'@LCL // save return address first',
|
|
|
|
'A=M-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1',
|
|
|
|
'D=M // D now holds the return address',
|
|
|
|
'@R14',
|
|
|
|
'M=D // Wrote the return address to R14',
|
|
|
|
|
|
|
|
"@SP // return for {$this->fn} starts",
|
2020-06-04 10:13:38 +00:00
|
|
|
'A=M-1',
|
|
|
|
'D=M',// Popped value to D
|
2020-06-04 15:35:02 +00:00
|
|
|
// And then write it to R14
|
2020-06-04 10:13:38 +00:00
|
|
|
'@ARG',
|
|
|
|
'A=M',
|
|
|
|
'M=D',
|
2020-06-03 19:18:54 +00:00
|
|
|
|
2020-06-03 18:20:34 +00:00
|
|
|
// SP=ARG+1
|
2020-06-04 10:13:38 +00:00
|
|
|
'@ARG',
|
|
|
|
'D=M+1',
|
|
|
|
'@SP',
|
|
|
|
'M=D // @SP = ARG+1',
|
|
|
|
'@LCL',
|
|
|
|
'D=M',
|
|
|
|
'@R13',
|
2020-06-04 15:35:02 +00:00
|
|
|
'M=D // Save LCL to R13 = FRAME',
|
2020-06-03 19:18:54 +00:00
|
|
|
|
|
|
|
// now we go restoring THAT, THIS, ARG, LCL
|
2020-06-04 10:13:38 +00:00
|
|
|
'A=D-1 // A=*LCL-1',
|
|
|
|
'D=M // D=*(*LCL-1)',
|
|
|
|
'@THAT // A=THAT',
|
|
|
|
'M=D // *that = *(*lcl-1)',
|
2020-06-04 15:35:02 +00:00
|
|
|
|
2020-06-03 19:18:54 +00:00
|
|
|
// now we restore THIS
|
2020-06-04 10:13:38 +00:00
|
|
|
'@R13',
|
|
|
|
'A=M-1',
|
|
|
|
'A=A-1 // A=*LCL-2',
|
|
|
|
'D=M // D=*(*LCL-2)',
|
|
|
|
'@THIS // A=THIS',
|
|
|
|
'M=D // *THIS = *(*lcl-2)',
|
2020-06-03 19:18:54 +00:00
|
|
|
|
|
|
|
// now we restore ARG
|
2020-06-04 10:13:38 +00:00
|
|
|
'@R13',
|
|
|
|
'A=M-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1 // A=*LCL-3',
|
|
|
|
'D=M // D=*(*LCL-3)',
|
|
|
|
'@ARG // A=ARG',
|
|
|
|
'M=D // *ARG = *(*lcl-3)',
|
2020-06-03 19:18:54 +00:00
|
|
|
|
|
|
|
// now we restore LCL
|
2020-06-04 10:13:38 +00:00
|
|
|
'@R13',
|
|
|
|
'A=M-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1',
|
|
|
|
'A=A-1 // A=*LCL-4',
|
|
|
|
'D=M // D=*(*LCL-4)',
|
|
|
|
'@LCL // A=LCL',
|
|
|
|
'M=D // *LCL = *(*lcl-4)',
|
2020-06-03 19:18:54 +00:00
|
|
|
|
|
|
|
// Now we hyperjump
|
2020-06-04 15:35:02 +00:00
|
|
|
'@R14',
|
|
|
|
'A=M',
|
|
|
|
'0;JMP // HyperJump to *(LCL-5)',
|
2020-06-03 18:20:34 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 19:55:06 +00:00
|
|
|
/**
|
|
|
|
* Translates the "function" start.
|
|
|
|
* function $name $args
|
|
|
|
*/
|
2020-06-03 18:20:34 +00:00
|
|
|
public function writeFunction($name, $numArgs) {
|
2020-06-03 19:55:06 +00:00
|
|
|
// This is used for labels within the current function
|
2020-06-03 18:20:34 +00:00
|
|
|
$this->fn = $name;
|
2020-06-03 19:55:06 +00:00
|
|
|
|
2020-06-03 18:20:34 +00:00
|
|
|
$this->write([
|
|
|
|
"($name) // function $name $numArgs",
|
|
|
|
]);
|
2020-06-03 19:55:06 +00:00
|
|
|
|
|
|
|
// We write this once at the top
|
|
|
|
// And for subsequent loops, we can re-use
|
|
|
|
// the @SP call at the end where we bump the stack by 1
|
|
|
|
if ($numArgs > 0) {
|
|
|
|
$this->write([
|
|
|
|
// This is only required for the first argument
|
2020-06-04 10:13:38 +00:00
|
|
|
'@SP',
|
|
|
|
'A=M',
|
2020-06-03 19:55:06 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// For every argument in the function
|
|
|
|
// push a zero to the stack
|
2020-06-03 18:20:34 +00:00
|
|
|
for($i=0;$i<$numArgs;$i++) {
|
|
|
|
$this->write([
|
2020-06-04 10:13:38 +00:00
|
|
|
'M=0',
|
|
|
|
'@SP',
|
|
|
|
'AM=M+1',
|
2020-06-03 18:20:34 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
private function copy($registerA, $registerB) {
|
|
|
|
$this->write([
|
|
|
|
"@$registerA",
|
|
|
|
'D=M',
|
|
|
|
"@$registerB",
|
|
|
|
"M=D // Update $registerB=$registerA",
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadOffsetToD($register, $offset, $sign = '-') {
|
|
|
|
$this->write([
|
|
|
|
"@$register",
|
|
|
|
"D=M",
|
|
|
|
]);
|
|
|
|
|
|
|
|
for ($i=0; $i < $offset; $i++) {
|
|
|
|
$this->write([
|
|
|
|
'D=D' . $sign . "1 // should repeat $offset times",
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-03 20:33:46 +00:00
|
|
|
/**
|
|
|
|
* Function call executed, save state and JUMP
|
|
|
|
*/
|
|
|
|
public function writeCall(String $functionName, $numArgs) {
|
2020-06-04 15:35:02 +00:00
|
|
|
$returnLabel = bin2hex(random_bytes(16));
|
2020-06-03 20:33:46 +00:00
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
$this->push('label', $returnLabel);
|
2020-06-03 20:33:46 +00:00
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
// push the exact values that these registers are holding
|
|
|
|
$this->push('register', 'LCL', true);
|
|
|
|
$this->push('register', 'ARG', true);
|
|
|
|
$this->push('register', 'THIS', true);
|
|
|
|
$this->push('register', 'THAT', true);
|
|
|
|
$this->copy('SP', 'LCL');
|
|
|
|
|
|
|
|
// Reduce D height times = numArgs+5
|
|
|
|
$offset = $numArgs + 5;
|
|
|
|
|
|
|
|
$this->loadOffsetToD('SP', $offset, '-');
|
|
|
|
// now D = SP-n-5
|
|
|
|
|
|
|
|
// now we need to write D to ARG
|
2020-06-03 20:33:46 +00:00
|
|
|
$this->write([
|
2020-06-04 15:35:02 +00:00
|
|
|
'@ARG // write SP-$offset to ARG',
|
2020-06-04 10:13:38 +00:00
|
|
|
'M=D',
|
2020-06-04 15:35:02 +00:00
|
|
|
"@$functionName // Jump to $functionName",
|
|
|
|
'0;JMP',
|
|
|
|
"($returnLabel) // return back from function here (CALL ENDS)",
|
2020-06-03 20:33:46 +00:00
|
|
|
]);
|
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
// If we have returned from the Sys.init function call
|
|
|
|
// then put an infinite loop here
|
|
|
|
if ($functionName === "Sys.init") {
|
2020-06-03 20:33:46 +00:00
|
|
|
$this->write([
|
2020-06-04 15:35:02 +00:00
|
|
|
'(__GLOBAL_TERMINATE__)',
|
|
|
|
'@__GLOBAL_TERMINATE__',
|
|
|
|
'0;JMP',
|
2020-06-03 20:33:46 +00:00
|
|
|
]);
|
|
|
|
}
|
2020-06-04 15:35:02 +00:00
|
|
|
}
|
2020-06-03 20:33:46 +00:00
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
private function writeConstantToRegister(String $register, Int $constant) {
|
|
|
|
if ($constant < 0) {
|
|
|
|
$constant = abs($constant);
|
|
|
|
$this->write([
|
|
|
|
"@$constant",
|
|
|
|
"D=-A"
|
|
|
|
]);
|
|
|
|
} else if ($constant === 0) {
|
|
|
|
$this->write(["D=0"]);
|
|
|
|
} else {
|
2020-06-03 20:33:46 +00:00
|
|
|
$this->write([
|
2020-06-04 15:35:02 +00:00
|
|
|
"@$constant",
|
|
|
|
"D=A"
|
2020-06-03 20:33:46 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
$this->write([
|
2020-06-04 15:35:02 +00:00
|
|
|
"@$register",
|
|
|
|
"M=D // initialized $register to $constant",
|
2020-06-03 20:33:46 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 18:20:34 +00:00
|
|
|
/**
|
|
|
|
* Writes the preable to initialize the VM
|
|
|
|
*/
|
2020-06-04 15:35:02 +00:00
|
|
|
private function writeInit($initExists = true) {
|
|
|
|
$this->writeConstantToRegister("SP", 256);
|
|
|
|
$this->writeConstantToRegister("LCL", -1);
|
|
|
|
$this->writeConstantToRegister("ARG", -2);
|
|
|
|
$this->writeConstantToRegister("THIS", -3);
|
|
|
|
$this->writeConstantToRegister("THAT", -4);
|
2020-06-04 10:13:38 +00:00
|
|
|
|
|
|
|
$this->writeCall('Sys.init', 0);
|
2020-06-03 18:20:34 +00:00
|
|
|
}
|
|
|
|
|
2020-06-02 11:58:20 +00:00
|
|
|
function setInputFileName($inputFileName) {
|
|
|
|
$this->vm = basename($inputFileName, ".vm");
|
|
|
|
}
|
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
function nextSourceLine() {
|
|
|
|
$this->sourceLine += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function __destruct() {
|
|
|
|
fclose($this->file);
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* Puts the closing non-terminating
|
|
|
|
* loop
|
|
|
|
*/
|
2020-06-01 21:49:28 +00:00
|
|
|
function close() {
|
|
|
|
$endJump = $this->ic+1;
|
|
|
|
$this->write([
|
|
|
|
"@$endJump",
|
2020-06-04 10:13:38 +00:00
|
|
|
'0;JMP',
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* Writes label X as (fnlabel)
|
|
|
|
*/
|
|
|
|
function writeLabel(String $label) {
|
|
|
|
$globalLabel = $this->resolveLabel($label);
|
|
|
|
$this->write([
|
2020-06-04 10:13:38 +00:00
|
|
|
"($globalLabel) // end label $label",
|
2020-06-03 12:59:50 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates a unique global label
|
|
|
|
* by using the current function name
|
|
|
|
*/
|
|
|
|
private function resolveLabel(String $label) {
|
|
|
|
if($this->fn === null)
|
|
|
|
return "__GLOBAL__.$label";
|
|
|
|
return $this->fn . $label;
|
|
|
|
}
|
|
|
|
|
2020-06-03 13:36:04 +00:00
|
|
|
/**
|
|
|
|
* Writes a unconditional jump statement
|
|
|
|
*/
|
|
|
|
public function writeGoto(String $label) {
|
|
|
|
$globalLabel = $this->resolveLabel($label);
|
|
|
|
$this->write([
|
|
|
|
"@$globalLabel",
|
2020-06-04 10:13:38 +00:00
|
|
|
"0;JMP // end goto $label",
|
2020-06-03 13:36:04 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* Writes corresponding code for if-goto
|
|
|
|
* if value == true, goto X
|
|
|
|
* else keep executing
|
|
|
|
*/
|
|
|
|
function writeIf(String $label) {
|
|
|
|
$globalLabel = $this->resolveLabel($label);
|
|
|
|
$this->write([
|
|
|
|
// Read top of the stack to D
|
|
|
|
'@SP',
|
|
|
|
'AM=M-1',
|
|
|
|
'D=M',
|
|
|
|
"@$globalLabel",
|
2020-06-04 10:13:38 +00:00
|
|
|
"D;JNE // end if-goto $label",
|
2020-06-03 12:59:50 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
private function binaryMath(String $command) {
|
|
|
|
$map = [
|
|
|
|
'sub' => '-',
|
|
|
|
'add' => '+',
|
|
|
|
'and' => '&',
|
|
|
|
'or' => '|'
|
|
|
|
];
|
|
|
|
|
|
|
|
$symbol = $map[$command];
|
|
|
|
|
|
|
|
$this->write([
|
|
|
|
'A=A-1',
|
|
|
|
"M=D{$symbol}M",
|
|
|
|
]);
|
|
|
|
|
|
|
|
if ($command === 'sub') {
|
|
|
|
$this->write([
|
|
|
|
'M=-M'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function unaryMath(String $command) {
|
|
|
|
$symbol = [
|
|
|
|
'neg' => '-',
|
|
|
|
'not' => '!'
|
|
|
|
][$command];
|
|
|
|
|
|
|
|
$this->write([
|
|
|
|
"M={$symbol}M // end $command",
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function booleanCompare(String $compare) {
|
|
|
|
$compare = strtoupper($compare);
|
|
|
|
$command = "D;J$compare";
|
|
|
|
|
|
|
|
$jumpPointer = $this->ic+10;
|
|
|
|
$this->write([
|
|
|
|
'A=A-1',
|
|
|
|
'D=M-D',
|
|
|
|
'M=0',
|
|
|
|
'M=M-1',
|
|
|
|
"@$jumpPointer",
|
|
|
|
$command,
|
|
|
|
'@SP',
|
|
|
|
'A=M-1',
|
|
|
|
'A=A-1',
|
|
|
|
'M=0',
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:19:21 +00:00
|
|
|
function writeArithmetic(String $command) {
|
2020-06-01 21:49:28 +00:00
|
|
|
$stackDecrease=true;
|
|
|
|
// Read top of stack to D
|
|
|
|
$this->write([
|
|
|
|
"@SP // ==== $command ====",
|
2020-06-04 10:13:38 +00:00
|
|
|
'A=M-1',
|
|
|
|
'D=M'
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
switch ($command) {
|
|
|
|
// TODO: Combine all the binary math commands into one
|
|
|
|
// And the unary math commands into one
|
|
|
|
case 'sub':
|
|
|
|
case 'add':
|
2020-06-04 15:35:02 +00:00
|
|
|
case 'and':
|
|
|
|
case 'or':
|
|
|
|
$this->binaryMath($command);
|
2020-06-01 21:49:28 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'neg':
|
|
|
|
case 'not':
|
2020-06-04 15:35:02 +00:00
|
|
|
$this->unaryMath($command);
|
2020-06-01 21:49:28 +00:00
|
|
|
$stackDecrease = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// TODO: Combine all the boolean commands
|
|
|
|
case 'lt':
|
|
|
|
case 'gt':
|
|
|
|
case 'eq':
|
2020-06-04 15:35:02 +00:00
|
|
|
$this->booleanCompare($command);
|
2020-06-01 21:49:28 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \Exception("$command not Implemented", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stackDecrease) {
|
|
|
|
$this->write([
|
|
|
|
'@SP',
|
2020-06-04 10:13:38 +00:00
|
|
|
"M=M-1 // end $command"
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function write(Array $lines) {
|
2020-06-04 15:35:02 +00:00
|
|
|
// print_r(debug_backtrace());
|
|
|
|
// exit(1);
|
|
|
|
$callingLine = debug_backtrace()[0]['line'];
|
|
|
|
$callingFunction = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)[1]['function'];
|
|
|
|
$args = join(", ", debug_backtrace(false, 2)[1]['args']);
|
2020-06-01 21:49:28 +00:00
|
|
|
foreach ($lines as $line) {
|
2020-06-04 15:35:02 +00:00
|
|
|
if ($this->comments === true ) {
|
|
|
|
$line = "$line \t\t\t\t// (L{$this->sourceLine}:{$this->ic}) ($callingFunction($args):$callingLine)\n";
|
|
|
|
} else {
|
|
|
|
$line = "$line\n";
|
|
|
|
}
|
|
|
|
fwrite($this->file, $line);
|
2020-06-04 10:13:38 +00:00
|
|
|
if (substr($line, 0, 2) !== "//" and substr($line, 0, 1) !== "(") {
|
2020-06-01 21:49:28 +00:00
|
|
|
$this->ic += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function resolveTemp(Int $index) {
|
|
|
|
// Temp section starts from R5;
|
|
|
|
$tempBase = 5;
|
|
|
|
$ramAddress = $tempBase + $index;
|
|
|
|
return "@R$ramAddress";
|
|
|
|
}
|
|
|
|
|
|
|
|
function writePush(String $segment, Int $index) {
|
|
|
|
switch ($segment) {
|
|
|
|
case 'constant':
|
|
|
|
$this->write([
|
|
|
|
// Take the constant
|
|
|
|
"@$index // push $segment $index",
|
|
|
|
// Write it to D
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=A',
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
case 'argument':
|
|
|
|
case 'local':
|
|
|
|
case 'this':
|
|
|
|
case 'that':
|
2020-06-03 12:59:50 +00:00
|
|
|
$register = $this->resolveSegmentToRegister($segment);
|
2020-06-01 21:49:28 +00:00
|
|
|
if ($index !== 0) {
|
|
|
|
$this->resolveSegmentToR13($segment, $index);
|
|
|
|
$register = "@R13";
|
|
|
|
}
|
|
|
|
$this->write([
|
|
|
|
$register,
|
2020-06-04 10:13:38 +00:00
|
|
|
'A=M',
|
|
|
|
'D=M',
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-02 11:58:20 +00:00
|
|
|
case 'static':
|
|
|
|
$symbol = $this->resolveStatic($index);
|
|
|
|
$this->write([
|
|
|
|
$symbol,
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M'
|
2020-06-02 11:58:20 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-02 11:34:51 +00:00
|
|
|
case 'pointer':
|
|
|
|
$register = $this->resolvePointer($index);
|
|
|
|
$this->write([
|
|
|
|
"$register // pointer $index",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M'
|
2020-06-02 11:34:51 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
case 'temp':
|
|
|
|
$register = $this->resolveTemp($index);
|
|
|
|
$this->write([
|
|
|
|
"$register // temp $index",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M'
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \Exception("Not Implemented $segment", 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-06-04 15:35:02 +00:00
|
|
|
$this->writeDtoSPAndBump();
|
2020-06-01 21:49:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* Resolves a given segment to a register
|
|
|
|
*/
|
|
|
|
private function resolveSegmentToRegister(String $segment) {
|
2020-06-01 21:49:28 +00:00
|
|
|
return [
|
|
|
|
'local' => '@LCL',
|
|
|
|
'argument' => '@ARG',
|
|
|
|
'this' => '@THIS',
|
|
|
|
'that' => '@THAT',
|
|
|
|
][$segment];
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* For cases where we need calculations on both LHS and RHS, we temporarily
|
|
|
|
* store the resolved address of the memory segment to R13. This is the code
|
|
|
|
* that does that
|
|
|
|
*/
|
2020-06-01 21:49:28 +00:00
|
|
|
private function resolveSegmentToR13(string $segment, Int $index) {
|
2020-06-03 12:59:50 +00:00
|
|
|
$register = $this->resolveSegmentToRegister($segment);
|
2020-06-01 21:49:28 +00:00
|
|
|
$this->write([
|
|
|
|
"$register // $segment $index" ,
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M',
|
2020-06-01 21:49:28 +00:00
|
|
|
"@$index // write $index to A",
|
|
|
|
"D=D+A // D = segment+index",
|
|
|
|
"@R13 // save it to R13",
|
|
|
|
"M=D // write $register+$index to R13",
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:59:50 +00:00
|
|
|
/**
|
|
|
|
* Static variables are just the same labels repeated again
|
|
|
|
* They are unique across a file
|
|
|
|
*/
|
2020-06-02 11:58:20 +00:00
|
|
|
private function resolveStatic(Int $index) {
|
|
|
|
return "@{$this->vm}.$index";
|
|
|
|
}
|
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
private function writePop(String $segment, Int $index) {
|
|
|
|
switch ($segment) {
|
|
|
|
// The address is given by LCL+INDEX
|
|
|
|
case 'local':
|
|
|
|
case 'argument':
|
|
|
|
case 'this':
|
|
|
|
case 'that':
|
|
|
|
if($index !== 0) {
|
|
|
|
$this->resolveSegmentToR13($segment, $index);
|
|
|
|
$lookupRegister = '@R13';
|
|
|
|
} else{
|
2020-06-03 12:59:50 +00:00
|
|
|
$lookupRegister = $this->resolveSegmentToRegister($segment);
|
2020-06-01 21:49:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->write([
|
|
|
|
"@SP // pop",
|
|
|
|
"AM=M-1",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M',
|
2020-06-01 21:49:28 +00:00
|
|
|
$lookupRegister,
|
|
|
|
"A=M // Read $lookupRegister to A (for $segment $index)",
|
2020-06-04 10:13:38 +00:00
|
|
|
"M=D // end pop $segment $index",
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-02 11:58:20 +00:00
|
|
|
case 'static':
|
|
|
|
$symbol = $this->resolveStatic($index);
|
|
|
|
$this->write([
|
|
|
|
"@SP //pop $segment $index",
|
|
|
|
"AM=M-1",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M',
|
2020-06-02 11:58:20 +00:00
|
|
|
$symbol,
|
2020-06-04 10:13:38 +00:00
|
|
|
"M=D // end pop $segment $index"
|
2020-06-02 11:58:20 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-02 11:34:51 +00:00
|
|
|
case 'pointer':
|
|
|
|
// pointer points to a 2 register segment holding [this, that]
|
|
|
|
$register = $this->resolvePointer($index);
|
|
|
|
$this->write([
|
|
|
|
"@SP // pop",
|
|
|
|
"AM=M-1",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M',
|
2020-06-02 11:34:51 +00:00
|
|
|
$register,
|
2020-06-04 10:13:38 +00:00
|
|
|
"M=D //"
|
2020-06-02 11:34:51 +00:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
case 'temp':
|
|
|
|
$tempRegister = $this->resolveTemp($index);
|
|
|
|
$this->write([
|
|
|
|
"@SP",
|
|
|
|
"AM=M-1",
|
2020-06-04 10:13:38 +00:00
|
|
|
'D=M',
|
2020-06-01 21:49:28 +00:00
|
|
|
"$tempRegister",
|
2020-06-04 10:13:38 +00:00
|
|
|
"M=D // end pop temp $index"
|
2020-06-01 21:49:28 +00:00
|
|
|
]);
|
|
|
|
break;
|
2020-06-03 12:59:50 +00:00
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
default:
|
|
|
|
throw new \Exception("Not implemented pop $segment");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-03 19:55:06 +00:00
|
|
|
/**
|
|
|
|
* Resolves pointer [0|1] to the this|that register
|
|
|
|
*/
|
2020-06-02 11:34:51 +00:00
|
|
|
private function resolvePointer(Int $index) {
|
2020-06-04 15:35:02 +00:00
|
|
|
return ($index === 0) ? '@THIS' : '@THAT';
|
2020-06-02 11:34:51 +00:00
|
|
|
}
|
|
|
|
|
2020-06-01 21:49:28 +00:00
|
|
|
// Keeping this because book asked me to
|
|
|
|
function writePushPop(Int $command, String $segment , Int $index) {
|
|
|
|
switch ($command) {
|
|
|
|
case CommandType::PUSH:
|
|
|
|
$this->writePush($segment, $index);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CommandType::POP:
|
|
|
|
$this->writePop($segment, $index);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Exception("Invalid Command Type", 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|