Edited at

6 単項演算に対応する

More than 1 year has passed since last update.


はじめに

以前の記事で優先順位付けの括弧に対応しました。

そのParserでの優先順位付けの解析方法と-1+1のような単項演算の解析方法は、近いところがあります。

近いついでで実装してみましょう。


単項演算でやりたいこと

例えば下のようなプログラムがあったら、計算結果aの値が-1になることを目指します。

a = -1


実装の仕方

実装は構文解析(Parser)とインタプリタ(Interpreter)へ行います。

それぞれについて順に実装の仕方を考えていきます。


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

はじめにで優先順位付けの括弧に近いといいました。

その実装の仕方のところで、3番目にくるトークンはParserのlead()メソッドで処理されましたが、

今回のa = -1-も3番目にくるので、Parserのlead()メソッドで処理できます。

続く1も式としてexpression()メソッドで解析できます。

また構文解析の次のインタプリタでこの解析結果を処理するときに、二項演算、つまり2 - 1-なのか、単項演算子、つまり-1-なのか区別できるように、-1-だとわかるようにしておきます。


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

インタプリタでは単純に-1-を処理するメソッドを追加します。


Javaで実装してみる

実装にうつります。


Parser.java

Parser.javaの実装です。

まずトークンの意味に対して、どう動作するかの定義を変更します。

<-- Addの箇所で単項演算子を識別するための定義を追加します。


Parser.java

public class Parser {

private Map<String, Integer> degrees;
private List<String> factorKinds;
private List<String> binaryKinds;
private List<String> rightAssocs;
private List<String> unaryOperators; // <-- Add

public Parser() {
degrees = new HashMap<>();
degrees.put("(", 80);
degrees.put("*", 60);
degrees.put("/", 60);
degrees.put("+", 50);
degrees.put("-", 50);
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[] { "+", "-" }); // <-- Add
}


<-- Addの箇所へ単項演算子の処理を追加しました。

まずはトークンが単項演算子であることを確認します。

token.kind = "unary";で演算子の意味が、単項演算子であるとわかるようにしてます。

つづいてtoken.left = expression(70);で単項演算子につづく式を解析します。

-11の部分です。

expression()の引数を70で呼び出しているのは、単項演算子を*/より優先させたいが、関数呼び出しよりは後回しにしたいためです。


Parser.java

    private Token lead(Token token) throws Exception {

if (factorKinds.contains(token.kind)) {
return token;
} else if (unaryOperators.contains(token.value)) { // <-- Add
token.kind = "unary";
token.left = expression(70);
return token;
} else if (token.kind.equals("paren") && token.value.equals("(")) {
Token expr = expression(0);
consume(")");
return expr;
} else {
throw new Exception("The token cannot place there.");
}
}


Interpreter.java

Interpreter.javaの実装です。

トークンの意味によって、それぞれを処理するメソッドを呼び分ける部分の変更です。

単項演算子の計算処理呼び出しを<-- Addがある箇所へ加えました。


Interpreter.java

    public Object expression(Token expr) throws Exception {

if (expr.kind.equals("digit")) {
return digit(expr);
} else if (expr.kind.equals("ident")) {
return ident(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")) { // <-- Add
return unaryCalc(expr);
} else if (expr.kind.equals("sign")) {
return calc(expr);
} else {
throw new Exception("Expression error");
}
}

unaryCalc()は単項演算子の計算を実行します。

この記事でやりたかったことです。


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 {
throw new Exception("Unknown sign for unary calc");
}
}

単項演算子の対応が反映されるかの確認のために、式をString text = "a = -1";へ変えました。

標準出力に-1がでるはずです。


Interpreter.java

    public static void main(String[] args) throws Exception {

String text = "a = -1"; // <-- Update
text += "println(a)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> -1
}

実装は以上です。

ありががとうございました。


おわりに

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

Calc

https://github.com/quwahara/Calc/tree/article-6-unary/Calc/src/main/java

続きの記事があります。

単純な関数定義と呼び出しを追加する

http://qiita.com/quwahara/items/be71bac4b4359f5e6727