はじめに
以前の記事で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文字の演算子も増えたのが、目新しいかもしれません。
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
ところへ追加しています。
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の実装です。
条件式が真かを判定するメソッドに便利なので、引数の型違いで多様性をもたせました。
public boolean isTrue(Token token) throws Exception {
return isTrue(value(expression(token)));
}
public boolean isTrue(Integer value) throws Exception {
return 0 != value;
}
この言語で真偽値は整数であらわしているので、論理値型を整数へ変換するメソッドを用意しておきます。
真は1
へ変換するようにしました。
public Integer toInteger(boolean b) {
return b ? 1 : 0;
}
単項演算子を実行するメソッドです。
<-- Add
のところへ、論理否定を追加しました。
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
のところから、新しい演算の実装を追加しています。
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))
を実行し、標準出力へ1
、0
、1
と出力します。
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