スクリプト言語の自作（超簡易）


# 簡単な自作言語を作る
小規模な自作言語を作成してみます

## 言語仕様
言語の仕様を以下のように適当に決めます

- １文はセミコロンで区切る
- 1行で構文を終わらせる（改行して文を続けることはできない）
- 関数定義の開始は `FUNCTION_BEGIN [関数名];` とし、 `FUNCTION_END;` で関数定義の終了とする
- 関数呼び出しは `CALL_FUNCTION [関数名];` とする
- 組み込み関数 AH があり実行すると 標準出力に Ah! と表示する
- 組み込み関数 YEAH があり実行すると 標準出力に Yeah! と表示する

引数の指定はできない、四則演算などは実行不可、IF文、ループ文は無い、と
こんな感じで、プログラミング言語といって良いのかというくらいやれることがありません

## ソースコードと解説

以下ソースコードとなり、ソース内コメントで解説していきます

``` python3

import re

# 組み込み関数 AH の実装
def embeded_function_AH():
    print("Ah!")

# 組み込み関数 YEAH の実装
def embeded_function_YEAH():
    print("Yeah!")

def execute_embeded_function(function_name: str):
    if function_name == "AH":
        # 組み込み関数 AH の呼び出し
        embeded_function_AH()
    elif function_name == "YEAH":
        # 組み込み関数 YEAH の呼び出し
        embeded_function_YEAH()
    else:
        raise Exception(f"unknown function name {function_name}")

def execute_statements(statements, execute_key):
    for statement in statements[execute_key]:
        match_call_function = re.match(r"CALL_FUNCTION\s+(\w+)\s*;\s*", statement)
        if match_call_function is not None and len(match_call_function.groups()) == 1:
            function_name = match_call_function.group(1)
            scope_function_name = execute_key + "_" + function_name
            if scope_function_name in statements:
                execute_statements(statements, scope_function_name)
            else:
                execute_embeded_function(function_name=function_name)
        else:
            raise Exception(f"unknown statement {statement}")

def execute_script_string(script_string: str):
    # 余計な改行や空白を除去した statement のみ集める
    # statement は global、関数があれば関数スコープ毎にまとめられる
    lines = script_string.splitlines()
    scope_name_stack = ["global"]
    scope_name = scope_name_stack[-1]
    statements = {}
    for l in lines:
        statement = l.strip()
        # 空行の除去
        if len(statement) < 1:
            continue

        # 関数宣言開始、新たなスコープの認識
        match_begin_function = re.match(r"FUNCTION_BEGIN\s+(\w+)\s*;\s*", statement)
        if match_begin_function is not None and len(match_begin_function.groups()) == 1:
            function_name = match_begin_function.group(1)
            scope_name_stack.append(function_name)
            scope_name = "_".join(scope_name_stack)
            continue

        # 関数宣言終了、前のスコープ状態に戻す
        match_end_function = re.match(r"FUNCTION_ENDs*;\s*", statement)
        if match_end_function is not None:
            scope_name_stack.pop()
            scope_name = "_".join(scope_name_stack)
            continue

        # 現在のスコープのstatementに追加していく
        if scope_name not in statements:
            statements[scope_name] = []
        statements[scope_name].append(statement)

    # スコープ名が global 以外ということは関数宣言を終了していないなどの、ネスト異常が発生している
    if scope_name != "global":
        raise Exception("Nesting not properly closed")

    # global ステートメントの実行
    execute_statements(statements=statements, execute_key="global")

def main():
    script = """
    FUNCTION_BEGIN hoge;
        CALL_FUNCTION AH;
        CALL_FUNCTION YEAH;
    FUNCTION_END;

    FUNCTION_BEGIN piyo;
        CALL_FUNCTION YEAH;
        CALL_FUNCTION YEAH;
        FUNCTION_BEGIN fuga;
            CALL_FUNCTION AH;
            CALL_FUNCTION AH;
        FUNCTION_END;
        CALL_FUNCTION fuga;
    FUNCTION_END;

    CALL_FUNCTION AH;
    CALL_FUNCTION YEAH;
    CALL_FUNCTION hoge;
    CALL_FUNCTION piyo;
    """
    execute_script_string(script_string=script)

if __name__ == "__main__":
    main()

```

正常に実行できれば下記のようになります

```
Ah!
Yeah!
Ah!
Yeah!
Yeah!
Yeah!
Ah!
Ah!
```

# 使い道があるかどうか
現時点では使えなさそうですね、せめて IF文 あればなんとか？という感じでしょうか
なんらかのサービスを提供しており、その客に実行順序を編集・指定させたい　みたいな状況でもしかしたらこんな簡易スクリプトでも使い道あるかもしれないですが…