hack_assembler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ [![Build Status](https://travis-ci.org/Fuffi/hack2asm.svg?branch=master)](https://travis-ci.org/Fuffi/hack_assembler)
2
+
3
+ # Hack Assembler
4
+
5
+ Converts assembly code for the Hack machine built during the nand2tetris exercises.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'hack_assembler'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install hack_assembler
20
+
21
+ ## Usage
22
+
23
+ After installing the Gem you can run this command from your terminal:
24
+ hack_assembler <input-file.asm> <output-file.hack>
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/evn ruby
2
+ require_relative '../lib/hack_assembler'
3
+
4
+ input_file = ARGV[0]
5
+ output_file = ARGV[1]
6
+
7
+ if input_file.nil? || output_file.nil?
8
+ puts "Use the following format: hack_assembler <input_file.asm> <output_file.hack>"
9
+ exit -1
10
+ end
11
+
12
+ unless File.file?(input_file)
13
+ puts "The file #{input_file} doesn't exists"
14
+ exit -1
15
+ end
16
+
17
+ HackAssembler.translate_file(input_file, output_file)
data/hack2asm.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hack_assembler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hack_assembler"
8
+ spec.version = HackAssembler::VERSION
9
+ spec.authors = ["Federico Rebora"]
10
+ spec.email = ["ufinii@gmail.com"]
11
+ spec.description = %q{Assembler that takes .hack code and converts it to Hack machine ASM}
12
+ spec.summary = %q{Converts .hack files to .asm}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables << 'hack_assembler'
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.10"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest"
24
+ end
@@ -0,0 +1,14 @@
1
+ module HackAssembler
2
+ module AInstruction
3
+ def self.translate(instruction)
4
+ matched = /@([0-9]+)/.match(instruction)
5
+ raise ParserError if matched.nil?
6
+
7
+ value = matched[1].to_i
8
+ binary_value = value.to_s(2)
9
+ padded_binary_value = binary_value.rjust(16, '0')
10
+
11
+ padded_binary_value
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module HackAssembler
2
+ module Assembler
3
+ def self.translate(source_code)
4
+ machine_code = ''
5
+
6
+ source_code_extract = source_code.gsub(/^[\s]*$\n/, '')
7
+
8
+ source_code_extract.each_line do |line|
9
+ next if line.start_with? '//'
10
+
11
+ clean_line = line.strip
12
+
13
+ instruction = clean_line.start_with?('@') ? AInstruction : CInstruction
14
+
15
+ machine_code << instruction.translate(clean_line) << "\n"
16
+ end
17
+
18
+ machine_code
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module HackAssembler
2
+ module CInstruction
3
+ def self.translate(instruction)
4
+ matched = /((.+)=)?([^;]+)(;([A-Z]+))?/.match(instruction)
5
+ destination = matched[2]
6
+ computation = matched[3]
7
+ jump = matched[5]
8
+
9
+ bytecode = '1110000000000000'
10
+ bytecode[3..9] = ComputationTranslator.translate(computation)
11
+ bytecode[10..12] = DestinationTranslator.translate(destination)
12
+ bytecode[13..15] = JumpTranslator.translate(jump)
13
+
14
+ bytecode
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module HackAssembler
2
+ module ComputationTranslator
3
+ def self.translate(computation)
4
+ a_or_m_register = '0'
5
+ if computation.include? 'M'
6
+ a_or_m_register = '1'
7
+ end
8
+
9
+ generic_computation = computation.gsub(/X/, '$').gsub(/(A|M)/, 'X')
10
+
11
+ operation_bytecode = @@translation_table[generic_computation]
12
+ raise ParserError if operation_bytecode.nil?
13
+
14
+ bytecode = a_or_m_register + operation_bytecode
15
+
16
+ bytecode
17
+ end
18
+
19
+ private
20
+ @@translation_table = {
21
+ '0' => '101010',
22
+ '1' => '111111',
23
+ '-1' => '111010',
24
+ 'X' => '110000',
25
+ 'D' => '001100',
26
+ '!D' => '001101',
27
+ '!X' => '110001',
28
+ '-D' => '001111',
29
+ '-X' => '110011',
30
+ 'D+1' => '011111',
31
+ 'X+1' => '110111',
32
+ 'D-1' => '001110',
33
+ 'X-1' => '110010',
34
+ 'D+X' => '000010',
35
+ 'D-X' => '010011',
36
+ 'X-D' => '000111',
37
+ 'D&X' => '000000',
38
+ 'D|X' => '010101'
39
+ }
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ module HackAssembler
2
+ module DestinationTranslator
3
+ def self.translate(destination)
4
+ bytecode = @@translation_table[destination]
5
+ raise ParserError if bytecode.nil?
6
+
7
+ bytecode
8
+ end
9
+
10
+ private
11
+ @@translation_table = {
12
+ nil => '000',
13
+ 'M' => '001',
14
+ 'D' => '010',
15
+ 'MD' => '011',
16
+ 'A' => '100',
17
+ 'AM' => '101',
18
+ 'AD' => '110',
19
+ 'AMD' => '111'
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module HackAssembler
2
+ module JumpTranslator
3
+ def self.translate(jump)
4
+ bytecode = @@translation_table[jump]
5
+ raise ParserError if bytecode.nil?
6
+
7
+ bytecode
8
+ end
9
+
10
+ private
11
+ @@translation_table = {
12
+ nil => '000',
13
+ 'JMP' => '111',
14
+ 'JGT' => '001',
15
+ 'JEQ' => '010',
16
+ 'JGE' => '011',
17
+ 'JLT' => '100',
18
+ 'JNE' => '101',
19
+ 'JLE' => '110'
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module HackAssembler
2
+ class ParserError < Exception
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module HackAssembler
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'hack_assembler/version'
2
+ require_relative 'hack_assembler/parser_error'
3
+ require_relative 'hack_assembler/a_instruction'
4
+ require_relative 'hack_assembler/computation_translator'
5
+ require_relative 'hack_assembler/destination_translator'
6
+ require_relative 'hack_assembler/jump_translator'
7
+ require_relative 'hack_assembler/c_instruction'
8
+ require_relative 'hack_assembler/assembler'
9
+
10
+ module HackAssembler
11
+ def self.translate_file(input_file, output_file)
12
+ source_file = File.open(input_file)
13
+ source_code = source_file.read
14
+
15
+ machine_code = Assembler.translate(source_code)
16
+
17
+ File.open(output_file, 'w') { |file| file.write(machine_code); file.close }
18
+
19
+ source_file.close
20
+ end
21
+ end