Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # __ __ _ ___ _ _ ___ _ _ ___ ___ ___ ___ ___
- # | \/ | /_\ / __| || |_ _| \| | __| / __/ _ \| \| __|
- # | |\/| |/ _ \ (__| __ || || .` | _| | (_| (_) | |) | _|
- # |_| |_/_/ \_\___|_||_|___|_|\_|___| \___\___/|___/|___|
- # ___ ___ _ _ ___ ___ _ _____ ___ ___
- # / __| __| \| | __| _ \ /_\_ _/ _ \| _ \
- # | (_ | _|| .` | _|| / / _ \| || (_) | /
- # \___|___|_|\_|___|_|_\/_/ \_\_| \___/|_|_\
- #
- # Written by Kyle Noll
- # in Ruby
- #
- # This program converts assembly code into machine code based
- # on the Nand2Tetris course. Type your assembly code into a file named
- # 'assembly.txt' by default in the same directory and run the
- # program. The binary output will be placed into a file named 'machine.txt'
- # by default in the same directory.
- # ===================== MAIN =========================
- input_file = "assembly.txt"
- output_file = "machine.txt"
- # Read assembly.txt
- lines = []
- File.open(input_file) do |file|
- file.each do |line|
- lines << line
- end
- end
- # First loop: Strip down the code and collect variables
- raw_lines = []
- jump_points = []
- line_number = 0
- lines.each do |line|
- line.chomp!
- line.gsub!(/\s+/, "")
- if is_declaring_jump_point?(line)
- add_jump_point(line, line_number)
- end
- if !line.empty? and !full_line_comment?(line) and !is_declaring_jump_point?(line)
- # Remove comments on lines that also have code
- line = line.slice(0..line.index('//')-1) if !!line.index('//')
- raw_lines << {contents: line, line_number: line_number}
- line_number += 1
- if is_declaring_variable?(line)
- add_variable(line[1..-1])
- end
- end
- end
- # Second loop: set variable and jump point names to their respective values
- machine_lines = []
- raw_lines.each_with_index do |line, i|
- raw_line = line[:contents]
- if is_declaring_variable?(raw_line)
- variable_name = raw_line[1..-1]
- value = @variables[variable_name]
- raw_line = "@#{value}"
- end
- if is_referring_to_jump_point?(raw_line)
- jump_point_name = raw_line[1..-1]
- value = @jump_points[jump_point_name]
- raw_line = "@#{value}"
- end
- if raw_line[0] == "@"
- # a-command
- binary = a_command_to_bin(raw_line)
- else
- # c-command
- binary = c_command_to_bin(raw_line)
- end
- # Assemble
- machine_lines << binary
- end
- File.open(output_file, 'w+') {|f| f.write(machine_lines)}
- # ================= Methods & Tools ===================
- class String
- def is_i?
- !!(self =~ /\A[-+]?[0-9]+\z/)
- end
- def is_s?
- self[/[a-zA-Z]+/] == self
- end
- end
- def get_instruction_type(line)
- if line[0] == "@"
- return "a"
- else
- return "c"
- end
- end
- def full_line_comment?(line)
- line[0,2] == '//'
- end
- def is_upper?(char)
- if char.is_s?
- return char == char.upcase
- else
- return false
- end
- end
- def is_lower?(char)
- if char.is_s?
- return char == char.downcase
- else
- return false
- end
- end
- def is_only_letters?(line)
- !!line.match(/^[[:alpha:]]+$/)
- end
- def is_declaring_variable?(line)
- line[0] == "@" && is_lower?(line[1..-1]) && is_only_letters?(line[1..-1])
- end
- def add_variable(name)
- @variable_location_pointer ||= 16
- @variables ||= {}
- @variables.each do |variable|
- if @variables.has_key?(name)
- return
- end
- end
- @variables[name] = @variable_location_pointer
- @variable_location_pointer += 1
- end
- def is_referring_to_jump_point?(line)
- line[0] == "@" && is_upper?(line[1..-1]) && is_only_letters?(line[1..-1])
- end
- def is_declaring_jump_point?(line)
- line[0] == "("
- end
- def add_jump_point(line, location)
- @jump_points ||= {}
- name = line[1..-2]
- @jump_points.each do |jump_point|
- if @jump_points.has_key?(name)
- return
- end
- end
- @jump_points[name] = location
- end
- def dec_to_bin(num)
- if num > 32768 or num < -32768
- raise "Error: Number must be greater than -32,768 and less than 32,768."
- end
- if num < 0
- negative = true
- num += 1
- else
- negative = false
- end
- num = num.abs
- bin = ""
- while num > 0 do
- bin.prepend((num % 2).to_s)
- num = num / 2
- end
- while bin.length < 15
- bin.prepend("0")
- end
- return bin
- end
- def a_command_to_bin(line)
- value = line[1..-1]
- return "0" + dec_to_bin(value.to_i)
- end
- def c_command_to_bin(line)
- # Define indices
- comp_index = {:"0"=>"0101010", :"1"=>"0111111", :"-1"=>"0111010", :D=>"0001100", :A=>"0110000", :"!D"=>"0001101", :"!A"=>"0110001", :"-D"=>"0001111", :"-A"=>"0110011", :"D+1"=>"0011111", :"A+1"=>"0110111", :"D-1"=>"0001110", :"A-1"=>"0110010", :"D+A"=>"0000010", :"D-A"=>"0010011", :"A-D"=>"0000111", :"D&A"=>"0000000", :"D|A"=>"0010101", :M=>"1110000", :"!M"=>"1110001", :"-M"=>"1110011", :"M+1"=>"1110111", :"M-1"=>"1110010", :"D+M"=>"1000010", :"D-M"=>"1010011", :"M-D"=>"1000111", :"D&M"=>"1000000", :"D|M"=>"1010101"}
- dest_index = {:M=>"001", :D=>"010", :MD=>"011", :A=>"100", :AM=>"101", :AD=>"110", :AMD=>"111"}
- jump_index = {:JGT=>"001", :JEQ=>"010", :JGE=>"011", :JLT=>"100", :JNE=>"101", :JLE=>"110", :JMP=>"111"}
- # Extract dest, comp, and jump
- dest, comp, jump = line.match(/([A-Z]+)?=?([A-Z\d\+\-\!\&\|]+);?([A-Z]+)?/).captures
- # Initialize binary
- binary = "111"
- # comp
- binary += comp_index[comp.to_sym]
- # dest
- if dest
- binary += dest_index[dest.to_sym]
- else
- binary += "000"
- end
- # jump
- if jump
- binary += jump_index[jump.to_sym]
- else
- binary += "000"
- end
- return binary
- end
Add Comment
Please, Sign In to add comment