You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
14 KiB
333 lines
14 KiB
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
module Test.Nand2Tetris.VM |
|
( |
|
spec |
|
) where |
|
|
|
import Test.Hspec (Spec, describe, it, shouldBe) |
|
|
|
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), |
|
BranchingCmd (..), |
|
Segment (SArgument, SConstant, SLocal, SPointer, SStatic, STemp, SThat, SThis), |
|
VMInstruction (VMArithmetic, VMBranching, VMMemAccess), |
|
compileInstruction, parseInstruction, |
|
runCompilerDef) |
|
import Text.Megaparsec (runParser) |
|
|
|
|
|
spec :: Spec |
|
spec = do |
|
describe "parseInstruction" parseInstructionSpec |
|
describe "compile push instruction" compilePushInstructionSpec |
|
describe "compile pop instruction" compilePopInstructionSpec |
|
describe "compile arithmetic instrucitons" compileArithInstructionSpec |
|
describe "compile branching instrucitons" compileBranchingInstructionSpec |
|
|
|
|
|
parseInstructionSpec :: Spec |
|
parseInstructionSpec = do |
|
it "Parses instruction: push argument 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 42)) |
|
|
|
it "Parses instruction: push local 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 42)) |
|
|
|
it "Parses instruction: push that 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 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 42)) |
|
|
|
it "Parses instruction: push temp 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 11)) |
|
|
|
it "Parses instruction: add" $ |
|
doParse "add" `shouldBe` Right (VMArithmetic ACAdd) |
|
|
|
it "Parses instruction: sub" $ |
|
doParse "sub" `shouldBe` Right (VMArithmetic ACSub) |
|
|
|
it "Parses instruction: neg" $ |
|
doParse "neg" `shouldBe` Right (VMArithmetic ACNeg) |
|
|
|
it "Parses instruction: eq" $ |
|
doParse "eq" `shouldBe` Right (VMArithmetic ACEq) |
|
|
|
it "Parses instruction: gt" $ |
|
doParse "gt" `shouldBe` Right (VMArithmetic ACGt) |
|
|
|
it "Parses instruction: lt" $ |
|
doParse "lt" `shouldBe` Right (VMArithmetic ACLt) |
|
|
|
it "Parses instruction: and" $ |
|
doParse "and" `shouldBe` Right (VMArithmetic ACAnd) |
|
|
|
it "Parses instruction: or" $ |
|
doParse "or" `shouldBe` Right (VMArithmetic ACOr) |
|
|
|
it "Parses instruction: not" $ |
|
doParse "not" `shouldBe` Right (VMArithmetic ACNot) |
|
|
|
it "Parses instruction: label foo" $ |
|
doParse "label foo" `shouldBe` Right (VMBranching (BLabel "foo")) |
|
|
|
it "Parses instruction: goto foo" $ |
|
doParse "goto foo" `shouldBe` Right (VMBranching (BGoto "foo")) |
|
|
|
it "Parses instruction: if-goto foo" $ |
|
doParse "if-goto foo" `shouldBe` Right (VMBranching (BIfGoto "foo")) |
|
where |
|
doParse = runParser parseInstruction "" |
|
|
|
compilePushInstructionSpec :: Spec |
|
compilePushInstructionSpec = do |
|
it "Compiles push argument 42" $ do |
|
let compiled = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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 = runCompilerDef $ 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) ] |
|
|
|
|
|
|
|
compilePopInstructionSpec :: Spec |
|
compilePopInstructionSpec = do |
|
it "pop argument 42" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SArgument 42)) |
|
head compiled `shouldBe` (Comment "pop argument 42") |
|
tail compiled `shouldBe` popValue "ARG" 42 |
|
|
|
it "pop local 42" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SLocal 42)) |
|
head compiled `shouldBe` (Comment "pop local 42") |
|
tail compiled `shouldBe` popValue "LCL" 42 |
|
|
|
it "pop this 42" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SThis 42)) |
|
head compiled `shouldBe` (Comment "pop this 42") |
|
tail compiled `shouldBe` popValue "THIS" 42 |
|
|
|
it "pop that 42" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SThat 42)) |
|
head compiled `shouldBe` (Comment "pop that 42") |
|
tail compiled `shouldBe` popValue "THAT" 42 |
|
|
|
it "pop static Foo.1" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SStatic "Foo.1")) |
|
head compiled `shouldBe` (Comment "pop static Foo.1") |
|
tail compiled `shouldBe` popLabelValue "Foo.1" |
|
|
|
it "pop pointer 0" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SPointer 0)) |
|
head compiled `shouldBe` (Comment "pop pointer 0") |
|
tail compiled `shouldBe` popLabelValue "THIS" |
|
|
|
it "pop pointer 1" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (SPointer 1)) |
|
head compiled `shouldBe` (Comment "pop pointer 1") |
|
tail compiled `shouldBe` popLabelValue "THAT" |
|
|
|
it "pop temp 1" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMMemAccess ACPop (STemp 1)) |
|
head compiled `shouldBe` (Comment "pop temp 1") |
|
tail compiled `shouldBe` popRam (5 + 1) |
|
|
|
|
|
where |
|
popValue reg value = [ |
|
Code (A (Label "SP")), |
|
Code (C [RegM] (SRegMinus1 RegM) JNone), |
|
|
|
Code (A (Imm value)), |
|
Code (C [RegD] (SReg RegA) JNone), |
|
Code (A (Label reg)), |
|
Code (C [RegD] SDPlusM JNone), |
|
Code (A (Imm 15)), |
|
Code (C [RegM] (SReg RegD) JNone), |
|
|
|
Code (A (Label "SP")), |
|
Code (C [RegA] (SReg RegM) JNone), |
|
Code (C [RegD] (SReg RegM) JNone), |
|
Code (A (Imm 15)), |
|
Code (C [RegA] (SReg RegM) JNone), |
|
Code (C [RegM] (SReg RegD) JNone) |
|
] |
|
|
|
popLabelValue label = [ |
|
Code (A (Label "SP")), |
|
Code (C [RegM] (SRegMinus1 RegM) JNone), |
|
Code (C [RegA] (SReg RegM) JNone), |
|
Code (C [RegD] (SReg RegM) JNone), |
|
Code (A . Label $ label), |
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
popRam offset = [ |
|
Code (A (Label "SP")), |
|
Code (C [RegM] (SRegMinus1 RegM) JNone), |
|
Code (C [RegA] (SReg RegM) JNone), |
|
Code (C [RegD] (SReg RegM) JNone), |
|
Code (A . Imm $ offset), |
|
Code (C [RegM] (SReg RegD) JNone) ] |
|
|
|
|
|
compileArithInstructionSpec :: Spec |
|
compileArithInstructionSpec = do |
|
it "compiles add" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMArithmetic ACAdd) |
|
head compiled `shouldBe` (Comment "add") |
|
tail compiled `shouldBe` |
|
[ |
|
Code (A (Label "SP")), |
|
Code (C [RegA] (SRegMinus1 RegM) JNone), |
|
Code (C [RegD] (SReg RegM) JNone), |
|
Code (C [RegA] (SRegMinus1 RegA) JNone), |
|
Code (C [RegM] SDPlusM JNone), |
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
Code (A (Label "SP")), |
|
Code (C [RegM] (SReg RegD) JNone) |
|
] |
|
|
|
it "compiles sub" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMArithmetic ACSub) |
|
head compiled `shouldBe` (Comment "sub") |
|
tail compiled `shouldBe` |
|
[ |
|
Code (A (Label "SP")), |
|
Code (C [RegA] (SRegMinus1 RegM) JNone), |
|
Code (C [RegD] (SReg RegM) JNone), |
|
Code (C [RegA] (SRegMinus1 RegA) JNone), |
|
Code (C [RegM] SMMinusD JNone), |
|
Code (C [RegD] (SRegPlus1 RegA) JNone), |
|
Code (A (Label "SP")), |
|
Code (C [RegM] (SReg RegD) JNone) |
|
] |
|
|
|
it "compiles neg" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMArithmetic ACNeg) |
|
head compiled `shouldBe` (Comment "neg") |
|
tail compiled `shouldBe` |
|
[ |
|
Code (A (Label "SP")), |
|
Code (C [RegA] (SRegMinus1 RegM) JNone), |
|
Code (C [RegM] (SNeg RegM) JNone) |
|
] |
|
|
|
compileBranchingInstructionSpec :: Spec |
|
compileBranchingInstructionSpec = do |
|
it "label foo" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMBranching (BLabel "foo")) |
|
head compiled `shouldBe` (Comment "label foo") |
|
tail compiled `shouldBe` [(ALabel "foo")] |
|
|
|
it "goto foo" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMBranching (BGoto "foo")) |
|
head compiled `shouldBe` (Comment "goto foo") |
|
tail compiled `shouldBe` [ Code (A (Label "foo")), |
|
Code (C [] S0 Jmp) |
|
] |
|
|
|
it "if-goto foo" $ do |
|
let compiled = runCompilerDef $ compileInstruction (VMBranching (BIfGoto "foo")) |
|
head compiled `shouldBe` (Comment "if-goto foo") |
|
tail compiled `shouldBe` [ Code (A (Label "SP")), |
|
Code (C [RegA, RegM] (SRegMinus1 RegM) JNone), |
|
Code (C [] (SReg RegM) JNe) |
|
] |
|
|
|
|