hackasm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +139 -0
  8. data/README.md +40 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/exe/hackasm +18 -0
  13. data/hackasm.gemspec +64 -0
  14. data/lib/hackasm.rb +6 -0
  15. data/lib/hackasm/assembler/constants/dests.rb +1 -0
  16. data/lib/hackasm/assembler/constants/jumps.rb +1 -0
  17. data/lib/hackasm/assembler/hack_parser.rb +50 -0
  18. data/lib/hackasm/assembler/instructions/address_instruction.rb +18 -0
  19. data/lib/hackasm/assembler/instructions/command_instruction.rb +76 -0
  20. data/lib/hackasm/assembler/symbol_table.rb +53 -0
  21. data/lib/hackasm/assembler/translator.rb +51 -0
  22. data/lib/hackasm/cli.rb +45 -0
  23. data/lib/hackasm/command.rb +121 -0
  24. data/lib/hackasm/commands/.gitkeep +1 -0
  25. data/lib/hackasm/commands/asm2binary.rb +26 -0
  26. data/lib/hackasm/commands/vm2asm.rb +36 -0
  27. data/lib/hackasm/templates/.gitkeep +1 -0
  28. data/lib/hackasm/templates/assemble/.gitkeep +1 -0
  29. data/lib/hackasm/templates/vm2asm/.gitkeep +1 -0
  30. data/lib/hackasm/version.rb +3 -0
  31. data/lib/hackasm/vm/instructions/arithmetic_instruction.rb +30 -0
  32. data/lib/hackasm/vm/instructions/arithmetic_operations/binary_operation.rb +47 -0
  33. data/lib/hackasm/vm/instructions/arithmetic_operations/comparison.rb +50 -0
  34. data/lib/hackasm/vm/instructions/arithmetic_operations/unary_operation.rb +34 -0
  35. data/lib/hackasm/vm/instructions/memory_access_instruction.rb +36 -0
  36. data/lib/hackasm/vm/instructions/memory_access_operations/constant_operation.rb +30 -0
  37. data/lib/hackasm/vm/instructions/memory_access_operations/fixed_segment_operation.rb +62 -0
  38. data/lib/hackasm/vm/instructions/memory_access_operations/static_operation.rb +43 -0
  39. data/lib/hackasm/vm/instructions/memory_access_operations/virtual_segment_operation.rb +65 -0
  40. data/lib/hackasm/vm/translator.rb +46 -0
  41. data/lib/hackasm/vm/vm_code_parser.rb +39 -0
  42. 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
@@ -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
+ #