Pythonを使って擬似的にプログラミング言語を自作する 改良版

[う　え　き　ぷ　に　き　あ　く　ん　笑 Advent Calendar 2021](https://adventar.org/calendars/6939 "うえきです")　16日目です

##環境##
macOS Catalina 10.15.7
python 3.9.7
clang 12.0.0

##やりたいこと##
[前回の記事](https://qiita.com/Ll_e_ki/items/7cc104525e95a4799cd9, "Pythonを使って擬似的にプログラミング言語を自作する")のコードを改良したい（主に計算量最適化など）
ces言語（作成した言語）の機能を少し増やしたい

##計算量を解析してみる##
最初に、前回の記事で最終的に完成したコードの計算量を簡単に解析してみます。めちゃくちゃ見にくいコードですが、当時のぼくを尊重して（？）一旦コピペをします（コメントアウト部分を少し変更しています）。

```python:ces.py
# コマンド実行・エラー発生時に途中終了するためのモジュールを読み込む
import sys
import os

# 変数の宣言
inp=""
tmp=0
out=""
INPUT=[]
OUTPUT=[]
ERROR=[]
k=0
com=""
bo=False

# トランスパイルの対応表
ces_command=["imp","usi","impio","impbit","out","box","!","#","retu","{","}"]
cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["#include <iostream>\nusing namespace std;"],["#include <bits/stdc++.h>\nusing namespace std;"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],[1],["// ",1],["return ",1,";"],["{"],["}"]]

# 引数の取得
fni=sys.argv
fn=fni[1]
cn=fni[2]

# 第1引数で指定されたファイルを読み込み、cesのコードをリストに追記していく
_file=open(fn,"r")
for line in _file:
    inp=line
    if inp[0]=="!":
        INPUT.append(["!",inp[1:len(inp)]])
    else:
        INPUT.append(list(map(str,inp.split())))

# ファイルを閉じる
_file.close()

# 対応表を使ってトランスパイル・エラーの判定
for i in S:
    if i[0] in ces_command:
        tmp = ces_command.index(i[0])
        for u in cpp_command[tmp]:
            if type(u) is int:
                if u<=len(i)-1:
                    out += i[u]
                else:
                    k=0
                    for q in cpp_command[tmp]:
                        if type(q) is int and q > k:
                            k=q
                    ERROR.append([str(i)," ^\nerror!\nThere are not enough arguments\nRequired amount : "+str(k)])
                    bo=True
                    break
            else:
                out += u
        OUTPUT.append([out])
        out = ""
    elif i != ["END"]:
        ERROR.append([i," ^\nerror!\nThere is no ",i," instruction"])
        bo=True

# エラーが出ていなければトランスパイルした結果を第2引数で指定されたファイル.cppに上書きし、トランスパイルの成功を通知する。エラーが出ていればエラーを出力する
if bo == True:
    
    # エラーの出力を見やすくするために一行空ける
    print()
    for i in E:
        for j in i:
            print(j)
        print()
else:
    print(":) < ces trance was successful!")
    _file = open(cn+".cpp","w")
    for i in O:
        for j in i:
            _file.write(j)
    # ファイルを閉じ、第2引数で指定されたファイル名の実行ファイルを作成する
    _file.close()
    com="g++ "+cn+".cpp -o "+cn
    os.system(com)
    # トランスパイルしてcppファイルは消しておく
    com="rm "+cn+".cpp"
    os.system(com)
```

なぁ〜にいっちょまえにinなんて使ってるんだか　頭が痛いです
一度コードの流れを書き出してみると次のようになっています。

cesを一行ずつ読み、その行が命令として定義されているかをリストを全探索して調べ、定義されているなら対応するcppのコードを取得する。その後、cppのコード中に引数を必要とする箇所があるかを調べ、あるなら渡す。
cesの命令として定義されていない場合や、必要な引数が用意されていない場合はエラーとする。

このとき、ces言語の行数, 命令の種類数をN, Mとし、cesに対応するcppコードをトランスパイルしやすく区切った量をKとすると、時間計算量はO(N(M+K))となります。

##計算量を改善する
cesとcppを対応させるのにdictを使いましょう
おわり　おわりです　計算量はO(NK)です

##機能を追加する
さすがにこれだけだとやばいので、機能を追加しようと思います

```python
# コマンド実行・エラー発生時に途中終了するためのモジュールを読み込む
import sys
import os

# 変数の宣言
inp=""
tmp=0
out=""
INPUT=[]
OUTPUT=[]
ERROR=[]
k=0
com=""
bo=False
inf_count=0

# トランスパイルの対応表
ces_to_cpp={
    "imp": ["#include <",1,">"], 
    "usi": ["using namespace ",1,";"], 
    "impio": ["#include <iostream>\nusing namespace std;"], 
    "impbit": ["#include <bits/stdc++.h>\nusing namespace std;"], 
    "out": ["cout << ",1," << endl",";"], 
    "box": [1," ",2," = ",3,";"], 
    "!": [1], 
    "#": ["// ",1], 
    "retu": ["return ",1,";"], 
    "{": ["{"], 
    "}": ["}"], 
    "rep{": ["for(int ",1,"=",2,";",1,3,4,";",1,"++){"], 
    "inf": ["while(true){"], 
    "break": ["break;"], 
    "func": [1," ",2,"()","{"]
}

# 引数の取得
fni=sys.argv
if len(fni)!=3:
    print("No input files")
    exit(1)
fn=fni[1]
cn=fni[2]

# 第1引数で指定されたファイルを読み込み、cesのコードをリストに追記していく
_file=open(fn,"r")
for line in _file:
    inp=line
    if inp[0]=="!":
        INPUT.append(["!",inp[1:len(inp)]])
    else:
        INPUT.append(list(map(str,inp.split())))

# ファイルを閉じる
_file.close()

# 対応表を使ってトランスパイル・エラーの判定
for i in INPUT:
    if not i:
        continue
    if i[-1][-2:] == "\n":
        i[-1] = i[-1][:-2]
    if i[0] in ces_to_cpp:
        tmp = ces_to_cpp[i[0]]
        if i[0] == "inf":
            inf_count += 1

        for u in tmp:
            if type(u) is int:
                if u<=len(i)-1:
                    out += i[u]
                else:
                    k=0
                    for q in tmp:
                        if type(q) is int and q > k:
                            k=q
                    ERROR.append([str(i)," ^\nerror!\nThere are not enough arguments\nRequired amount : "+str(k)])
                    bo=True
                    break
            else:
                out += u
        OUTPUT.append([out])
        out = ""
    elif i != ["END"]:
        ERROR.append([i," ^\nerror!\nThere is no ",i," instruction"])
        bo=True
for i in range(inf_count):
    OUTPUT.append(["}"])


# エラーが出ていなければトランスパイルした結果を第2引数で指定されたファイル.cppに上書きし、トランスパイルの成功を通知する。エラーが出ていればエラーを出力する
if bo == True:
    
    # エラーの出力を見やすくするために一行空ける
    print()
    for i in ERROR:
        for j in i:
            print(j)
        print()
else:
    print(":) < ces trance was successful!")
    _file = open(cn+".cpp","w")
    for i in OUTPUT:
        for j in i:
            _file.write(j)
    # ファイルを閉じ、第2引数で指定されたファイル名の実行ファイルを作成する
    _file.close()
    com="g++ "+cn+".cpp -o "+cn
    os.system(com)
    # トランスパイルしてcppファイルは消しておく
    com="rm "+cn+".cpp"
    os.system(com)
```
しました（え？）
無限ループ
for文
break
と、うまく動作しない部分を修正しました。
今回試したcesファイルです。

```
impio
func int main null
box int nyan 5
box int i 0
rep{ i 0 < nyan
out nyan
out i
}
inf
!i++;
out i
!if(i==10){
retu 0
break
}
}
```

##終わりです2
今回の記事を執筆するにあたって思ったことがあります。それは、
###保守性はめちゃくちゃ大事
ということです。改良するにあたり、コードを読む時間が本当に無限時間必要でした。いつか保守性についての記事も書いてみたいです。

明日の記事は〜〜〜うえきさん！だよ！！！