入れ子関数のビジターパターンを実装する方法


8

私はAntlrの初心者であり、Antlr4を使用して以下の実装を実行したかったのです。以下の機能を持っています。

1. FUNCTION.add(Integer a,Integer b)
2. FUNCTION.concat(String a,String b)
3. FUNCTION.mul(Integer a,Integer b)

そして、私はこのような関数のメタデータを保存しています。

Map<String,String> map=new HashMap<>();
        map.put("FUNCTION.add","Integer:Integer,Integer");
        map.put("FUNCTION.concat","String:String,String");
        map.put("FUNCTION.mul","Integer:Integer,Integer");

、どこInteger:Integer,Integerを表すInteger戻り値の型があり、入力はaccesptsが機能しているだろうparamsはInteger,Integer

入力がこのようなものである場合

FUNCTION.concat(Function.substring(String,Integer,Integer),String)
or
FUNCTION.concat(Function.substring("test",1,1),String)

ビジター実装を使用して、マップに格納されている関数メタデータに対して入力が検証されるかどうかを確認したいと思いました。

以下は、私が使用しているレクサーとパーサーです。

レクサーMyFunctionsLexer.g4:

lexer grammar MyFunctionsLexer;

FUNCTION: 'FUNCTION';

NAME: [A-Za-z0-9]+;

DOT: '.';

COMMA: ',';

L_BRACKET: '(';

R_BRACKET: ')';

パーサーMyFunctionsParser.g4:

parser grammar MyFunctionsParser;

options {
    tokenVocab=MyFunctionsLexer;
}

function : FUNCTION '.' NAME '('(function | argument (',' argument)*)')';

argument: (NAME | function);

WS : [ \t\r\n]+ -> skip;

Antlr4を使用しています。

以下は、提案された回答に従って使用している実装です。

訪問者の実装:パブリッククラスFunctionValidateVisitorImpl extends MyFunctionsParserBaseVisitor {

    Map<String, String> map = new HashMap<String, String>();

    public FunctionValidateVisitorImpl()
    {
        map.put("FUNCTION.add", "Integer:Integer,Integer");
        map.put("FUNCTION.concat", "String:String,String");
        map.put("FUNCTION.mul", "Integer:Integer,Integer");
        map.put("FUNCTION.substring", "String:String,Integer,Integer");
    }

    @Override
    public String visitFunctions(@NotNull MyFunctionsParser.FunctionsContext ctx) {
        System.out.println("entered the visitFunctions::");
        for (int i = 0; i < ctx.getChildCount(); ++i)
        {
            ParseTree c = ctx.getChild(i);
            if (c.getText() == "<EOF>")
                continue;
            String top_level_result = visit(ctx.getChild(i));
            System.out.println(top_level_result);
            if (top_level_result == null)
            {
                System.out.println("Failed semantic analysis: "+ ctx.getChild(i).getText());
            }
        }
        return null;
    }

    @Override
    public String visitFunction( MyFunctionsParser.FunctionContext ctx) {
        // Get function name and expected type information.
        String name = ctx.getChild(2).getText();
        String type=map.get("FUNCTION." + name);
        if (type == null)
        {
            return null; // not declared in function table.
        }
        String result_type = type.split(":")[0];
        String args_types = type.split(":")[1];
        String[] expected_arg_type = args_types.split(",");
        int j = 4;
        ParseTree a = ctx.getChild(j);
        if (a instanceof MyFunctionsParser.FunctionContext)
        {
            String v = visit(a);
            if (v != result_type)
            {
                return null; // Handle type mismatch.
            }
        } else {
            for (int i = j; i < ctx.getChildCount(); i += 2)
            {
                ParseTree parameter = ctx.getChild(i);
                String v = visit(parameter);
                if (v != expected_arg_type[(i - j)/2])
                {
                    return null; // Handle type mismatch.
                }
            }
        }
        return result_type;
    }


    @Override
    public String visitArgument(ArgumentContext ctx){
        ParseTree c = ctx.getChild(0);
        if (c instanceof TerminalNodeImpl)
        {
            // Unclear if what this is supposed to parse:
            // Mutate "1" to "Integer"?
            // Mutate "Integer" to "String"?
            // Or what?
            return c.getText();
        }
        else
            return visit(c);
    }


}

Testcalss:

public class FunctionValidate {


    public static void main(String[] args) {
        String input = "FUNCTION.concat(FUNCTION.substring(String,Integer,Integer),String)";
        ANTLRInputStream str = new ANTLRInputStream(input);
        MyFunctionsLexer lexer = new MyFunctionsLexer(str);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        MyFunctionsParser parser = new MyFunctionsParser(tokens);
        parser.removeErrorListeners(); // remove ConsoleErrorListener 
        parser.addErrorListener(new VerboseListener()); // add ours
        FunctionsContext tree = parser.functions();
        FunctionValidateVisitorImpl visitor = new FunctionValidateVisitorImpl();
        visitor.visit(tree);
    }


}

レクサー:

lexer grammar MyFunctionsLexer;
FUNCTION: 'FUNCTION';
NAME: [A-Za-z0-9]+;
DOT: '.';
COMMA: ',';
L_BRACKET: '(';
R_BRACKET: ')';
WS : [ \t\r\n]+ -> skip;

パーサー:

parser grammar MyFunctionsParser;
options { tokenVocab=MyFunctionsLexer; }
functions : function* EOF;
function : FUNCTION '.' NAME '(' (function | argument (',' argument)*) ')';
argument: (NAME | function);

冗長リスナー:

public class VerboseListener  extends BaseErrorListener  {

    @Override 
    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { 
        List<String> stack = ((Parser)recognizer).getRuleInvocationStack();
        Collections.reverse(stack); 
        throw new FunctionInvalidException("line "+line+":"+charPositionInLine+" at "+ offendingSymbol+": "+msg);

    }
}

出力:System.out.println("entered the visitFunctions::");ステートメントを印刷していないため、ビジター実装に移行していません。

回答:


4

以下はC#でのソリューションです。これにより、続行方法のアイデアが得られます。コードをJavaに簡単に変換できるはずです。

簡単にするために、NET Core C#でVisual Studio 2019用の拡張機能AntlrVSIXを使用してコードを実装しました。分割されたレクサー/パーサーの文法の構築、デバッグ、Antlr文法の編集に適したプラグインをサポートする完全なIDEを使用することで、作業が楽になります。

文法に関して注意すべき点がいくつかあります。まず、Antlr 4.7.2ではパーサーの文法が受け入れられません。プロダクション "WS:[\ t \ r \ n] +->スキップ;" レクサールールです。パーサーの文法には使用できません。レクサー文法に入る必要があります(または、結合文法を定義します)。第二に、私は個人的にはDOTのようなレクサーシンボルを定義せず、パーサーでレクサーシンボルのRHSをパーサー文法で直接使用します(例: '。')。混乱を招くし、IDEやエディターが "DOT: '。';"の定義に移動する方法を知っているとは思えない。カーソルを「。」に置いた場合、字句解析器の文法で パーサーの文法で。なぜAntlrで許可されているのか理解できませんでしたが、c'est la vieです。代わりに、定義したレクサーシンボルを使用します。第三、パーサーの文法を通常の方法でEOFで拡張することを検討します(例: "functions:function * EOF")。しかし、これは完全にあなた次第です。

これで、問題ステートメントで、入力例に不整合が含まれています。最初のケースである "substring(String、Integer、Integer)"では、入力はsubstring()のメタライクな記述にあります。2番目のケース「substring(\ "test \"、1,1)」では、コードを解析しています。最初のケースは文法で構文解析し、2番目のケースは構文解析しません-レクサー文法で定義された文字列リテラルレクサールールはありません。実際に何を解析したいのかは不明です。

全体的に、文字列に対してビジターコードを定義しました。つまり、各メソッドは、関数または引数の出力タイプを表す文字列を返します。静的セマンティックエラーの場合)。次に、解析ツリーノードの各子でVisit()を使用して、結果の文字列が期待どおりかどうかを確認し、必要に応じて一致を処理します。

注意すべきもう1つのこと。この問題は、ビジターまたはリスナークラスを介して解決できます。訪問者クラスは、純粋に合成された属性に役立ちます。このサンプルソリューションでは、関数のタイプを表す文字列、または関連する解析ツリーの引数を返し、各重要な子の値をチェックしています。リスナークラスは、L属性の文法(つまり、ツリー内の各ノードで左から右にDFS指向の方法で属性を渡す場合)に役立ちます。この例では、リスナークラスを使用して、Exit()関数のみをオーバーライドできますが、「コンテキスト」を属性(文字列)にマップするためにMap / Dictionaryが必要になります。

lexer grammar MyFunctionsLexer;
FUNCTION: 'FUNCTION';
NAME: [A-Za-z0-9]+;
DOT: '.';
COMMA: ',';
L_BRACKET: '(';
R_BRACKET: ')';
WS : [ \t\r\n]+ -> skip;
parser grammar MyFunctionsParser;
options { tokenVocab=MyFunctionsLexer; }
functions : function* EOF;
function : FUNCTION '.' NAME '(' (function | argument (',' argument)*) ')';
argument: (NAME | function);
using Antlr4.Runtime;

namespace AntlrConsole2
{
    public class Program
    {
        static void Main(string[] args)
        {
            var input = @"FUNCTION.concat(FUNCTION.substring(String,Integer,Integer),String)";
            var str = new AntlrInputStream(input);
            var lexer = new MyFunctionsLexer(str);
            var tokens = new CommonTokenStream(lexer);
            var parser = new MyFunctionsParser(tokens);
            var listener = new ErrorListener<IToken>();
            parser.AddErrorListener(listener);
            var tree = parser.functions();
            if (listener.had_error)
            {
                System.Console.WriteLine("error in parse.");
            }
            else
            {
                System.Console.WriteLine("parse completed.");
            }
            var visitor = new Validate();
            visitor.Visit(tree);
        }
    }
}
namespace AntlrConsole2
{
    using System;
    using Antlr4.Runtime.Misc;
    using System.Collections.Generic;

    class Validate : MyFunctionsParserBaseVisitor<string>
    {
        Dictionary<String, String> map = new Dictionary<String, String>();

        public Validate()
        {
            map.Add("FUNCTION.add", "Integer:Integer,Integer");
            map.Add("FUNCTION.concat", "String:String,String");
            map.Add("FUNCTION.mul", "Integer:Integer,Integer");
            map.Add("FUNCTION.substring", "String:String,Integer,Integer");
        }

        public override string VisitFunctions([NotNull] MyFunctionsParser.FunctionsContext context)
        {
            for (int i = 0; i < context.ChildCount; ++i)
            {
                var c = context.GetChild(i);
                if (c.GetText() == "<EOF>")
                    continue;
                var top_level_result = Visit(context.GetChild(i));
                if (top_level_result == null)
                {
                    System.Console.WriteLine("Failed semantic analysis: "
                        + context.GetChild(i).GetText());
                }
            }
            return null;
        }

        public override string VisitFunction(MyFunctionsParser.FunctionContext context)
        {
            // Get function name and expected type information.
            var name = context.GetChild(2).GetText();
            map.TryGetValue("FUNCTION." + name, out string type);
            if (type == null)
            {
                return null; // not declared in function table.
            }
            string result_type = type.Split(":")[0];
            string args_types = type.Split(":")[1];
            string[] expected_arg_type = args_types.Split(",");
            const int j = 4;
            var a = context.GetChild(j);
            if (a is MyFunctionsParser.FunctionContext)
            {
                var v = Visit(a);
                if (v != result_type)
                {
                    return null; // Handle type mismatch.
                }
            } else {
                for (int i = j; i < context.ChildCount; i += 2)
                {
                    var parameter = context.GetChild(i);
                    var v = Visit(parameter);
                    if (v != expected_arg_type[(i - j)/2])
                    {
                        return null; // Handle type mismatch.
                    }
                }
            }
            return result_type;
        }

        public override string VisitArgument([NotNull] MyFunctionsParser.ArgumentContext context)
        {
            var c = context.GetChild(0);
            if (c is Antlr4.Runtime.Tree.TerminalNodeImpl)
            {
                // Unclear if what this is supposed to parse:
                // Mutate "1" to "Integer"?
                // Mutate "Integer" to "String"?
                // Or what?
                return c.GetText();
            }
            else
                return Visit(c);
        }
    }
}

System.out.println( "entered the visitFunctions ::");を印刷していないため、ビジター実装を開始していません。ステートメント。
vikram

IntelliJIDEAのようなIDEを使用して、printlnに依存するのではなく、プログラムをデバッグしてみてください。プログラムは変更を加えて機能します。文字列を!と比較するときは、!=演算子を置き換える必要があります。およびequals()。VerboseListenerのコードが見つからなかったので、試してみるとコメントアウトしました。また、不足しているimportステートメントをコードに追加して、コードをコンパイルできるようにしました。
kaby76

コードでvisitor.visit(tree);を使用します。ステートメント私はそれがメソッドを入力していないと思いますが、visitor.visitFunctions(tree)を使用する場合、それは印刷しています。何か不足していますか?
ビクラム

例外をスローするために使用したVerboseListenerも更新しました。
vikram

「visitor.visit(tree)」にブレークポイントを設定し、visitFunctions()の最初の行を設定してから、関数visit()にステップインし、ロジックが実行するすべての関数にシングルステップで進むようにしましたか?何が起こっているのかわかりません。「visitor.visit(tree)」は私のために働きます。
kaby76
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.