|
|
|
|
@ -7,46 +7,53 @@ module Test.Nand2Tetris.VM
@@ -7,46 +7,53 @@ module Test.Nand2Tetris.VM
|
|
|
|
|
|
|
|
|
|
import Test.Hspec (Spec, describe, it, shouldBe) |
|
|
|
|
|
|
|
|
|
import Nand2Tetris.VM (VMInstruction(VMMemAccess, VMArithmetic), |
|
|
|
|
SegmentOffset(SegmentOffset), |
|
|
|
|
AccessType(ACPush, ACPop), |
|
|
|
|
SegmentId(SArgument, SLocal, SThis, SThat, SConstant, STemp, SPointer, SStatic), |
|
|
|
|
SegmentOffset(SegmentOffset), |
|
|
|
|
parseInstruction, ArithmeticCmd (ACAdd, ACNeg, ACSub, ACEq, ACGt, ACLt, ACAnd, ACOr, ACNot)) |
|
|
|
|
import Nand2Tetris.Hack (HackInstruction (..), Imm (..), Jump (..), |
|
|
|
|
Reg (..), Source (..)) |
|
|
|
|
import Nand2Tetris.VM (AccessType (ACPop, ACPush), |
|
|
|
|
AnnotatedAsmLine (..), |
|
|
|
|
ArithmeticCmd (ACAdd, ACAnd, ACEq, ACGt, ACLt, ACNeg, ACNot, ACOr, ACSub), |
|
|
|
|
Segment (SArgument, SConstant, SLocal, SPointer, SStatic, STemp, SThat, SThis), |
|
|
|
|
VMInstruction (VMArithmetic, VMMemAccess), |
|
|
|
|
compileInstruction, parseInstruction) |
|
|
|
|
import Text.Megaparsec (runParser) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
spec :: Spec |
|
|
|
|
spec = do |
|
|
|
|
describe "parseInstruction" $ do |
|
|
|
|
describe "parseInstruction" parseInstructionSpec |
|
|
|
|
describe "compileInstruction" compileInstructionSpec |
|
|
|
|
|
|
|
|
|
parseInstructionSpec :: Spec |
|
|
|
|
parseInstructionSpec = do |
|
|
|
|
it "Parses instruction: push argument 42" $ |
|
|
|
|
doParse "push argument 42" `shouldBe` Right (VMMemAccess ACPush SArgument (SegmentOffset 42)) |
|
|
|
|
doParse "push argument 42" `shouldBe` Right (VMMemAccess ACPush (SArgument 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: pop argument 42" $ |
|
|
|
|
doParse "pop argument 42" `shouldBe` Right (VMMemAccess ACPop SArgument (SegmentOffset 42)) |
|
|
|
|
doParse "pop argument 42" `shouldBe` Right (VMMemAccess ACPop (SArgument 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push local 42" $ |
|
|
|
|
doParse "push local 42" `shouldBe` Right (VMMemAccess ACPush SLocal (SegmentOffset 42)) |
|
|
|
|
doParse "push local 42" `shouldBe` Right (VMMemAccess ACPush (SLocal 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push this 42" $ |
|
|
|
|
doParse "push this 42" `shouldBe` Right (VMMemAccess ACPush SThis (SegmentOffset 42)) |
|
|
|
|
doParse "push this 42" `shouldBe` Right (VMMemAccess ACPush (SThis 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push that 42" $ |
|
|
|
|
doParse "push that 42" `shouldBe` Right (VMMemAccess ACPush SThat (SegmentOffset 42)) |
|
|
|
|
doParse "push that 42" `shouldBe` Right (VMMemAccess ACPush (SThat 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push constant 42" $ |
|
|
|
|
doParse "push constant 42" `shouldBe` Right (VMMemAccess ACPush SConstant (SegmentOffset 42)) |
|
|
|
|
doParse "push constant 42" `shouldBe` Right (VMMemAccess ACPush (SConstant 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push static 42" $ |
|
|
|
|
doParse "push static 42" `shouldBe` Right (VMMemAccess ACPush SStatic (SegmentOffset 42)) |
|
|
|
|
it "Parses instruction: push static Foo.1" $ |
|
|
|
|
doParse "push static Foo.1" `shouldBe` Right (VMMemAccess ACPush (SStatic "Foo.1")) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push pointer 42" $ |
|
|
|
|
doParse "push pointer 42" `shouldBe` Right (VMMemAccess ACPush SPointer (SegmentOffset 42)) |
|
|
|
|
doParse "push pointer 42" `shouldBe` Right (VMMemAccess ACPush (SPointer 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push temp 42" $ |
|
|
|
|
doParse "push temp 42" `shouldBe` Right (VMMemAccess ACPush STemp (SegmentOffset 42)) |
|
|
|
|
doParse "push temp 42" `shouldBe` Right (VMMemAccess ACPush (STemp 42)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: push temp 11" $ |
|
|
|
|
doParse "push temp 11" `shouldBe` Right (VMMemAccess ACPush STemp (SegmentOffset 11)) |
|
|
|
|
doParse "push temp 11" `shouldBe` Right (VMMemAccess ACPush (STemp 11)) |
|
|
|
|
|
|
|
|
|
it "Parses instruction: add" $ |
|
|
|
|
doParse "add" `shouldBe` Right (VMArithmetic ACAdd) |
|
|
|
|
@ -77,3 +84,91 @@ spec = do
@@ -77,3 +84,91 @@ spec = do
|
|
|
|
|
|
|
|
|
|
where |
|
|
|
|
doParse = runParser parseInstruction "" |
|
|
|
|
|
|
|
|
|
compileInstructionSpec :: Spec |
|
|
|
|
compileInstructionSpec = do |
|
|
|
|
it "Compiles push argument 42" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SArgument 42)) |
|
|
|
|
head compiled `shouldBe` (Comment "push argument 42") |
|
|
|
|
tail compiled `shouldBe` pushValue "ARG" 42 |
|
|
|
|
|
|
|
|
|
it "Compiles push local 42" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SLocal 42)) |
|
|
|
|
head compiled `shouldBe` (Comment "push local 42") |
|
|
|
|
tail compiled `shouldBe` pushValue "LCL" 42 |
|
|
|
|
|
|
|
|
|
it "Compiles push this 41" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SThis 41)) |
|
|
|
|
head compiled `shouldBe` (Comment "push this 41") |
|
|
|
|
tail compiled `shouldBe` pushValue "THIS" 41 |
|
|
|
|
|
|
|
|
|
it "Compiles push that 41" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SThat 41)) |
|
|
|
|
head compiled `shouldBe` (Comment "push that 41") |
|
|
|
|
tail compiled `shouldBe` pushValue "THAT" 41 |
|
|
|
|
|
|
|
|
|
it "Compiles push constant 17" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SConstant 17)) |
|
|
|
|
head compiled `shouldBe` (Comment "push constant 17") |
|
|
|
|
tail compiled `shouldBe` [ |
|
|
|
|
Code (A (Imm 17)), |
|
|
|
|
Code (C [RegD] (SReg RegA) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegA] (SReg RegM) JNone), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone), |
|
|
|
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
|
|
|
|
|
|
it "Compiles push static Foo.1" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SStatic "Foo.1")) |
|
|
|
|
head compiled `shouldBe` (Comment "push static Foo.1") |
|
|
|
|
tail compiled `shouldBe` pushLabel "Foo.1" |
|
|
|
|
|
|
|
|
|
it "Compiles push pointer 0" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SPointer 0)) |
|
|
|
|
head compiled `shouldBe` (Comment "push pointer 0") |
|
|
|
|
tail compiled `shouldBe` pushLabel "THIS" |
|
|
|
|
|
|
|
|
|
it "Compiles push pointer 1" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (SPointer 1)) |
|
|
|
|
head compiled `shouldBe` (Comment "push pointer 1") |
|
|
|
|
tail compiled `shouldBe` pushLabel "THAT" |
|
|
|
|
|
|
|
|
|
it "Compiles push temp 2" $ do |
|
|
|
|
let compiled = compileInstruction (VMMemAccess ACPush (STemp 2)) |
|
|
|
|
head compiled `shouldBe` (Comment "push temp 2") |
|
|
|
|
tail compiled `shouldBe` pushRam (5 + 2) |
|
|
|
|
|
|
|
|
|
where |
|
|
|
|
pushRam offset = [ Code (A (Imm offset)), |
|
|
|
|
Code (C [RegD] (SReg RegM) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegA] (SReg RegM) JNone), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone), |
|
|
|
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
|
pushLabel label = [ Code (A (Label label)), |
|
|
|
|
Code (C [RegD] (SReg RegM) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegA] (SReg RegM) JNone), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone), |
|
|
|
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
|
|
|
|
|
|
pushValue reg value = [ Code (A (Imm value)), |
|
|
|
|
Code (C [RegD] (SReg RegA) JNone), |
|
|
|
|
Code (A (Label reg)), |
|
|
|
|
Code (C [RegA] (SReg RegM) JNone), |
|
|
|
|
Code (C [RegA] SDPlusA JNone), |
|
|
|
|
Code (C [RegD] (SReg RegM) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegA] (SReg RegM) JNone), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone), |
|
|
|
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
|
|
|
Code (A (Label "SP")), |
|
|
|
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|