LoginSignup
16
12

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-10-01

環境

macOS Mojave 10.14.4
python 3.5.4
g++ 9.2.0

やりたいこと

なるべく気軽にプログラミング言語を作って遊びたい
できれば実行ファイルを生成したい

どういう風にコンパイルするか

・コンパイラを作る

これはそこまで気軽でない&技術的に厳しい...ので別の方法を考える

・トランスパイルをしてから既存のコンパイラを使ってコンパイルする

コンパイラを作るのよりは気軽だし、個人的に楽しむだけなので強引な書き方をしてもいいのでこれにする

とりあえずトランスパイラを作ってみる

正しい作り方は知らないが別に今回は深く考えずに作ってみる
変換元の言語はcesという名前にし、返還後はc++のコードにすることにした

cesの仕様

・基本的にはBASICのような構文(というよりDckuino.jsのような感じかも)
・トランスパイルするとき、関数の引数は対応表の多重リスト内の番号で判別する

まずは単純にトランスパイルをしたソースコードを出力してみる

ces.py
# 変数の宣言
inp=""
tmp=0
out=""
INPUT=[]
OUTPUT=[]
k=0

# トランスパイルの対応表
ces_command=["imp","usi","out","box","#","retu","{","}","func"]
cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],["// ",1],["return ",1,";","\n}"],["{"],["}"],[1," ",2,"()","{"]]

# ENDと入力されるまで入力されたcesのコードをリストに追記
while inp != "END":
    inp=input()
    INPUT.append(list(map(str,inp.split())))

# 出力を見やすくするために一行空ける
print()

# 対応表を使ってトランスパイル
for i in INPUT:
    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:
                out += u
        OUTPUT.append([out])
        out = ""
# トランスパイルした結果を出力 
for i in OUTPUT:
    for j in i:
        print(j)

結果

---入力---
# いつものおまじない
imp bits/stdc++.h
imp string
usi std
# 変数の宣言・代入
box int i 1
box int j 10
box string s “HelloWorld!”
# main関数
func int main
# 出力
out s
out i+j
# 0を返す
retu 0
# 終了
END

---出力---
// いつものおまじない
#include <bits/stdc++.h>
#include <string>
using namespace std;
// 変数の宣言・代入
int i = 1;
int j = 10;
string s = “HelloWorld!”;
// main関数
int main(){
// 出力
cout << s << endl;
cout << i+j << endl;
// 0を返す
return 0;
}
// 終了

うまくいった(インデントは汚いけど最終的にコンパイルするので気にしないでおく)

エラーコードを出力してみる

今のままだと存在しない命令を呼び出したときや、関数の引数が足りない場合(引数が多い場合、余計な引数は無視される)、無視してそのままトランスパイルされてしまうので、その場合はエラーメッセージを出力してトランスパイルを失敗させたい。
今回は存在しない命令を呼び出したときと関数の引数が足りないときの2パターン分のエラーメッセージを用意すればいいので、

"^\nerror!\nThere are not enough arguments\nRequired amount : "n

" ^\nerror!\nThere is no ","code"," instruction"

Google先生の協力のもとエラーメッセージを作成した
これを先ほどのコードに混ぜると

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

# トランスパイルの対応表
ces_command=["imp","usi","out","box","#","retu","{","}","func"]
cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],["// ",1],["return ",1,";","\n}"],["{"],["}"],[1," ",2,"()","{"]]

# ENDと入力されるまで入力されたcesのコードをリストに追記
while inp != "END":
    inp=input()
    if inp[0]=="!":
        INPUT.append(["!",inp[1:len(inp)]])
    else:
        INPUT.append(list(map(str,inp.split())))

# 出力を見やすくするために一行空ける
print()

# 対応表を使ってトランスパイル・エラーの判定
for ind,i in enumerate(INPUT,1):
    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=0RR
                    for q in cpp_command[tmp]:
                        if type(q) is int and q > k:
                            k=q
                    ERROR.append([str(i)," ^\nline : "+str(ind)+" error!\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," ^\nline : "+str(ind)+" error!\nThere is no ",i," instruction"])
        bo=True

# エラーが出ていなければトランスパイルした結果を出力し、エラーが出ていればエラーを出力する
if bo == True:
    for i in ERROR:
        for j in i:
            print(j)
        print()
else:
    for i in OUTPUT:
        for j in i:
            print(j)

こうなり、

---入力---
imp bits/stdc++.h      
hello
world
niceday
hello
box int
out
END

---出力---
['hello']
 ^
line : 2 error!
There is no 
['hello']
 instruction

['world']
 ^
line : 3 error!
There is no 
['world']
 instruction

['niceday']
 ^
line : 4 error!
There is no 
['niceday']
 instruction

['hello']
 ^
line : 5 error!
There is no 
['hello']
 instruction

['box', 'int']
^
line : 6 error!
There are not enough arguments
Required amount : 3

['out']
^
line : 7 error!
There are not enough arguments
Required amount : 1

うまくいった!

ここまできたらあとはコンパイルをするだけ

コンパイルをどうやってするか

とりあえずコンパイラはg++を使うのは確定しているので、cesファイルを読み込んでトランスパイルをしたfile.cppを生成して、 g++ "file.cpp" -o "file.out" みたいなコマンドを実行するようにすれば良さそう

ces → c++ → 実行ファイルまでを実装したもの

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)

試してみる

cesファイル

ces_test.ces
imp bits/stdc++.h
imp string
usi std
box int i 1
box int j 10
box string s "hello"
box string t "world"
func int main null
out i+j
out s
out t
out "niceday"
retu 0

コンパイル

user:~ user$ python ces.py ces_test.ces ces_test.out
:) < ces trance was successful!
user:~ user$ ./ces_test.out
11
hello
world
niceday
user:~ user$ 

コンパイルまでできてる!!!

すごく嬉しい:)

最後に

思ったよりも簡単にできたし、何より楽しいので、ぜひ一度擬似的にプログラミング言語を自作してみることをオヌヌメします。今回は個人的に楽しむようなのでまだ機能不足すぎますが、これからどんどん強化するのも楽しそうです。機能の拡張をやり次第追記していこうと思います。

16
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
12