2020-05-29 14:22:50 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
fn = ARGV[0]
|
2020-05-29 19:48:04 +00:00
|
|
|
|
|
|
|
# Not implemented yet
|
|
|
|
class SymbolTable
|
2020-05-29 20:25:43 +00:00
|
|
|
def initialize
|
2020-05-29 22:38:30 +00:00
|
|
|
@st = { SP: 0,
|
|
|
|
LCL: 1,
|
|
|
|
ARG: 2,
|
|
|
|
THIS: 3,
|
|
|
|
THAT: 4,
|
|
|
|
R0: 0,
|
|
|
|
R1: 1,
|
|
|
|
R2: 2,
|
|
|
|
R3: 3,
|
|
|
|
R4: 4,
|
|
|
|
R5: 5,
|
|
|
|
R6: 6,
|
|
|
|
R7: 7,
|
|
|
|
R8: 8,
|
|
|
|
R9: 9,
|
|
|
|
R10: 10,
|
|
|
|
R11: 11,
|
|
|
|
R12: 12,
|
|
|
|
R13: 13,
|
|
|
|
R14: 14,
|
|
|
|
R15: 15,
|
|
|
|
SCREEN: 16_384,
|
|
|
|
KBD: 24_576 }
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def add(symb, address)
|
2020-05-29 20:41:31 +00:00
|
|
|
@st[symb.to_sym] = address
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
|
|
|
|
2020-05-29 20:41:31 +00:00
|
|
|
def has?(symb)
|
2020-05-29 22:38:30 +00:00
|
|
|
@st.key? symb.to_sym
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def get(symb)
|
2020-05-29 20:41:31 +00:00
|
|
|
@st[symb.to_sym]
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
2020-05-29 19:48:04 +00:00
|
|
|
end
|
2020-05-29 14:22:50 +00:00
|
|
|
|
|
|
|
class Code
|
|
|
|
DEST_MAP = {
|
2020-05-29 22:38:30 +00:00
|
|
|
nil => '000',
|
|
|
|
'M' => '001',
|
|
|
|
'D' => '010',
|
|
|
|
'MD' => '011',
|
|
|
|
'A' => '100',
|
|
|
|
'AM' => '101',
|
|
|
|
'AD' => '110',
|
|
|
|
'AMD' => '111'
|
2020-05-29 14:22:50 +00:00
|
|
|
}.freeze
|
|
|
|
|
|
|
|
JUMP_MAP = {
|
2020-05-29 22:38:30 +00:00
|
|
|
nil => '000',
|
|
|
|
'JGT' => '001',
|
|
|
|
'JEQ' => '010',
|
|
|
|
'JGE' => '011',
|
|
|
|
'JLT' => '100',
|
|
|
|
'JNE' => '101',
|
|
|
|
'JLE' => '110',
|
|
|
|
'JMP' => '111'
|
2020-05-29 14:22:50 +00:00
|
|
|
}.freeze
|
|
|
|
COMP_MAP = {
|
|
|
|
'0' => '101010',
|
|
|
|
'1' => '111111',
|
|
|
|
'-1' => '111010',
|
|
|
|
'D' => '001100',
|
|
|
|
'Y' => '110000',
|
|
|
|
'!D' => '001101',
|
|
|
|
'!Y' => '110001',
|
|
|
|
'-D' => '001111',
|
|
|
|
'-Y' => '110011',
|
|
|
|
'D+1' => '011111',
|
|
|
|
'Y+1' => '110111',
|
|
|
|
'D-1' => '001110',
|
|
|
|
'Y-1' => '110010',
|
|
|
|
'D+Y' => '000010',
|
|
|
|
'D-Y' => '010011',
|
|
|
|
'Y-D' => '000111',
|
|
|
|
'D&Y' => '000000',
|
|
|
|
'D|Y' => '010101'
|
|
|
|
}.freeze
|
|
|
|
def self.dest(str)
|
|
|
|
Code::DEST_MAP[str]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.jump(str)
|
|
|
|
Code::JUMP_MAP[str]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.comp(str)
|
2020-05-29 20:41:31 +00:00
|
|
|
str.strip!
|
2020-05-29 14:22:50 +00:00
|
|
|
key = str.gsub(/[AM]/, 'Y')
|
2020-05-29 22:38:30 +00:00
|
|
|
(str.include?('M') ? '1' : '0') + COMP_MAP[key]
|
2020-05-29 14:22:50 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Parser
|
|
|
|
def more_commands?
|
|
|
|
@ic < @lines.size
|
|
|
|
end
|
|
|
|
|
|
|
|
def advance
|
|
|
|
@ic += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
def line
|
|
|
|
@lines[@ic]
|
|
|
|
end
|
|
|
|
|
|
|
|
def command_type
|
|
|
|
case line[0]
|
|
|
|
when '@'
|
|
|
|
:A_COMMAND
|
|
|
|
when '('
|
|
|
|
:L_COMMAND
|
|
|
|
else
|
|
|
|
:C_COMMAND
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def symbol
|
|
|
|
case command_type
|
|
|
|
when :A_COMMAND
|
|
|
|
line[1..-1]
|
|
|
|
when :L_COMMAND
|
|
|
|
line[1..-2]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
def dest
|
|
|
|
return nil unless line.include? '='
|
2020-05-29 22:38:30 +00:00
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
line.split('=').first
|
|
|
|
end
|
2020-05-29 14:22:50 +00:00
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
def comp
|
2020-05-29 20:02:31 +00:00
|
|
|
if dest && jump
|
2020-05-29 19:48:04 +00:00
|
|
|
line.split('=').last.split(';').first
|
|
|
|
elsif dest
|
2020-05-29 20:02:31 +00:00
|
|
|
line.split('=').last
|
2020-05-29 19:48:04 +00:00
|
|
|
elsif jump
|
|
|
|
line.split(';').first
|
|
|
|
else
|
|
|
|
line
|
|
|
|
end
|
|
|
|
end
|
2020-05-29 14:22:50 +00:00
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
def jump
|
|
|
|
return nil unless line.include? ';'
|
2020-05-29 22:38:30 +00:00
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
line.split(';').last
|
|
|
|
end
|
2020-05-29 20:25:43 +00:00
|
|
|
|
|
|
|
# Return
|
|
|
|
def symbol_type
|
|
|
|
symbol[0].match(/^\d/) ? :number : :variable
|
|
|
|
end
|
|
|
|
|
|
|
|
def reset
|
|
|
|
@ic = 0
|
|
|
|
end
|
|
|
|
|
2020-05-29 14:22:50 +00:00
|
|
|
def run
|
2020-05-29 19:48:04 +00:00
|
|
|
res = []
|
|
|
|
while more_commands?
|
|
|
|
case command_type
|
|
|
|
when :A_COMMAND
|
2020-05-29 20:25:43 +00:00
|
|
|
res << [command_type, [symbol_type, symbol]]
|
2020-05-29 19:48:04 +00:00
|
|
|
when :C_COMMAND
|
|
|
|
res << [command_type, [dest, comp, jump]]
|
|
|
|
when :L_COMMAND
|
|
|
|
res << [command_type, [symbol]]
|
|
|
|
end
|
|
|
|
advance
|
|
|
|
end
|
|
|
|
res
|
2020-05-29 14:22:50 +00:00
|
|
|
end
|
|
|
|
|
2020-05-29 19:48:04 +00:00
|
|
|
def initialize(fn)
|
2020-05-29 14:22:50 +00:00
|
|
|
@ic = 0
|
|
|
|
@lines = []
|
|
|
|
File.readlines(fn).each do |line|
|
2020-05-29 20:41:31 +00:00
|
|
|
line.strip!
|
2020-05-29 14:22:50 +00:00
|
|
|
next if line.empty?
|
|
|
|
next if line[0...2] == '//'
|
|
|
|
|
2020-05-29 20:41:31 +00:00
|
|
|
if line.include? '//'
|
|
|
|
comment_start = line.index('//')
|
|
|
|
line = line[0...comment_start]
|
|
|
|
line.strip!
|
|
|
|
end
|
|
|
|
|
2020-05-29 14:22:50 +00:00
|
|
|
@lines << line
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-05-29 19:48:04 +00:00
|
|
|
|
|
|
|
class Assembler
|
|
|
|
attr_reader :res
|
|
|
|
def initialize(fn)
|
|
|
|
@res = []
|
2020-05-29 20:25:43 +00:00
|
|
|
@st = SymbolTable.new
|
2020-05-29 19:48:04 +00:00
|
|
|
parser = Parser.new(fn)
|
2020-05-29 20:25:43 +00:00
|
|
|
# instruction counter for first pass
|
|
|
|
ic = 0
|
|
|
|
# initial memory pointer starts just after R15
|
|
|
|
memory_pointer = 16
|
|
|
|
# First pass
|
|
|
|
parser.run.each do |command|
|
|
|
|
# Our parser has already dropped all comments and whitespace
|
|
|
|
# so everything in this pass is either a A or a C instruction
|
|
|
|
if command[0] == :L_COMMAND
|
|
|
|
symbol = command[1][0]
|
|
|
|
@st.add(symbol, ic)
|
2020-05-29 20:41:31 +00:00
|
|
|
else
|
2020-05-29 22:38:30 +00:00
|
|
|
ic += 1
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
# we rewind our parser
|
|
|
|
parser.reset
|
2020-05-29 19:48:04 +00:00
|
|
|
parser.run.each do |command|
|
|
|
|
instruction = command[0]
|
|
|
|
case instruction
|
|
|
|
when :A_COMMAND
|
2020-05-29 20:25:43 +00:00
|
|
|
val = nil
|
|
|
|
symbol_type, symbol = command[1]
|
|
|
|
if symbol_type === :variable
|
|
|
|
if @st.has? symbol
|
2020-05-29 22:38:30 +00:00
|
|
|
val = @st.get(symbol)
|
2020-05-29 20:25:43 +00:00
|
|
|
else
|
|
|
|
val = memory_pointer
|
2020-05-29 20:41:31 +00:00
|
|
|
@st.add(symbol, memory_pointer)
|
2020-05-29 22:38:30 +00:00
|
|
|
memory_pointer += 1
|
2020-05-29 20:25:43 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
val = symbol.to_i
|
|
|
|
end
|
2020-05-29 22:38:30 +00:00
|
|
|
@res << val.to_i.to_s(2).rjust(16, '0')
|
2020-05-29 19:48:04 +00:00
|
|
|
when :C_COMMAND
|
2020-05-29 22:38:30 +00:00
|
|
|
dest, comp, jump = command[1]
|
2020-05-29 19:48:04 +00:00
|
|
|
@res << "111#{Code.comp(comp)}#{Code.dest(dest)}#{Code.jump(jump)}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if ARGV[0]
|
|
|
|
asm = Assembler.new(fn)
|
|
|
|
puts asm.res.join("\n")
|
|
|
|
end
|