JavaScriptで関数型言語を作ろう(4)

[前記事](https://qiita.com/hoge1e3/items/5f1ec29732b130d68ee7)では、コードの生成に必要なライブラリ`lang/runtime.ts`について見ただけで終わってしまいましたので、今回はいよいよコードを生成している`lang/CodeGen.ts`を見て行きます。

~~~lang/CodeGen.ts
import { ValueExpression, NumberLiteral, Identifier, MemberAccess, Call} from "./Expressions";

export function generate(expr:ValueExpression):string {
    const g=generate;
    if (expr instanceof NumberLiteral) {
        return `Num(${expr.value})`;
    } else if (expr instanceof Identifier) {
        return expr.text;
    } else if (expr instanceof MemberAccess) {
        return `${g(expr.left)}.${expr.name.text}`;
    } else {// if (expr instanceof Call) {
        const a=expr.args.map(g);
        return `${g(expr.left)}(${a.join(",")})`;
    }
}
~~~
今のところ、`generate`という関数1つだけです。

`generate(expr:ValueExpression):string`
と宣言されていることから、`ValueExpression`オブジェクトから文字列の変換であることがわかりますが、`ValueExpression`って何だったかをちゃんと見てなかったですので、定義している`lang/Expresions.tsを見ておきましょう。

~~~~lang/Expressions.ts
export class NumberLiteral {
    constructor(public value:number){}
}
export class Identifier {
    constructor(public text:string){}
    toString() {return this.text;}
}
export class MemberAccess {
    constructor(public left:ValueExpression, public name:Identifier){}
}
export class Call {
    constructor(public left:ValueExpression, public args:ValueExpression[]){}
}
export type ValueExpression=NumberLiteral|Identifier|MemberAccess|Call;
~~~~
これらは、「式」として解釈可能なオブジェクトの一覧です。現状定義されているのはこの4つです

- `NumberLiteral`(数値定数)
- `Identifier`(変数)
- `MemberAccess` (`object.x` のようなプロパティへのアクセス)
- `Call` (`f(x)`のような関数呼び出し)

さきほどの`generate`メソッドは、上4つのそれぞれの場合について、対応するJavaScriptのコードを生成します。

## 数値定数→コード

~~~ts
if (expr instanceof NumberLiteral) {
        return `Num(${expr.value})`;
~~~

数値定数の場合は、[前記事](https://qiita.com/hoge1e3/items/5f1ec29732b130d68ee7)の`lang/runtime.ts`で定義した`Num`関数を使って、例えば`3`に対して`Num(3)`のようにコードを生成します。

## 変数→コード

~~~ts
 } else if (expr instanceof Identifier) {
        return expr.text;
~~~

変数の場合は、テキストの中身をそのまま生成します……って、これは実はまだ使用されていません。`3.add(2)`とかの`.add`の部分はこれではなく、次の「MemberAccess」で生成しています。

## MemberAccess→コード

~~~ts
} else if (expr instanceof MemberAccess) {
        return `${g(expr.left)}.${expr.name.text}`;
~~~

MemberAccessには、`left`と呼んでいる式が含まれます。これは`object.x`の`object`の部分（すなわち、`.`の「左側」）に相当します。ここには別の式が来るので、`generate`を再帰呼び出しします（`g`と省略できるように`generate`の冒頭で宣言しています）

「左側」を生成したら、それに`.`と名前（`object.x`の`x`のほう）をくっつけて生成完了です。

## Call→コード

~~~ts
} else {// if (expr instanceof Call) {
        const a=expr.args.map(g);
        return `${g(expr.left)}(${a.join(",")})`;
}
~~~

まず、余談から。なんで`// if (expr instanceof Call) {`がコメントになっているかというと、この部分があるとTypeScriptエラーを吐くからです。このコメントを外すと「`expr`が`Call`でなかった場合に`return`がないからダメよ」とおっしゃるのですが、`lang/Expression.ts`の`ValueExpression`の定義を見ればわかる通り、`NumberLiteral|Identifier|MemberAccess|Call`の4つしかないので、それ以外はありえないのに……と思うんです。しかも、このelseの内部、ちゃんと、`expr`が`Call`であることを推論してくれる（前3つのifでそれ以外の型の可能性を排除しているから）のに、なんで`if (expr instanceof Call) {`があるときは気をきかせてくれないのか……[2021/02/27追記] [こんな方法](https://twitter.com/suin/status/1365184105172717574)があるそうです。次回修正版を紹介します。

閑話休題．

この内部は`Call`だった時の処理です。`Call`は`MemberAccess`同様に`left`(`f(x)`の`f`の部分)があって、その後ろに`args`が来ます。`args`は引数ですので、`ValueExpression`の配列になっています。これらの引数すべてに`g`(`generate`)を適用させて、引数部分のコードを生成します（`a`はJavaScriptコードになった文字列の配列）。leftの部分を生成した後、`(`と`a`の中身のカンマ区切り、`)`を生成して生成完了です。

# 例

例えば式`3.add(2)`をJavaScriptに変換する場合、

- 式全体は`Call`で、`left`は`3.add`、`args`は`[2]`
- `left`部分を生成
    - `3.add`は`MemberAccess`で、`left`は`3`、`name`は`add`
        - `left`部分を生成
            - `3` は `NumberLiteral`なので、`Num(3)`を生成
        - `Num(3)`に`.`と`add`をくっつけて、`Num(3).add`を生成
- `args`部分を生成
    - `args`は要素1個の配列で、0個目は`2`、これは`NumberLiteral`なので、`Num(2)`を生成      
- `left`で生成したものと、`(`と、`args`で生成したものと、`)` をくっつける
    - `Num(3).add(Num(2))` を生成（完成）

# 実行器

さて、コードは生成されたのでいよいよ実行する部分を見てみます。第1回で見た`index.ts`をもう一度見てみましょう。

~~~index.ts
//前略
function run(src:string) {
    const t=MyTokenizer(src);
    const tokens=t.tokenize();
    //★A
    tokens.forEach((token,i)=>console.log(`${i}:[${token.type}] ${token.text}`));
    const p=MyParser(tokens);
    const tree=p.parse();
    //★B
    console.log(tree);
    const js=`
    const {Num}=runtime;
    return ${generate(tree)};
    `;
    //★C
    console.log(js);
    const func=new Function("runtime", js);
    const res=func(runtime);
    //★D
    console.log(res);
}
~~~

★Aから★Dは、それぞれ次の結果を表示しています。

- //★A 字句解析の結果
- //★B 構文解析の結果
- //★C コード生成の結果
- //★D 実行の結果

//★Cの手前のコード

~~~ts
    const js=`
    const {Num}=runtime;
    return ${generate(tree)};
    `;
~~~
で、`generate`が使われています。さきほどの`3.add(2)`という式であれば、

~~~js
const {Num}=runtime
return Num(3).add(Num(2));
~~~

というコードが生成されて、変数`js`に入るはずです。

`runtime`は（[前回](https://qiita.com/hoge1e3/items/5f1ec29732b130d68ee7)見た`lang/runtime.ts`）index.tsの冒頭でインポートされています。この中に`Num`の定義があります。

~~~ts
import * as runtime from "./lang/runtime";
~~~

そして、変数`js`の内容から`Function`オブジェクトを生成しています。`Function`オブジェクトは、`new Function(引数名, コード文字列)`という形式で関数を動的に生成できます。

~~~ts
   const func=new Function("runtime", js);
   const res=func(runtime); 
   //★D 
   console.log(res);
~~~

生成された`func`の内容はこんな感じになります

~~~js
const func=function (runtime) {
  const {Num}=runtime
  return Num(3).add(Num(2));
}
~~~~

これを`func(runtime)`で呼び出して、実行を行っているわけです。

[次回](https://qiita.com/hoge1e3/items/6f8e70934746113e7e47)は、意味解析について見て行きます。

# 理解度チェック

[前回](https://qiita.com/hoge1e3/items/5f1ec29732b130d68ee7)の「理解度チェック」で出て来た次の式をtinyfuncで記述可能な形式に書き換えて、実際に計算させてみましょう。

- 20+30-5 → 実際には `20.add(3).sub(5)`と書かないと動かないです
- 50-10-20
- 50-(10-20)
- 20*4+30*2
- 20*(4+30)*2
- 100/10/2
- 100/(10/2)