Processingを用いて、エディタ・コンソール・プレビュー画面を備えた自作ライブコーディング言語の実行環境を実装する

# はじめに
これは2020年9月17日に明治大学FMS学科のEP演習内で発表されたやつです。まあそんなことどうでもいいんで適当に見てってください。[ソースコード](https://github.com/rytar/VisualProcessor)はgithubに置きました。

# 概要
プログラム自体はProcessingというjavaに皮を被せたみたいな言語を使って実装しています。実行画面はテキストエディタ、コンソール、プレビュー画面で構成されています。
![D4A19692-CDAF-4B48-B178-A0C1720EA227.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/ded79f45-007c-9cee-60a0-14d803a558b8.jpeg)

# 言語文法
基本的にはjavaと同じような構文で書くことが出来ます。いくつかjavaとは異なる部分があるのでそこの説明を少し。

## 変数宣言
変数宣言は以下のような構文となります。

```rust
let a = 10;
println(a); // -> 10
println(typeof(a)); // -> int

let b: float = 10;
println(typeof(b)); // -> float
```
現在使用可能な組み込み型は

+ ``int``
+ ``float``
+ ``bool``
+ ``string``

の4種類とその配列です。気が向いたらそのうち増やしていきたい。
配列の宣言は以下のように書けます。

```rust
let arr1 = {0, 1, 2};
let arr2 = {{0, 1}, {1, 2}};
let arr3 = int[2][2]; // make {{0, 0}, {0, 0}}
```

また、似た文法の言語にRustがありますが、Rustの場合``let``で宣言すると定数に、``mut``を付けると変数になるのに対し、この言語では``let``で変数が宣言されます。定数の宣言はまだ作ってないけど、JavaScriptみたいに``const``文にしようかなと思ってます。

さらに、以下のような書き方をすることもできます。

```rust
let c, d = 10, 20;
println(c, d); // -> 10 20
c, d = d, c; // swap
println(c, d); // -> 20 10
```

## 関数定義
関数は以下の構文で定義することが出来ます。

```rust
fn add(a: int, b: int) -> int {
    return a + b;
}
```

## 補足
かなり突貫工事で作っているので、他の言語と比べてサポート出来ていない組み込み関数が多かったり、そもそもオブジェクト指向をサポート出来ていないなどの問題が山積みだったりします。許してください...

# 使用方法
次に、このプログラムをどう動かすのか見ていきましょう。

## ウィンドウの生成
プレビュー画面内にウィンドウ(に対応する画面)を生成します。これはProcessingと同じような書き方で行えます。
![369C08DD-1B9F-4309-B204-637C42AAEEE9.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/4b2f1bbc-aefe-273f-7993-740c3bd00710.jpeg)

この例では、800×600のウィンドウに対応する比率の画面をプレビュー画面に表示しています。
また、Processingと同じように、``background()``関数を使用して背景色を指定できます。

##　コンソールの使用
コンソールに文字列を出力するには、``println()``関数を使用します。
行末に改行を出力しない場合、``print()``関数を使用することもできます。
![F741BD52-53ED-4A46-BC77-8EB8A0C5A810.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/179b0c6b-31e2-81a5-a1e9-38d5a131cf65.jpeg)

コンソールに入力する機能はまだ未実装です。ごめんね。


## フレームごとの処理

Processingと同じように、``draw()``関数を定義することでフレームごとの処理を書くことができます。
次の例では、だんだんと背景色を黒から白に変化させていく挙動を実装しています。
![B1C3DE40-D435-4DBB-B7AC-779F4C83A61B.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/f2206441-1a2e-e3df-8391-410f2ab3440a.jpeg)
![C21F3A6A-0BD1-4D9A-93F4-3C7D743AD2EE.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/4fec4540-4437-572e-e093-282e019eaba4.jpeg)
![6CA58A85-3ECC-4D18-B0F5-1C33075FC954.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/47fda57a-6eb3-57a0-c964-9ffb1a8f7eb3.jpeg)

## マウスに対する処理
マウスの座標を示す``mouseX``, ``mouseY``と、``mousePressed()``関数を定義しておくとマウスクリック時に呼び出す機能を実装しました。なので、以下のようなクリックした部分とその上下左右のマスを反転させるこんな感じの処理を書けます。
![083D3856-E7E3-4756-A4E1-2C5EFAABF1A8.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/3a09b0eb-fb32-48da-94c6-47add59e432c.jpeg)
![C1B173B7-AF1C-4601-AA90-37E710CF0522.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/8325f614-59f1-9b0c-d2ba-d2c2ac31cd22.jpeg)
![3AE8D23F-CBA3-49ED-86BF-BA14C93DE979.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354215/10ca9333-c0a0-cbe4-c962-e379e5868e51.jpeg)

コードが見切れているので、別に載せておきます。

```rust
size(500, 500);

let board = int[5][5];
let count = 0;

board[0][2] = 1;
board[1][1] = 1;
board[1][2] = 1;
board[2][3] = 1;
board[2][4] = 1;
board[3][3] = 1;

fn draw() -> void {
    background(0);
    fill(255, 255, 0);
    count = 0;
    for(let i = 0; i < 5; i++) {
        for(let j = 0; j < 5; j++) {
            if(board[i][j] == 1) {
                rect(100 * i, 100 * j, 100, 100);
            }
            else {
                count++;
            }
        }
    }
    if(count == 25) {
        textSize(32);
        textAlign(CENTER);
        text("CLEAR!", width / 2, height / 2);
    }
}

fn mousePressed() -> void {
    let x = mouseX / 100;
    let y  = mouseY / 100;
    for(let i = max(0, x - 1); i <= min(4, x + 1); i++) {
        board[i][y] = 1 - board[i][y];
    }
    for(let i = max(0, y - 1); i <= min(4, y + 1); i++) {
        board[x][i] = 1 - board[x][i];
    }
    board[x][y] = 1 - board[x][y];
}
```

## 使用できる組み込み関数
上のプログラムで、Processingで見慣れた関数がいくつか登場しましたね。Processingで使用できる関数のいくつかは、この自作言語でも使用できるように実装されています。現在使用できる組み込み関数は、Processingにあるのもないのも含めて以下の全てです。

+ ``size()``
+ ``background()``
+ ``fill()``
+ ``stroke()``
+ ``strokeWeight()``
+ ``textSize()``
+ ``textAlign()``
+ ``text()``
+ ``circle()``
+ ``rect()``
+ ``ellipse()``
+ ``println()``
+ ``print()``
+ ``typeof()`` (変数の型を調べる)
+ ``millis()``
+ ``wait()``（wait(ms) => msミリ秒間処理を停止する）
+ ``random()``
+ ``min()`` (複数の引数の中で最小の値を返す)
+ ``max()`` (複数の引数の中で最大の値を返す)
+ ``range()`` (指定された範囲の整数値が順に並んだ配列を返す)

# 内部の実装
では、内部の詳しい実装の話に移りましょう。ここから先は、Processingに関する知識が不十分な方やプログラミングの経験があまり多くない方には少し難しいかもしれません。

## エディタの実装
### 文字の入力
``keyPressed``, ``keyPressed()``関数、``keyTyped()``関数などを駆使して実装しています。
まず、``draw()``関数内にある以下のコードを見てみましょう。

```java
if(keyPressed) {
    if(prevKey.equals(String.valueOf(key))) {
        if(keyt < dur) keyt++;
        else {
            keyt = dur / 2;
            if(key != CODED) keyTyped();
        }
    }
    else {
        keyt = 0;
        if(key != CODED) keyTyped();
    }
}
else {
    keyt = 0;
    prevKey = "";
}
```
``prevKey``は前回入力されたキーを保持しておく変数です。``keyt``はキーが入力されてから何フレームが経過したかを表します。``dur``は``keyt``の閾値として設定された値です。
前回と同じキーが入力されている間、``keyt``が閾値``dur``を超えたら``keyt``に``dur / 2``を代入し、``key``がShiftやControlなどの符号化されたキーではないとき、``keyTyped()``関数を呼び出しています。前回とは異なるキーが入力されていたら、``keyt``のチェックはせず``keyt``に``0``を代入します。
また、``keyPressed()``関数は以下のような実装になっています。

```java
void keyPressed() {
    if(key == CODED) {
        if(keyCode == LEFT) {
            cursor.prev();
            beforeCursor.prev();
        }
        else if(keyCode == RIGHT) {
            cursor.next();
            beforeCursor.next();
        }
        else if(keyCode == UP) {
            cursor.up();
            beforeCursor.up();
        }
        else if(keyCode == DOWN) {
            cursor.down();
            beforeCursor.down();
        }
        else {
            keyFlags.put(keyCode, true);
        }
    }
}
```
``cursor``, ``beforeCursor``は``Cursor``型オブジェクトであり、行と列を表すメソッド``line``, ``row``を持っています。``cursor``は現在のカーソル位置を表しています。``beforeCursor``は範囲選択を実装するために使っているものなのでまた後ほど説明します。また、``keyFlags``は``HashMap<Integer, Boolean>``型オブジェクトであり、十字キー以外の符号化されたキーを示す``keyCode``が入力されているかどうかを表します。
この``keyPressed()``関数では、十字キーが押されたときカーソルを動かし、それ以外の符号化されたキーが入力されたとき``keyFlags``を変更する、といった処理を行っています。また、``keyFlags``が``true``になっているキーが離された時に``keyFlags``を変更するため、以下のように``keyReleased()``関数を定義しました。

```java
void keyReleased() {
    if(key == CODED) {
        if(keyFlags.get(keyCode) != null) {
            keyFlags.put(keyCode, false);
        }
    }
    keyPressed = false;
}
```
``keyPressed``を``false``にする処理は、本来する必要がない処理のはずなのですが、なぜか書かないと``keyPressed``の挙動がおかしくなるので書いておきます。

``keyTyped()``関数では、それぞれ入力されたキーに対応する挙動を記述しています。ここに書くと長くなるので、見たい方は[ソースコードのリンク](https://github.com/rytar/VisualProcessor)から確認してみるといいかもです。

### 文字列の表示
入力された文字列は、行ごとに``ArrayList<String>``型オブジェクトに格納されています。それを順に表示していくだけですね。
行数がエディタの表示範囲を超えているとき、エディタの右側にスクロールバーを表示しています。スクロールバーの位置にしたがって文字列を出力する高さを変化させます。

### 機能の実装
エディタを名乗るには、さすがに文字を入力できるだけだとメモ帳となんら変わりないので、いくつか機能が必要になります。現在実装しているエディタの機能として以下が挙げられます。

+ マウスを用いた範囲選択
+ 選択されている範囲の文字列をクリップボードへコピー
+ クリップボードからのペースト
+ 全選択
+ ``(``に対する``)``や``{``に対する``}``などの簡易な補完
+ コードブロック内での自動インデント
+ 行数の表示　など

範囲の選択を実装するにあたって、``beforeCursor``と``cursor``の一方を範囲の始端、他方を範囲の終端とすることで実現しています。あとはまあ``keyTyped()``関数のなかにごちゃごちゃ書いているので興味があれば見てみてください。

## コンソールの実装
コンソールも同じように``ArrayList<String>``型オブジェクトに文字列を格納しています。表示もエディタと同じようにしています。

## 言語処理系の実装
今回実装した言語は簡易な動的型付けのインタプリンタ言語です。ただし、僕は動的型付け言語が好きでは無いので、コードの見た目は型推論を行っている静的型付け言語のような見た目になっています。
処理の流れは少し特殊で、本来行われる字句解析と構文解析をして、その抽象構文木に対して解釈・実行を行うのですが、今回実装したものは諸々を省いた雑な実装になっています。
例えば、以下のコードを実行することを考えてみましょう。

```rust
let a = 10;
```

このとき、``Lexer``クラスは以下のような計算結果を生成します。

```rust
[
    Token("let", "let"),
    Token("id", "a"),
    Token("expr", "expr"),
    Token("int", "10"),
    Token("endExpr", "endExpr"),
    Token("endLet", "endLet")
]
```

これは、``ArrayList<Token>``型のオブジェクトです。``Token``型のオブジェクトは、``kind``と``value``の2つのフィールドを持っています。``Token(a, b)``と書いてあるのは、``kind``の値が``a``, ``value``の値が``b``の``Token``型オブジェクトを表していると思ってください。
そして、この配列を命令列として解釈し、実行します。``let``文の実行は以下のコードにより行われます。

```java
    .
    .
    .
else if(res.get(i).kind.equals("let")) {
    ArrayList<String> vars = new ArrayList<String>();
    ArrayList<String> values = new ArrayList<String>();
    i++;
    String type = "", exprType = "";
    if(res.get(i).kind.equals("type")) {
        type = res.get(i).value;
        i++;
    }
    while(!res.get(i).kind.equals("endLet")) {
        if(res.get(i).kind.equals("id")) {
            vars.add(res.get(i).value);
            varNames.add(loc + "$" + res.get(i).value);
        }
        else if(res.get(i).kind.equals("expr")) {
            ArrayList<Token> expr = new ArrayList<Token>();
            i++;
            int exprNum = 0;
            while(exprNum > 0 || !res.get(i).kind.equals("endExpr")) {
                if(res.get(i).kind.equals("expr")) exprNum++;
                if(res.get(i).kind.equals("endExpr")) exprNum--;
                expr.add(res.get(i));
                i++;
            }
            exprType = calc(expr, loc);
            values.add(expr.get(0).value);
        }
        i++;
    }
    int vs = values.size();
    for(int j = 0; j < vars.size(); j++) {
        if(j < vs) {
            if(type.isEmpty()) {
                variables.put(loc + "$" + vars.get(j), new Variable(exprType, vars.get(j), values.get(j)));
            }
            else {
                variables.put(loc + "$" + vars.get(j), new Variable(type, vars.get(j), values.get(j)));
            }
        }
        else {
            if(type.isEmpty()) {
                variables.put(loc + "$" + vars.get(j), new Variable(exprType, vars.get(j), values.get(vs - 1)));
            }
            else {
                variables.put(loc + "$" + vars.get(j), new Variable(type, vars.get(j), values.get(vs - 1)));
            }
        }
    }
}
```

``res``は``Lexer``の計算結果を受け取る``ArrayList<Token>``型の変数です。``variables``は変数を保存しておく、``HashMap<String, Variable>``型の変数です。``varNames``はスコープも考慮した変数の名前を保存している``ArrayList<String>``型の変数です。変数のスコープ管理は、``$``を用いています。具体的には、グローバル領域にある変数``a``のスコープを考慮した名前は``main$a``であり、グローバル領域にある関数``func()``内で定義された変数``a``のスコープを考慮した名前は``main$func$a``となります。
``expr``から``endExpr``は式を表しているので、``expr``から``endExpr``の``Token``列を``calc()``関数に渡してあげることで、式を計算することができます。
このようにして、``let``文を実行することができます。

他の文についても同じように``Token``列を生成して実行することを行っていますので、それぞれの詳しい実装は``statement()``関数の実装を参照してください。

# おしまい
他にも実装について解説できる部分は多くあるのですが、ネタは鮮度が命ですので、この作品を発表した当日中に記事を投稿したいということでここら辺で解説を終わらせたいと思います。解説を追加して欲しいなどの要望や質問、提案、指摘、罵倒、嘲笑などがありましたら、この記事にコメントするなり[僕のTwitterアカウント](https://twitter.com/rytaryu)に直接リプライやDMを送るなりしてください。

雑な実装、雑な解説、雑の終わらせ方と全てにおいて雑であったことを深くお詫びします。ごめんね。

より詳しく丁寧な言語処理系の実装についての解説が聞きたい人は[こちらの記事](https://qiita.com/kgtkr/items/48520fd19358c73bc400)をご覧ください。