前回は例が簡単すぎてListenerやVisitorの具体的な使い方がぴんと来なかったので、今回はListenerで計算機を作るための試行錯誤をしてみる。
文法の定義
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共通のコードを作成
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))
>Calculator01.exe "1+2*3"
([] ([0] 1) + ([18] ([0 18] 2) * ([12 18] 3)))
>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))
Listenerを作成
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());
}
}
}
試しに色々出してみる
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
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
;
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()
で結果を計算できない。