View

300x250

 

Ch. 6 Project - 1

이 글은 insight 출판사의 [밑바닥부터 만드는 컴퓨팅 시스템 / The Elements of Computing System]이라는 책에 있는 프로젝트(과제)를 수행하는 글입니다.

해외에서는 nand2tetris라는 이름의 프로젝트로 알려져 있습니다! 동일한 내용으로 구성되어 있으니, nand2tetris를 공부할 때 참고바랍니다

 

프로젝트 수행을 위한 언어로 C#을 사용하였습니다.

 

Chapter 6의 내용정리 글 보러가기


Chapter 6. 어셈블러 - Project

Chapter 6 프로젝트의 목표는 Hack Assembly 언어로 작성된 asm 코드를 Hack Binary Code로 변환하는 Assembler를 구현하는 것이다.

 

구현시 참고사항

  • 상수는 10진법으로 표기된 음이 아닌 수
  • 사용자 정의 기호는 숫자로 시작하지 않고 문자, 숫자, ‘_’, ‘.’, ‘$’, ‘;’로 구성
  • 공백과 “//“ 뒤의 주석은 무시된다
  • 어셈블러는 MainProgram, Parser, Code, Symbol Table 4개의 모듈로 구분지어 작성할 수 있다.

처음에 Assembler를 작성할 때는 Symbol이 없는 hack 파일을 처리하는 Assembler를 먼저 구현하고, Symbol 모듈을 추가하면 더 쉽게 구현할 수 있다. 따라서, 이를 따라 기호가 없는 Assembler를 먼저 구현해보겠다.

 

Parser 모듈

Parser 모듈은 어셈블리 명령들을 필드와 기호로 분해하는 역할을 수행한다. 입력에 대한 접근을 캡슐화한다. 모든 공백과 주석을 제거하고, 명령의 필드, 기호와 같은 세부 정보를 편리하게 사용할 수 있게 가공한다.

Assembler class를 만들어 Parser 모듈을 작성해주었다. 책에서 제공된 API를 따라 모듈을 설명하여 보겠다.

  • Constructor - 입력 스트림을 열고 분석 준비하기
string inputFile; // 변수 선언

public Assembler(string fileName) {
    inputFile = fileName;
    SR = new StreamReader(inputFile);
}
  • bool HasMoreCommands( ) - 명령이 뒤에 더 있는가?
public bool HasMoreCommands() {
    if(SR.Peek() > -1) { // StreamReader 의 Peek 함수 는 뒤에 line이 더 있는지 검사를 수행한다.
        return true;
    }
		return false;
}
  • void Advance( ) - 다음 줄로 넘어가기
public void Advance() {
		// 뒤에 Command가 있을 때 수행해야 한다.
    if(HasMoreCommands()) { 
       line = RemoveSpace(SR.ReadLine());
    }
}

// 공백 제거를 위한 메서드
string RemoveSpace(string input) {
	return input.Replace(" ", "").Replace("    ", "");
}
  • Char CommandType( ) - 현재 명령의 Type 반환하기
public char CommandType() {
		// 주석인 경우 또는 빈 줄인 경우
    if (line.StartsWith("//") || line.Length == 0) {
        return 'X';
    } 
		// @로 시작하는 경우
    else if(line[0]=='@') {
        return 'A';
    }
		// (로 시작하는 경우
    else if (line[0]=='(') {
        return 'L';
    }
		// 그 외
    else {
        return 'C';
    }
}
  • String Symbol( ) - 명령어의 기호 또는 선언된 10진수 반환하기
public string Symbol() {
    char[] sym;
    string symString;
    
    if(CommandType()=='A') {
        sym = new char[line.Length-1];    
						// Symbol 앞에 @ 이 추가되었으므로, Symbol의 길이는 명령어의 Length-1
        line.CopyTo(1, sym, 0, line.Length-1); // Symbol만 Copy해오기

        //Value 인 경우 16자리 2진수 string으로 반환
        if('0' <= sym[0] && sym[0] <= '9') {
            return Convert.ToString(Convert.ToInt32(String.Join("", sym)),2).PadLeft(16, '0');
        } 
        //Symbol 인 경우
        else {
				// 지금은 기호가 없는 hack만 처리한다!
        }
        
    } 
    else if(CommandType()=='L') {
	       // 지금은 기호가 없는 hack만 처리한다!
    }
    
    // 제외되는 경우 없음
    return null;
} 
  • String Dest( ) - C 명령어의 dest 연상기호 반환하기
public string Dest() {
    int equalIndex = line.IndexOf('=');
    
    // dest는 = 이 있을 때만 존재한다
    // = 이 없는 경우 생략
    if(equalIndex < 0){
        return "000";
    }

    char[] dest = new char[equalIndex];
    line.CopyTo(0, dest, 0, equalIndex);
    
    return String.Join("",dest);
}
  • String Comp( ) - C 명령어의 comp 연상기호 반환하기
public string Comp() {
		// '=' 와 ';'의 유무에 따라 dest, jump의 유무가 결정된다.
    int equalIndex = line.IndexOf('=');
    int colonIndex = line.IndexOf(';');
    
    char[] comp;
    
		// colon이 없는 경우 ( D=M-D 같은 경우 )
    if(colonIndex < 0) {
        comp = new char[line.Length-1 - equalIndex]; // equal 뒷부분만 가져오기
        line.CopyTo(equalIndex+1, comp, 0, line.Length-1-equalIndex);
        return String.Join("", comp);
    }
    
		// equal이 없는 경우 ( D;JMP 같은 경우 )
    if(equalIndex < 0) {
        comp = new char[colonIndex];    // colon 앞부분만 가져오기
        line.CopyTo(0, comp, 0, colonIndex);
        return String.Join("", comp);
    }

		// 둘 다 있는 경우는 없다.
    return null;
}
  • String Jump( ) - C 명령어의 jump 연상기호 반환하기
public string Jump() {
    int colonIndex = line.IndexOf(';');
    char[] jump = new char[line.Length-colonIndex-1];

		// colon이 있을 때만 jump가 존재한다.
    if(colonIndex >= 0) {
        line.CopyTo(colonIndex+1, jump, 0, line.Length-1-colonIndex);
    }
    
    return String.Join("", jump);
}

 

Code 모듈

Code 모듈은 Hack assembly의 연상기호를 2진 코드로 번역하는 역할을 수행한다.

  • String code_dest( ) - dest의 연상기호의 2진 코드 반환하기
public string code_dest() {
    string destString = Dest();
    char[] result = new char[3] {'0','0','0'};
		
// 각 Bit에 대해 할당된 Destination의 유무에 따라 Bit Set
    if(destString.IndexOf('M') >= 0) {
        result[2] = '1';
    }
    if(destString.IndexOf('D') >= 0) {
        result[1] = '1';
    }
    if(destString.IndexOf('A') >= 0) {
        result[0] = '1';
    }
    
    return new string(result);
}
  • String code_comp( ) - comp의 연상기호의 2진 코드 반환하기
public string code_comp() {
    string compString = Comp();
    switch(compString) {
        case "0":
            return "0101010";
            
        case "1":
            return "0111111";
            
        case "-1":
            return "0111010";
        
        case "D":
            return "0001100";
            
        case "A" :
            return "0110000";
            
        case "M" :
            return "1110000";
        
        case "!D" : 
            return "0001101";
            
        case "!A" : 
            return "0110001";
            
        case "!M" :
            return "1110001";
            
        case "-D" :
            return "0001111";
            
        case "-A" :
            return "0110011";
            
        case "-M" :
            return "1110011";
            
        case "D+1" :
            return "0011111";
        
        case "A+1" :
            return "0110111";
            
        case "M+1" :
            return "1110111";
            
        case "D-1" :
            return "0001110";
            
        case "A-1" : 
            return "0110010";
            
        case "M-1" :
            return "1110010";
            
        case "D+A" :
            return "0000010";
            
        case "D+M" :
            return "1000010";
            
        case "D-A" :
            return "0010011";
            
        case "D-M" :
            return "1010011";
            
        case "A-M" :
            return "0000111";
            
        case "M-D" :
            return "1000111";
            
        case "D&A" :
            return "0000000";
            
        case "D&M" :
            return "1000000";
            
        case "D|A" :
            return "0010101";
        
        case "D|M" :
            return "1010101";
            
        default:
            return "0000000";
    }
    
}
  • String code_jump( ) - jump의 연상기호의 2진 코드 반환하기
public string code_jump() {
    string jumpString = Jump();
    switch(jumpString) {
        case "":
            return "000";
            
        case "JGT":
            return "001";
            
        case "JEQ":
            return "010";
            
        case "JGE":
            return "011";
            
        case "JLT":
            return "100";
            
        case "JNE":
            return "101";
            
        case "JLE":
            return "110";

        case "JMP":
            return "111";
            
        default:
            return "000";
    }
}

지금 우리는 Symbol이 없는 Assembler를 구현하고 있으므로, SymbolTable에 대한 내용은 생략하고 Assembler 구현을 완성할 예정이다. 온전한 Hack Assembler는 다음 글에서 만나볼 수 있다!

 

MainProgram 모듈

Main Program에서는 위에서 만든 Parser, Code 모듈을 결함하고 작동하도록 만들어주어야 한다. Input File을 SreamReader로 불러오고, 번역된 결과로 기계어를 SreamWriter를 통해 파일로 작성해주는 MainProgram을 작성해보자. 여기에는 Parser 모듈의 StreamReader 준비하는 과정 또한 포함되어있다.

// Assembler class 안에 변수 선언
string inputFile;
string outputFile;

StreamReader SR;
StreamWriter SW;

// 생성자가 MainProgram 역할 실행
public Assembler(string fileName) {
    // File Format이 .asm인지 검사
    if(!fileName.EndsWith(".asm")) {
        Console.WriteLine("Wrong File Format");
        return;
    }
    
		// Stream 열어주기
    inputFile = fileName;
    outputFile = String.Copy(fileName).Replace(".asm", ".hack");
    SR = new StreamReader(inputFile);
    SW = new StreamWriter(outputFile);

    string stringToWrite;

    Console.WriteLine("Start");
		// StreamReader의 작업이 끝날 때 까지 반복하며
		// 명령의 종류에 따라 작업 수행
    while(HasMoreCommands()) {
        Advance();
        switch(CommandType()) {
            case 'A':
                stringToWrite = Symbol();
                SW.WriteLine(stringToWrite);
                break;
                
            case 'C':
                stringToWrite = ("111" + code_comp() + code_dest() + code_jump());
                SW.WriteLine(stringToWrite);
                break;
                
            case 'L':
                break;
                
            default:
                break;
        }
    }

		// 다 끝나면 Stream 닫아주기 <- 이걸 몰라서 20분 헤맸다;;
    SR.Close();
    SW.Close();
    Console.WriteLine("End");
}

여기까지 만들면 Symbol이 없는 단순한 assembly 파일을 hack 기계어로 바꿀 수 있는 Assembler를 구현하였습니다. 아래 코드는 기호처리를 하지 않는 Assembly 전체 코드입니다.

기호 처리가 포함된 Assembler에 대해서는 다음 글에서 정리하도록 하겠습니다!

using System;
using System.Collections;
using System.IO;

namespace HackAssembler2 {
    public class Assembler {

// ********** MainProgram Module **********
        string inputFile;
        string outputFile;

        StreamReader SR;
        StreamWriter SW;

        public Assembler(string fileName) {
            // File Format이 .asm인지 검사
            if(!fileName.EndsWith(".asm")) {
                Console.WriteLine("Wrong File Format");
                return;
            }
            
            inputFile = fileName;
            outputFile = String.Copy(fileName).Replace(".asm", ".hack");
            SR = new StreamReader(inputFile);
            SW = new StreamWriter(outputFile);

            string stringToWrite;

            Console.WriteLine("Start");
            while(HasMoreCommands()) {
                Advance();
                switch(CommandType()) {
                    case 'A':
                        stringToWrite = Symbol();
                        SW.WriteLine(stringToWrite);
                        break;
                        
                    case 'C':
                        stringToWrite = ("111" + code_comp() + code_dest() + code_jump());
                        SW.WriteLine(stringToWrite);
                        break;
                        
                    case 'L':
                        break;
                        
                    default:
                        break;
                }
            }

            SR.Close();
            SW.Close();
            Console.WriteLine("End");
        }

// ********** Parser Module **********
        // 현재 처리중인 line을 저장하는 변수 선언
        string line;

        public bool HasMoreCommands() {
            if(SR.Peek() > -1) { // StreamReader 의 Peek 함수 는 뒤에 line이 더 있는지 검사를 수행한다.
                return true;
            }
                return false;
        }

        public void Advance() {
            // 뒤에 Command가 있을 때 수행해야 한다.
            if(HasMoreCommands()) { 
                line = RemoveSpaceComment(SR.ReadLine());
            }
        }

            // 공백 제거를 위한 메서드
        string RemoveSpaceComment(string input) {
            return input.Replace(" ", "").Replace("    ", "").Split("//")[0];
        }
        
        public char CommandType() {
                // 주석인 경우 또는 빈 줄인 경우
            if (line.StartsWith("//") || line.Length == 0) {
                return 'X';
            } 
                // @로 시작하는 경우
            else if(line[0]=='@') {
                return 'A';
            }
                // (로 시작하는 경우
            else if (line[0]=='(') {
                return 'L';
            }
                // 그 외
            else {
                return 'C';
            }
        }

        public string Symbol() {
            char[] sym;
            
            if(CommandType()=='A') {
                sym = new char[line.Length-1];    
                                // Symbol 앞에 @ 이 추가되었으므로, Symbol의 길이는 명령어의 Length-1
                line.CopyTo(1, sym, 0, line.Length-1); // Symbol만 Copy해오기

                //Value 인 경우 16자리 2진수 string으로 반환
                if('0' <= sym[0] && sym[0] <= '9') {
                    return Convert.ToString(Convert.ToInt32(String.Join("", sym)),2).PadLeft(16, '0');
                } 
                //Symbol 인 경우
                else {
                        // 지금은 기호가 없는 hack만 처리한다!
                }
                
            } 
            else if(CommandType()=='L') {
                // 지금은 기호가 없는 hack만 처리한다!
            }
            
            // 제외되는 경우 없음
            return null;
        } 

        public string Dest() {
            int equalIndex = line.IndexOf('=');
            
            // dest는 = 이 있을 때만 존재한다
            // = 이 없는 경우 생략

            if(equalIndex < 0){
                return "000";
            }

            char[] dest = new char[equalIndex];
            line.CopyTo(0, dest, 0, equalIndex);
            
            return String.Join("",dest);
        }

        public string Comp() {
                // '=' 와 ';'의 유무에 따라 dest, jump의 유무가 결정된다.
            int equalIndex = line.IndexOf('=');
            int colonIndex = line.IndexOf(';');
            
            char[] comp;
            
                // colon이 없는 경우 ( D=M-D 같은 경우 )
            if(colonIndex < 0) {
                comp = new char[line.Length-1 - equalIndex]; // equal 뒷부분만 가져오기
                line.CopyTo(equalIndex+1, comp, 0, line.Length-1-equalIndex);
                return String.Join("", comp);
            }
            
                // equal이 없는 경우 ( D;JMP 같은 경우 )
            if(equalIndex < 0) {
                comp = new char[colonIndex];    // colon 앞부분만 가져오기
                line.CopyTo(0, comp, 0, colonIndex);
                return String.Join("", comp);
            }

                // 둘 다 있는 경우는 없다.
            return null;
        }     

        public string Jump() {
            int colonIndex = line.IndexOf(';');
            char[] jump = new char[line.Length-colonIndex-1];

                // colon이 있을 때만 jump가 존재한다.
            if(colonIndex >= 0) {
                line.CopyTo(colonIndex+1, jump, 0, line.Length-1-colonIndex);
            }
            
            return String.Join("", jump);
        }   

// ********** Code Module **********
        public string code_dest() {
            string destString = Dest();
            char[] result = new char[3] {'0','0','0'};
                
            // 각 Bit에 대해 할당된 Destination의 유무에 따라 Bit Set
            if(destString.IndexOf('M') >= 0) {
                result[2] = '1';
            }
            if(destString.IndexOf('D') >= 0) {
                result[1] = '1';
            }
            if(destString.IndexOf('A') >= 0) {
                result[0] = '1';
            }
            
            return new string(result);
        }

        public string code_comp() {
            string compString = Comp();
            switch(compString) {
                case "0":
                    return "0101010";
                    
                case "1":
                    return "0111111";
                    
                case "-1":
                    return "0111010";
                
                case "D":
                    return "0001100";
                    
                case "A" :
                    return "0110000";
                    
                case "M" :
                    return "1110000";
                
                case "!D" : 
                    return "0001101";
                    
                case "!A" : 
                    return "0110001";
                    
                case "!M" :
                    return "1110001";
                    
                case "-D" :
                    return "0001111";
                    
                case "-A" :
                    return "0110011";
                    
                case "-M" :
                    return "1110011";
                    
                case "D+1" :
                    return "0011111";
                
                case "A+1" :
                    return "0110111";
                    
                case "M+1" :
                    return "1110111";
                    
                case "D-1" :
                    return "0001110";
                    
                case "A-1" : 
                    return "0110010";
                    
                case "M-1" :
                    return "1110010";
                    
                case "D+A" :
                    return "0000010";
                    
                case "D+M" :
                    return "1000010";
                    
                case "D-A" :
                    return "0010011";
                    
                case "D-M" :
                    return "1010011";
                    
                case "A-M" :
                    return "0000111";
                    
                case "M-D" :
                    return "1000111";
                    
                case "D&A" :
                    return "0000000";
                    
                case "D&M" :
                    return "1000000";
                    
                case "D|A" :
                    return "0010101";
                
                case "D|M" :
                    return "1010101";
                    
                default:
                    return "0000000";
            }
        }

        public string code_jump() {
            string jumpString = Jump();
            switch(jumpString) {
                case "":
                    return "000";
                    
                case "JGT":
                    return "001";
                    
                case "JEQ":
                    return "010";
                    
                case "JGE":
                    return "011";
                    
                case "JLT":
                    return "100";
                    
                case "JNE":
                    return "101";
                    
                case "JLE":
                    return "110";

                case "JMP":
                    return "111";
                    
                default:
                    return "000";
            }
        }
    }
}

 

이를 실행하기 위한 메인 프로그램은 아래와 같다.

using System;
using HackAssembler2;

class MainClass {
    public static void Main (string[] args) {
        Assembler test = new Assembler("Input2.asm");
    }
}

 

 

320x100
Share Link
reply
반응형
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31