使用其他表达式中的表达式评估树

Evaluate tree with expression inside other expression

对于初学者,如果我在这件事上不够准确,我想道歉。

grammar Test;

@parser::header {#pragma warning disable 3021}
@lexer::header {#pragma warning disable 3021}

prog                       : expression? EOF;
expression                 : TEXT #text
                       | shift_left #shiftLeft
                       | shift_right #shiftRight
                       | upper_case #upperCase
                       | lower_case #lowerCase
                       | substring #ssubstring
                       | expression CONCANTENATE expression #concatenate
                       ;
substring : SUBSTRING OBRACKET expression COMMA NUMBER COMMA NUMBER CBRACKET;
shift_left : SHIFT_LEFT OBRACKET expression COMMA NUMBER CBRACKET;
shift_right : SHIFT_RIGHT OBRACKET expression COMMA NUMBER CBRACKET;
upper_case  : UPPER OBRACKET expression CBRACKET;
lower_case : LOWER OBRACKET expression CBRACKET;

compileUnit
    :   EOF
    ;

/*
 * Lexer Rules
 */

fragment L          : ('L'|'l') ;
fragment E          : ('E'|'e') ;
fragment F          : ('F'|'f') ;
fragment T          : ('T'|'t') ;
fragment U          : ('U'|'u') ;
fragment P          : ('P'|'p') ;
fragment R          : ('R'|'r') ;
fragment O          : ('O'|'o') ;
fragment W          : ('W'|'w') ;
fragment I          : ('I'|'i') ;
fragment G          : ('G'|'g') ;
fragment H          : ('H'|'h') ;
fragment S          : ('S'|'s') ;
fragment B          : ('B'|'b') ;
fragment N          : ('N'|'n') ;

COMMA               : ',';
OBRACKET            : '(';
CBRACKET            : ')';
CONCANTENATE        : '+';

NUMBER              : [1-9] (DIGIT)*;
DIGIT               : [0-9];


SHIFT_RIGHT         : R I G H T;
UPPER               : U P P E R;
LOWER               : L O W E R;
SUBSTRING           : S U B S T R I N G;
SHIFT_LEFT          : L E F T;

TEXT                : '"' .*? '"';
WHITESPACE          : (' '|'\t'|'\r'|'\n')+ -> skip ;

WS
   :   ' ' -> channel(HIDDEN)
   ;

我想要实现的是评估树(或者换句话说 - 能够实际执行简单的操作)。语法的整个思想是对字符串执行简单的操作。问题本身是我不知道如何实际遍历树并评估其表达式。我在这里举个例子会容易得多:

"upper(left("text"),2)" <- 这个操作是一个嵌套操作,它应该: 1. 将 "text" 向左移动 2(什么并不重要)实际上确实如此)。 2. Return 移位值 "up"。 3. 上层表达式应该收集 left() 产生的任何内容并做它的事情,在这种情况下,将 "text" 转换为大写。

整个 "nested expressions" 都是导致问题的原因。我已经实现了我自己的访问者 class 并且我有很多方法可以重写,例如表达式、子字符串、shiftright 等等——全部取自语法但我不知道如何在我的情况下使用它们面对,使用什么方法才能真正使用语法。

首先,你有很多几乎相同的名字,比如 shift_leftshiftLeft 只是在乞求引入错误,所以我强烈建议你重构你的语法:

expression : text
           | shiftLeft
           | shiftRight
           | upperCase
           | lowerCase
           | substring
           | concatenate
           ;
text : TEXT;
substring : SUBSTRING OBRACKET expression COMMA NUMBER COMMA NUMBER CBRACKET;
shiftLeft : SHIFT_LEFT OBRACKET expression COMMA NUMBER CBRACKET;
shiftRight : SHIFT_RIGHT OBRACKET expression COMMA NUMBER CBRACKET;
upperCase  : UPPER OBRACKET expression CBRACKET;
lowerCase : LOWER OBRACKET expression CBRACKET;
concatenate : expression CONCANTENATE expression;

或:

expression : TEXT #text
           | SHIFT_LEFT OBRACKET expression COMMA NUMBER CBRACKET #shiftLeft
           | SHIFT_RIGHT OBRACKET expression COMMA NUMBER CBRACKET #shiftRight
           | UPPER OBRACKET expression CBRACKET #upperCase
           | LOWER OBRACKET expression CBRACKET #lowerCase
           | SUBSTRING OBRACKET expression COMMA NUMBER COMMA NUMBER CBRACKET #substring
           | expression CONCANTENATE expression #concatenate
           ;

我会选择后者,因为它生成的树更简单。

要访问嵌套表达式,只需对子表达式递归调用 Visit,然后适当组合结果。因此,涵盖您的示例的访问者可能看起来像这样:

override String VisitText(TextContext ctx) {
    return ctx.TEXT().Text();
}

override String VisitUpper(UpperContext ctx) {
    return Visit(ctx.expression()).ToUpper();
}

override String VisitShiftLeft(ShiftLeftContext ctx) {
    int n = int.Parse(ctx.NUMBER().Text());
    // I'm assuming here that "shift left by N" means "remove N first chars"
    return Visit(ctx.expression()).Substring(n);
}

其他表达式类型的访问方法将遵循相同的逻辑。

感谢@sepp2k - 我将整个解决方案放在访问者的 C# 中:

public sealed class TreeEvaluationVisitor : TestBaseVisitor<Object> {

   public override object VisitText([NotNull] TestParser.TextContext context) {

        int string_length = context.TEXT().ToString().Length;
        return context.TEXT().ToString().Substring(1, string_length - 2);     
        //Substring() up here is for omitting the quote marks in the final output
    }

    public override object VisitUpperCase([NotNull] TestParser.UpperCaseContext context) {

        int string_length = Visit(context.expression()).ToString().Length;
        return Visit(context.expression()).ToString().ToUpper();
    }

    public override object VisitLowerCase([NotNull] TestParser.LowerCaseContext context) {

        int string_length = Visit(context.expression()).ToString().Length;
        return Visit(context.expression()).ToString().ToLower();
    }

    public override object VisitShiftLeft([NotNull] TestParser.ShiftLeftContext context) {

        int n = int.Parse(context.NUMBER().ToString());
        return sh_left(Visit(context.expression()).ToString(), n);
    }

    public override object VisitShiftRight([NotNull] TestParser.ShiftRightContext context) {

        int n = int.Parse(context.NUMBER().ToString());
        return sh_right(Visit(context.expression()).ToString(), n);
    }

    public override object VisitConcatenate([NotNull] TestParser.ConcatenateContext context) {
        string left = Visit(context.expression(0)).ToString();
        string right = Visit(context.expression(1)).ToString();

        return left + right;
    }

    public override object VisitSubstring([NotNull] TestParser.SubstringContext context) {

        int n1 = int.Parse(context.NUMBER(0).ToString());
        int n2 = int.Parse(context.NUMBER(1).ToString());

        return Visit(context.expression()).ToString().Substring(n1, n2);

    }

    //shift methods for shifting strings, i. e. left("abc",2) -> result = cab
    private static string sh_left(string chain, int amount) {  

        return (chain.Substring(amount) + chain.Substring(0, amount));

    }
    private static string sh_right(string chain, int amount) {

        return chain.Substring(chain.Length - amount) 
               + chain.Substring(0, chain.Length - amount);

    }