#はじめに
「コンピューターシステムの理論と実装」、第6章のassemblerをC++で作ってみた。作っていく過程で得た知識をここに並べていく。一番下に完成したcodeを貼り付けておく。
(省略しているが全て、using namespace std;をしている)
#1.main関数に対する引数
Cを学ぶ上で最初に出会う
int main()
だが、このmain関数に引数を与えることができる。通例
int main (int argc, char *argv[])
として引数が与えられる。
argc,argvとは?
によると
argc=argument count
argv=argument vector
らしい。
なんらかのfileを実行するとき、例えばhogehogeだとして
hogehoge neko sarada
などとして実行すると引数の中身は
argc=3
argv[0]=hogehoge
argv[1]=neko
argv[2]=sarada
となる。
これを用いて関数に.asmのfileを引数として与えることができる。(実際には何かと面倒なのでcinを使って与えている。)
#fileの取得方法と一行ずつ受け取る
C++のライブリにある
#include<fstream>
ifstream file1(read-filename);
ofstream file2(write-filename);
//file.close()
を使用した。
最初はCにある fopenを使おうと思ったけど上記の方が書くのが楽なのでこちらを採用した。参考にしたのはこれ
C++でテキストに書き出す方法
fileの変数がその場で作られるのが楽だと思う。
この場合開くのはAdd.asm等のassemble fileなので各行を文字列として取得したい。そんなときに活躍してくれたのがgetline関数である。こちらは
string str;
ifstream file("Pong.asm");
while(getline(file,str)){
cout<<str<<endl;
}
これだけで一行ずつstrに代入していってくれる。参考にしたのはこれ
getline()で文字列分割する方法
取得できればあとはコメントやラベルシンボル、空白等を除外してCOMMANDを抽出していく。
ちなみに各行の文字でNULLだったりを確認するのに私が使ったのが型変換である。
string str;
ifstream ifile("Add.asm");
while(getline(ifile,str)){
for(int i=0;i<24;i++){
int a=str[i];
cout<<a<<" ";
}
}
こうすれば取得した行の各文字のUTF-8における文字コードが得られる(char→intに型変換してるだけ)。
これでCOMMANDの抽出が楽にできた。
#mapの作成
assembler作成で初めて触ったmapだが便利だった。
mapはsymbolとそのsymbolがさすaddressに使用した(というか本でもそれを推奨している)。
#include<map>
map<string,int> table;
int line=0;
int var=16;
Global 変数として symbol table・行変数・変数symbolの値を定義しておく。
定義済みシンボルの初期化をしたら完了である。
mapに関してはここを参考にした。
std::mapまとめ
#stringで特定の文字の取得方法
C_COMMANDにおいては '=' ';'の存在がdest,jumpを決定づける。この文字を見つけるのにfind関数を使ってもいいけど戻り値がキモかったので自分で作った。
int eq=0;
int semi=0;
for(int i=0;i<size;i++){
if(str[i]=='=')
{eq=i;} else
if(str[i]==';')
{semi=i;}
}
作ったといってもこれだけである。しかし、こうすればeq,semiが真偽判定にも使えるので便利だった。(変数 sizeはその行が改行またはコメントが入るまでの値。)
#charからintへの変換
これは先程も書いたが型変換で終わり
#stringからintへの変換
今回のfileではstoi関数を使ったが自分でも実装できると思うので今後作ってみたい。
string str="1234";
int a=stoi(str);
これでaに数値1234が代入された。
#string.erase()の使い方
これは使いずらかったので今後はあまり使いたくない。
string str="0123456789";
str.erase(0,2);
cout<<str<<endl;
erase(m,n)でm文字目からn文字消去してくれる。nを指定しなければm文字目以降を消去する。上の場合では0と1が消えて23456789が表示される。この場合、strは消去された状態で保存されるため、C_COMMANDを抽出するときは新しくstrを作って欲しいCOMMANDの部分以外を消去していた。
文字列全般に関してはここを参考にした。
C++ の文字列操作まとめ
#関数への引数とポインタの関係 戻り値
引数として変数を渡して、その関数内で変数が変更されても親元の関数ではその変数はそのままである。変更を残したい時に役立つのがポインタである。関数への引数にポインタを渡すには
void judge(bool *A_COMMAND)
judge(&A_COMMAND);
などの書き方がある。参考はここ
関数の作り方と呼び出し方
#Alphabetかどうかの判定
UTF-8に限るが単純に文字コードを使用すれば良い
しかし、パッと見て分かりやすいのは以下の表現である
if(('A'<=c)&&(c<='z')){*L_COMMAND=true;}else *A_COMMAND=true;}
Aとzなら上の表からzの方が上である。これだけでsymbolかどうか判定できる。
#stringの連結
上で文字列の参考ページは出した。そこにもあるように+演算子で足し合わせるだけであるが、私の環境では
str0=str0+str1;
このように自分自身を最初に置かなければerrorが起きた。
#数値のバイナリ変換
assemblerの主目的はニモニックを01に変換することである。
C命令に関しては場合分けで01を作ったがA命令のaddressに関しては数値をバイナリにするのが良い。そこで使ったのがである。
#include<bitset>
bitset<15>b1(address);
return b1.to_string();
address内の数値を15bitの二進数に変換して、それを文字列として返す関数の一部である。
参考は特にないがbitsetを確認してね。
#おわりに
最初はよく分からない状態で調べて時間かかるけど、こうやって実際に何か作るとその言語を自分の物にできる。急がば回れというが作り終われば全部無駄じゃないと思える。
そうやって何か作る場所の一つがAtCorder何だろうなー、やらなきゃなー
#Assembler
一応貼っとくね
#include<iostream>
#include<stdio.h>
#include<string>
#include<algorithm>
#include<cmath>
#include<fstream>
#include<bitset>
#include<map>
using namespace std;
map<string,int> table;
int line=0;
int var=16;
#define Pi (3.141592653589793238)
void makemap(){
table["R0"]=0;
table["R1"]=1;
table["R2"]=2;
table["R3"]=3;
table["R4"]=4;
table["R5"]=5;
table["R6"]=6;
table["R7"]=7;
table["R8"]=8;
table["R9"]=9;
table["R10"]=10;
table["R11"]=11;
table["R12"]=12;
table["R13"]=13;
table["R14"]=14;
table["R15"]=15;
table["SP"]=0;
table["LCL"]=1;
table["ARG"]=2;
table["THIS"]=3;
table["THAT"]=4;
table["SCREEN"]=16384;
table["KBD"]=24576;
}
void commandType(bool *A_COMMAND,bool *C_COMMAND,bool *L_COMMAND,char c1,char c2){
if(c1=='@'){
if(('A'<=c2)&&(c2<='z')){*L_COMMAND=true;}else *A_COMMAND=true; }
else {*C_COMMAND=true;}
}
int counter(string str){
int count=0;
for(int i=0;i<sizeof(str);i++){
if((str[i])&&(str[i]!='/')){
count+=1;}else break;
}
return count;
} //文字数+1の値 "@R0" なら4となる
void loopsymbol(string str){
string symbol;
for(int i=1;i<64;i++){
if(str[i]==')'){break;}
symbol=symbol+str[i];
}
table[symbol]=line;
}
string address(string str){
str.erase(0,1);
int address=stoi(str);
bitset<15>b1(address);
return b1.to_string();
}
string symbol(string str){
string symbol;
for(int i=1;i<64;i++){
if(str[i+1]=='\0'){break;}
symbol=symbol+str[i];
}
if(!table.count(symbol)){
table[symbol]=var;
var=var+1;
}
bitset<15>b1(table[symbol]);
return b1.to_string();
}
string comper(string comp){
int size=counter(comp)-1;
string code="0000000";
if(comp[0]=='0'){code="0101010";}
if(comp[0]=='1'){code="0111111";}
if(comp[0]=='D'){code="0001100";}
if(comp[0]=='A'){code="0110000";}
if(comp[0]=='M'){code="1110000";}
if((comp[0]=='-')&&(comp[1]=='1')){code="0111010";}
if((comp[0]=='!')&&(comp[1]=='D')){code="0001101";}
if((comp[0]=='-')&&(comp[1]=='D')){code="0001111";}
if((comp[0]=='!')&&(comp[1]=='A')){code="0110001";}
if((comp[0]=='-')&&(comp[1]=='A')){code="0110011";}
if((comp[0]=='!')&&(comp[1]=='M')){code="1110001";}
if((comp[0]=='-')&&(comp[1]=='M')){code="1110011";}
if((comp[0]=='D')&&(comp[1]=='+')&&(comp[2]=='1')){code="0011111";}
if((comp[0]=='A')&&(comp[1]=='+')&&(comp[2]=='1')){code="0110111";}
if((comp[0]=='D')&&(comp[1]=='-')&&(comp[2]=='1')){code="0001110";}
if((comp[0]=='A')&&(comp[1]=='-')&&(comp[2]=='1')){code="0110010";}
if((comp[0]=='D')&&(comp[1]=='+')&&(comp[2]=='A')){code="0000010";}
if((comp[0]=='D')&&(comp[1]=='-')&&(comp[2]=='A')){code="0010011";}
if((comp[0]=='A')&&(comp[1]=='-')&&(comp[2]=='D')){code="0000111";}
if((comp[0]=='D')&&(comp[1]=='&')&&(comp[2]=='A')){code="0000000";}
if((comp[0]=='D')&&(comp[1]=='|')&&(comp[2]=='A')){code="0010101";}
if((comp[0]=='M')&&(comp[1]=='+')&&(comp[2]=='1')){code="1110111";}
if((comp[0]=='M')&&(comp[1]=='-')&&(comp[2]=='1')){code="1110010";}
if((comp[0]=='D')&&(comp[1]=='+')&&(comp[2]=='M')){code="1000010";}
if((comp[0]=='D')&&(comp[1]=='-')&&(comp[2]=='M')){code="1010011";}
if((comp[0]=='M')&&(comp[1]=='-')&&(comp[2]=='D')){code="1000111";}
if((comp[0]=='D')&&(comp[1]=='&')&&(comp[2]=='M')){code="1000000";}
if((comp[0]=='D')&&(comp[1]=='|')&&(comp[2]=='M')){code="1010101";}
return code;
}
string dester(string str){
int length=str.size();
string dest="000";
for(int i=0;i<length;i++){
if(str[i]=='A'){dest[0]='1';}
if(str[i]=='D'){dest[1]='1';}
if(str[i]=='M'){dest[2]='1';}
}
return dest;
}
string jumper(string str){
string jump="111";
if((str[0]=='G')&&(str[1]=='T')){jump[0]='0'; jump[1]='0';}
if((str[0]=='E')&&(str[1]=='Q')){jump[0]='0'; jump[2]='0';}
if((str[0]=='G')&&(str[1]=='E')){jump[0]='0';}
if((str[0]=='L')&&(str[1]=='T')){jump[1]='0'; jump[2]='0';}
if((str[0]=='N')&&(str[1]=='E')){jump[1]='0';}
if((str[0]=='L')&&(str[1]=='E')){jump[2]='0';}
return jump;
}
string ALU(string str,int size){
int eq=0;
int semi=0;
for(int i=0;i<size;i++){
if(str[i]=='=')
{eq=i;} else
if(str[i]==';')
{semi=i;}
}
string strc=str;
string comp;
if(!semi){comp=strc.erase(0,eq+1);}
if(!eq){comp=strc.erase(semi);}
comp=comper(comp);
string dest="000";
string strd=str;
string strj=str;
if(eq>0){dest=dester(strd.erase(eq));}
string jump="000";
if(semi>0){semi=semi+2; jump=jumper(str.erase(0,semi));}
string code=comp+dest+jump;
return code;
}
string Parser(string str){
string code="";
bool A_COMMAND=false;
bool C_COMMAND=false;
bool L_COMMAND=false;
int size=counter(str);
commandType(&A_COMMAND,&C_COMMAND,&L_COMMAND,str[0],str[1]);
if(A_COMMAND){code=code+'0'+address(str);}
if(C_COMMAND){code=code+"111"+ALU(str,size);}
if(L_COMMAND){code=code+'0'+symbol(str);}
code=code+"\n";
return code;
}
int main (int argc, char *argv[]){
string filename;
cin>>filename;
ifstream file(filename);
//open the file
string str;
string code;
makemap();
while(getline(file,str)){
if((str[0]=='@')||(str[3]=='@')) {
do{
if(str[0]==' '){str.erase(0,3);}
if(str[0]=='('){
loopsymbol(str);
}else{line=line+1;}
} while(getline(file,str));
break;
}
}
file.close();
string name;
for(int i=0;i<sizeof(filename);i++){
if(filename[i]=='.'){break;}
name=name+filename[i];
}
name=name+".hack";
ifstream file2(filename);
ofstream ofile(name);
while(getline(file2,str)){
if((str[0]=='@')||(str[3]=='@')) {
do{
if(str[0]=='('){continue;}
if(str[0]==' '){str.erase(0,3);}
code=Parser(str);
ofile<<code;
} while(getline(file2,str));
break;
}
}
file2.close();
ofile.close();
return 0;
}