0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2020-06-08

前回は例が簡単すぎて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で出力したツリー構造も添えて実行結果を確認(前回の記事を参照)。

>Calculator01.exe "1+2"
([] ([0] 1) + ([12] 2))

antlr4_parse_tree1_tr.png

>Calculator01.exe "1+2*3"
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))

antlr4_parse_tree2_tr.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

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の2つ目の通りに起動する。
  • Enter**は、EnterNumberが起動しない。
  • 枝が有る(=終端ではない)ノードでもVisitTerminalが起動する。
  • Exit***でのcontextのGetText()では、その要素の値ではなく、その要素とその配下の値が取得される。
  • Exit***でその要素の値を取得するのは、grammarで定義したNUMBER等のルールの名前を持つメソッドで取得する。
    例)
    Console.WriteLine(context.NUMBER());

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()
で結果を計算できない。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?