ANTLR4をC#から使ってみる #03 Listenerでの計算機作成のための試行錯誤

前回は例が簡単すぎてListenerやVisitorの具体的な使い方がぴんと来なかったので、今回はListenerで計算機を作るための試行錯誤をしてみる。

# 文法の定義
```Calculator.g4
grammar Calculator;

PLUS : '+';
MINUS: '-';
MULTI: '*';
DIV  : '/';
NUMBER : [0-9]+;
WHITESPACE : [ \r\n\t]+ -> skip;

expression 
	: NUMBER
	| '(' expression ')'
	| expression MULTI expression
	| expression DIV expression
	| expression PLUS expression
	| expression MINUS expression
;
```
# Listener/Visitor共通のコードを作成

```Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime;

namespace Calculator01
{
    class Program
    {
        static void Main(string[] args)
        {
            string parsedString;
            if (args.Length == 0)
            {
                Console.WriteLine("引数に数式を指定してください");
                return;
            }
            else
            {
                parsedString = args[0];
            }
            var inputStream = new AntlrInputStream(parsedString);
            var lexer = new CalculatorLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(lexer);
            var parser = new CalculatorParser(commonTokenStream);
            var graphContext = parser.expression();
            Console.WriteLine(graphContext.ToStringTree());
        }
    }
}
```

- 実行結果

javaで出力したツリー構造も添えて実行結果を確認([前回の記事](https://qiita.com/Hidenori/items/cf1e02badde9a76feedf)を参照)。

```
>Calculator01.exe "1+2"
([] ([0] 1) + ([12] 2))
```
![antlr4_parse_tree1_tr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71468/4e5163c4-f501-5d30-9678-3ef2b0f2fc89.png)

```
>Calculator01.exe "1+2*3"
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))
```
![antlr4_parse_tree2_tr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71468/637ef59a-2f91-cbaf-990c-2252daf9ff18.png)

```
>Calculator01.exe "(1+2)*3+4"
([] ([0] ([0 0] ( ([5 0 0] ([0 5 0 0] 1) + ([18 5 0 0] 2)) )) * ([12 0] 3)) + ([18] 4))
```
![antlr4_parse_tree3_tr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71468/ab8d77f4-25a9-46ff-d375-2ab98c2aeaea.png)

# Listenerを作成
```Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime;

namespace Calculator01_2
{
    class Program
    {
        static void Main(string[] args)
        {
            string parsedString;
            if (args.Length == 0)
            {
                Console.WriteLine("引数に数式を指定してください");
                return;
            }
            else
            {
                parsedString = args[0];
            }
            var inputStream = new AntlrInputStream(parsedString);
            var lexer = new CalculatorLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(lexer);
            var parser = new CalculatorParser(commonTokenStream);
            CalculatorListener listener = new CalculatorListener();
            parser.AddParseListener(listener);
            var graphContext = parser.expression();
            Console.WriteLine(graphContext.ToStringTree());
        }
    }
}
```


## 試しに色々出してみる
```CalculatorListener.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;

namespace Calculator01_2
{
    class CalculatorListener : CalculatorBaseListener
    {
        public override void VisitTerminal([NotNull] ITerminalNode node)
        {
            base.VisitTerminal(node);
            Console.WriteLine("--VisitTerminal--");
            Console.WriteLine(" GetText():{0}", node.GetText());
        }
        public override void ExitExpression([NotNull] CalculatorParser.ExpressionContext context)
        {
            Console.WriteLine("--ExitExpression--");
            Console.WriteLine("GetText():{0}, NUM:{1} *:{2} /:{3} +:{4} -:{5}", context.GetText(), context.NUMBER(), context.MULTI(), context.DIV(), context.PLUS(), context.MINUS());

            base.ExitExpression(context);

            for (var i = 0; i < context.children.Count; i++)
            {
                Console.WriteLine(" children {0}: {1}", i, context.children[i].GetText());
            }
        }
        public override void EnterExpression([NotNull] CalculatorParser.ExpressionContext context)
        {
            base.EnterExpression(context);
            Console.WriteLine("--EnterExpression {0}--", context.GetText());
            Console.WriteLine("GetText():{0}, NUM:{1} *:{2} /:{3} +:{4} -:{5}", context.GetText(), context.NUMBER(), context.MULTI(), context.DIV(), context.PLUS(), context.MINUS());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine(" children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }

        public override void EnterEveryRule([NotNull] ParserRuleContext context)
        {
            base.EnterEveryRule(context);
            Console.WriteLine("--EnterEveryRule"); // {0},{1}--",context.Start,context.stop);
            Console.WriteLine(" GetText():{0}", context.GetText());
        }
        public override void ExitEveryRule([NotNull] ParserRuleContext context)
        {
            base.ExitEveryRule(context);
            Console.WriteLine("--ExitEveryRule"); // {0},{1}--",context.Start,context.Stop);
            Console.WriteLine(" GetText():{0}", context.GetText());

        }
    }
}

```

実行

```
>Calculator01_2.exe 1+2*3
--EnterEveryRule
 GetText():
--EnterExpression --
GetText():, NUM: *: /: +: -:
--VisitTerminal--
 GetText():1
--ExitExpression--
GetText():1, NUM:1 *: /: +: -:
 children 0: 1
--ExitEveryRule
 GetText():1
--EnterEveryRule
 GetText():1
--EnterExpression 1--
GetText():1, NUM: *: /: +: -:
 children 0: 1
--VisitTerminal--
 GetText():+
--EnterEveryRule
 GetText():
--EnterExpression --
GetText():, NUM: *: /: +: -:
--VisitTerminal--
 GetText():2
--ExitExpression--
GetText():2, NUM:2 *: /: +: -:
 children 0: 2
--ExitEveryRule
 GetText():2
--EnterEveryRule
 GetText():2
--EnterExpression 2--
GetText():2, NUM: *: /: +: -:
 children 0: 2
--VisitTerminal--
 GetText():*
--EnterEveryRule
 GetText():
--EnterExpression --
GetText():, NUM: *: /: +: -:
--VisitTerminal--
 GetText():3
--ExitExpression--
GetText():3, NUM:3 *: /: +: -:
 children 0: 3
--ExitEveryRule
 GetText():3
--ExitExpression--
GetText():2*3, NUM: *:* /: +: -:
 children 0: 2
 children 1: *
 children 2: 3
--ExitEveryRule
 GetText():2*3
--ExitExpression--
GetText():1+2*3, NUM: *: /: +:+ -:
 children 0: 1
 children 1: +
 children 2: 2*3
--ExitEveryRule
 GetText():1+2*3
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))
```

## 代替ラベルで出力を見やすくする
Parserの定義の中でexpressionの取りうる要素をORでつないでいるので、これまでのやり方だと実際にどの要素が来たのかはわからない。
そこで、ORでつなぐ全要素（一部だとエラー）に # で代替ラベルを定義する。
すると、それぞれごとにリスナーのメソッドが定義される。
https://github.com/antlr/antlr4/blob/master/doc/parser-rules.md#alternative-labels

```Calculator.g4
grammar Calculator;

PLUS : '+';
MINUS: '-';
MULTI: '*';
DIV  : '/';
NUMBER : [0-9]+;
WHITESPACE : [ \r\n\t]+ -> skip;

expression 
	: NUMBER						# Number
	| '(' expression ')'			# Parentheses
	| expression MULTI expression	# Multiplication
	| expression DIV expression		# Division
	| expression PLUS expression	# Addition
	| expression MINUS expression	# Subtraction
;
```

```CalculatorListener.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;

namespace Calculator01_2
{
    class CalculatorListener : CalculatorBaseListener
    {
        public override void EnterAddition([NotNull] CalculatorParser.AdditionContext context)
        {
            base.EnterAddition(context);
            Console.WriteLine("--EnterAddition--");
            Console.WriteLine("	GetText():{0}, Node:{1}", context.GetText(), context.PLUS());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitAddition([NotNull] CalculatorParser.AdditionContext context)
        {
            base.ExitAddition(context);
            Console.WriteLine("--ExitAddition--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.PLUS());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void EnterDivision([NotNull] CalculatorParser.DivisionContext context)
        {
            base.EnterDivision(context);
            Console.WriteLine("--EnterDivision--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.DIV());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitDivision([NotNull] CalculatorParser.DivisionContext context)
        {
            base.ExitDivision(context);
            Console.WriteLine("--ExitDivision--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.DIV());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }

        public override void EnterMultiplication([NotNull] CalculatorParser.MultiplicationContext context)
        {
            base.EnterMultiplication(context);
            Console.WriteLine("--EnterMultiplication--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.MULTI());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitMultiplication([NotNull] CalculatorParser.MultiplicationContext context)
        {
            base.ExitMultiplication(context);
            Console.WriteLine("--ExitMultiplication--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.MULTI());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void EnterNumber([NotNull] CalculatorParser.NumberContext context)
        {
            base.EnterNumber(context);
            Console.WriteLine("--EnterNumber--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.NUMBER());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitNumber([NotNull] CalculatorParser.NumberContext context)
        {
            base.ExitNumber(context);
            Console.WriteLine("--ExitNumber--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.NUMBER());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void EnterParentheses([NotNull] CalculatorParser.ParenthesesContext context)
        {
            base.EnterParentheses(context);
            Console.WriteLine("--EnterParentheses--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitParentheses([NotNull] CalculatorParser.ParenthesesContext context)
        {
            base.ExitParentheses(context);
            Console.WriteLine("--ExitParentheses--");
            Console.WriteLine("	GetText():{0}", context.GetText());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void EnterSubtraction([NotNull] CalculatorParser.SubtractionContext context)
        {
            base.EnterSubtraction(context);
            Console.WriteLine("--EnterSubtraction--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.MINUS());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }
        public override void ExitSubtraction([NotNull] CalculatorParser.SubtractionContext context)
        {
            base.ExitSubtraction(context);
            Console.WriteLine("--ExitSubtraction--");
            Console.WriteLine("	GetText():{0},Node:{1}", context.GetText(), context.MINUS());

            base.ExitExpression(context);
            if (context.children != null)
            {
                for (var i = 0; i < context.children.Count; i++)
                {
                    Console.WriteLine("	children {0}: {1}", i, context.children[i].GetText());
                }
            }
        }

        public override void VisitTerminal([NotNull] ITerminalNode node)
        {
            base.VisitTerminal(node);
            Console.WriteLine("--VisitTerminal--");
            Console.WriteLine("	GetText():{0}", node.GetText());
        }
    }
}

```

実行結果

```
>Calculator01_2.exe 1+2*3
--VisitTerminal--
        GetText():1
--ExitNumber--
        GetText():1,Node:1
        children 0: 1
--EnterAddition--
        GetText():1, Node:
        children 0: 1
--VisitTerminal--
        GetText():+
--VisitTerminal--
        GetText():2
--ExitNumber--
        GetText():2,Node:2
        children 0: 2
--EnterMultiplication--
        GetText():2,Node:
        children 0: 2
--VisitTerminal--
        GetText():*
--VisitTerminal--
        GetText():3
--ExitNumber--
        GetText():3,Node:3
        children 0: 3
--ExitMultiplication--
        GetText():2*3,Node:*
        children 0: 2
        children 1: *
        children 2: 3
--ExitAddition--
        GetText():1+2*3,Node:+
        children 0: 1
        children 1: +
        children 2: 2*3
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))
```

## メモ
- Exit\*\*\*は想定通りに起動するが、Enter\*\*\*は起動しない場合が有り、VisitTerminalは起動しない想定の所でも起動する。
 - Exit***は[Tree structures processing and unified AST](http://blog.ptsecurity.com/2016/07/tree-structures-processing-and-unified.html)の2つ目の通りに起動する。  
 - Enter**は、EnterNumberが起動しない。
 - 枝が有る(=終端ではない)ノードでもVisitTerminalが起動する。
- Exit\*\*\*でのcontextのGetText()では、その要素の値ではなく、その要素とその配下の値が取得される。
- Exit\*\*\*でその要素の値を取得するのは、grammarで定義したNUMBER等のルールの名前を持つメソッドで取得する。  
例)
`            Console.WriteLine(context.NUMBER());`

<!--
# Parserのルールの要素へのラベル付け
https://github.com/antlr/antlr4/blob/master/doc/parser-rules.md#rule-element-labels

```
expression 
	: NUMBER						# Number
	| '(' expression ')'			# Parentheses
	| expression MULTI expression	# Multiplication
	| expression DIV expression		# Division
	| expression PLUS expression	# Addition
	| expression MINUS expression	# Subtraction
;
```
これを、下のようにleft=やright=を付けると要素に名前を付けることができ、プログラムからその名前でそのcontextにアクセスできる。

```
expression 
	: NUMBER						# Number
	| '(' expression ')'			# Parentheses
	| left=expression MULTI right=expression	# Multiplication
	| left=expression DIV right=expression		# Division
	| left=expression PLUS right=expression	# Addition
	| left=expression MINUS right=expression	# Subtraction
;
```
-->

# Exit\*\*\*とスタックで計算機を作成できる
Exit\*\*\*は 親ノードの左下のノード→親の右下のノード→親 の順で起動する。
親ノードには + - * / がなる。
よって、1 + 2は、
> 1 2 +

 の順にExit\*\*\*が呼ばれるし、 1*2+3/4は、

> 1 2 * 3 4 / +

の順にExit\*\*\*が呼ばれる。

よって、数値のExit\*\*\*はスタックにpushし、+ - * / のExit\*\*\*では、スタックから2つpopして、それらを+ - * / で計算し、結果をスタックにpushすれば計算ができる。

1*2+3/4は、
> 1 2 * 3 4 / +

が

> 2 0.75 +

となり、

> 2.75

となる。

## 注意
スタックからpopして最初に出てくるのは右辺。
よって、 左辺と右辺の順番で結果が変わる割り算と引き算については、
`stack.Pop() - stack.Pop()`
で結果を計算できない。