ANTLR4をC#から使ってみる #06 Visitorでの計算機の完成


前回まででVisitorの使い方がわかったので、計算機を完成させる。

実装

文法ファイル

Calculator.g4
grammar Calculator;

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

expression 
    : NUMBER                        # Number
    | '(' inside=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
;

演算(+ - / *)の左辺 右辺を区別するために、Rule Element Labelsという仕組みを使う。
そのため、grammarファイルで演算の左のexpressionをleft,右のexpressionをrightとラベルを付ける。
そのために、grammarファイルに下記のようにleft=,right=を追記する。
またカッコについても大本のexpressionと区別するためinsideのラベルを付ける。

    | left=expression MULTI right=expression    # Multiplication

Visitorクラス作成

CalculatorBaseVisitorを継承して作成。

CalculatorVisitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime.Misc;

namespace Calculator02_2
{
    class CalculatorVisitor: CalculatorBaseVisitor<float>
    {
        public override float VisitAddition([NotNull] CalculatorParser.AdditionContext context)
        {
            return base.Visit(context.left) + base.Visit(context.right);
        }
        public override float VisitDivision([NotNull] CalculatorParser.DivisionContext context)
        {
            return base.Visit(context.left) / base.Visit(context.right);
        }
        public override float VisitMultiplication([NotNull] CalculatorParser.MultiplicationContext context)
        {
            return base.Visit(context.left) * base.Visit(context.right);
        }
        public override float VisitSubtraction([NotNull] CalculatorParser.SubtractionContext context)
        {
            return base.Visit(context.left) - base.Visit(context.right);
        }
        public override float VisitParentheses([NotNull] CalculatorParser.ParenthesesContext context)
        {
            return base.Visit(context.inside);
        }
        public override float VisitNumber([NotNull] CalculatorParser.NumberContext context)
        {
            return float.Parse(context.NUMBER().GetText());
        }
    }
}

各演算から、左辺/右辺をvisit。数値は数値をそのまま返す。

Program.cs

visitがfloatを返すようになったので、それを表示するように変更。

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

namespace Calculator02_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);
            var graphContext = parser.expression();
            CalculatorVisitor visitor = new CalculatorVisitor();
            Console.WriteLine(visitor.Visit(graphContext));
        }
    }
}

実行結果

>Calculator02_2.exe 3-1
2

>Calculator02_2.exe 3-1*5
-2

>Calculator02_2.exe 3-1*5/2
0.5

>Calculator02_2.exe (3-1)*5/2
5

>Calculator02_2.exe 1*2+3/4
2.75

メモ

Visitorから別のVisitorをvisitする際に、base.を付ける必要あるか。
付ける場合と付けない場合の違いはなにか。
後日調べる?