So implementieren Sie das Besuchermuster für verschachtelte Funktionen

8

Ich bin ein Neuling bei Antlr und wollte, dass die folgende Implementierung mit Antlr4 durchgeführt wird. Ich habe die unten beschriebenen Funktionen.

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

Und ich speichere die Funktionsmetadaten so.

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");

Wo, Integer:Integer,Integer steht Integerder Rückgabetyp und Eingang params die Funktion accespts ist Integer,Integer.

wenn die Eingabe so etwas ist

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

Mit der Besucherimplementierung wollte ich überprüfen, ob die Eingabe anhand der in der Karte gespeicherten Funktionsmetadaten validiert ist oder nicht.

Unten ist der Lexer und Parser, den ich benutze:

Lexer MyFunctionsLexer.g4:

lexer grammar MyFunctionsLexer;

FUNCTION: 'FUNCTION';

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

DOT: '.';

COMMA: ',';

L_BRACKET: '(';

R_BRACKET: ')';

Parser MyFunctionsParser.g4:

parser grammar MyFunctionsParser;

options {
    tokenVocab=MyFunctionsLexer;
}

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

argument: (NAME | function);

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

Ich benutze Antlr4.

Unten ist die Implementierung, die ich gemäß der vorgeschlagenen Antwort verwende.

Besucherimplementierung: öffentliche Klasse FunctionValidateVisitorImpl erweitert 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:

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

Parser:

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

Ausführlicher Zuhörer:

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);

    }
}

Ausgabe: Die Besucherimplementierung wird nicht eingegeben, da keine System.out.println("entered the visitFunctions::");Anweisung gedruckt wird .

Vikram
quelle

Antworten:

4

Unten finden Sie eine Lösung in C #. Dies sollte Ihnen eine Vorstellung davon geben, wie Sie vorgehen sollen. Sie sollten den Code problemlos in Java übersetzen können.

Der Einfachheit halber habe ich den Code mit meiner Erweiterung AntlrVSIX für Visual Studio 2019 mit NET Core C # implementiert. Es erleichtert das Leben mit einer vollständigen IDE, die das Erstellen von geteilten Lexer / Parser-Grammatiken, das Debuggen und ein Plug-In unterstützt, das zum Bearbeiten von Antlr-Grammatiken geeignet ist.

Bei Ihrer Grammatik sind verschiedene Dinge zu beachten. Erstens wird Ihre Parser-Grammatik von Antlr 4.7.2 nicht akzeptiert. Produktion "WS: [\ t \ r \ n] + -> überspringen;" ist eine Lexer-Regel, sie kann nicht in eine Parser-Grammatik aufgenommen werden. Es muss in die Lexer-Grammatik gehen (oder Sie definieren eine kombinierte Grammatik). Zweitens würde ich persönlich keine Lexersymbole wie DOT definieren und dann im Parser die RHS des Lexersymbols direkt in der Parsergrammatik verwenden, z. B. '.'. Es ist verwirrend und ich bin mir ziemlich sicher, dass es keine IDE gibt oder ein Editor weiß, wie man zur Definition "DOT: '.';" in der Lexer-Grammatik, wenn Sie den Cursor auf das '.' in der Parser-Grammatik. Ich habe nie verstanden, warum es in Antlr erlaubt ist, aber c'est la vie. Ich würde stattdessen das von Ihnen definierte Lexersymbol verwenden. Dritte, Ich würde in Betracht ziehen, die Parser-Grammatik auf die übliche Weise mit EOF zu erweitern, z. B. "Funktionen: Funktion * EOF". Aber das liegt ganz bei Ihnen.

In der Problemstellung enthält Ihre Beispieleingabe nun eine Inkonsistenz. Im ersten Fall "Teilzeichenfolge (String, Integer, Integer)" befindet sich die Eingabe in einer metaähnlichen Beschreibung von Teilzeichenfolge (). Im zweiten Fall, "Teilzeichenfolge (" Test ", 1,1)", analysieren Sie Code. Der erste Fall analysiert Ihre Grammatik, der zweite nicht - in Ihrer Lexer-Grammatik ist keine String-Literal-Lexer-Regel definiert. Es ist unklar, was Sie wirklich analysieren möchten.

Insgesamt habe ich den Besuchercode über Zeichenfolgen definiert, dh, jede Methode gibt eine Zeichenfolge zurück, die den Ausgabetyp der Funktion oder des Arguments darstellt, z. B. "Integer" oder "String" oder null, wenn ein Fehler aufgetreten ist (oder Sie eine Ausnahme auslösen könnten für statische semantische Fehler). Überprüfen Sie dann mit Visit () für jedes untergeordnete Element im Analysebaumknoten die resultierende Zeichenfolge, wenn dies erwartet wird, und behandeln Sie Übereinstimmungen nach Ihren Wünschen.

Eine andere Sache zu beachten. Sie können dieses Problem über eine Besucher- oder Hörerklasse lösen. Die Besucherklasse ist nützlich für rein synthetisierte Attribute. In dieser Beispiellösung gebe ich eine Zeichenfolge zurück, die den Typ der Funktion darstellt, oder argumentiere im zugehörigen Analysebaum und überprüfe den Wert für jedes wichtige untergeordnete Element. Die Listener-Klasse ist nützlich für L-zugeordnete Grammatiken, dh wenn Sie Attribute DFS-orientiert von links nach rechts an jedem Knoten im Baum übergeben. In diesem Beispiel könnten Sie die Listener-Klasse verwenden und nur die Exit () -Funktionen überschreiben. Dann benötigen Sie jedoch eine Map / ein Wörterbuch, um einen "Kontext" einem Attribut (einer Zeichenfolge) zuzuordnen.

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);
        }
    }
}
kaby76
quelle
Die Besucherimplementierung wird nicht eingegeben, da System.out.println nicht gedruckt wird ("enterFunctions ::" eingegeben). Aussage.
Vikram
Sie sollten versuchen, eine IDE wie IntelliJIDEA zu verwenden und Ihr Programm zu debuggen, anstatt sich auf println zu verlassen. Ihr Programm arbeitet mit Änderungen. Sie sollten die Operatoren! = Ersetzen, wenn Sie Zeichenfolgen mit! Vergleichen. und gleich (). Ich habe Ihren Code für VerboseListener nicht gesehen, also habe ich das beim Versuch auskommentiert. Außerdem habe ich Ihrem Code fehlende Importanweisungen hinzugefügt, um den zu kompilierenden Code zu erhalten.
Kaby76
Im Code mit Besucher.visit (Baum); Anweisung Ich denke, es wird keine Methode eingegeben, aber wenn ich besucher.visitFunctions (Baum) verwende, wird gedruckt. Vermisse ich etwas
Vikram
Ich habe den VerboseListener jetzt auch aktualisiert, den ich zum Auslösen von Ausnahmen verwendet habe.
Vikram
Haben Sie versucht, einen Haltepunkt bei "visit.visit (tree)" und in der ersten Zeile von visitFunctions () festzulegen, dann die Funktion visit () aufzurufen und der Logik zu folgen, während Sie in einem Schritt absolut jede aufgerufene Funktion ausführen? Ich kann nicht sagen, was passiert. "besucher.visit (baum)" funktioniert bei mir.
Kaby76