Edited at

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

More than 1 year has passed since last update.


はじめに

以前の記事でif文に対応しました。

if文の条件文に指定できる演算子がないので対応したいと思います。


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

簡単にやりたいことを確認します。

例えば下のようなプログラムがあります。

1行目は不等号が正しいので、真をあらわす1を出力します。

2行目は等号付き不等号が間違いなので、偽をあらわす0を出力します。

3行目は論理演算の結果が真になるので、1を出力します。

例では出力するだけですが、if文の条件式として記述できます。

println(10 > 1)

println(5 <= 1)
println(!(1 == 2) && (3 != 4))


実装の仕方

実装の仕方については、構文解析の実装インタプリタの実装で行ったことから、

新しいことがないのでそちらを参照して下さい。


Javaで実装してみる

実装にうつります。

字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)について、

変更と追加をしたところを順にみていきます。


Lexer.java

Lexer.javaの実装です。

追加した演算子に対応しています。

いままでの演算子はすべて1文字でしたが、2文字の演算子も増えたのが、目新しいかもしれません。


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ところへ追加しています。


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の実装です。

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


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へ変換するようにしました。


Interpreter.java

    public Integer toInteger(boolean b) {

return b ? 1 : 0;
}

単項演算子を実行するメソッドです。

<-- Addのところへ、論理否定を追加しました。


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のところから、新しい演算の実装を追加しています。


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

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

println(10 > 1)

println(5 <= 1)
println(!(1 == 2) && (3 != 4))

を実行し、標準出力へ101と出力します。


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