6 単項演算に対応する

# はじめに

以前の記事で[優先順位付けの括弧に対応](http://qiita.com/quwahara/items/b76c6e438aeb32450391)しました。
そのParserでの優先順位付けの解析方法と`-1`や`+1`のような単項演算の解析方法は、近いところがあります。
近いついでで実装してみましょう。

# 単項演算でやりたいこと

例えば下のようなプログラムがあったら、計算結果`a`の値が`-1`になることを目指します。

```JS
a = -1
```

# 実装の仕方

実装は構文解析(Parser)とインタプリタ(Interpreter)へ行います。
それぞれについて順に実装の仕方を考えていきます。

## 構文解析(Parser)の実装の仕方

はじめにで[優先順位付けの括弧](http://qiita.com/quwahara/items/b76c6e438aeb32450391)に近いといいました。
その実装の仕方のところで、３番目にくるトークンはParserの`lead()`メソッドで処理されましたが、
今回の`a = -1`の`-`も３番目にくるので、Parserの`lead()`メソッドで処理できます。
続く`1`も式として`expression()`メソッドで解析できます。
また構文解析の次のインタプリタでこの解析結果を処理するときに、二項演算、つまり`2 - 1`の`-`なのか、単項演算子、つまり`-1`の`-`なのか区別できるように、`-1`の`-`だとわかるようにしておきます。

## インタプリタ(Interpreter)の実装の仕方

インタプリタでは単純に`-1`の`-`を処理するメソッドを追加します。

# Javaで実装してみる

実装にうつります。

## Parser.java

Parser.javaの実装です。
まずトークンの意味に対して、どう動作するかの定義を変更します。
`<-- Add`の箇所で単項演算子を識別するための定義を追加します。

```Java:Parser.java
public class Parser {

    private Map<String, Integer> degrees;
    private List<String> factorKinds;
    private List<String> binaryKinds;
    private List<String> rightAssocs;
    private List<String> unaryOperators;    // <-- Add

    public Parser() {
        degrees = new HashMap<>();
        degrees.put("(", 80);
        degrees.put("*", 60);
        degrees.put("/", 60);
        degrees.put("+", 50);
        degrees.put("-", 50);
        degrees.put("=", 10);
        factorKinds = Arrays.asList(new String[] { "digit", "ident" });
        binaryKinds = Arrays.asList(new String[] { "sign" });
        rightAssocs = Arrays.asList(new String[] { "=" });
        unaryOperators = Arrays.asList(new String[] { "+", "-" }); // <-- Add
    }
```

`<-- Add`の箇所へ単項演算子の処理を追加しました。
まずはトークンが単項演算子であることを確認します。
`token.kind = "unary";`で演算子の意味が、単項演算子であるとわかるようにしてます。
つづいて`token.left = expression(70);`で単項演算子につづく式を解析します。
`-1`の`1`の部分です。
`expression()`の引数を`70`で呼び出しているのは、単項演算子を`*`や`/`より優先させたいが、関数呼び出しよりは後回しにしたいためです。

```Java:Parser.java
    private Token lead(Token token) throws Exception {
        if (factorKinds.contains(token.kind)) {
            return token;
        } else if (unaryOperators.contains(token.value)) { // <-- Add
            token.kind = "unary";
            token.left = expression(70);
            return token;
        } else if (token.kind.equals("paren") && token.value.equals("(")) {
            Token expr = expression(0);
            consume(")");
            return expr;
        } else {
            throw new Exception("The token cannot place there.");
        }
    }
```

## Interpreter.java

Interpreter.javaの実装です。

トークンの意味によって、それぞれを処理するメソッドを呼び分ける部分の変更です。
単項演算子の計算処理呼び出しを`<-- Add`がある箇所へ加えました。

```Java:Interpreter.java
    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("paren")) {
            return invoke(expr);
        } else if (expr.kind.equals("sign") && expr.value.equals("=")) {
            return assign(expr);
        } else if (expr.kind.equals("unary")) { // <-- Add
            return unaryCalc(expr);
        } else if (expr.kind.equals("sign")) {
            return calc(expr);
        } else {
            throw new Exception("Expression error");
        }
    }
```

`unaryCalc()`は単項演算子の計算を実行します。
この記事でやりたかったことです。

```Java:Interpreter.java
    public Object unaryCalc(Token expr) throws Exception {
        Integer left = value(expression(expr.left));
        if (expr.value.equals("+")) {
            return left;
        } else if (expr.value.equals("-")) {
            return -left;
        } else {
            throw new Exception("Unknown sign for unary calc");
        }
    }
```

単項演算子の対応が反映されるかの確認のために、式を`String text = "a = -1";`へ変えました。
標準出力に`-1`がでるはずです。

```Java:Interpreter.java
    public static void main(String[] args) throws Exception {
        String text = "a = -1";    // <-- Update
        text += "println(a)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> -1
    }
```

実装は以上です。
ありががとうございました。

# おわりに

ソースの全文はこちらで公開しています。

**Calc**
https://github.com/quwahara/Calc/tree/article-6-unary/Calc/src/main/java


続きの記事があります。

**単純な関数定義と呼び出しを追加する**
http://qiita.com/quwahara/items/be71bac4b4359f5e6727