猫語しかしゃべらなくなった先輩のためにCatLangageを作成した
catlangage compiler
コンパイラといっていますがmingw用のアセンブラをはくだけです。
機能はかなり少ないですが、猫語からアセンブラをはくことが可能です。
こちらがソースコードです。
疑問・不備等があった場合は私のツイッターでご連絡ください。@jumdtw
事の経緯
ツイッターでにゃんにゃんしか言わなくなってしまった方がいらっしゃります。
※本人の許可はとっております。
彼に何があったのでしょうか。それを想像するには私は未熟すぎます。しかし、何とかしてあげたい。そこで猫語からアセンブリ言語をはきだすコンパイラ、catcomを作成することにしました。
catcomができること
まぁ、要するにネタコンパイラなのでできることは相当限られてきます。
・加算減算を行いその結果を終了コードとしてはく(しかも、数字は0~7までしか使えません)
・特定の文字列を出力する
これだけです。
しかし、これだけあれば彼は特定の文字列のみですが会話ができ終了コードを吐き出すことができるのです!!!!
ちなみに特定の文字列とは以下です。
・I want meet.
・goodmorning.
・Im tired.
もっといっぱい実装はできますがネタなのでこんなものでしょう。
catlangage 仕様
そんでもって猫語からアセンブリ言語をはくといいましたが、「にゃー」だの「ゴロゴロ」だの日本語でやるのは少々面倒くさいので英語表記での猫語で処理をしていきます。以下はサイトを参考にして得たものです。
・meow:にゃー
・purr:ゴロゴロ
・roar:ガオー
これを数値と演算子などに当てはめていきます。
数値
meowを0、purrを1として表現している。
数値は "meow" から始まる
- 0:meowmeowmeowmeow
- 1:meowmeowmeowpurr
- 2:meowmeowpurrmeow
- 3:meowmeowpurrpurr
- 4:meowpurrmeowmeow
- 5:meowpurrmeowpurr
- 6:meowpurrpurrmeow
- 7:meowpurrpurrpurr
演算子と文字列表示
演算子は "roar" から始まります。
+:roarmeow
-:roarpurr
文字列表示は "purr" から始まります。
・I want meet. :purrmeowmeow
・goodmorning. :purrmeowpurr
・Im tired. :purrpurrpurr
実装方法
以下のサイトがとても参考になりました。
私が説明しようとするとほとんど以下のサイトの反復説明になってしますので今回のcatlangage特有部分のみ説明していこうと思います。
トークンナイザ
以下がトークンナイザでの判別になります。
// tokenize.cpp //空白文字をスキップ if(isspace(*p)||*p=='\n'||*p=='\t'){ p++; continue; } // ステーキの判別 if(strncmp(p,"purrmeowmeow",12)==0&&!is_alnum(p[12])){ token.ty = TK_MEET; token.str = p; p+=12; tokens.push_back(token); continue; } // . // . // こんな感じで文字列の判別をおこなう // . // . // +の判別 if(strncmp(p,"roarmeow",8)==0&&!is_alnum(p[8])){ token.ty = TK_PLUS; token.str = p; p+=8; tokens.push_back(token); continue; } // . // . // こんな感じで+-の判別をおこなう // . // . // 数値の判別 // 0 if(strncmp(p,"meowmeowmeowmeow",16)==0&&!is_alnum(p[16])){ token.ty = TK_NUM; token.val = 0; token.str = p; p+=16; tokens.push_back(token); continue; } // . // . // こんな感じで数値の判別をおこなう // . // .
このように一つ一つ判別していきます。
そのためあんまり多くの判別はできません(本来はこんなことをしないで文字か数字かで判断しますが、猫語のためにこんな判断をしています)。
パーサ
以下のようにパースしていきます。
expr = add add = primary ("+" primary | "-" primary)* | meet | tired | hello primary = num
それで実際にこれをコードで実装すると以下のようになります。
Node *primary(){ if(tokens[pos].ty == TK_NUM){ return new_node_num(tokens[pos++].val); } printf("%s\n",tokens[pos].str); printf("error gen node\n"); exit(1); } Node *add(){ if(tokens[pos].ty==TK_MEET){ return new_node(ND_MEET,NULL,NULL); }else if(tokens[pos].ty==TK_HELLO){ return new_node(ND_HELLO,NULL,NULL); }else if(tokens[pos].ty==TK_TIRED){ return new_node(ND_TIRED,NULL,NULL); } Node *node = primary(); for(;;){ if(tokens[pos].ty==TK_PLUS){ pos++; node = new_node(ND_PLUS,node,primary()); }else if(tokens[pos].ty==TK_MINUS){ pos++; node = new_node(ND_MINUS,node,primary()); }else{ return node; } } } Node *expr(){ return add(); }
expr始動で解析をしていきます。
最初に文字列の表示をするか否かを判別し、文字列でなかったら数字確定なので加算減算での解析をします。機能も少ない分コードもそこまで複雑ではないです。
コードジェネレーター
私の環境では以下の環境でアセンブラを処理します。
> gcc --version gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. > as --version GNU assembler (GNU Binutils) 2.30 Copyright (C) 2018 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or later. This program has absolutely no warranty. This assembler was configured for a target of `x86_64-w64-mingw32'.
なのでそのためのコードを生成します。
以下はサンプルです。
// 文字列の表示 .text .LC0: .ascii "hello\0" .globl main .def main; .scl 2; .type 32; .endef .seh_proc main main: pushq %rbp .seh_pushreg %rbp movq %rsp, %rbp .seh_setframe %rbp, 0 subq $32, %rsp .seh_stackalloc 32 .seh_endprologue call __main leaq .LC0(%rip), %rcx call puts movl $0, %eax addq $32, %rsp popq %rbp ret .seh_endproc
// 加算 .text .def __main; .scl 2; .type 32; .endef .globl main .def main; .scl 2; .type 32; .endef .seh_proc main main: pushq %rbp .seh_pushreg %rbp movq %rsp, %rbp .seh_setframe %rbp, 0 subq $32, %rsp .seh_stackalloc 32 .seh_endprologue call __main push $1 // 数値を二つpushしておく push $4 pop %rdi // push した値をそれぞれレジスタに入れる pop %rax add %rdi, %rax // その値を使って加算する push %rax pop %rax // raxにpopして終了コードとして返す addq $32, %rsp popq %rbp ret .seh_endproc
これらコードを生成するためのコードジェネレータが以下のようになります。
// comcat.cpp void gen(Node *node){ if(node->ty == ND_MEET){ printf(" .text\n"); printf(".LC0:\n"); printf(" .ascii "); printf("\""); printf("I want meet.\\0"); printf("\""); printf("\n"); printf(" .globl main\n"); printf(" .def main; .scl 2; .type 32; .endef\n"); printf(" .seh_proc main\n"); printf("main:\n"); printf(" pushq %%rbp\n"); printf(" .seh_pushreg %%rbp\n"); printf(" movq %%rsp, %%rbp\n"); printf(" .seh_setframe %%rbp, 0\n"); printf(" subq $32, %%rsp\n"); printf(" .seh_stackalloc 32\n"); printf(" .seh_endprologue\n"); printf(" call __main\n"); printf(" leaq .LC0(%%rip), %%rcx\n"); printf(" call puts\n"); printf(" movl $0, %%eax\n"); printf(" addq $32, %%rsp\n"); printf(" popq %%rbp\n"); printf(" ret\n"); printf(" .seh_endproc\n"); return; } // . // . // ND_MEETなどだったらその段階で文字列表示確定なのでそのままサンプルのようなコードをはく // . // . // 数値は計算に必要なのでスタックにpushする if(node->ty == ND_NUM){ printf(" push $%%d\n",node->val); return; } // 数値・文字列でなかったらスタックの値を使って計算していく printf(" .text\n"); printf(" .def __main; .scl 2; .type 32; .endef\n"); printf(" .globl main\n"); printf(" .def main; .scl 2; .type 32; .endef\n"); printf(" .seh_proc main\n"); printf("main:\n"); printf(" pushq %%rbp\n"); printf(" .seh_pushreg %%rbp\n"); printf(" movq %%rsp, %%rbp\n"); printf(" .seh_setframe %%rbp, 0\n"); printf(" subq $32, %%rsp\n"); printf(" .seh_stackalloc 32\n"); printf(" .seh_endprologue\n"); printf(" call __main\n"); gen(node->lhs); gen(node->rhs); printf(" pop %%rdi\n"); printf(" pop %%rax\n"); switch(node->ty){ case ND_PLUS: printf(" add %%rdi, %%rax\n"); break; case ND_MINUS: printf(" sub %%rdi, %%rax\n"); break; } printf(" addq $32, %%rsp\n"); printf(" popq %%rbp\n"); printf(" ret\n"); printf(" .seh_endproc\n"); }
実行結果
文字列出力コードは以下です。
// test.cat purrmeowmeow
実行コードはこちらです。ちなみに、アセンブラをprintfで表示しているだけなのでパイプなどを使っていい感じにファイルに出力します。
// 文字列出力 > .\bin\comcat.exe test.cat > test.s > as -o test.o test.s > gcc -o test test.o > test.exe I want meet.
演算のコードは以下です。
// calc.cat meowmeowmeowpurr roarmeow meowmeowmeowpurr
実行コードはこちらです。windowsでは%errorlevel%で終了コードを確認できます。
> .\bin\comcat.exe calc.cat > calc.s > as -o calc.o calc.s > gcc -o calc calc.o > calc.exe > echo %errorlevel% 2
こんな感じです。これで彼は「おはよう」・「疲れた」・「肉食べたい」を猫語のみで表現できるのです!!!
まとめ
まぁ、ネタなのでこんなもんでしょう。コンパイラの勉強に使用した以下のサイトはとても勉強になったのでおすすめです。
低レイヤを知りたい人のためのCコンパイラ作成入門
参考
MinGWのアセンブラでhello world
低レイヤを知りたい人のためのCコンパイラ作成入門
猫の英語辞典:猫の鳴き声って英語でなんて言うの?