finish project eight backend compiler
- modify existing implementation to use variant types for a more accurate representation of the vm byte code - switch to fold from recursion for the main translate function - use separate modules for translating different vm commands - move static arithmetic command translations to a map
This commit is contained in:
parent
5973c64e78
commit
b7dee58454
@ -1,2 +1,2 @@
|
||||
profile = janestreet
|
||||
version = 0.24.1
|
||||
version = 0.26.2
|
61
compiler/backend/bin/compiler.ml
Normal file
61
compiler/backend/bin/compiler.ml
Normal file
@ -0,0 +1,61 @@
|
||||
open Core
|
||||
open Backend
|
||||
|
||||
let read_file file =
|
||||
let lines = In_channel.read_lines file in
|
||||
let not_empty str = not (String.is_empty str) in
|
||||
List.filter lines ~f:not_empty
|
||||
|
||||
|
||||
let is_vm_file f =
|
||||
match Filename.split_extension f with
|
||||
| _, Some ext -> String.equal ext "vm"
|
||||
| _, None -> false
|
||||
|
||||
|
||||
let generate_hack f =
|
||||
let bytecode = read_file f in
|
||||
let class_name = Filename.basename f in
|
||||
Translate.translate class_name bytecode
|
||||
|
||||
|
||||
let write_hack path hack =
|
||||
let output_channel = Out_channel.create path in
|
||||
Out_channel.output_lines output_channel hack;
|
||||
Out_channel.close output_channel
|
||||
|
||||
|
||||
let compile path =
|
||||
let ext = ".asm" in
|
||||
let absolute_path x = path ^ x in
|
||||
let open Filename_base in
|
||||
if Sys_unix.is_directory_exn path then
|
||||
let output_file = concat path (basename path ^ ext) in
|
||||
let hack =
|
||||
path
|
||||
|> Sys_unix.ls_dir
|
||||
|> List.map ~f:absolute_path
|
||||
|> List.filter ~f:is_vm_file
|
||||
|> List.concat_map ~f:generate_hack
|
||||
|> List.append Translate.bootstrap
|
||||
in
|
||||
write_hack output_file hack
|
||||
else if Sys_unix.is_file_exn path && is_vm_file path then
|
||||
let output_file = chop_extension path ^ ext in
|
||||
let hack = generate_hack path in
|
||||
write_hack output_file hack
|
||||
else
|
||||
failwith "Incompatible!"
|
||||
|
||||
|
||||
let command =
|
||||
let summary = "Translate Jack byte code to Hack assembly" in
|
||||
let readme () = "Compiler (backend) for project 7/8 of Nand2Tetris" in
|
||||
let param_spec = Command.Param.(anon ("path" %: string)) in
|
||||
let param_handler path () = compile path in
|
||||
Command.basic
|
||||
~summary:summary ~readme:readme
|
||||
(Command.Param.map param_spec ~f:param_handler)
|
||||
|
||||
|
||||
let () = Command_unix.run command
|
1
compiler/backend/bin/compiler.mli
Normal file
1
compiler/backend/bin/compiler.mli
Normal file
@ -0,0 +1 @@
|
||||
val command : Command.t
|
@ -1,3 +1,5 @@
|
||||
(executable
|
||||
(modes byte exe)
|
||||
(name compiler)
|
||||
(public_name compiler)
|
||||
(libraries backend core core_unix.command_unix))
|
0
compiler/backend/compiler.opam
Normal file
0
compiler/backend/compiler.opam
Normal file
2
compiler/backend/dune-project
Normal file
2
compiler/backend/dune-project
Normal file
@ -0,0 +1,2 @@
|
||||
(lang dune 3.13)
|
||||
(using menhir 3.0)
|
78
compiler/backend/lib/arithmetic.ml
Normal file
78
compiler/backend/lib/arithmetic.ml
Normal file
@ -0,0 +1,78 @@
|
||||
open! Ast
|
||||
open! Base
|
||||
|
||||
let counter = ref 0
|
||||
|
||||
let count =
|
||||
fun () ->
|
||||
counter := (!counter) + 1;
|
||||
!counter
|
||||
|
||||
|
||||
let arithmetic =
|
||||
Map.of_alist_exn (module String)
|
||||
[ "add", [ "@SP"; "AM=M-1"; "D=M"; "A=A-1"; "M=D+M" ]
|
||||
; "sub", [ "@SP"; "AM=M-1"; "D=M"; "A=A-1"; "M=M-D" ]
|
||||
; "and", [ "@SP"; "AM=M-1"; "D=M"; "A=A-1"; "M=D&M" ]
|
||||
; "or" , [ "@SP"; "AM=M-1"; "D=M"; "A=A-1"; "M=D|M" ]
|
||||
; "neg", [ "@SP"; "A=M-1"; "M=-M" ]
|
||||
; "not", [ "@SP"; "A=M-1"; "M=!M" ]
|
||||
]
|
||||
|
||||
let eq count =
|
||||
[ "@SP"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "A=A-1"
|
||||
; "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_EQ_" ^ count
|
||||
; "D;JEQ"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_EQ_" ^ count ^ ")"
|
||||
]
|
||||
|
||||
let gt count =
|
||||
[ "@SP"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "A=A-1"
|
||||
; "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_GT_" ^ count
|
||||
; "D;JGT"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_GT_" ^ count ^ ")"
|
||||
]
|
||||
|
||||
let lt count =
|
||||
[ "@SP"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "A=A-1"
|
||||
; "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_LT_" ^ count
|
||||
; "D;JLT"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_LT_" ^ count ^ ")"
|
||||
]
|
||||
|
||||
let translate command =
|
||||
let i = Int.to_string (count ()) in
|
||||
match command with
|
||||
| Add -> Map.find_exn arithmetic "add"
|
||||
| Sub -> Map.find_exn arithmetic "sub"
|
||||
| Neg -> Map.find_exn arithmetic "neg"
|
||||
| And -> Map.find_exn arithmetic "and"
|
||||
| Not -> Map.find_exn arithmetic "not"
|
||||
| Or -> Map.find_exn arithmetic "or"
|
||||
| Eq -> eq i
|
||||
| Gt -> gt i
|
||||
| Lt -> lt i
|
1
compiler/backend/lib/arithmetic.mli
Normal file
1
compiler/backend/lib/arithmetic.mli
Normal file
@ -0,0 +1 @@
|
||||
val translate : Ast.arithmetic_command -> string list
|
41
compiler/backend/lib/ast.ml
Normal file
41
compiler/backend/lib/ast.ml
Normal file
@ -0,0 +1,41 @@
|
||||
type arithmetic_command =
|
||||
| Add
|
||||
| Sub
|
||||
| Neg
|
||||
| Eq
|
||||
| Gt
|
||||
| Lt
|
||||
| And
|
||||
| Or
|
||||
| Not
|
||||
|
||||
type memory_segment =
|
||||
| Argument
|
||||
| Local
|
||||
| Static
|
||||
| Constant
|
||||
| This
|
||||
| That
|
||||
| Pointer
|
||||
| Temp
|
||||
|
||||
type memory_command =
|
||||
| Push
|
||||
| Pop
|
||||
|
||||
type branching_command =
|
||||
| Label
|
||||
| Goto
|
||||
| Ifgoto
|
||||
|
||||
type function_command =
|
||||
| Function
|
||||
| Call
|
||||
|
||||
type expr =
|
||||
| Comment of string
|
||||
| ArithmeticCommand of arithmetic_command
|
||||
| MemoryAccessCommand of memory_command * memory_segment * int
|
||||
| ProgramFlowCommand of branching_command * string
|
||||
| FunctionCallCommand of function_command * string * int
|
||||
| Return
|
8
compiler/backend/lib/dune
Normal file
8
compiler/backend/lib/dune
Normal file
@ -0,0 +1,8 @@
|
||||
(ocamllex lexer)
|
||||
|
||||
(menhir
|
||||
(modules parser))
|
||||
|
||||
(library
|
||||
(name backend)
|
||||
(libraries core))
|
95
compiler/backend/lib/functioncall.ml
Normal file
95
compiler/backend/lib/functioncall.ml
Normal file
@ -0,0 +1,95 @@
|
||||
open! Ast
|
||||
open! Base
|
||||
|
||||
let counter = ref 0
|
||||
|
||||
let count =
|
||||
fun () ->
|
||||
counter := (!counter) + 1;
|
||||
!counter
|
||||
|
||||
let addr a = "@" ^ a
|
||||
|
||||
let label name = "(" ^ name ^ ")"
|
||||
|
||||
let function_definition name arg =
|
||||
let push_inner n = [
|
||||
addr n
|
||||
; "D=A"
|
||||
; "@LCL"
|
||||
; "A=D+M"
|
||||
; "M=0"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
] in
|
||||
let locals = arg
|
||||
|> List.init ~f:Int.to_string
|
||||
|> List.concat_map ~f:push_inner in
|
||||
List.append [ label name ] locals
|
||||
|
||||
let function_call name arg =
|
||||
let i = Int.to_string (count ()) in
|
||||
let arg = Int.to_string arg in
|
||||
let ret = name ^ "_RETURN_" ^ i in
|
||||
[ addr ret
|
||||
; "D=A"
|
||||
; "@SP"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
; "// push LCL "
|
||||
; "@LCL"
|
||||
; "D=M"
|
||||
; "@SP"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
; "// push ARG "
|
||||
; "@ARG"
|
||||
; "D=M"
|
||||
; "@SP"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
; "// push THIS "
|
||||
; "@THIS"
|
||||
; "D=M"
|
||||
; "@SP"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
; "// push THAT "
|
||||
; "@THAT"
|
||||
; "D=M"
|
||||
; "@SP"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "@SP"
|
||||
; "M=M+1"
|
||||
; "// reposition ARG"
|
||||
; "D=M"
|
||||
; addr arg
|
||||
; "D=D-A"
|
||||
; "@5"
|
||||
; "D=D-A"
|
||||
; "@ARG"
|
||||
; "M=D"
|
||||
; "// reposition LCL"
|
||||
; "@SP"
|
||||
; "D=M"
|
||||
; "@LCL"
|
||||
; "M=D"
|
||||
; "// goto function"
|
||||
; addr name
|
||||
; "0;JMP"
|
||||
; label ret ]
|
||||
|
||||
let translate command name arg =
|
||||
match command with
|
||||
| Function -> function_definition name arg
|
||||
| Call -> function_call name arg
|
||||
|
1
compiler/backend/lib/functioncall.mli
Normal file
1
compiler/backend/lib/functioncall.mli
Normal file
@ -0,0 +1 @@
|
||||
val translate : Ast.function_command -> string -> int -> string list
|
49
compiler/backend/lib/lexer.mll
Normal file
49
compiler/backend/lib/lexer.mll
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
open Parser
|
||||
}
|
||||
|
||||
let period = '.'
|
||||
let colon = ':'
|
||||
let underscore = '_'
|
||||
let numbers = ['0'-'9']+
|
||||
let alphabets = ['a'-'z' 'A'-'Z']+
|
||||
let white = [' ' '\t']+
|
||||
let comment = "//" _*
|
||||
|
||||
|
||||
let address = (numbers)+
|
||||
let id = (alphabets | numbers | colon | period | underscore)+
|
||||
|
||||
rule read =
|
||||
parse
|
||||
| white { read lexbuf }
|
||||
| comment as c { COMMENT (c) }
|
||||
| "add" { ADD }
|
||||
| "sub" { SUB }
|
||||
| "neg" { NEG }
|
||||
| "eq" { EQ }
|
||||
| "gt" { GT }
|
||||
| "lt" { LT }
|
||||
| "and" { AND }
|
||||
| "or" { OR }
|
||||
| "not" { NOT }
|
||||
| "pop" { POP }
|
||||
| "push" { PUSH }
|
||||
| "argument" { ARGUMENT }
|
||||
| "local" { LOCAL }
|
||||
| "static" { STATIC }
|
||||
| "this" { THIS }
|
||||
| "that" { THAT }
|
||||
| "pointer" { POINTER }
|
||||
| "temp" { TEMP }
|
||||
| "constant" { CONSTANT }
|
||||
| "label" { LABEL }
|
||||
| "goto" { GOTO }
|
||||
| "if-goto" { IFGOTO }
|
||||
| "function" { FUNCTION }
|
||||
| "call" { CALL }
|
||||
| "return" { RETURN }
|
||||
| address as a { ADDRESS (int_of_string a) }
|
||||
| id as i { ID (i) }
|
||||
| eof { EOF }
|
||||
| _ as c { failwith (Printf.sprintf "unexpected character: %C" c) }
|
68
compiler/backend/lib/memoryaccess.ml
Normal file
68
compiler/backend/lib/memoryaccess.ml
Normal file
@ -0,0 +1,68 @@
|
||||
open! Ast
|
||||
open! Base
|
||||
|
||||
let addr a = "@" ^ a
|
||||
|
||||
let push = [ "@SP"; "A=M"; "M=D"; "@SP"; "M=M+1" ]
|
||||
let pop = [ "@R13"; "M=D"; "@SP"; "AM=M-1"; "D=M"; "@R13"; "A=M"; "M=D" ]
|
||||
|
||||
let common = function
|
||||
| Local -> "@LCL"
|
||||
| This -> "@THIS"
|
||||
| That -> "@THAT"
|
||||
| Argument -> "@ARG"
|
||||
| _ -> failwith "Invalid Segment"
|
||||
|
||||
let pointer = function
|
||||
| "0" -> "@THIS"
|
||||
| "1" -> "@THAT"
|
||||
| _ -> failwith "Invalid Pointer"
|
||||
|
||||
let push_constant a = [ addr a; "D=A" ] @ push
|
||||
|
||||
let push_pointer a = [ pointer a; "D=M" ] @ push
|
||||
|
||||
let push_static offset class_name=
|
||||
[ addr (class_name ^ ":" ^ offset); "D=M" ] @ push
|
||||
|
||||
let push_temp a = [ addr a; "D=A"; "@5"; "A=D+A"; "D=M" ] @ push
|
||||
|
||||
let pop_pointer a =
|
||||
[ "@SP"; "AM=M-1"; "D=M"; pointer a; "M=D" ]
|
||||
|
||||
let pop_static offset class_name =
|
||||
[ "@SP"; "AM=M-1"; "D=M"; addr (class_name ^ ":" ^ offset); "M=D" ]
|
||||
|
||||
let push_common segment offset =
|
||||
[ addr offset ; "D=A" ; common segment ; "A=D+M" ; "D=M" ] @ push
|
||||
|
||||
let pop_temp a =
|
||||
[ addr a; "D=A"; "@5" ; "D=D+A" ] @ pop
|
||||
|
||||
let pop_common segment offset = [
|
||||
addr offset
|
||||
; "D=A"
|
||||
; common segment
|
||||
; "D=D+M" ] @ pop
|
||||
|
||||
let process_push segment offset class_name =
|
||||
match segment with
|
||||
| Local | Argument | This | That -> push_common segment offset
|
||||
| Constant -> push_constant offset
|
||||
| Temp -> push_temp offset
|
||||
| Static -> push_static class_name offset
|
||||
| Pointer -> push_pointer offset
|
||||
|
||||
let process_pop segment offset class_name =
|
||||
match segment with
|
||||
| Local | Argument | This | That -> pop_common segment offset
|
||||
| Temp -> pop_temp offset
|
||||
| Static -> pop_static class_name offset
|
||||
| Pointer -> pop_pointer offset
|
||||
| Constant -> failwith "Invalid Segment"
|
||||
|
||||
let translate command segment offset class_name =
|
||||
let offset = Int.to_string offset in
|
||||
match command with
|
||||
| Pop -> process_pop segment offset class_name
|
||||
| Push -> process_push segment offset class_name
|
3
compiler/backend/lib/memoryaccess.mli
Normal file
3
compiler/backend/lib/memoryaccess.mli
Normal file
@ -0,0 +1,3 @@
|
||||
val translate :
|
||||
Ast.memory_command ->
|
||||
Ast.memory_segment -> int -> string -> string list
|
93
compiler/backend/lib/parser.mly
Normal file
93
compiler/backend/lib/parser.mly
Normal file
@ -0,0 +1,93 @@
|
||||
%{
|
||||
open Ast
|
||||
%}
|
||||
|
||||
|
||||
(* arithmetic and logical commands *)
|
||||
%token ADD SUB EQ GT LT AND OR NEG NOT
|
||||
|
||||
(* memory commands *)
|
||||
%token POP PUSH
|
||||
|
||||
(* memory segments *)
|
||||
%token CONSTANT STATIC POINTER TEMP
|
||||
ARGUMENT LOCAL THIS THAT
|
||||
|
||||
(* branching commands *)
|
||||
%token LABEL GOTO IFGOTO
|
||||
|
||||
(* function commands*)
|
||||
%token FUNCTION CALL RETURN
|
||||
|
||||
%token <int> ADDRESS
|
||||
%token <string> ID
|
||||
%token <string> COMMENT
|
||||
%token EOF
|
||||
|
||||
%start <Ast.expr> prog
|
||||
|
||||
%%
|
||||
|
||||
|
||||
prog:
|
||||
| e = expr; EOF { e }
|
||||
;
|
||||
|
||||
expr:
|
||||
| c = COMMENT { Comment c }
|
||||
| e = arithmetic { e }
|
||||
| POP; p = pop { p }
|
||||
| PUSH; p = push { p }
|
||||
| b = branching { b }
|
||||
| FUNCTION; f = function_definition { f }
|
||||
| CALL; c = function_call { c }
|
||||
| RETURN { Return }
|
||||
| e = expr; COMMENT { e }
|
||||
;
|
||||
|
||||
arithmetic:
|
||||
| ADD { ArithmeticCommand Add }
|
||||
| SUB { ArithmeticCommand Sub }
|
||||
| EQ { ArithmeticCommand Eq }
|
||||
| GT { ArithmeticCommand Gt }
|
||||
| LT { ArithmeticCommand Lt }
|
||||
| AND { ArithmeticCommand And }
|
||||
| OR { ArithmeticCommand Or }
|
||||
| NEG { ArithmeticCommand Neg }
|
||||
| NOT { ArithmeticCommand Not }
|
||||
;
|
||||
|
||||
pop:
|
||||
| ARGUMENT; a = ADDRESS { MemoryAccessCommand (Pop, Argument, a) }
|
||||
| LOCAL; a = ADDRESS { MemoryAccessCommand (Pop, Local, a) }
|
||||
| STATIC; a = ADDRESS { MemoryAccessCommand (Pop, Static, a) }
|
||||
| THIS; a = ADDRESS { MemoryAccessCommand (Pop, This, a) }
|
||||
| THAT; a = ADDRESS { MemoryAccessCommand (Pop, That, a) }
|
||||
| POINTER; a = ADDRESS { MemoryAccessCommand (Pop, Pointer, a) }
|
||||
| TEMP; a = ADDRESS { MemoryAccessCommand (Pop, Temp, a) }
|
||||
;
|
||||
|
||||
push:
|
||||
| ARGUMENT; a = ADDRESS { MemoryAccessCommand (Push, Argument, a) }
|
||||
| LOCAL; a = ADDRESS { MemoryAccessCommand (Push, Local, a) }
|
||||
| STATIC; a = ADDRESS { MemoryAccessCommand (Push, Static, a) }
|
||||
| THIS; a = ADDRESS { MemoryAccessCommand (Push, This, a) }
|
||||
| THAT; a = ADDRESS { MemoryAccessCommand (Push, That, a) }
|
||||
| POINTER; a = ADDRESS { MemoryAccessCommand (Push, Pointer, a) }
|
||||
| TEMP; a = ADDRESS { MemoryAccessCommand (Push, Temp, a) }
|
||||
| CONSTANT; a = ADDRESS { MemoryAccessCommand (Push, Constant, a) }
|
||||
;
|
||||
|
||||
branching:
|
||||
| LABEL; i = ID { ProgramFlowCommand (Label, i) }
|
||||
| GOTO; i = ID { ProgramFlowCommand (Goto, i) }
|
||||
| IFGOTO; i = ID { ProgramFlowCommand (Ifgoto, i) }
|
||||
;
|
||||
|
||||
function_definition:
|
||||
| i = ID; a = ADDRESS { FunctionCallCommand (Function, i, a) }
|
||||
;
|
||||
|
||||
function_call:
|
||||
| i = ID; a = ADDRESS { FunctionCallCommand (Call, i, a) }
|
||||
;
|
12
compiler/backend/lib/programflow.ml
Normal file
12
compiler/backend/lib/programflow.ml
Normal file
@ -0,0 +1,12 @@
|
||||
open! Ast
|
||||
open! Base
|
||||
|
||||
let addr a = "@" ^ a
|
||||
|
||||
let label name = "(" ^ name ^ ")"
|
||||
|
||||
let translate command name =
|
||||
match command with
|
||||
| Label -> [ label name ]
|
||||
| Goto -> [ addr name; "0;JMP" ]
|
||||
| Ifgoto -> [ "@SP"; "AM=M-1"; "D=M"; addr name; "D;JNE" ]
|
1
compiler/backend/lib/programflow.mli
Normal file
1
compiler/backend/lib/programflow.mli
Normal file
@ -0,0 +1 @@
|
||||
val translate : Ast.branching_command -> string -> string list
|
47
compiler/backend/lib/return.ml
Normal file
47
compiler/backend/lib/return.ml
Normal file
@ -0,0 +1,47 @@
|
||||
let translate =
|
||||
[ "// Save LCL to R14, Return Address to R15 "
|
||||
; "@LCL"
|
||||
; "D=M"
|
||||
; "@R14"
|
||||
; "M=D"
|
||||
; "@R5"
|
||||
; "A=D-A"
|
||||
; "D=M"
|
||||
; "@R15"
|
||||
; "M=D"
|
||||
; "// POP return value to ARG0, SP=ARG+1 "
|
||||
; "@SP"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "@ARG"
|
||||
; "A=M"
|
||||
; "M=D"
|
||||
; "D=A"
|
||||
; "@SP"
|
||||
; "M=D+1"
|
||||
; "// reposition THAT, THIS, ARG, LCL "
|
||||
; "@R14"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "@THAT"
|
||||
; "M=D"
|
||||
; "@R14"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "@THIS"
|
||||
; "M=D"
|
||||
; "@R14"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "@ARG"
|
||||
; "M=D"
|
||||
; "@R14"
|
||||
; "AM=M-1"
|
||||
; "D=M"
|
||||
; "@LCL"
|
||||
; "M=D"
|
||||
; "// Jump to Return Address "
|
||||
; "@R15"
|
||||
; "A=M"
|
||||
; "0;JMP"
|
||||
]
|
1
compiler/backend/lib/return.mli
Normal file
1
compiler/backend/lib/return.mli
Normal file
@ -0,0 +1 @@
|
||||
val translate : string list
|
27
compiler/backend/lib/translate.ml
Normal file
27
compiler/backend/lib/translate.ml
Normal file
@ -0,0 +1,27 @@
|
||||
open! Ast
|
||||
open! Base
|
||||
|
||||
let parse str =
|
||||
let lexbuf = Lexing.from_string str in
|
||||
Parser.prog Lexer.read lexbuf
|
||||
|
||||
let bootstrap =
|
||||
[ "@256"; "D=A"; "@SP"; "M=D" ]
|
||||
@ Functioncall.translate Call "Sys.init" 0
|
||||
|
||||
let translate class_name lines : string list =
|
||||
lines
|
||||
|> List.map ~f:parse
|
||||
|> List.bind ~f:(function
|
||||
| ArithmeticCommand a
|
||||
-> Arithmetic.translate a
|
||||
| MemoryAccessCommand (command, segment, offset)
|
||||
-> Memoryaccess.translate command segment offset class_name
|
||||
| ProgramFlowCommand (command, name)
|
||||
-> Programflow.translate command name
|
||||
| FunctionCallCommand (command, name, arg)
|
||||
-> Functioncall.translate command name arg
|
||||
| Return
|
||||
-> Return.translate
|
||||
| Comment _ -> []
|
||||
)
|
10
compiler/backend/lib/translate.mli
Normal file
10
compiler/backend/lib/translate.mli
Normal file
@ -0,0 +1,10 @@
|
||||
open Ast
|
||||
|
||||
(* Parse a string to the Ast.expr type. *)
|
||||
val parse : string -> expr
|
||||
|
||||
(* Set the Stack Pointer to 256 and call Sys.init function. *)
|
||||
val bootstrap : string list
|
||||
|
||||
(* Translate from parsed byte code [Ast.expr] to assembly *)
|
||||
val translate : string -> string list -> string list
|
31
compiler/backend/test/test_backend.ml
Normal file
31
compiler/backend/test/test_backend.ml
Normal file
@ -0,0 +1,31 @@
|
||||
open OUnit2
|
||||
open Backend.Ast
|
||||
open Backend.Translate
|
||||
|
||||
let peq case value = assert_equal value (parse case)
|
||||
|
||||
let test_parse_a_command _ =
|
||||
peq "add" (ArithmeticCommand Add);
|
||||
peq "sub" (ArithmeticCommand Sub);
|
||||
peq "neg" (ArithmeticCommand Neg);
|
||||
peq "eq" (ArithmeticCommand Eq);
|
||||
peq "gt" (ArithmeticCommand Gt);
|
||||
peq "lt" (ArithmeticCommand Lt);
|
||||
peq "and" (ArithmeticCommand And);
|
||||
peq "or" (ArithmeticCommand Or);
|
||||
peq "not" (ArithmeticCommand Not)
|
||||
;;
|
||||
|
||||
let test_parse_m_command _ =
|
||||
peq "pop local 12" (MemoryAccessCommand (Pop, Local, 12));
|
||||
peq "push argument 4" (MemoryAccessCommand (Push, Argument, 4))
|
||||
;;
|
||||
|
||||
let suite =
|
||||
"suite"
|
||||
>::: [ "A Instruction" >:: test_parse_a_command
|
||||
; "M Instruction" >:: test_parse_m_command
|
||||
]
|
||||
;;
|
||||
|
||||
let () = run_test_tt_main suite
|
@ -1,32 +0,0 @@
|
||||
open Core
|
||||
open Backend
|
||||
|
||||
let read_file file =
|
||||
let not_empty str = not (String.is_empty str) in
|
||||
List.filter (In_channel.read_lines file) ~f:not_empty
|
||||
;;
|
||||
|
||||
let classname file = Filename.chop_extension (Filename.basename file)
|
||||
let outfile file = String.concat [ Filename.chop_extension file; ".asm" ]
|
||||
|
||||
let gen_hack file =
|
||||
let ircode = read_file file in
|
||||
let assembly = Be_translate.translate (classname file) ircode in
|
||||
let outchan = Out_channel.create (outfile file) in
|
||||
Out_channel.output_lines outchan assembly;
|
||||
Out_channel.close outchan
|
||||
;;
|
||||
|
||||
let param =
|
||||
let open Command.Param in
|
||||
anon ("filename" %: string)
|
||||
;;
|
||||
|
||||
let command =
|
||||
Command.basic
|
||||
~summary:"Translate <filename>.vm to <filename>.asm"
|
||||
~readme:(fun () -> "Compiler (backend) for project 7 of Nand2Tetris")
|
||||
(Command.Param.map param ~f:(fun filename () -> gen_hack filename))
|
||||
;;
|
||||
|
||||
let () = Command_unix.run command
|
@ -1,2 +0,0 @@
|
||||
(lang dune 3.3)
|
||||
(using menhir 2.0)
|
@ -1,4 +0,0 @@
|
||||
type expr =
|
||||
| Comment of string
|
||||
| Acommand of string
|
||||
| Mcommand of string * string * string
|
@ -1,57 +0,0 @@
|
||||
{
|
||||
open Be_parser
|
||||
}
|
||||
|
||||
let address = ['0'-'9']+
|
||||
|
||||
(* let ADD = "add"
|
||||
let SUB = "sub"
|
||||
let NEG = "neg"
|
||||
let EQ = "eq"
|
||||
let GT = "gt"
|
||||
let LT = "lt"
|
||||
let AND = "and"
|
||||
let OR = "or"
|
||||
let NOT = "not"
|
||||
let POP = "pop"
|
||||
let PUSH = "push" *)
|
||||
|
||||
let command = "add"
|
||||
| "sub"
|
||||
| "neg"
|
||||
| "eq"
|
||||
| "gt"
|
||||
| "lt"
|
||||
| "and"
|
||||
| "or"
|
||||
| "not"
|
||||
| "pop"
|
||||
| "push"
|
||||
|
||||
let segment = "argument"
|
||||
| "local"
|
||||
| "static"
|
||||
| "this"
|
||||
| "that"
|
||||
| "pointer"
|
||||
| "temp"
|
||||
| "constant"
|
||||
|
||||
let white = [' ' '\t']+
|
||||
let comment = "//" _*
|
||||
|
||||
(*
|
||||
let digit = ['0'-'9']
|
||||
let letter = ['a'-'z' 'A'-'Z']
|
||||
let special = ['.' '$' '_' '-' '+' '&' '!' '|']
|
||||
let id = (digit | letter | special)+
|
||||
*)
|
||||
|
||||
rule read =
|
||||
parse
|
||||
| white { read lexbuf }
|
||||
| address as a { ADDRESS (a) }
|
||||
| command as c { COMMAND (c) }
|
||||
| segment as s { SEGMENT (s) }
|
||||
| comment as c { COMMENT (c) }
|
||||
| eof { EOF }
|
@ -1,24 +0,0 @@
|
||||
%{
|
||||
open Be_ast
|
||||
%}
|
||||
|
||||
%token <string> ADDRESS
|
||||
%token <string> COMMAND
|
||||
%token <string> SEGMENT
|
||||
%token <string> COMMENT
|
||||
%token EOF
|
||||
|
||||
%start <Be_ast.expr> prog
|
||||
|
||||
%%
|
||||
|
||||
prog:
|
||||
| e = expr; EOF { e }
|
||||
;
|
||||
|
||||
expr:
|
||||
| c = COMMENT { Comment c }
|
||||
| c = COMMAND; { Acommand c }
|
||||
| c = COMMAND; s = SEGMENT; a = ADDRESS { Mcommand (c, s, a) }
|
||||
| e = expr; COMMENT { e }
|
||||
;
|
@ -1,77 +0,0 @@
|
||||
open Base
|
||||
|
||||
let _unary = [ "@SP"; "A=M-1" ]
|
||||
let _binary = [ "@SP"; "AM=M-1"; "D=M"; "A=A-1" ]
|
||||
let _add = _binary @ [ "M=M+D" ]
|
||||
let _sub = _binary @ [ "M=M-D" ]
|
||||
let _neg = _unary @ [ "M=-M" ]
|
||||
let _and = _binary @ [ "M=D&M" ]
|
||||
let _or = _binary @ [ "M=D|M" ]
|
||||
let _not = _unary @ [ "M=!M" ]
|
||||
|
||||
let _eq loc =
|
||||
_binary
|
||||
@ [ "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_EQ_" ^ loc
|
||||
; "D;JEQ"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_EQ_" ^ loc ^ ")"
|
||||
]
|
||||
;;
|
||||
|
||||
let _gt loc =
|
||||
_binary
|
||||
@ [ "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_GT_" ^ loc
|
||||
; "D;JGT"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_GT_" ^ loc ^ ")"
|
||||
]
|
||||
;;
|
||||
|
||||
let _lt loc =
|
||||
_binary
|
||||
@ [ "D=M-D"
|
||||
; "M=-1"
|
||||
; "@END_LT_" ^ loc
|
||||
; "D;JLT"
|
||||
; "@SP"
|
||||
; "A=M-1"
|
||||
; "M=0"
|
||||
; "(END_LT_" ^ loc ^ ")"
|
||||
]
|
||||
;;
|
||||
|
||||
let push = [ "@SP"; "A=M"; "M=D"; "@SP"; "M=M+1" ]
|
||||
let pop = [ "@R13"; "M=D"; "@SP"; "AM=M-1"; "D=M"; "@R13"; "A=M"; "M=D" ]
|
||||
|
||||
let s_pointer = function
|
||||
| "0" -> "@THIS"
|
||||
| "1" -> "@THAT"
|
||||
| _ -> failwith "Invalid Pointer"
|
||||
;;
|
||||
|
||||
let s_common = function
|
||||
| "local" -> "@LCL"
|
||||
| "this" -> "@THIS"
|
||||
| "that" -> "@THAT"
|
||||
| "argument" -> "@ARG"
|
||||
| _ -> failwith "Invalid Segment"
|
||||
;;
|
||||
|
||||
let push_constant a = [ "@" ^ a; "D=A" ] @ push
|
||||
let push_common a sp = [ "@" ^ a; "D=A"; s_common sp; "A=D+M"; "D=M" ] @ push
|
||||
let push_temp a = [ "@" ^ a; "D=A"; "@R5"; "A=D+A"; "D=M" ] @ push
|
||||
let push_pointer a = [ s_pointer a; "D=M" ] @ push
|
||||
let push_static a cn = [ "@" ^ cn ^ "." ^ a; "D=M" ] @ push
|
||||
let pop_common a sp = [ "@" ^ a; "D=A"; s_common sp; "D=D+M" ] @ pop
|
||||
let pop_temp a = [ "@" ^ a; "D=A"; "@R5"; "D=D+A" ] @ pop
|
||||
let pop_pointer a = [ "@SP"; "AM=M-1"; "D=M"; s_pointer a; "M=D" ]
|
||||
let pop_static a cn = [ "@SP"; "AM=M-1"; "D=M"; "@" ^ cn ^ "." ^ a; "M=D" ]
|
||||
let _end = [ "(TERMINATE)"; "@TERMINATE"; "0;JMP" ]
|
@ -1,65 +0,0 @@
|
||||
open Base
|
||||
open Be_ast
|
||||
open Be_predef
|
||||
|
||||
let parse s =
|
||||
let lexbuf = Lexing.from_string s in
|
||||
let ast = Be_parser.prog Be_lexer.read lexbuf in
|
||||
ast
|
||||
;;
|
||||
|
||||
let generate_exprs lines = List.map lines ~f:parse
|
||||
|
||||
let a_command loc a =
|
||||
let suffix = Int.to_string loc in
|
||||
match a with
|
||||
| "add" -> _add
|
||||
| "sub" -> _sub
|
||||
| "neg" -> _neg
|
||||
| "eq" -> _eq suffix
|
||||
| "gt" -> _gt suffix
|
||||
| "lt" -> _lt suffix
|
||||
| "and" -> _and
|
||||
| "or" -> _or
|
||||
| "not" -> _not
|
||||
| _ -> failwith "a_command: Invalid Command"
|
||||
;;
|
||||
|
||||
let process_push cn sp a =
|
||||
match sp with
|
||||
| "local" | "argument" | "this" | "that" -> push_common a sp
|
||||
| "constant" -> push_constant a
|
||||
| "temp" -> push_temp a
|
||||
| "static" -> push_static a cn
|
||||
| "pointer" -> push_pointer a
|
||||
| _ -> failwith "PROCESS PUSH: Invalid Segment"
|
||||
;;
|
||||
|
||||
let process_pop cn sp a =
|
||||
match sp with
|
||||
| "local" | "argument" | "this" | "that" -> pop_common a sp
|
||||
| "temp" -> pop_temp a
|
||||
| "static" -> pop_static a cn
|
||||
| "pointer" -> pop_pointer a
|
||||
| _ -> failwith "PROCESS POP: Invalid Segment"
|
||||
;;
|
||||
|
||||
let m_command cn (c, s, a) =
|
||||
match c with
|
||||
| "pop" -> process_pop cn s a
|
||||
| "push" -> process_push cn s a
|
||||
| _ -> failwith "m_command: Invalid Command"
|
||||
;;
|
||||
|
||||
let rec _translate expr cn loc tt =
|
||||
match expr with
|
||||
| [] -> tt
|
||||
| Acommand a :: t -> a_command loc a @ _translate t cn (loc + 1) tt
|
||||
| Mcommand (c, s, a) :: t -> m_command cn (c, s, a) @ _translate t cn loc tt
|
||||
| _ :: t -> _translate t cn loc tt
|
||||
;;
|
||||
|
||||
let translate cn lines =
|
||||
let exprs = generate_exprs lines in
|
||||
_translate exprs cn 0 [] @ _end
|
||||
;;
|
@ -1,9 +0,0 @@
|
||||
(ocamllex be_lexer)
|
||||
|
||||
(menhir
|
||||
(modules be_parser))
|
||||
|
||||
(library
|
||||
(name backend)
|
||||
(modules be_ast be_predef be_translate be_lexer be_parser)
|
||||
(libraries base))
|
@ -1,31 +0,0 @@
|
||||
open OUnit2
|
||||
open Backend.Be_ast
|
||||
open Backend.Be_translate
|
||||
|
||||
let peq case value = assert_equal value (parse case)
|
||||
|
||||
let test_parse_a_command _ =
|
||||
peq "add" (Acommand "add");
|
||||
peq "sub" (Acommand "sub");
|
||||
peq "neg" (Acommand "neg");
|
||||
peq "eq" (Acommand "eq");
|
||||
peq "gt" (Acommand "gt");
|
||||
peq "lt" (Acommand "lt");
|
||||
peq "and" (Acommand "and");
|
||||
peq "or" (Acommand "or");
|
||||
peq "not" (Acommand "not")
|
||||
;;
|
||||
|
||||
let test_parse_m_command _ =
|
||||
peq "pop local 12" (Mcommand ("pop", "local", "12"));
|
||||
peq "push argument 4" (Mcommand ("push", "argument", "4"))
|
||||
;;
|
||||
|
||||
let suite =
|
||||
"suite"
|
||||
>::: [ "A Instruction" >:: test_parse_a_command
|
||||
; "M Instruction" >:: test_parse_m_command
|
||||
]
|
||||
;;
|
||||
|
||||
let () = run_test_tt_main suite
|
Loading…
Reference in New Issue
Block a user