LoginSignup
1
1

More than 5 years have passed since last update.

15 文字列に対応する

Last updated at Posted at 2017-04-11

はじめに

以前の記事で関数式に対応しました。
関数式の対応で、値として扱う型がIntegerのみから、関数式も値として扱うようになりました。
同じよう要領で文字列も値として扱えるように対応します。

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

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

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

実装の仕方

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

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

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

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

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

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

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

Javaで実装してみる

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

Lexer.java

Lexer.javaの実装です。

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

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

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

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

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

文字列リテラルを表すkindstringにします。

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のところへ、追加したメソッドの呼び出しを追加します。
これで文字列リテラルのトークンへ分解できます。

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)の実装はこれで終わりです。

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を返すだけです。

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だった場合にも対応しました。

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への変換を暗黙的に試みるように変更しました。

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へ暗黙的に変換します。

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()メソッドを呼び出します。

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()メソッドを呼び出します。
右辺は暗黙の変換をされることがあります。

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の文字列を偽と判定するようにしました。

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;
        }
    }

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

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

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

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1