Как реализовать шаблон посетителя для вложенной функции - PullRequest
9 голосов
/ 21 октября 2019

Я новичок в 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 тип возвратаи входные параметры, которые будет принимать функция, равны Integer,Integer.

, если вход имеет что-то вроде этого

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

Используя реализацию посетителя, я хотел проверить, является ли вход действительным или нетметаданные функций, хранящиеся в карте.

Ниже приводится лексер и анализатор, которые я использую:

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;

Я использую 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:

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

Verbose Listener:

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

    }
}

Вывод: не заданРеализация посетителя ering, поскольку она не печатает System.out.println("entered the visitFunctions::"); оператор.

1 Ответ

4 голосов
/ 25 октября 2019

Ниже приведено решение в C #. Это должно дать вам представление о том, как действовать. Вы должны быть в состоянии легко перевести код на Java.

Для простоты я реализовал код, используя мое расширение AntlrVSIX для Visual Studio 2019 с NET Core C #. Это облегчает жизнь, используя полную IDE, которая поддерживает построение разделенных грамматик лексера / синтаксического анализатора, отладку и плагин, который подходит для редактирования грамматик Antlr.

Есть несколько вещей, которые следует отметить с вашей грамматикой. Во-первых, ваша грамматика синтаксического анализатора не принимается Antlr 4.7.2. Производство "WS: [\ t \ r \ n] + -> skip;"это правило лексера, оно не может входить в грамматику синтаксического анализатора. Это должно войти в грамматику лексера (или вы определяете комбинированную грамматику). Во-вторых, я лично не стал бы определять символы лексера, такие как DOT, а затем использовать в синтаксическом анализаторе RHS символа лексера непосредственно в грамматике синтаксического анализатора, например, «.». Это сбивает с толку, и я уверен, что нет IDE или редактор знает, как перейти к определению "DOT: '.';"в грамматике лексера, если вы поместили курсор на «.»в грамматике парсера. Я никогда не понимал, почему это разрешено в Antlr, но c'est la vie. Я бы вместо этого использовал символ лексера, который вы определили. В-третьих, я хотел бы рассмотреть расширение грамматики синтаксического анализатора обычным способом с помощью EOF, например, «functions: function * EOF». Но это полностью ваше дело.

Теперь, в заявлении о проблеме, ваш пример ввода содержит несоответствие. В первом случае, «substring (String, Integer, Integer)», входные данные находятся в мета-подобном описании substring (). Во втором случае «substring (\" test \ ", 1,1)", вы анализируете код. Первый случай анализирует вашу грамматику, второй - нет - в вашей лексерской грамматике не определено правило строкового литерала. Непонятно, что вы действительно хотите анализировать.

В целом, я определил код посетителя для строк, т. Е. Каждый метод возвращает строку, представляющую тип вывода функции или аргумента, например, "Integer" или "String"или null, если произошла ошибка (или вы можете выдать исключение для статических семантических ошибок). Затем, используя метод Visit () для каждого дочернего элемента в узле дерева синтаксического анализа, проверьте полученную строку, если она ожидается, и обрабатывайте совпадения так, как вам нравится.

Еще одна вещь, на которую следует обратить внимание. Вы можете решить эту проблему через класс посетителя или слушателя. Класс посетителя полезен для чисто синтезированных атрибутов. В этом примере решения я возвращаю строку, представляющую тип функции, или аргументирую ассоциированное дерево разбора, проверяя значение для каждого важного дочернего элемента. Класс слушателя полезен для L-атрибутивных грамматик, т. Е. Когда вы передаете атрибуты в DFS-ориентированной манере слева направо в каждом узле дерева. В этом примере вы можете использовать класс слушателя и переопределить только функции Exit (), но тогда вам потребуется карта / словарь для сопоставления «контекста» с атрибутом (строкой).

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