summaryrefslogtreecommitdiff
path: root/VMTranslator.hs
blob: 9337ab24e6aa546269807f79d0272d667673a957 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
--VMTranslator translating vm files to hack assembly language
--Coursework for Nand2Tetris (http://nand2tetris.org/)
--Author: Yuchen Pei (me@ypei.me)
--usage: ./VMTranslator path/to/dir/
--will produce path/to/dir/dir.asm from path/to/dir/*.vm
import Data.Char (toUpper)
import Data.List.Split (splitOn)
import System.Environment (getArgs)
import Data.Maybe (fromJust)
import Data.List (elemIndex)
import System.Directory (listDirectory)

--preamble = "@256\nD=A\n@SP\nM=D\n"
--For ProgramFlow and SimpleFunction preamble should be set as ""
--preamble = ""
--For StaticsTest and FibonacciElement, preamble should be setSP ++ callInit
preamble = setSP ++ callInit
  where setSP = "@256\nD=A\n@SP\nM=D\n"
        callInit = parse' "call" ["Sys.init", "0"] 0 "" ""

--epilogue = "(END)\n@END\n0;JMP"
epilogue = ""

-- parse' command restOfCommand vmLineNumber fileName functionName = asm code
parse' :: [Char] -> [[Char]] -> Int -> [Char] -> [Char] -> [Char]

parse' "return" [] _ _ _ = backupLCL ++ backupRet ++ popToARG ++ moveSP ++ restore "THAT" 
                           ++ restore "THIS" ++ restore "ARG" ++ restore "LCL"
                           ++ gotoRet
  where backupLCL = "@LCL\nD=M\n@R13\nM=D\n"
        backupRet = "@5\nA=D-A\nD=M\n@R14\nM=D\n"
        popToARG = pop "M" ++ "@ARG\nA=M\nM=D\n"
        moveSP = "@ARG\nD=M+1\n@SP\nM=D\n"
        restore name = "@R13\nAM=M-1\nD=M\n@" ++ name ++ "\nM=D\n"
        gotoRet = "@R14\nA=M\n0;JMP\n"

parse' op [] n fileName _
  | op `elem` ["add", "sub", "and", "or"] = pop "M" ++ pop ('M':(f op):"D") ++ push "D"
  | op `elem` ["eq", "gt", "lt"] = pop "M" ++ pop "M-D" ++ ifThenElse op n fileName
  | otherwise = pop "M" ++ push (f op:"D")
    where f "add" = '+'; f "sub" = '-'; f "and" = '&'; 
          f "or" = '|'; f "neg" = '-'; f "not" = '!'

parse' "push" ["constant", x] _ _ _ = "@" ++ x ++ "\nD=A\n" ++ push "D"

parse' cmd [seg, x] _ _ _ | seg `elem` ["local", "argument", "this", "that"] =
  case cmd of 
    "push" -> getAddr seg x ++ "A=D\nD=M\n" ++ push "D"
    "pop" -> getAddr seg x ++ "@R13\nM=D\n" ++ pop "M" ++ "@R13\nA=M\nM=D\n"

parse' cmd [seg, x] _ _ _ | seg `elem` ["pointer", "temp"] = 
  case cmd of 
    "push" -> getAddr' seg x ++ "D=M\n" ++ push "D"
    "pop" -> pop "M" ++ getAddr' seg x ++ "M=D\n"

parse' cmd ["static", x] _ filename _ =
  case cmd of
    "push" -> getAddr'' x filename ++ "D=M\n" ++ push "D"
    "pop" -> pop "M" ++ getAddr'' x filename ++ "M=D\n"

parse' "label" [x] _ _ fName = "(" ++ fName ++ "$" ++ x ++ ")\n"

parse' "goto" [x] _ _ fName = "@" ++ fName ++ "$" ++ x ++ "\n0;JMP\n" 

parse' "if-goto" [x] _ _ fName = pop "M" ++ "@" ++ fName ++ "$" ++ x ++ "\nD;JNE\n"

parse' "function" [f, n] _ _ _ = "(" ++ f ++ ")\n" ++ (mconcat $ replicate (read n) $ push "0")

parse' "call" [f, m] n _ _ = pushRet ++ save "LCL" ++ save "ARG" ++ save "THIS" 
                             ++ save "THAT" ++ setLCL ++ setARG ++ gotoF ++ placeRet
  where pushRet = "@RET" ++ show n ++ "\nD=A\n" ++ push "D"
        save x = "@" ++ x ++ "\nD=M\n" ++ push "D"
        setLCL = "@SP\nD=M\n@LCL\nM=D\n"
        setARG = "@5\nD=D-A\n@" ++ m ++ "\nD=D-A\n@ARG\nM=D\n"
        placeRet = "(RET" ++ show n ++ ")\n"
        gotoF = "@" ++ f ++ "\n0;JMP\n" 
--To do the return-addr: generate label RET582 if say n==582, place the label after the goto f jump

getAddr seg x = "@" ++ seg2Lab seg ++ "\nD=M\n@" ++ x ++ "\nD=A+D\n"

getAddr' seg x = "@" ++ show (read x + (if seg == "pointer" then 3 else 5)) ++ "\n"

getAddr'' x filename = "@" ++ filename ++ "." ++ x ++ "\n"

seg2Lab seg = case seg of
  "local" -> "LCL"
  "argument" -> "ARG"
  "this" -> "THIS" 
  "that" -> "THAT"

push :: [Char] -> [Char]
push xs = "@SP\nA=M\nM=" ++ xs ++ "\n@SP\nM=M+1\n"

-- should change @label to @IFlabel
ifThenElse :: [Char] -> Int -> [Char] -> [Char]
ifThenElse cond n fileName = "@IF" ++ label ++ "\nD;J" ++ cond' ++ "\n" ++ push "0"
                             ++ "@ENDIF" ++ label ++ "\n0;JMP\n(IF" ++ label ++ ")\n" 
                             ++ push "-1" ++ "(ENDIF" ++ label ++ ")\n"
  where label = fileName ++ show n
        cond' = toUpper <$> cond

pop :: [Char] -> [Char]
pop xs = "@SP\nAM=M-1\nD=" ++ xs ++ "\n"

parseline :: [[Char]] -> Int -> [Char] -> [Char] -> [Char] -> [Char]
parseline [] _ acc _ _ = acc
parseline (line:lines) n acc filename funName = parseline lines (n + 1) (acc ++ parse' cmd target n filename funName') filename funName'
  where cmd:target = words line
        funName' = if cmd == "function" then head target else funName

stripJunk :: [Char] -> [[Char]]
stripJunk = filter (not . isEmptyLine) . fmap (head . splitOn "//") . lines . replCrWithNl

isEmptyLine :: [Char] -> Bool
isEmptyLine = null . filter (not . flip elem " \t") 

parseCode :: [Char] -> [Char] -> [Char]
parseCode xs filename = (parseline (stripJunk xs) 0 "" filename "")

parseCodes :: [[Char]] -> [[Char]] -> [Char]
parseCodes codes filenames = preamble ++ (mconcat $ zipWith parseCode codes filenames) ++ epilogue

replCrWithNl :: [Char] -> [Char]
replCrWithNl = fmap cr2nl 
  where cr2nl '\r' = '\n'
        cr2nl c = c

lastSplit c xs = (take (prefix - 1) xs, drop prefix xs)
  where prefix = length xs - (fromJust . elemIndex c . reverse) xs


-- assuming the input is a dir with a trailing '/'
main = do
  dir <- head <$> getArgs
  filesWODir <- filter isVMfile <$> listDirectory dir 
  let vmFiles = (dir++) <$> filesWODir
  let ofPath = dir ++ (snd $ lastSplit '/' $ init dir) ++ ".asm"
  let filenames = removeExt <$> filesWODir
  codes <- sequence $ readFile <$> vmFiles
  writeFile ofPath $ parseCodes codes filenames
    where isVMfile xs = drop (length xs - 3) xs == ".vm"
          removeExt xs = take (length xs - 3) xs