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

# 環境
Visual Studio 2017
Windows 10

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

# Hello abc
趣味プログラミングの部屋の[(01)ANTLR4をC#で使ってみる](https://sites.google.com/site/hobbyprogrammingroom/game/gamescript/01)を参考に、簡単なプログラムを作る。
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) 

<!--
Enter要素名の場合は、その要素の処理の始めで呼ばれる。
Exit要素名の場合は、その要素の処理の終わりで呼ばれる。
EnterEveryRuleはすべての要素の処理の始めで呼ばれる。
ExitEveryRuleはすべての要素の処理の終わりで呼ばれる。
VisitTerminalは終端要素の処理時に呼ばれる。今回だと、helloとかabc。
VisitErrorNodeは、不明。
-->

## 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でリスナーを設定。

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