我正在尝试使用antlr4为我的DSL编写语法。从本质上讲,我正在尝试创建一种DSL,用于在树结构中描述函数应用程序。
目前,我在创建正确的语法(或在c#中正确使用访问者)失败解析表达式,如
#func1(jsonconfig)
#func1(jsonconfig, #func2(...))
#func1(#func2(...), #func3(...), ..., #func_n(...))
#func1(jsonconfig, #func2(...), #func3(...), ..., #func_n(...))
我的语法(为了简洁,删除了一些部分)
func
: FUNCTION_START IDENTIFIER LPAREN (config?) (argumentList?) RPAREN
;
argument
: func
;
argumentList
: (ARG_SEPARATOR argument)+
| ARG_SEPARATOR? argument
;
config
: json
;
但是,当尝试解析表达式时,我只得到第一个参数,而不是其余的。
这是我的访客:
public class DslVisitor : JustDslBaseVisitor<Instruction>
{
public override Instruction VisitFunc(JustDslParser.FuncContext context)
{
var name = context.IDENTIFIER().GetText();
var conf = context.config()?.GetText();
var arguments = context.argumentList()?.argument() ?? Array.Empty<JustDslParser.ArgumentContext>();
var instruction = new Instruction
{
Name = name,
Config = conf == null ? null : JObject.Parse(conf),
Bindings = arguments.Select(x => x.Accept(this)).ToList()
};
return instruction;
}
public override Instruction VisitArgument(JustDslParser.ArgumentContext context)
{
return context.func().Accept(this);
}
}
我认为antlr
定义中可能存在一些语法错误,因为它无法解析列表,但成功解析了单个项目。在过去,我有一个稍微不同的语法,但它要求我总是传递一个不符合我需要的配置对象。
谢谢!
试试这样写:
func
: FUNCTION_START IDENTIFIER LPAREN functionArgs RPAREN
;
functionArgs :
: config (ARG_SEPARATOR argument)*
| argument (ARG_SEPARATOR argument)*
;
argument
: func
;
config
: json
;
或者(如果您确实需要argumentList
节点)
func
: FUNCTION_START IDENTIFIER LPAREN functionArgs RPAREN
;
functionArgs :
: config (ARG_SEPARATOR argumentList)?
| argumentList
;
argumentList :
: argument (ARG_SEPARATOR argument)*
;
argument
: func
;
config
: json
;
你的代码有几个问题。
首先,在解析之后,您没有在代码中实际测试解析结果。您应该添加一个ErrorListener,并测试词法分析器和/或解析器是否真的发现了错误。您还可以使用它将输出分流到您喜欢的任何位置。
public class ErrorListener<S> : ConsoleErrorListener<S>
{
public bool had_error;
public override void SyntaxError(TextWriter output, IRecognizer recognizer, S offendingSymbol, int line,
int col, string msg, RecognitionException e)
{
had_error = true;
base.SyntaxError(output, recognizer, offendingSymbol, line, col, msg, e);
}
}
简单地创建一个侦听器,为解析器调用AddErrorListener()
,调用解析方法,然后为侦听器测试had_error
。注意,您还应该向词法分析器添加一个侦听器。
。为了得到人们期望的输入,我们对c#代码进行了大量的编辑。我删除了c#转义并重新格式化了它,以获得以下输入:
#obj(
#property(
#unit(
{"value":"phoneNumbers"}
),
#agr_obj(
#valueof(
{"path":"$.phone_numbers"}
),
#current(
#valueof(
{"path":"$.type"}
) ),
#current(
#valueof(
{"path":"$.number"}
) ) ) ),
#property(
#unit(
{"value":"addrs"}
),
#agr_obj(
#valueof(
{"path":"$.addresses"}
),
#current(
#valueof(
{"path":"$.type"}
) ),
#current(
#obj(
#property(
#unit(
{"value":"city"}
),
#valueof(
{"path":"$.city"}
) ),
#property(
#unit(
{"value":"country"}
),
#valueof(
{"path":"$.country"}
) ),
#property(
#unit(
{"value":"street"}
),
#str_join(
{"separator":", "},
#valueof(
{"path":"$.street1"}
),
#valueof(
{"path":"$.street2"}
) ) ) ) ) ) ) )
第三。您不能使用在规则末尾有EOF的条目规则来扩展语法。eof增强规则强制解析器使用所有输入。这里,我只是添加了"start":
的规则start : func EOF ;
您需要将入口点更改为start()
而不是func()
。
最后,您的语法不能识别json
参数后面跟着可选的func
参数。由于func
的第一个参数可以是json
或json , func
或func
,因此需要对第一个参数进行异常处理。这个语法修复了:
grammar JustDsl;
LPAREN: '(';
RPAREN: ')';
FUNCTION_START: '#';
ARG_SEPARATOR: ',';
IDENTIFIER
: [a-zA-Z] [a-zA-Z-_] *
;
start : func EOF ;
func
: FUNCTION_START IDENTIFIER LPAREN argumentList? RPAREN
;
argument
: func
;
argumentList
: (config config_rest)?
| no_config_rest?
;
config_rest
: (ARG_SEPARATOR argument)*
;
no_config_rest
: argument (ARG_SEPARATOR argument)*
;
config
: json
;
json
: value
;
obj
: '{' pair (',' pair)* '}'
| '{' '}'
;
pair
: STRING ':' value
;
arr
: '[' value (',' value)* ']'
| '[' ']'
;
value
: STRING
| NUMBER
| obj
| arr
| 'true'
| 'false'
| 'null'
;
STRING
: '"' (ESC | SAFECODEPOINT)* '"'
;
fragment ESC
: '\' (["\/bfnrt] | UNICODE)
;
fragment UNICODE
: 'u' HEX HEX HEX HEX
;
fragment HEX
: [0-9a-fA-F]
;
fragment SAFECODEPOINT
: ~ ["\u0000-u001F]
;
NUMBER
: '-'? INT ('.' [0-9] +)? EXP?
;
fragment INT
: '0' | [1-9] [0-9]*
;
// no leading zeros
fragment EXP
: [Ee] [+-]? INT
;
// - since - means "range" inside [...]
WS
: [ tnr] + -> skip
;
Mike在正确的轨道上(但是在functionArgs
规则上有一个打字错误)。但是如果没有输入,这个问题很难解决。