15 文字列に対応する

# はじめに

以前の記事で[関数式に対応](http://qiita.com/quwahara/items/ae33ed944afc34cc5fac)しました。
関数式の対応で、値として扱う型が`Integer`のみから、関数式も値として扱うようになりました。
同じよう要領で文字列も値として扱えるように対応します。

# 文字列対応でやりたいこと

文字列対応でやりたいことを確認します。
例えば下のようなプログラムがあります。
`"`で囲まれた文字列は文字列リテラルとします。
文字列がif文などの条件で論理値として扱われる場合は、長さ0の文字列を偽とします。
文字列は`+`で連結できるようにします。
プログラム最後の`println()`メソッド呼び出しでは`Hello world!`が出力されることを目指します。

```JS
var object = ""
if (!object) {
 object = "world"
}
println("Hello " + object + "!")
```

# 実装の仕方

実装の仕方について、字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)と順に考えていきます。

## 字句解析(Lexer)の実装の仕方

`"`で囲まれた文字列リテラルを解析する機能がないので追加します。

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

文字列リテラルは数値リテラルと同じように扱えばよいので、数値リテラルに準じて定義を追加してあげます。

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

`String`も`Integer`と同様に値として扱えるようにしていきます。
`Integer`だけしか考慮していなかった単項演算子と二項演算子の処理を、
`String`も扱えるように変更します。

# Javaで実装してみる

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

## Lexer.java

Lexer.javaの実装です。

`"`で囲まれた文字列を字句解析する機能を追加します。

`isStringStart()`メソッドを追加します。
`"`囲みの開始を検出します。

```Java:Lexer.java
    private boolean isStringStart(char c) {
        return c == '"';
    }
```

`string()`メソッドを追加します。
`"`囲みの終わりまでを1つのトークンへ解析します。

下のJSON の string を参考に実装します。
http://json.org/json-ja.html
参考にある 4 Hexadecimal digits の実装は端折りました。

文字列リテラルを表す`kind`は`string`にします。

```Java:Lexer.java
    private Token string() throws Exception {
        StringBuilder b = new StringBuilder();
        next();
        while (c() != '"') {
            if (c() != '\\') {
                b.append(next());
            } else {
                next();
                char c = c();
                if (c == '"') {
                    b.append('"');
                    next();
                } else if (c == '\\') {
                    b.append('\\');
                    next();
                } else if (c == '/') {
                    b.append('/');
                    next();
                } else if (c == 'b') {
                    b.append('\b');
                    next();
                } else if (c == 'f') {
                    b.append('\f');
                    next();
                } else if (c == 'n') {
                    b.append('\n');
                    next();
                } else if (c == 'r') {
                    b.append('\r');
                    next();
                } else if (c == 't') {
                    b.append('\t');
                    next();
                } else {
                    throw new Exception("string error");
                }
            }
        }
        next();
        Token t = new Token();
        t.kind = "string";
        t.value = b.toString();
        return t;
    }
```

`nextToken()`メソッドを変更します。
`// Add`のところへ、追加したメソッドの呼び出しを追加します。
これで文字列リテラルのトークンへ分解できます。

```Java:Lexer.java
    public Token nextToken() throws Exception {
        skipSpace();
        if (isEOT()) {
            return null;
        } else if (isSignStart(c())) {
            return sign();
        } else if (isDigitStart(c())) {
            return digit();
            // Add
        } else if (isStringStart(c())) {
            return string();
        } else if (isIdentStart(c())) {
            return ident();
        } else if (isParenStart(c())) {
            return paren();
        } else if (isCurlyStart(c())) {
            return curly();
        } else if (isSymbolStart(c())) {
            return symbol();
        } else {
            throw new Exception("Not a character for tokens");
        }
    }
```

## Parser.java

Parser.javaの実装です。

トークンの意味に対して、どう動作するかの定義を追加します。
`// Update`のところへ`"string"`を追加しました。
文字列リテラルは数字リテラルや変数名などと同じように、
それ以上分解できない構文なので、factorの分類に加えます。
factorの構文解析は実装済みなので、構文解析(Parser)の実装はこれで終わりです。

```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);
        degrees.put("!=", 40);
        degrees.put("<", 40);
        degrees.put("<=", 40);
        degrees.put(">", 40);
        degrees.put(">=", 40);
        degrees.put("&&", 30);
        degrees.put("||", 30);
        degrees.put("=", 10);
        // Update
        factorKinds = Arrays.asList(new String[] { "digit", "ident", "string" });
        binaryKinds = Arrays.asList(new String[] { "sign" });
        rightAssocs = Arrays.asList(new String[] { "=" });
        unaryOperators = Arrays.asList(new String[] { "+", "-", "!" });
        reserved = Arrays.asList(new String[] { "function", "return", "if", "else", "while", "break", "var" });
    }
```

## Interpreter.java

Interpreter.javaの実装です。

`expression()`メソッドの変更です。
式を表すトークンの意味(kind)によって分岐する処理です。
`// Add`の下へ文字列リテラルを表すトークン`string`のための分岐を追加しました。
`string`トークンの処理は単純にその`value`を返すだけです。

```Java:Interpreter.java
    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
            // Add
        } else if (expr.kind.equals("string")) {
            return string(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("func")) {
            return func(expr);
        } else if (expr.kind.equals("fexpr")) {
            return fexpr(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")) {
            return unaryCalc(expr);
        } else if (expr.kind.equals("sign")) {
            return calc(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

    public String string(Token token) {
        return token.value;
    }
```

`value()`メソッドの変更です。
`value()`メソッドは、引数が`1`のような値そのものだったらその値を返し、
変数だったら、変数が保持する値を返します。
引数が`String`だった場合にも対応しました。

```Java:Interpreter.java
    public Object value(Object value) throws Exception {
        if (value instanceof Integer) {
            return value;
        } else if (value instanceof String) {
            return value;
        } else if (value instanceof Func) {
            return value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return value(v.value);
        }
        throw new Exception("right value error");
    }
```

`integer()`メソッドの変更です。
`integer()`メソッドは引数が`Integer`であることを保証します。
引数が`String`だった場合に`Integer`への変換を暗黙的に試みるように変更しました。

```Java:Interpreter.java
    public Integer integer(Object value) throws Exception {
        if (value instanceof Integer) {
            return (Integer) value;
        } else if (value instanceof String) {
            return Integer.decode((String) value);
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return integer(v.value);
        }
        throw new Exception("right value error");
    }
```

`string()`メソッドを追加しました。
`string()`メソッドは引数が`String`であることを保証します。
引数が`Integer`だった場合に`String`へ暗黙的に変換します。

```Java:Interpreter.java
    public String string(Object value) throws Exception {
        if (value instanceof String) {
            return (String) value;
        } else if (value instanceof Integer) {
            return value.toString();
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return string(v.value);
        }
        throw new Exception("right value error");
    }
```

`unaryCalc()`メソッドの変更です。
`unaryCalc()`メソッドは単項演算子を処理するメソッドです。
変更前は`Integer`のみに対応していました。
`String`へ対応するため、`value()`メソッドの呼び出し結果によって、
`Integer`だったら`Integer`用の単項演算子を処理する`unaryCalcInteger()`メソッドを呼び出します。
`String`だったら`String`用の単項演算子を処理する`unaryCalcString()`メソッドを呼び出します。

```Java:Interpreter.java
    public Object unaryCalc(Token expr) throws Exception {
        Object value = value(expression(expr.left));
        if (value instanceof Integer) {
            return unaryCalcInteger(expr.value, (Integer) value);
        } else if (value instanceof String) {
            return unaryCalcString(expr.value, (String) value);
        } else {
            throw new Exception("unaryCalc error");
        }
    }

    public Object unaryCalcInteger(String sign, Integer left) throws Exception {
        if (sign.equals("+")) {
            return left;
        } else if (sign.equals("-")) {
            return -left;
        } else if (sign.equals("!")) {
            return toInteger(!isTrue(left));
        } else {
            throw new Exception("unaryCalcInteger error");
        }
    }

    public Object unaryCalcString(String sign, String left) throws Exception {
        if (sign.equals("!")) {
            return toInteger(!isTrue(left));
        } else {
            throw new Exception("unaryCalcString error");
        }
    }
```


`calc()`メソッドの変更です。
`calc()`メソッドは二項演算子を処理するメソッドです。
変更前は`Integer`のみに対応していました。
`String`へ対応するため、左辺の`value()`メソッドの呼び出し結果によって、
`Integer`だったら`Integer`用の二項演算子を処理する`calcInteger()`メソッドを呼び出します。
`String`だったら`String`用の二項演算子を処理する`calcString()`メソッドを呼び出します。
右辺は暗黙の変換をされることがあります。

```Java:Interpreter.java
    public Object calc(Token expr) throws Exception {
        Object left = value(expression(expr.left));
        Object right = value(expression(expr.right));
        Integer ileft = null;
        String sleft = null;

        if (left instanceof Integer) {
            ileft = (Integer) left;
        } else if (left instanceof String) {
            sleft = (String) left;
        }

        if (ileft != null) {
            return calcInteger(expr.value, ileft, right);
        } else if (sleft != null) {
            return calcString(expr.value, sleft, right);
        } else {
            throw new Exception("calc error");
        }
    }

    public Object calcInteger(String sign, Integer left, Object right) throws Exception {
        if (sign.equals("+")) {
            return left + integer(right);
        } else if (sign.equals("-")) {
            return left - integer(right);
        } else if (sign.equals("*")) {
            return left * integer(right);
        } else if (sign.equals("/")) {
            return left / integer(right);
        } else if (sign.equals("==")) {
            return toInteger(left == integer(right));
        } else if (sign.equals("!=")) {
            return toInteger(left != integer(right));
        } else if (sign.equals("<")) {
            return toInteger(left < integer(right));
        } else if (sign.equals("<=")) {
            return toInteger(left <= integer(right));
        } else if (sign.equals(">")) {
            return toInteger(left > integer(right));
        } else if (sign.equals(">=")) {
            return toInteger(left >= integer(right));
        } else if (sign.equals("&&")) {
            if (!isTrue(left)) {
                return left;
            }
            return right;
        } else if (sign.equals("||")) {
            if (isTrue(left)) {
                return left;
            }
            return right;
        } else {
            throw new Exception("calcIteger error");
        }
    }

    public Object calcString(String sign, String left, Object right) throws Exception {
        if (sign.equals("+")) {
            return left + string(right);
        } else if (sign.equals("==")) {
            return toInteger(left.equals(string(right)));
        } else if (sign.equals("!=")) {
            return toInteger(!left.equals(string(right)));
        } else if (sign.equals("<")) {
            return toInteger(left.compareTo(string(right)) < 0);
        } else if (sign.equals("<=")) {
            return toInteger(left.compareTo(string(right)) <= 0);
        } else if (sign.equals(">")) {
            return toInteger(left.compareTo(string(right)) > 0);
        } else if (sign.equals(">=")) {
            return toInteger(left.compareTo(string(right)) >= 0);
        } else if (sign.equals("&&")) {
            if (!isTrue(left)) {
                return left;
            }
            return right;
        } else if (sign.equals("||")) {
            if (isTrue(left)) {
                return left;
            }
            return right;
        } else {
            throw new Exception("calcString error");
        }
    }
```

`isTrue()`メソッドの変更です。
`String`を論理値として処理できるように変更しました。
長さ0の文字列を偽と判定するようにしました。

```Java:Interpreter.java
    public boolean isTrue(Object value) throws Exception {
        if (value instanceof Integer) {
            return 0 != ((Integer) value);
        } else if (value instanceof String) {
            return !"".equals(value);
        } else if (value instanceof Func) {
            return true;
        } else {
            return false;
        }
    }
```

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

```JS
var object = ""
if (!object) {
 object = "world"
}
println("Hello " + object + "!")
```

を実行し、
プログラム最後の`println()`メソッド呼び出しで`Hello world!`を出力します。

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

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

# おわりに

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

**Calc**
https://github.com/quwahara/Calc/tree/article-15-string-r3/Calc/src/main/java

続きの記事があります。

**メソッド呼び出しに対応する**
http://qiita.com/quwahara/items/f1bddefe984c8d233e02