自作言語に引数実装(コード付き)

# 自作言語のお時間です。

どうも、新型MacをかってM1を試してみたいdangomushiです。
今回は、自作言語に「スコープ」の実装を施しました。また、機械語のように自分で読んでいてわからなくなっては元も子もないので、可読性も重視しました。
この記事は、前回の[自作言語でHelloWorld](https://qiita.com/ThinkpadL540/items/e2acdbb1eeba0eb0a71a)の続きです。まだご覧になってない方は、ぜひご覧ください。

## どうやったのか

前述のとおり、引数の追加を行いました。仕掛けは簡単です。変数用と関数用とで辞書を分け、変数用は関数がcallされたときにリセットしただけです。

# サンプルコード

まず最初は、「書き込んだファイルを仮想機械語に変換」するコード。

```python:main.py
#usr/bin/env python3

import sys, os, re, glob


#TODO:Main / カーネル
class Main:
    def __init__(self):
        pass

    #TODO:data = すべてのデータ
    def run(self, vald, lis):
        valts = []
        i = 2
        case = 0
        arg = ""

        name = sys.argv[1]
        file = open(name, encoding="utf-8")
        self.conf = open(name.split(".")[0]+".las", "a", encoding="utf_8")
        data = file.readlines()
        file.close()

        valts.append(data)
        valts = [e for inner_list in valts for e in inner_list]
        self.start(valts, i)

    def start(self, valts, i):
        for data in valts:
            data = data.replace("\n", "")

            if data.endswith("{") and data.startswith("fn"):
                data1 = data.split(" ")[1].replace(")", "").split("(")[0]
                arg = data.split("(")[1].split(")")[0]

                if data1 == "main():":
                    pass

                else:
                    data = data1.replace("{", "") + "({}):".format(arg)

                self.conf.write("\n{}".format(data))

            else:
                data = data.strip("    ").replace(";", "")
                data = data.split("//")[0]

                if data.startswith("if"):
                    if data.split(" ")[2] == "more_than":
                        self.conf.write("\n    jne {}, {}, _L{}".format(data.split(" ")[1], data.split(" ")[3],i))
                    
                    elif data.split(" ")[2] == "less_than":
                        self.conf.write("\n    ja {}, {}, _L{}".format(data.split(" ")[1], data.split(" ")[3],i))
                    
                    self.conf.write("\n    ret")
                    self.conf.write("\n_L{}:".format(i))
                    i += 1

                else:
                    if data.startswith("ret"):
                        rfun = data.split(" ")[1]
                        
                        try:
                            rarg = data.split(" ")[2]
                            self.conf.write("\n    mov {}, {}".format(rfun, rarg))
                        
                        except IndexError:
                            pass

                        self.conf.write("\n    ret")

                    elif data.startswith("use"):
                        try:
                            self.conf.write("\n    call {}{}".format(data.split(" ")[1].split("(")[0]+"(", data.split("(")[1]))
                        except KeyError:
                            self.conf.write("\n    call {}{}".format(data.split(" ")[1].split("(")[0]))

                        
                    elif "open" in data:
                            self.conf.write("\n    open:{}".format(data.split(":")[1]))#, data.split("=")[1]))

                    elif "make" in data:
                            self.conf.write("\n    {}, make".format(data.split(" ")[1]))
                        
                    elif data.endswith("close"):
                            self.conf.write("\n    {}, close".format(data.split(" ")[0]))
                        
                    elif "write" in data:
                            try:
                                self.conf.write("\n    write:{}".format(vald["".join(data.split(":")[1:])]))
                            except KeyError:
                                self.conf.write("\n    write:{}".format("".join(data.split(":")[1:])))


                    elif data.startswith("consolp:"):
                        self.conf.write("\n    msg {}".format(data.split("consolp:")[1]))

                    elif data.startswith("int"):
                        if "," in data:
                            datav = data.split("=")[0].split("int ")[1].replace(" ", "").split(",")
                            datanum = int(data.split("=")[1].replace(" ", "").split(","))
                            valdl = dict(zip(datav, datanum))
                            for datav in datav:
                                self.conf.write("\n    mov {}, {}".format(datav, valdl[datav]))
                        else:
                            datav = data.split(" ")[1]
                            datanum = int(data.split(" ")[3])
                            self.conf.write("\n    mov {}, {}".format(datav, datanum))
                    
                    elif data.startswith("str"):
                        if "," in data:
                            datav = data.split("=")[0].split("int ")[1].replace(" ", "").split(",")
                            datanum = data.split("=")[1].replace(" ", "").split(",")
                            valdl2 = dict(zip(datav, datanum))
                            for datav in datav:
                                self.conf.write("\n    mov {}, {}".format(datav, valdl[datav]))
                        else:
                            datav = data.split(" ")[1]
                            datanum = data.split(" ")[3]
                            self.conf.write("\n    mov {}, {}".format(datav, datanum))
                        try:
                            valdl.update(valdl2)
                        
                        except UnboundLocalError:
                            pass
                    #elif data.startswith("}"):
                    #    self.conf.write("\n    mov end")

                    elif data.startswith("while"):
                        c = data.split(" ")[1]
                        wc = 0
                        cou = 0
                        self.conf.write("\n    jmp _L{}, {}".format(i, c))
                        self.conf.write("\n    ret")
                        self.conf.write("\n_L{}, {}:".format(i, c))
                        wc = 2
                        if ">" in data or "<" in data:
                            self.conf.write("\n    cmp {}, {}".format(c, cou))
                            self.conf.write("\n    add {}, {}, 1".format("cou", "cou"))
                            wc = 1
                        i += 1

                    # add x, 10, 2
                    elif data.startswith("~") is False:
                        if "+" in data:
                            data = data.replace(" ", "")#.split(":")[1]
                            val = data.split("=")[0]
                            data2 = data.split("=")[1].split("+")[0]
                            data3 = data.split("=")[1].split("+")[1]
                            self.conf.write("\n    add {}, {}, {}".format(val, data2, data3))

                        elif "-" in data:
                            data = data.replace(" ", "")
                            val = data.split("=")[0]
                            data2 = data.split("=")[1].split("-")[0]
                            data3 = data.split("=")[1].split("-")[1]
                            self.conf.write("\n    sub {}, {}, {}".format(val, data2, data3))

                        elif "*" in data:
                            data = data.replace(" ", "")
                            val = data.split("=")[0]
                            data2 = data.split("=")[1].split("*")[0]
                            data3 = data.split("=")[1].split("*")[1]
                            self.conf.write("\n    mul {}, {}, {}".format(val, data2, data3))

                        elif "/" in data:
                            data = data.replace(" ", "")
                            val = data.split("=")[0]
                            data2 = data.split("=")[1].split("/")[0]
                            data3 = data.split("=")[1].split("/")[1]
                            self.conf.write("\n    div {}, {}, {}".format(val, data2, data3))
        self.conf.write("\ncall main()")

if __name__ == '__main__':
    file_list = glob.glob("*las")
    for file in file_list:
        os.remove(file)
    f = open(sys.argv[1].split(".")[0]+".las", "x", encoding="utf_8")
    f.close()
    vald = {}
    lis = []
    omega = Main()
    omega.run(vald, lis)
    
```

できるだけ実行時の速度を速めたかったので、次のコードは短めです。と言いたいところですが、実はこっちのコードは長くなりました。辞書の処理などがあるからですね。
次は、仮想機械を立ち上げて機械語を実行する
部分のコード。

```python:runn.py
import sys

class Run:
    def __init__(self):
        self.wc = 0

    def run(self, i):
        file = open(sys.argv[1], encoding="utf-8")
        data2 = file.readlines()

        dick = {}
        lis = []
        lisf = []
        lisw = []
        wc = 0
        ji = 0
        vald = {}

        for data in data2:
            data = data.replace("\n", "")
            if data.startswith("    "):
                if wc == 1:
                    lisw.append(data.replace("    ", "") + ";")
                else:
                    lis.append("{};".format(data.replace("    ", "")))
            
            elif data.startswith("    ret"):
                break

            elif data.endswith(":"):
                if data.startswith("_"):
                    try:
                    
                        try:
                            ji = int(data.split(" ")[1].replace(":", ""))
                    
                        except IndexError:
                            pass
                    
                    except ValueError:
                        ji = "True"

                func = data.replace(":", "")
                lisf.append(func)

            elif data.startswith("call"):
                dick2 = dict(zip(lisf, "".join(lis).split("ret;")))
                dick.update(dick2)
                self.Main(dick, vald, data.split(" ")[1], i, ji)

        file.close()
    
    def Main(self, dick, vald, f, i, ji):
        lis = str(dick[f]).split(";")
        lisf = f
        for data in lis:
            data = data.split(";")
            data = [a for a in data if a != '']

            for data in data:
                if data.endswith(")") and data.startswith("call") is False:
                    val = data.split("msg ")[1].split("(")[0]+"("+data.split("(")[1]
                    if '"' in val:
                        print(val.replace('"', ""))

                    else:
                        self.Main(dick, vald, val, i, ji)
                        print(dick[val])

                else:
                    data = str(data)
                    if data.startswith("mov"):
                        if data.split(" ")[1] == "end":
                            break
                        else:
                            val = data.split(", ")[1]
                            arg = data.split(", ")[0].split(" ")[1]
                            vald[arg] = val

                    elif data.startswith("strf"):
                        dick[data.split(", ")[0].split(" ")[1]] = data.split(", ")[1]

                    elif "open" in data:
                        name = "".join(data.split(":")[1:]).replace(" ", "").split(",")
                        opf = open(dick[name[0]], name[1], encoding=name[2])
                        
                    elif data.endswith("close"):
                        opf.close()
                        
                    elif data.endswith("make"):
                        file = data.split(" ")[1]
                        with open(file.replace(",", ""), "w", encoding="utf_8") as opf:
                            pass
                    
                    elif "write" in data:
                        opf.write("".join(data.split(":")[1]))

                    elif data.startswith("call"):
                        func = data.split(" ")[1]
                        if func.endswith("()"):
                            pass
                        else:
                            argn = func.split("(")[1].replace(")", "")
                            arg = vald[argn]
                            vald = {}
                            vald[argn] = arg
                        self.Main(dick, vald, func, i, ji)

                    elif data.startswith("msg"):
                        if ' "' in data.split("msg")[1]:
                            print(data.split("msg")[1].replace(' "', "").replace('"', ""))
                        else:
                            dick2 = {}
                            try:
                                dick2["1"] = vald[data.split("msg ")[1]]
                            except KeyError:
                                print("変数名'{}'が見つかりません。終了。".format(data.split("msg ")[1]))
                                sys.exit()
                            self.Main(dick2, vald, "1", i, ji)
                            if str(vald[data.split("msg ")[1]]).startswith("open"):
                                pass
                            else:
                                print(vald[data.split("msg ")[1]])

                    elif data.startswith("add"):
                        formula = data.split(",")[1].replace(" ", "")
                        formula2 = data.split(",")[2].replace(" ", "")
                        val = data.split(",")[0].split(" ")[1]
                        try:
                            vald[val] = int(vald[formula]) + int(vald[formula2])

                        except KeyError:

                            try:
                                vald[val] = int(vald[formula2]) + int(formula)
                            except KeyError:
                                vald[val] = int(formula) + int(formula2)

                    elif data.startswith("sub"):
                        formula = data.split(",")[1].replace(" ", "")
                        formula2 = data.split(",")[2].replace(" ", "")
                        val = data.split(",")[0].split(" ")[1]
                        try:
                            vald[val] = int(vald[formula]) - int(vald[formula2])

                        except KeyError:

                            try:
                                vald[val] = int(vald[formula]) - int(formula2)

                            except KeyError:
                                vald[val] = int(formula) - int(formula2)

                    elif data.startswith("mul"):
                        formula = data.split(",")[1].replace(" ", "")
                        formula2 = data.split(",")[2].replace(" ", "")
                        val = data.split(",")[0].split(" ")[1]
                        try:
                            vald[val] = int(vald[formula]) * int(vald[formula2])

                        except KeyError:

                            try:
                                vald[val] = int(formula) * int(vald[formula2])

                            except KeyError:
                                vald[val] = int(formula) * int(formula2)

                    elif data.startswith("div"):
                        formula = data.split(",")[1].replace(" ", "")
                        formula2 = data.split(",")[2].replace(" ", "")
                        val = data.split(",")[0].split(" ")[1]
                        try:
                            vald[val] = int(vald[formula]) / int(vald[formula2])

                        except KeyError:

                            try:
                                vald[val] = int(formula) / int(vald[formula])

                            except KeyError:
                                vald[val] = int(formula) / int(formula2)
                    
                    elif data == "break":
                        break

                    elif data.startswith("jmp"):
                        jmp = data.replace("jmp ", "")
                        try:
                            if ji == "True":
                                while True:
                                    self.Main(dick, vald, jmp, i, "True")

                            else:
                                for dan in range(ji):
                                    self.Main(dick, vald, jmp, i, 0)
                                break
                        except KeyError:
                            pass

                    elif data.startswith("jne"):
                        data = data.replace(",", "").split(" ")
                        func = data[3]
                        try:
                            if int(vald[data[1]]) > int(vald[data[2]]):
                                self.Main(dick, vald, func, i, ji)
                            
                            else:
                                break

                        except KeyError:
                            try:
                                if int(vald[data[1]]) > int(data[2]):
                                    self.Main(dick, vald, func, i, ji)
                                else:
                                    break

                            except KeyError:#TODO
                                if int(data[1]) > int(vald[data[2]]):
                                    self.Main(dick, vald, func, i, ji)
                                else:
                                    break
                    
                    elif data.startswith("ja"):
                        data = data.replace(",", "").split(" ")
                        func = data[3]
                        try:
                            if int(vald[data[1]]) < int(vald[data[2]]):
                                self.Main(dick, func, i, ji)
                            
                            else:
                                break

                        except KeyError:
                            try:
                                if int(vald[data[1]]) < int(data[2]):
                                    self.Main(dick, vald, func, i, ji)
                                else:
                                    break

                            except KeyError:
                                if int(data[1]) < int(data[2]):
                                    self.Main(dick, vald, func, i, ji)
                                else:
                                    break


if __name__ == '__main__':
    Run().run(0)
```

# サンプルコード

例えば、lapislazuliでは次のようなコードを実行できます。

```lapislazuli:main.la
fn iffunc(x) {
    if x more_than 3 {
        x = x + 1;
    }
    
    ret func(x) x;
}

fn main() {
    int x = 6;
    use iffunc(x);
    consolp:x;
    ret main() 0;
}
```

lasバイトコードがこちら。

```las:main.las
iffunc(x):
    jne x, 3, _L2
    ret
_L2:
    add x, x, 1
    mov func(x), x
    ret
main():
    mov x, 6
    call iffunc(x)
    msg x
    mov main(), 0
    ret
call main()
```

```text:結果
7
```

英語の勉強がしたかったので、>とか<を使わずにmore_thanとless_tahanを使いました。(more_than=> / less_than=<)
関数定義は、Rustにあこがれて「fn」です。けっこうRustの構文とか好きなんですよ。私。
前回に引き続き、最初に呼び出される（callされる）のがmain()関数なので、Cとかそのあたりと同じで最初の処理はそこに書きます。解説はそのくらいですかね。また何かありましたら[私のついった](https://twitter.com/dangomu39300363)までお来しくださいませ。

### ご連絡
ただいま私dangomushiは、自作言語などで情報の共有ができる「仲間」を探しております。もし興味があって情報共有したいよ～って方がいらっしゃいましたら遠慮なく連絡してください。

# まとめ

今回は、たった2つのファイルで「if文,while文, 関数/変数定義、スコープ」ができる言語を作りました。スコープと引数を実装した際、辞書型がこんがらがったのが印象的でした。
課題としては、

- if文を実行するともれなく不定期でバグ
- 実行速度が遅い
- 関数の実行の仕方が一通り

の三つかと思います。次は改善していこうとおもいます。
それでは、次の記事でお会いしましょう!!
さようなら！