hackasm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +139 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/hackasm +18 -0
- data/hackasm.gemspec +64 -0
- data/lib/hackasm.rb +6 -0
- data/lib/hackasm/assembler/constants/dests.rb +1 -0
- data/lib/hackasm/assembler/constants/jumps.rb +1 -0
- data/lib/hackasm/assembler/hack_parser.rb +50 -0
- data/lib/hackasm/assembler/instructions/address_instruction.rb +18 -0
- data/lib/hackasm/assembler/instructions/command_instruction.rb +76 -0
- data/lib/hackasm/assembler/symbol_table.rb +53 -0
- data/lib/hackasm/assembler/translator.rb +51 -0
- data/lib/hackasm/cli.rb +45 -0
- data/lib/hackasm/command.rb +121 -0
- data/lib/hackasm/commands/.gitkeep +1 -0
- data/lib/hackasm/commands/asm2binary.rb +26 -0
- data/lib/hackasm/commands/vm2asm.rb +36 -0
- data/lib/hackasm/templates/.gitkeep +1 -0
- data/lib/hackasm/templates/assemble/.gitkeep +1 -0
- data/lib/hackasm/templates/vm2asm/.gitkeep +1 -0
- data/lib/hackasm/version.rb +3 -0
- data/lib/hackasm/vm/instructions/arithmetic_instruction.rb +30 -0
- data/lib/hackasm/vm/instructions/arithmetic_operations/binary_operation.rb +47 -0
- data/lib/hackasm/vm/instructions/arithmetic_operations/comparison.rb +50 -0
- data/lib/hackasm/vm/instructions/arithmetic_operations/unary_operation.rb +34 -0
- data/lib/hackasm/vm/instructions/memory_access_instruction.rb +36 -0
- data/lib/hackasm/vm/instructions/memory_access_operations/constant_operation.rb +30 -0
- data/lib/hackasm/vm/instructions/memory_access_operations/fixed_segment_operation.rb +62 -0
- data/lib/hackasm/vm/instructions/memory_access_operations/static_operation.rb +43 -0
- data/lib/hackasm/vm/instructions/memory_access_operations/virtual_segment_operation.rb +65 -0
- data/lib/hackasm/vm/translator.rb +46 -0
- data/lib/hackasm/vm/vm_code_parser.rb +39 -0
- metadata +467 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Assembler
|
2
|
+
module Instructions
|
3
|
+
class AddressInstruction
|
4
|
+
def initialize(instruction, symbol_table)
|
5
|
+
@instruction = instruction.compact.map { |key, value| [key, value.to_s] }.to_h
|
6
|
+
@symbol_table = symbol_table
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_b
|
10
|
+
if memory_address = @instruction[:memory_address]
|
11
|
+
memory_address.to_i
|
12
|
+
elsif identifier = @instruction[:identifier]
|
13
|
+
@symbol_table[identifier]
|
14
|
+
end.to_s(2).rjust(16, '0')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "../constants/jumps"
|
2
|
+
require_relative "../constants/dests"
|
3
|
+
|
4
|
+
module Assembler
|
5
|
+
module Instructions
|
6
|
+
class CommandInstruction
|
7
|
+
JUMPS_TABLE = [nil, *JUMPS].zip(
|
8
|
+
(0..8).map { |i| i.to_s(2).rjust(3, "0") }
|
9
|
+
).to_h.freeze
|
10
|
+
DESTS_TABLE = [nil, *DESTS].zip(
|
11
|
+
(0..8).map { |i| i.to_s(2).rjust(3, "0") }
|
12
|
+
).to_h.freeze
|
13
|
+
COMPS_TABLE = {
|
14
|
+
"0" => "0101010",
|
15
|
+
"1" => "0111111",
|
16
|
+
"-1" => "0111010",
|
17
|
+
"D" => "0001100",
|
18
|
+
"A" => "0110000",
|
19
|
+
"!D" => "0001101",
|
20
|
+
"!A" => "0110001",
|
21
|
+
"-D" => "0001111",
|
22
|
+
"-A" => "0110011",
|
23
|
+
"D+1" => "0011111",
|
24
|
+
"A+1" => "0110111",
|
25
|
+
"D-1" => "0001110",
|
26
|
+
"A-1" => "0110010",
|
27
|
+
"D+A" => "0000010",
|
28
|
+
"D-A" => "0010011",
|
29
|
+
"A-D" => "0000111",
|
30
|
+
"D&A" => "0000000",
|
31
|
+
"D|A" => "0010101",
|
32
|
+
"M" => "1110000",
|
33
|
+
"!M" => "1110001",
|
34
|
+
"-M" => "1110011",
|
35
|
+
"M+1" => "1110111",
|
36
|
+
"M-1" => "1110010",
|
37
|
+
"D+M" => "1000010",
|
38
|
+
"D-M" => "1010011",
|
39
|
+
"M-D" => "1000111",
|
40
|
+
"D&M" => "1000000",
|
41
|
+
"D|M" => "1010101",
|
42
|
+
}
|
43
|
+
|
44
|
+
def initialize(instruction)
|
45
|
+
@instruction = instruction
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_b
|
49
|
+
comp_bits = COMPS_TABLE[comp]
|
50
|
+
|
51
|
+
binary_instruction = "111"
|
52
|
+
binary_instruction << COMPS_TABLE[comp]
|
53
|
+
binary_instruction << DESTS_TABLE[dest]
|
54
|
+
binary_instruction << JUMPS_TABLE[jump]
|
55
|
+
|
56
|
+
binary_instruction
|
57
|
+
rescue
|
58
|
+
binding.pry
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def dest
|
64
|
+
@instruction[:dest] && @instruction[:dest].to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def jump
|
68
|
+
@instruction[:jump] && @instruction[:jump].to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
def comp
|
72
|
+
@instruction[:comp] && @instruction[:comp].to_s.tr(" ", "")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Assembler
|
2
|
+
class SymbolTable
|
3
|
+
DEFAULT_SYMBOL_TABLE = {
|
4
|
+
"SP" => 0,
|
5
|
+
"LCL" => 1,
|
6
|
+
"ARG" => 2,
|
7
|
+
"THIS" => 3,
|
8
|
+
"THAT" => 4,
|
9
|
+
"SCREEN" => 16384,
|
10
|
+
"KBD" => 24576,
|
11
|
+
}.merge(("0".."15").map { |i| ["R" + i, i.to_i] }.to_h).freeze
|
12
|
+
|
13
|
+
def initialize(expressions)
|
14
|
+
@expressions = expressions
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
DEFAULT_SYMBOL_TABLE.merge(
|
19
|
+
label_symbols
|
20
|
+
).merge(
|
21
|
+
variable_symbols
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def label_symbols
|
28
|
+
@label_symbols ||= begin
|
29
|
+
label_count = 0
|
30
|
+
@expressions.each.with_index.with_object({}) do |(expression, index), symbol_table|
|
31
|
+
symbol = expression.dig(:jump_label, :identifier)
|
32
|
+
symbol = symbol && symbol.to_s
|
33
|
+
if symbol
|
34
|
+
symbol_table[symbol] = index - label_count
|
35
|
+
label_count += 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def variable_symbols
|
42
|
+
memory_location = 16
|
43
|
+
@expressions.each.with_index.with_object({}) do |(expression, index), symbol_table|
|
44
|
+
symbol = expression.dig(:address_instruction, :identifier)
|
45
|
+
symbol = symbol && symbol.to_s
|
46
|
+
if symbol && symbol_table[symbol].nil? && DEFAULT_SYMBOL_TABLE[symbol].nil? && label_symbols[symbol].nil?
|
47
|
+
symbol_table[symbol] = memory_location
|
48
|
+
memory_location += 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "pry"
|
2
|
+
require_relative "./hack_parser"
|
3
|
+
require_relative "./symbol_table"
|
4
|
+
require_relative "./instructions/address_instruction"
|
5
|
+
require_relative "./instructions/command_instruction"
|
6
|
+
|
7
|
+
module Assembler
|
8
|
+
class Translator
|
9
|
+
def initialize(text)
|
10
|
+
@text = text
|
11
|
+
end
|
12
|
+
|
13
|
+
def translate
|
14
|
+
expressions = parser.parse(@text)
|
15
|
+
expressions.map do |expression|
|
16
|
+
process_instruction(expression)
|
17
|
+
end.compact.join("\n") << "\n"
|
18
|
+
rescue Parslet::ParseFailed => failure
|
19
|
+
failure.parse_failure_cause.ascii_tree
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def process_instruction(instruction)
|
25
|
+
instruction_name = instruction.keys.first
|
26
|
+
instruction_body = instruction.values.first
|
27
|
+
|
28
|
+
case instruction_name
|
29
|
+
when :address_instruction
|
30
|
+
Instructions::AddressInstruction.new(instruction_body, symbol_table).to_b
|
31
|
+
when :command_instruction
|
32
|
+
Instructions::CommandInstruction.new(instruction_body).to_b
|
33
|
+
when :jump_label
|
34
|
+
nil
|
35
|
+
else raise "Unknown instruction!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parser
|
40
|
+
@parser ||= HackParser.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def expressions
|
44
|
+
@expressions ||= parser.parse(@text)
|
45
|
+
end
|
46
|
+
|
47
|
+
def symbol_table
|
48
|
+
@symbol_table ||= SymbolTable.new(expressions).to_h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/hackasm/cli.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Hackasm
|
6
|
+
# Handle the application command line parsing
|
7
|
+
# and the dispatch to various command objects
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class CLI < Thor
|
11
|
+
# Error raised by this runner
|
12
|
+
Error = Class.new(StandardError)
|
13
|
+
|
14
|
+
desc 'version', 'hackasm version'
|
15
|
+
def version
|
16
|
+
require_relative 'version'
|
17
|
+
puts "v#{Hackasm::VERSION}"
|
18
|
+
end
|
19
|
+
map %w(--version -v) => :version
|
20
|
+
|
21
|
+
desc 'vm2asm FILE', 'Translate *.vm file with virtual machine code or a folder of *.vm files to the single *.asm file of assembler commands'
|
22
|
+
method_option :help, aliases: '-h', type: :boolean,
|
23
|
+
desc: 'Display usage information'
|
24
|
+
def vm2asm(base_path)
|
25
|
+
if options[:help]
|
26
|
+
invoke :help, ['vm2asm']
|
27
|
+
else
|
28
|
+
require_relative 'commands/vm2asm'
|
29
|
+
Hackasm::Commands::Vm2Asm.new(base_path, options).execute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'asm2binary FILE', 'Translate *.asm file of assembler commands to *.hack file of binary commands'
|
34
|
+
method_option :help, aliases: '-h', type: :boolean,
|
35
|
+
desc: 'Display usage information'
|
36
|
+
def asm2binary(file)
|
37
|
+
if options[:help]
|
38
|
+
invoke :help, ['asm2binary']
|
39
|
+
else
|
40
|
+
require_relative 'commands/asm2binary'
|
41
|
+
Hackasm::Commands::Asm2Binary.new(file, options).execute
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Hackasm
|
6
|
+
class Command
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :command, :run
|
10
|
+
|
11
|
+
# Execute this command
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def execute(*)
|
15
|
+
raise(
|
16
|
+
NotImplementedError,
|
17
|
+
"#{self.class}##{__method__} must be implemented"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The external commands runner
|
22
|
+
#
|
23
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def command(**options)
|
27
|
+
require 'tty-command'
|
28
|
+
TTY::Command.new(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# The cursor movement
|
32
|
+
#
|
33
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def cursor
|
37
|
+
require 'tty-cursor'
|
38
|
+
TTY::Cursor
|
39
|
+
end
|
40
|
+
|
41
|
+
# Open a file or text in the user's preferred editor
|
42
|
+
#
|
43
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def editor
|
47
|
+
require 'tty-editor'
|
48
|
+
TTY::Editor
|
49
|
+
end
|
50
|
+
|
51
|
+
# File manipulation utility methods
|
52
|
+
#
|
53
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def generator
|
57
|
+
require 'tty-file'
|
58
|
+
TTY::File
|
59
|
+
end
|
60
|
+
|
61
|
+
# Terminal output paging
|
62
|
+
#
|
63
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def pager(**options)
|
67
|
+
require 'tty-pager'
|
68
|
+
TTY::Pager.new(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Terminal platform and OS properties
|
72
|
+
#
|
73
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def platform
|
77
|
+
require 'tty-platform'
|
78
|
+
TTY::Platform.new
|
79
|
+
end
|
80
|
+
|
81
|
+
# The interactive prompt
|
82
|
+
#
|
83
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def prompt(**options)
|
87
|
+
require 'tty-prompt'
|
88
|
+
TTY::Prompt.new(options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get terminal screen properties
|
92
|
+
#
|
93
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def screen
|
97
|
+
require 'tty-screen'
|
98
|
+
TTY::Screen
|
99
|
+
end
|
100
|
+
|
101
|
+
# The unix which utility
|
102
|
+
#
|
103
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def which(*args)
|
107
|
+
require 'tty-which'
|
108
|
+
TTY::Which.which(*args)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if executable exists
|
112
|
+
#
|
113
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def exec_exist?(*args)
|
117
|
+
require 'tty-which'
|
118
|
+
TTY::Which.exist?(*args)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../assembler/translator'
|
5
|
+
|
6
|
+
module Hackasm
|
7
|
+
module Commands
|
8
|
+
class Asm2Binary < Hackasm::Command
|
9
|
+
FILE_NAME = "a.hack".freeze
|
10
|
+
|
11
|
+
def initialize(file, options)
|
12
|
+
@file = file
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
assembler_code = File.read(@file)
|
18
|
+
|
19
|
+
binary = Assembler::Translator.new(assembler_code).translate
|
20
|
+
|
21
|
+
output.puts binary
|
22
|
+
File.write(FILE_NAME, binary)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../vm/translator'
|
5
|
+
|
6
|
+
module Hackasm
|
7
|
+
module Commands
|
8
|
+
class Vm2Asm < Hackasm::Command
|
9
|
+
attr_reader :base_path
|
10
|
+
|
11
|
+
def initialize(base_path, options)
|
12
|
+
@base_path = base_path
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
if File.directory?(base_path)
|
18
|
+
assembler_code = Dir[File.join(base_path, "*")].inject do |code_buffer, path|
|
19
|
+
object_name = File.basename(path, ".vm")
|
20
|
+
vm_code = File.read(path)
|
21
|
+
code_buffer + Vm::Translator.new(vm_code, object_name).translate
|
22
|
+
end
|
23
|
+
file_name = "#{base_path}.asm"
|
24
|
+
else
|
25
|
+
object_name = File.basename(base_path, ".vm")
|
26
|
+
vm_code = File.read(base_path)
|
27
|
+
assembler_code = Vm::Translator.new(vm_code, object_name).translate
|
28
|
+
file_name = File.join(File.dirname(base_path), "#{object_name}.asm")
|
29
|
+
end
|
30
|
+
|
31
|
+
output.puts assembler_code
|
32
|
+
File.write(file_name, assembler_code)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|