11 比較演算子と論理演算子に対応する

# はじめに

以前の記事で[if文に対応](http://qiita.com/quwahara/items/96a68cdee4f2a0452836)しました。
if文の条件文に指定できる演算子がないので対応したいと思います。

# 比較演算子と論理演算子対応でやりたいこと

簡単にやりたいことを確認します。
例えば下のようなプログラムがあります。
1行目は不等号が正しいので、真をあらわす`1`を出力します。
2行目は等号付き不等号が間違いなので、偽をあらわす`0`を出力します。
3行目は論理演算の結果が真になるので、`1`を出力します。
例では出力するだけですが、if文の条件式として記述できます。

```JS
println(10 > 1)
println(5 <= 1)
println(!(1 == 2) && (3 != 4))
```

# 実装の仕方

実装の仕方については、[構文解析の実装](http://qiita.com/quwahara/items/9bf468ff4286b28d2a24)と[インタプリタの実装](http://qiita.com/quwahara/items/30e93dfd2690913d66c0)で行ったことから、
新しいことがないのでそちらを参照して下さい。

# Javaで実装してみる

実装にうつります。
字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)について、
変更と追加をしたところを順にみていきます。

## Lexer.java

Lexer.javaの実装です。
追加した演算子に対応しています。
いままでの演算子はすべて1文字でしたが、2文字の演算子も増えたのが、目新しいかもしれません。

```Java:Lexer.java
    private boolean isSignStart(char c) {
        return c == '=' || c == '+' || c == '-' || c == '*' || c == '/' || c == '!' || c == '<' || c == '>' || c == '&'
                || c == '|';
    }

    private Token sign() throws Exception {
        Token t = new Token();
        t.kind = "sign";
        char c1 = next();
        char c2 = (char) 0;
        if (!isEOT()) {
            if (c1 == '=' || c1 == '!' || c1 == '<' || c1 == '>') {
                if (c() == '=') {
                    c2 = next();
                }
            } else if (c1 == '&') {
                if (c() == '&') {
                    c2 = next();
                }
            } else if (c1 == '|') {
                if (c() == '|') {
                    c2 = next();
                }
            }
        }
        String v;
        if (c2 == (char) 0) {
            v = Character.toString(c1);
        } else {
            v = Character.toString(c1) + Character.toString(c2);
        }
        t.value = v;
        return t;
    }
```

## Parser.java

Parser.javaの実装です。
トークンの意味に対して、どう動作するかの定義を追加します。
`<-- Add`のところから、新しい演算子を追加しました。
`!`が単項演算子なので、`<-- Update`ところへ追加しています。

```Java:Parser.java
    public Parser() {
        degrees = new HashMap<>();
        degrees.put("(", 80);
        degrees.put("*", 60);
        degrees.put("/", 60);
        degrees.put("+", 50);
        degrees.put("-", 50);
        degrees.put("==", 40); // <-- Add
        degrees.put("!=", 40);
        degrees.put("<", 40);
        degrees.put("<=", 40);
        degrees.put(">", 40);
        degrees.put(">=", 40);
        degrees.put("&&", 30);
        degrees.put("||", 30);
        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[] { "+", "-", "!" });                 // <-- Update
        reserved = Arrays.asList(new String[] { "function", "return", "if", "else" });
    }
```
## Interpreter.java

Interpreter.javaの実装です。

条件式が真かを判定するメソッドに便利なので、引数の型違いで多様性をもたせました。

```Java:Interpreter.java
    public boolean isTrue(Token token) throws Exception {
        return isTrue(value(expression(token)));
    }

    public boolean isTrue(Integer value) throws Exception {
        return 0 != value;
    }
```

この言語で真偽値は整数であらわしているので、論理値型を整数へ変換するメソッドを用意しておきます。
真は`1`へ変換するようにしました。

```Java:Interpreter.java
    public Integer toInteger(boolean b) {
        return b ? 1 : 0;
    }
```

単項演算子を実行するメソッドです。
`<-- Add`のところへ、論理否定を追加しました。

```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 if (expr.value.equals("!")) { // <-- Add
            return toInteger(!isTrue(left));
        } else {
            throw new Exception("Unknown sign for unary calc");
        }
    }
```

二項演算子を実行するメソッドです。
`<-- Add`のところから、新しい演算の実装を追加しています。

```Java:Interpreter.java
    public Object calc(Token expr) throws Exception {
        Integer left = value(expression(expr.left));
        Integer right = value(expression(expr.right));
        if (expr.value.equals("+")) {
            return left + right;
        } else if (expr.value.equals("-")) {
            return left - right;
        } else if (expr.value.equals("*")) {
            return left * right;
        } else if (expr.value.equals("/")) {
            return left / right;
        } else if (expr.value.equals("==")) { // <-- Add
            return toInteger(left == right);
        } else if (expr.value.equals("!=")) {
            return toInteger(left != right);
        } else if (expr.value.equals("<")) {
            return toInteger(left < right);
        } else if (expr.value.equals("<=")) {
            return toInteger(left <= right);
        } else if (expr.value.equals(">")) {
            return toInteger(left > right);
        } else if (expr.value.equals(">=")) {
            return toInteger(left >= right);
        } else if (expr.value.equals("&&")) {
            return toInteger(isTrue(left) && isTrue(right));
        } else if (expr.value.equals("||")) {
            return toInteger(isTrue(left) || isTrue(right));
        } else {
            throw new Exception("Unknown sign for Calc");
        }
    }
```

以上の実装を使って下のプログラム

```JS
println(10 > 1)
println(5 <= 1)
println(!(1 == 2) && (3 != 4))
```

を実行し、標準出力へ`1`、`0`、`1`と出力します。

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

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

# おわりに

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

**Calc**
https://github.com/quwahara/Calc/tree/article-11-compare-r2/Calc/src/main/java

続きの記事があります。

**while文に対応する**
http://qiita.com/quwahara/items/36f6704ae9c756240068