私は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)
_
ビジター実装を使用して、マップに格納されている関数メタデータに対して入力が検証されるかどうかを確認したいと思いました。
以下は、私が使用しているレクサーとパーサーです。
レクサー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 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::");
ステートメントを出力していないため、ビジター実装に移行していません。
以下はC#でのソリューションです。これにより、続行方法のアイデアが得られます。コードをJavaに簡単に変換できるはずです。
簡単にするために、拡張機能AntlrVSIX for Visual Studio 2019 for NET Core C#を使用してコードを実装しました。完全なIDEは、分割レクサー/パーサーの文法の構築、デバッグ、およびAntlr文法の編集に適したプラグインをサポートしています。
文法に関して注意すべき点がいくつかあります。まず、Antlr 4.7.2ではパーサーの文法が受け入れられません。プロダクション "WS:[\ t\r\n] +->スキップ;"レクサールールです。パーサーの文法では使用できません。レクサー文法に入る必要があります(または、結合文法を定義します)。第2に、個人的にはDOTのようなレクサーシンボルを定義せず、パーサーでレクサーシンボルのRHSをパーサー文法で直接使用します(例:「。」)。混乱を招くし、IDEがなければ、エディタが「DOT: '。';」の定義に移動する方法を知っていると思います。パーサーの文法の「。」にカーソルを合わせます。Antlrで許可されている理由がわかりませんでしたが、代わりに定義したレクサーシンボルを使用します。3番目に、パーサーの文法を通常の方法で拡張することを検討します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);
}
}
}