Cómo implementar el patrón de visitante para la función anidada

8

Soy un novato en Antlr y quería que la siguiente implementación se realizara usando Antlr4. Estoy teniendo las funciones escritas a continuación.

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

Y estoy almacenando las funciones de metadatos como esta.

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

Donde, Integer:Integer,Integerrepresenta Integeres el tipo de retorno y los parámetros de entrada a los que se accederá la función Integer,Integer.

si la entrada es algo como esto

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

Usando la implementación del visitante, quería verificar si la entrada es validada o no con las funciones de metadatos almacenados en el mapa.

A continuación se muestra el lexer y el analizador sintáctico que estoy usando:

Lexer MyFunctionsLexer.g4:

lexer grammar MyFunctionsLexer;

FUNCTION: 'FUNCTION';

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

DOT: '.';

COMMA: ',';

L_BRACKET: '(';

R_BRACKET: ')';

Analizador MyFunctionsParser.g4:

parser grammar MyFunctionsParser;

options {
    tokenVocab=MyFunctionsLexer;
}

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

argument: (NAME | function);

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

Estoy usando Antlr4.

A continuación se muestra la implementación que estoy usando según la respuesta sugerida.

Implementación del visitante: clase pública FunctionValidateVisitorImpl extiende 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;

Analizador:

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

Oyente detallado:

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

    }
}

Salida: no está entrando en la implementación del visitante ya que no está imprimiendo la System.out.println("entered the visitFunctions::");declaración.

vikram
fuente

Respuestas:

4

A continuación se muestra una solución en C #. Esto debería darle una idea de cómo proceder. Debería poder traducir fácilmente el código a Java.

Para mayor facilidad, implementé el código usando mi extensión AntlrVSIX para Visual Studio 2019 con NET Core C #. Hace la vida más fácil usando un IDE completo que admite la construcción de gramáticas divididas lexer / parser, depuración y un complemento que es adecuado para editar gramáticas Antlr.

Hay varias cosas a tener en cuenta con su gramática. Primero, su gramática del analizador no es aceptada por Antlr 4.7.2. Producción "WS: [\ t \ r \ n] + -> skip;" es una regla lexer, no puede ir en una gramática del analizador sintáctico. Tiene que ir a la gramática lexer (o definir una gramática combinada). En segundo lugar, personalmente no definiría símbolos lexer como DOT, y luego usaría en el analizador el RHS del símbolo lexer directamente en la gramática del analizador, por ejemplo, '.'. Es confuso, y estoy bastante seguro de que no hay un IDE o editor que sepa cómo ir a la definición "DOT: '.';" en la gramática lexer si posiciona el cursor en el '.' en la gramática del analizador sintáctico. Nunca entendí por qué está permitido en Antlr, pero c'est la vie. En su lugar, usaría el símbolo lexer que defina. Tercero, Consideraría aumentar la gramática del analizador sintáctico de la manera habitual con EOF, por ejemplo, "funciones: función * EOF". Pero, esto depende totalmente de usted.

Ahora, en la declaración del problema, su entrada de ejemplo contiene una inconsistencia. En el primer caso, "substring (String, Integer, Integer)", la entrada está en una descripción de metacadena de substring (). En el segundo caso, "subcadena (\" prueba \ ", 1,1)", está analizando el código. El primer caso se analiza con su gramática, el segundo no; no hay una regla de lexer literal de cadena definida en su gramática lexer. No está claro lo que realmente quieres analizar.

En general, definí el código del visitante sobre las cadenas, es decir, cada método devuelve una cadena que representa el tipo de salida de la función o argumento, por ejemplo, "Entero" o "Cadena" o nulo si hubo un error (o podría lanzar una excepción para errores semánticos estáticos). Luego, usando Visit () en cada elemento secundario en el nodo del árbol de análisis, verifique la cadena resultante si se espera y maneje las coincidencias como desee.

Otra cosa a tener en cuenta. Puede resolver este problema a través de una clase de visitante u oyente. La clase de visitante es útil para atributos puramente sintetizados. En esta solución de ejemplo, devuelvo una cadena que representa el tipo de la función o argumenta el árbol de análisis asociado, verificando el valor de cada elemento secundario importante. La clase de escucha es útil para las gramáticas atribuidas a L, es decir, cuando pasa atributos de una manera orientada a DFS, de izquierda a derecha en cada nodo del árbol. Para este ejemplo, podría usar la clase de escucha y solo anular las funciones Exit (), pero luego necesitaría un Mapa / Diccionario para mapear un "contexto" en un atributo (cadena).

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
fuente
No está entrando en la implementación del visitante ya que no está imprimiendo System.out.println ("ingresó a visitFunctions ::"); declaración.
vikram
Debería intentar usar un IDE como IntelliJIDEA y depurar su programa en lugar de confiar en println's. Su programa funciona con modificaciones. Debería reemplazar los operadores! = Al comparar cadenas con! y es igual a (). No vi su código para VerboseListener, así que lo comenté al intentarlo. Además, agregué declaraciones de importación faltantes a su código para obtener el código para compilar.
kaby76
En el código mediante el uso de visitor.visit (árbol); declaración Creo que no está ingresando ningún método, pero si uso visit.visitFunctions (árbol) se está imprimiendo. ¿Me estoy perdiendo de algo?
vikram
También actualicé VerboseListener ahora, que utilicé para lanzar excepciones.
vikram
¿Ha intentado establecer un punto de interrupción en "visitor.visit (tree)" y la primera línea de visitFunctions (), luego ingrese a la función visit () y siga la lógica a medida que avanza un paso hacia absolutamente todas las funciones que llama? No puedo decir lo que está pasando. "Visitor.visit (árbol)" funciona para mí.
kaby76