TSで自作言語インタプリタをつくろう

typescript
自作言語

こんにちは。 今回の内容は、自作言語インタプリタについてです。 自作言語MyScriptをブラウザ上で実行できます。 ぜひ遊んで行ってください。 まずMyScriptの仕様を軽く紹介し、その後にインタプリタ本体とサンプルコードを載せておきます。

MyScript

文法

MyScriptの文法では、最上位には「関数、クラス、グローバル変数」の定義群を書きます。 Runボタンを押すと、その中からmainという名前の引数がない関数が呼び出されて実行が始まります。 main関数は必ず定義する必要があります。文法の詳細は長くなるので省略します。

標準関数

以下は、定義しなくても使用できます。

関数 説明
void print(str msg) msgを出力します
str input() ユーザの入力を返します
num random() [0,1)のランダムな値を返します

Interpriter

使い方

インタプリタ上部にサンプルコードをコピペして(またはMyScriptコードを書いて)Runボタンをおすと、下の結果表示部分に実行結果が表示されます。 実行中に入力を要求された場合は、結果表示部分の一番下の>マークの行に入力してから再度Runボタンを押して確定してください。 また、コピペ時の色の不整合が気になる場合はプレーンテキストとして貼り付けてください。

Interpriter本体

Sample Code

Hello,World

まずは最も単純なHello,Worldです。

code
void main(){ print("Hello, World!"); }

数当てゲーム

次に数当てゲームです。 これは、1~100の数字を何回で当てられるか?というゲームです。 予想を解答するたびにそれが正解より大きいか小さいか教えてもらえるので、それをヒントに絞り込んでいきます。

code
void main(){
    num rnd = random()*100;
    num answer = rnd - rnd%1;
    print("数当てゲームをしましょう!");
    num count = 1;
    while(true){
        print("予想を入力してください。");
        num expect = (num)input();
        if(expect == answer){
            print("正解です! "+(str)count+"回で当たりました!");
            break;
        }else if(expect < answer){
            print("残念。予想は正解より小さいようです。(現在 "+(str)count+"回目)");
        }else{
            print("残念。予想は正解より大きいようです。(現在 "+(str)count+"回目)");
        }
        count++;
    }
}

モンスターバトル

魔王と魔法で戦う単純なバトルです。

code
num randomInt(num max, num bias){
    num r = random()*max + bias;
    return r - r%1;
}


class Effect{
    bool toSelf;
    num toHp;
    State state;
    num probability;


    void constructor(bool toSelf, num hp, State state, num probability){
        this.toSelf = toSelf;
        this.toHp = hp;
        this.state = state;
        this.probability = probability;
    }


    Effect copy(){
        return new Effect(this.toSelf, this.toHp, this.state, this.probability);
    }
}


class State{
    str name;
    bool canMove;
    num continueCount;
    Effect effect;


    void constructor(str name, bool canMove, num continueCount, Effect effect){
        this.name = name;
        this.canMove = canMove;
        this.continueCount = continueCount;
        this.effect = effect;
    }


    State copy(){
        return new State(this.name, this.canMove, this.continueCount, this.effect);
    }
}


class Magic{
    str name;
    num costMp;
    Effect effect;


    void constructor(str name, num cMp, Effect effect){
        this.name = name;
        this.costMp = cMp;
        this.effect = effect;
    }
}


class Monster{
    str name;
    str aa;
    num originalHp;
    num hp;
    num mp;
    num atk;
    num def;
    Magic magics[4];
    State state;


    void constructor(str name, str aa, Magic m1, Magic m2, Magic m3){
        this.name = name;
        this.aa = aa;
        this.magics[0] = new Magic("ノーマル",0,new Effect(false,10,null,0));
        this.magics[1] = m1;
        this.magics[2] = m2;
        this.magics[3] = m3;


        this.originalHp = randomInt(200,250);
        this.hp = this.originalHp;
        this.mp = randomInt(100,130);
        this.atk = randomInt(20,20);
        this.def = randomInt(20,0);
        this.state = new State("健康", true, 9999, new Effect(true,0,null,0));
    }


    void receive(Effect ef){
        num dHp = ef.toHp - this.def;
        if(ef.toHp >=0 && dHp < 0) dHp = 0;
        this.hp -= dHp;
        if(this.hp > this.originalHp) this.hp = this.originalHp;


        str dhpn = (str)dHp+"のダメージを受けた";
        if(dHp < 0) dhpn = (str)(-dHp)+"回復した";
        print(this.name+"は"+dhpn);


        if(ef.state == null) return;
        num rnd = randomInt(100,1);
        if(rnd <= ef.probability){
            this.state = ef.state.copy();
            print(this.name+"は"+this.state.name+"状態になった");
        }
    }


    num selectMagic(){
        num index = randomInt(3,1);
        if(this.magics[index].costMp > this.mp) return 0;
        return index;
    }


    Effect attack(num magicIndex){
        Magic selectedMagic = this.magics[magicIndex];
        print(this.name+"は"+selectedMagic.name+"を唱えた");
        if(this.mp < selectedMagic.costMp){
            print("しかしMPが足りなかった");
            return null;
        }


        this.mp -= selectedMagic.costMp;
        Effect me = selectedMagic.effect.copy();
        num org = me.toHp;
        me.toHp += this.atk + randomInt(20,-10);
        //符号は変えない
        if(me.toHp*org < 0) me.toHp = 0;
        return me;
    }


    void resolveState(){
        if(this.state.name == "健康") return;
        num dHp = this.state.effect.toHp;
        this.hp -= dHp;
        if(this.hp > this.originalHp) this.hp = this.originalHp;
        str dhpn = (str)dHp+"のダメージを受けた";
        if(dHp < 0) dhpn = (str)(-dHp)+"回復した";
        print(this.name+"は"+this.state.name+"で"+dhpn);


        if(this.state.continueCount == 0){
            print(this.name+"の"+this.state.name+"状態が解消された");
            this.state = new State("健康", true, 9999, new Effect(true,0,null,0));
            return;
        }
        this.state.continueCount--;
    }


    void showState(){
        print(this.name+" "+this.aa+" "+this.state.name+" HP:"+(str)this.hp+" MP:"+(str)this.mp);
    }
}


bool isFinish(Monster player,Monster enemy){
    if(player.hp <= 0){
        print("勇者は死んでしまった…");
        return true;
    }
    if(enemy.hp <= 0){
        print("魔王を倒した!\n勇者は世界に平和をもたらした!");
        return true;
    }
    return false;
}


void main(){
    State burned = new State("やけど",true,3,new Effect(true,10,null,0));
    State poison = new State("どく",true,5,new Effect(true,20,null,0));
    State heal = new State("ヒール",true,5,new Effect(true,-10,null,0));


    Magic mlis[10] = {
        new Magic("ファイア",10,new Effect(false,20,burned,30)),
        new Magic("アイス",15,new Effect(false,25,null,0)),
        new Magic("サンダー",20,new Effect(false,30,null,0)),
        new Magic("ヒール",20,new Effect(true,-60,null,0)),
        new Magic("スゴイファイア",20,new Effect(false,40,burned,60)),
        new Magic("スゴイアイス",30,new Effect(false,50,null,0)),
        new Magic("スゴイサンダー",40,new Effect(false,60,null,0)),
        new Magic("スゴイヒール",40,new Effect(true,-120,null,0)),
        new Magic("ジゾクヒール",40,new Effect(true,-10,heal,100)),
        new Magic("ポイズン",15,new Effect(false,15,poison,50))
    };


    num mk = 10;
    num ei = randomInt(mk,0);
    num pi = randomInt(mk,0);
    Monster enemy = new Monster("魔王","(V)o¥o(V)",mlis[ei],mlis[(ei+2)%mk],mlis[(ei+5)%mk]);
    Monster player = new Monster("勇者","(; ・`д・´)",mlis[pi],mlis[(pi+2)%mk],mlis[(pi+5)%mk]);
    print("魔王が現れた!");


    while(true){
        enemy.showState();
        player.showState();
        
        //Player's attack
        print("どの魔法を使用しますか?(番号で選んで入力してください)");
        str options = "";
        for(num i=0; i<4; i++){
            options += (str)i+":"+player.magics[i].name+" ";
        }
        print(options);


        num index = (num)input();
        Effect pe = player.attack(index);
        if(pe != null){
            if(pe.toSelf){
                player.receive(pe);
            }else{
                enemy.receive(pe);
            }
        }
        player.resolveState();


        if(isFinish(player,enemy)) break;


        //Enemy's attack
        Effect ee = enemy.attack(enemy.selectMagic());
        if(ee != null){
            if(ee.toSelf){
                enemy.receive(ee);
            }else{
                player.receive(ee);
            }
        }
        enemy.resolveState();


        if(isFinish(player,enemy)) break;
    }
}

おしまい

今回の記事はこれでおしまいです。 四則演算から適当に拡張しながらつくったものですが、意外とプログラミング言語っぽいものになってませんか? 受動的に読むだけでMyScriptができあがる全10回くらいの記事…とかおもしろそうですね。