Como implementar o padrão de visitante para a função aninhada

8

Sou iniciante no Antlr e queria que a implementação abaixo fosse feita usando o Antlr4. Estou tendo as funções abaixo escritas.

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

E estou armazenando os metadados de funções como este.

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

Onde, Integer:Integer,Integerrepresenta Integeré o tipo de retorno e os parâmetros de entrada que a função aceitará Integer,Integer.

se a entrada é algo como isto

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

Usando a implementação do visitante, eu queria verificar se a entrada é validada ou não com relação aos metadados de funções armazenados no mapa.

Abaixo está o lexer e o analisador que estou usando:

Lexer MyFunctionsLexer.g4:

lexer grammar MyFunctionsLexer;

FUNCTION: 'FUNCTION';

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

DOT: '.';

COMMA: ',';

L_BRACKET: '(';

R_BRACKET: ')';

Analisador MyFunctionsParser.g4:

parser grammar MyFunctionsParser;

options {
    tokenVocab=MyFunctionsLexer;
}

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

argument: (NAME | function);

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

Estou usando o Antlr4.

Abaixo está a implementação que estou usando, conforme a resposta sugerida.

Implementação do visitante: classe pública FunctionValidateVisitorImpl estende 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;

Analisador:

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

Ouvinte verboso:

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

    }
}

Saída: não está entrando na implementação do visitante, pois não está imprimindo uma System.out.println("entered the visitFunctions::");instrução.

Vikram
fonte

Respostas:

4

Abaixo está uma solução em c #. Isso deve lhe dar uma idéia de como proceder. Você poderá traduzir facilmente o código para Java.

Para facilitar, implementei o código usando minha extensão AntlrVSIX for Visual Studio 2019 com NET Core C #. Facilita a vida usando um IDE completo que suporta a criação de gramáticas de lexer / analisador de divisão, depuração e um plug-in adequado para editar gramáticas do Antlr.

Há várias coisas a serem observadas na sua gramática. Primeiro, sua gramática do analisador não é aceita pelo Antlr 4.7.2. Produção "WS: [\ t \ r \ n] + -> skip;" é uma regra lexer, não pode ser inserida em uma gramática do analisador. Ele precisa entrar na gramática lexer (ou você define uma gramática combinada). Segundo, eu pessoalmente não definiria símbolos lexer como DOT e, em seguida, usaria no analisador o RHS do símbolo lexer diretamente na gramática do analisador, por exemplo, '.'. É confuso e tenho certeza de que não existe um IDE ou editor saberia como ir para a definição "DOT: '.';" na gramática lexer se você posicionou o cursor no '.' na gramática do analisador. Eu nunca entendi por que isso é permitido no Antlr, mas c'est la vie. Em vez disso, eu usaria o símbolo lexer que você define. Terceiro, Eu consideraria aumentar a gramática do analisador da maneira usual com o EOF, por exemplo, "functions: function * EOF". Mas, isso depende inteiramente de você.

Agora, na declaração do problema, sua entrada de exemplo contém uma inconsistência. No primeiro caso, "substring (String, Inteiro, Inteiro)", a entrada está em uma descrição meta-like de substring (). No segundo caso, "substring (\" test \ ", 1,1)", você está analisando o código. O primeiro caso analisa a sua gramática, o segundo não - não há regra de lexer literal de cadeia de caracteres definida em sua gramática. Não está claro o que você realmente deseja analisar.

No geral, eu defini o código do visitante sobre as strings, ou seja, cada método retorna uma string que representa o tipo de saída da função ou argumento, por exemplo, "Inteiro" ou "String" ou nulo se houver um erro (ou você poderá lançar uma exceção para erros semânticos estáticos). Em seguida, usando Visit () em cada filho no nó da árvore de análise, verifique a sequência resultante, se for o esperado, e manipule as correspondências como desejar.

Uma outra coisa a observar. Você pode resolver esse problema através de uma classe de visitante ou ouvinte. A classe de visitante é útil para atributos puramente sintetizados. Nesta solução de exemplo, retorno uma string que representa o tipo da função ou arg up na árvore de análise associada, verificando o valor de cada filho importante. A classe listener é útil para gramáticas atribuídas a L - ou seja, onde você está transmitindo atributos de maneira orientada ao DFS, da esquerda para a direita em cada nó da árvore. Neste exemplo, você poderia usar a classe listener e substituir apenas as funções Exit (), mas seria necessário um Mapa / Dicionário para mapear um "contexto" em um atributo (string).

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
fonte
Ele não está entrando na implementação do visitante, pois não está imprimindo System.out.println ("digitou o visitFunctions ::"); declaração.
Vikram
Você deve tentar usar um IDE como o IntelliJIDEA e depurar seu programa em vez de confiar nos println's. Seu programa trabalha com modificações. Você deve substituir os operadores! = Ao comparar cadeias com! e igual a (). Não vi seu código para o VerboseListener, então comentei isso ao tentar. Além disso, adicionei instruções de importação ausentes ao seu código para obter o código para compilar.
kaby76
No código usando visitor.visit (tree); Eu acho que não está inserindo nenhum método, mas se eu usar o visitor.visitFunctions (tree), ele está imprimindo. Estou esquecendo de algo?
Vikram
Atualizei o VerboseListener também agora, que usei para lançar exceções.
Vikram
Você já tentou definir um ponto de interrupção em "visitor.visit (tree)" e a primeira linha de visitFunctions () e, em seguida, entra na função visit () e segue a lógica conforme você avança em absolutamente todas as funções que chama? Não sei dizer o que está acontecendo. "visitor.visit (tree)" funciona para mim.
kaby76