ANTLR4をC#から使ってみる #01


環境

Visual Studio 2017
Windows 10

セットアップ

ツール > 拡張機能と更新プログラム > antlrで検索
antlr language supportをダウンロード

Hello abc

趣味プログラミングの部屋の(01)ANTLR4をC#で使ってみるを参考に、簡単なプログラムを作る。
Hello.g4とProgram.csはこのサイトからの引用。

  • 新規プロジェクト作成 コンソールアプリ(.NET Framework)
  • antlr4パッケージインストール nugetパッケージマネージャーコンソールで実行。
    Install-Package antlr4
  • Grammarファイル作成 ソリューションエクスプローラー > 追加 > ANTLR4 Combined Grammar
Hello.g4
// Define a grammar called Hello`

grammar Hello;
r  : 'hello' ID ;         // match keyword hello followed by an identifier
ID : [a-z]+ ;             // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
  • Program.cs編集
    • using追加 using Antlr4.Runtime;
      を追加。
    • mainを書き換える
using Antlr4.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AntlrTest01
{
    class Program
    {
        static void Main(string[] args)
        {
            string parsedString = "hello abc";
            var inputStream = new AntlrInputStream(parsedString);
            var lexer = new HelloLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(lexer);
            var parser = new HelloParser(commonTokenStream);
            HelloListener listener = new HelloListener();
            parser.AddParseListener(listener);
            var graphContext = parser.r();
            Console.WriteLine(graphContext.ToStringTree());
        }
    }
}

  • ビルドして実行
    ([] hello abc)

Listenerを使ってみる

Listenerで自動生成されるクラスとメソッド

グラマーファイルで定義したgrammarの名前 + 'BaseListener'という名前のクラスが自動生成されている。
この中ではグラマーファイルで定義したrの要素などのListenerが定義されている。
今回だと、下記が定義されている。

  • public virtual void EnterR([NotNull] HelloParser.RContext context)
  • public virtual void ExitR([NotNull] HelloParser.RContext context)
  • public virtual void EnterEveryRule([NotNull] ParserRuleContext context)
  • public virtual void ExitEveryRule([NotNull] ParserRuleContext context)
  • public virtual void VisitTerminal([NotNull] ITerminalNode node)
  • public virtual void VisitErrorNode([NotNull] IErrorNode node)

Listenerを使った簡単な実装をしてみる

この継承したクラスを定義し、Listenerをoverrideして作成しListenerを記述する。

HelloListener.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 AntlrTest01
{
    public class HelloListener : HelloBaseListener
    {
        public override void EnterR([NotNull] HelloParser.RContext context)
        {

            base.EnterR(context);
            Console.WriteLine("--EnterR--");
            Console.WriteLine(" GetText():{0} ID:{1} ", context.GetText(),context.ID());
        }
        public override void EnterEveryRule([NotNull] ParserRuleContext context)
        {
            base.EnterEveryRule(context);
            Console.WriteLine("--EnterEveryRule--");
            Console.WriteLine(" GetText():{0}", context.GetText());
        }
        public override void ExitEveryRule([NotNull] ParserRuleContext context)
        {
            base.ExitEveryRule(context);
            Console.WriteLine("--ExitEveryRule--");
            Console.WriteLine(" GetText():{0}", context.GetText());
        }
        public override void ExitR([NotNull] HelloParser.RContext context)
        {
            base.ExitR(context);
            Console.WriteLine("--ExitR--");
            Console.WriteLine(" GetText():{0} ID:{1}", context.GetText(),context.ID());


            for(var i= 0 ; i< context.children.Count;i++)
            {
                Console.WriteLine(" child({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());
        }
    }
}
  • mainでリスナーを設定。
        static void Main(string[] args)
        {
            string parsedString = "hello abc";
            var inputStream = new AntlrInputStream(parsedString);
            var lexer = new HelloLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(lexer);
            var parser = new HelloParser(commonTokenStream);
            HelloListener listener = new HelloListener(); //追加
            parser.AddParseListener(listener);            //追加
            var graphContext = parser.r();
            Console.WriteLine(graphContext.ToStringTree());
        }

実行結果

--EnterEveryRule--
 GetText():
--EnterR--
 GetText(): ID:
--VisitTerminal--
 GetText():hello
--VisitTerminal--
 GetText():abc
--ExitR--
 GetText():helloabc ID:abc
 child(0): hello
 child(1): abc
--ExitEveryRule--
 GetText():helloabc
([] hello abc)

Visitorを使ってみる

Visitorで自動生成されるクラスとメソッド

グラマーファイルで定義したgrammarの名前 + 'BaseVisitor'という名前のクラスが自動生成されている。
この中ではグラマーファイルで定義したrの要素のVisitorが定義されている。
Listenerだと、多くのメソッドが自動生成されていたが、Visitorでは下の1つだけ。

  • public virtual Result VisitR([NotNull] HelloParser.RContext context)

これをoverrideして利用する。

Visitorを使った簡単な実装をしてみる

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

namespace AntlrTest02
{
    public class HelloVisitor : HelloBaseVisitor<object>
    {
        public override object VisitR([NotNull] HelloParser.RContext context)
        {
            Console.WriteLine(context.ID());
            return base.VisitR(context);
        }
    }
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime;

namespace AntlrTest02
{
    class Program
    {
        static void Main(string[] args)
        {
            string parsedString = "hello abc";
            if (args.Length > 0) parsedString = args[0];
            var inputStream = new AntlrInputStream(parsedString);
            var lexer = new HelloLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(lexer);
            var parser = new HelloParser(commonTokenStream);
            HelloParser.RContext context = parser.r();
            HelloVisitor visitor = new HelloVisitor();
            visitor.Visit(context);
        }
    }
}
  • 実行結果

abc