【问题标题】:ANTLR Grammar if StatementANTLR 语法 if 语句
【发布时间】:2012-02-16 15:29:47
【问题描述】:

我一直在努力学习 ANTLR,以创建一种特定领域的语言。要求之一是将此 DSL 翻译成 C。我已经能够获得识别 DSL 的基本语法,但是我在将其翻译成 C 时遇到了问题。主要是,我的问题来自尝试将 DSL if 语句翻译成C if 语句。我曾尝试在语法中使用 print 语句,但无济于事(我使用的是 C#)。

这是我一直在测试的语法:

**ifTest.g**
grammar ifTest;

options
{
backtrack=true;
output=AST;
language=CSharp2;
}

/*************************
PARSER RULES
*************************/
prog    :   lambda
|   statements EOF;

lambda  :   /* Empty */;

statements
:   statement+;

statement
:   logical
|   assignment
|   NEWLINE;


logical :   IF a=logical_Expr THEN b=statements 
        {
            System.Console.Write("\tif (" + $a.text + ")\n\t{\n\t" + "\t" +     $b.text + "\n\n\t}");   
        }
        ( ELSE c=statements      
       {    
        System.Console.Write("\n\telse {\n\t\t\t" + $c.text + "\n\t}"); 
    } )?
    ENDIF   
    {
        System.Console.Write("\n}");
    }
;

logical_Expr
    :   expr    
    ;

expr    :   (simple_Expr) (op expr)*
    ;

simple_Expr     : MINUS expr
    | identifier
    | number
    ;

identifier  : parameter
    | VARIABLE
    ;

parameter   : norm_parameter
    ;

norm_parameter  : spec_label
    | reserved_parm
    ;

spec_label  : LABEL
                ;

reserved_parm   : RES_PARM
                ;

op  :   PLUS
|   MINUS
|   MULT
|   DIV
|   EQUALS
|   GT
|   LT
|   GE
|   LE
;

number      : INT
    | FLOAT
    | HEX
                ;

assignment  : identifier GETS expr
;

/*************************
    LEXER RULES
*************************/
WS  :       (' '|'\t')+ {$channel=HIDDEN;};

COMMENT :   '/*' (options {greedy=false;}:.)* '*/' {$channel=HIDDEN;}
                ;

LINECOMMENT
    :   '#' ~('\n'|'\r')* NEWLINE {$channel=HIDDEN;}
    ;

NEWLINE :   '\r'?'\n' {$channel=HIDDEN;};

IF  :   I F;
THEN    :   T H E N;
ELSE    :   E L S E;
ENDIF   :   E N D I F;

PLUS    :   '+';
MINUS   :   '-';
MULT    :   '*';
DIV :   '/';
EQUALS  :   '=';
GT  :   '>';
LT  :   '<';
GE  :   '>=';
LE  :   '<=';
ULINE   :   '_';
DOT :   '.';
GETS    :   ':=';

LABEL   :   (LETTER|ULINE)(LETTER|DIGIT|ULINE)*;

INT     :   '-'?DIGIT+;

FLOAT   :   '-'? DIGIT* DOT DIGIT+;

HEX :   ('0x'|'0X')(HEXDIGIT)HEXDIGIT*;

RES_PARM:    DIGIT LABEL;

VARIABLE:    '\$' LABEL;


fragment A:'A'|'a';    fragment B:'B'|'b';    fragment C:'C'|'c';    fragment D:'D'|'d';    
fragment E:'E'|'e';    fragment F:'F'|'f';    fragment G:'G'|'g';    fragment H:'H'|'h';    
fragment I:'I'|'i';    fragment J:'J'|'j';    fragment K:'K'|'k';    fragment L:'L'|'l';
fragment M:'M'|'m';    fragment N:'N'|'n';    fragment O:'O'|'o';    fragment P:'P'|'p';    
fragment Q:'Q'|'q';    fragment R:'R'|'r';    fragment S:'S'|'s';    fragment T:'T'|'t';    
fragment U:'U'|'u';    fragment V:'V'|'v';    fragment W:'W'|'w';    fragment X:'X'|'x';
fragment Y:'Y'|'y';    fragment Z:'Z'|'z';


fragment DIGIT
:   '0'..'9';

fragment LETTER
:   A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z;

fragment HEXDIGIT   
:   '0..9'|'a..f'|'A'..'F';

当使用这个 C# 类进行测试时

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr.Runtime;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string inputString = "if $variable1 = 0 then\n  if $variable2 > 250 then\n   $variable3 := 0\n  endif\n endif";

            Console.WriteLine("Here is the input string:\n " + inputString + "\n");

            ANTLRStringStream input = new ANTLRStringStream(inputString);

            ifTestLexer lexer = new ifTestLexer(input);

            CommonTokenStream tokens = new CommonTokenStream(lexer);

            ifTestParser parser = new ifTestParser(tokens);

            parser.prog();

            Console.Read();
        }
    }
}

输出和我想象的不太一样。

**Output**
if ($variable2 > 250)
    {
            $variable3 := 0

    }
}       if ($variable1 = 0)
    {
            if $variable2 > 250 then
           $variable3 := 0
           endif

    }
}

问题似乎是第二个 if 语句打印了两次,但不是按照我希望的顺序。我认为这与我只是试图在打印语句中发出语句块有关,但我不太确定如何让它正常工作。我一直在阅读 StringTemplate,或者创建一个 AST 并使用 Tree Walker 来遍历它,但是有没有办法将上面的输出修复成这样?

if ($variable1 = 0)
{
    if ($variable2 > 250)
    {
         $variable3 := 0
    }
}

对于我应该采取的方向的任何帮助将不胜感激。跳到 StringTemplate 对我来说会更好,还是有什么方法可以让我使用基本的操作代码来做到这一点?如果我遗漏了任何信息,请随时询问。

【问题讨论】:

  • 哦,顺便说一句,问题的结构非常好。为你 +1!

标签: antlr


【解决方案1】:

是的,问题是您试图在解析阶段发出“编译结果”(C 程序)。解析器将回溯,通常您不能期望解析器的每个部分只运行一次并且每次都采用正确的路径。

AST 输出绝对是我建议查看的内容,然后遍历 AST 以产生您的输出。 TreeWalker 听起来确实是一个有用的工具。

总的来说,不,我不相信对于任何重要的语法都可以仅通过解析操作来创建所需的输出。

奇怪的是,你是我在过去几天看到的第二个尝试这样做的人。我当然可以看到“用解析器做所有事情!”这个想法的吸引力,但我真的不认为这是可行的。 ANTLR 是一个非常棒的工具,但它的输出是 AST;不是编译的可执行文件。

如果您有兴趣,这里是另一个类似问题的链接:
Parsing Java code with ANTLR "need concept"

【讨论】:

    【解决方案2】:

    如果您删除回溯,这在您的情况下很容易完成,您可以让解析器立即构建 C 代码。

    请注意,解析器规则可以接受参数(下面我的示例中的缩进级别)并且可以返回自定义对象(示例中的Strings):

    这是你的语法,没有回溯和输出到 C 代码(我不太擅长 C#,所以演示是用 Java 编写的):

    grammar ifTest;
    
    prog    
     : statements[""] EOF {System.out.println($statements.str);}
     ;
    
    statements[String indent] returns [String str]
    @init{$str = "";}
     : (statement[indent] {$str += indent + $statement.str + "\n";})*
     ;
    
    statement[String indent] returns [String str]
     : if_statement[indent] {$str = $if_statement.str;}
     | assignment           {$str = $assignment.str;}
     ;
    
    if_statement[String indent] returns [String str]
     : IF expr THEN s1=statements[indent + "  "] {$str = "if (" + $expr.str + ")\n" + indent + "{\n" + $s1.str;}
       (ELSE s2=statements[indent + "  "]        {$str += indent + "}\n" + indent + "else\n" + indent + "{\n" + $s2.str;} )? 
       ENDIF                                     {$str += indent + "}";}
     ;
    
    assignment returns [String str]
     : identifier GETS expr {$str = $identifier.str + " = " + $expr.str + ";";}
     ;
    
    expr returns [String str]
     : rel_expr {$str = $rel_expr.str;}
     ;
    
    rel_expr returns [String str]
     : e1=eq_expr {$str = $e1.str;} ( LT e2=eq_expr {$str += " < "  + $e2.str;}
                                    | GT e2=eq_expr {$str += " > "  + $e2.str;}
                                    | LE e2=eq_expr {$str += " <= " + $e2.str;}
                                    | GE e2=eq_expr {$str += " >= " + $e2.str;}
                                    )?
     ;
    
    eq_expr returns [String str]
     : e1=add_expr {$str = $e1.str;} (EQUALS e2=add_expr {$str += " == " + $e2.str;})?
     ;
    
    add_expr returns [String str]
     : e1=mult_expr {$str = $e1.str;} ( PLUS  e2=mult_expr {$str += " + " + $e2.str;}
                                      | MINUS e2=mult_expr {$str += " - " + $e2.str;}
                                      )*
     ;
    
    mult_expr returns [String str]
     : e1=unary_expr {$str = $e1.str;} ( MULT e2=unary_expr {$str += " * " + $e2.str;}
                                       | DIV  e2=unary_expr {$str += " / " + $e2.str;}
                                       )*
     ;
    
    unary_expr returns [String str]
     : MINUS term {$str = "-" + $term.str;}
     | term       {$str = $term.str;}
     ;
    
    term returns [String str]
     : identifier {$str = $identifier.str;}
     | number     {$str = $number.text;}
     ;
    
    identifier returns [String str]
     : LABEL    {$str = $LABEL.text;}
     | RES_PARM {$str = $RES_PARM.text;}
     | VARIABLE {$str = $VARIABLE.text.substring(1);}
     ;
    
    number
     : INT   
     | FLOAT
     | HEX
     ;
    
    WS          : (' '|'\t')+ {$channel=HIDDEN;};
    COMMENT     : '/*' .* '*/' {$channel=HIDDEN;};
    LINECOMMENT : '#' ~('\n'|'\r')* NEWLINE {$channel=HIDDEN;};
    NEWLINE     : '\r'?'\n' {$channel=HIDDEN;};
    IF          : I F;
    THEN        : T H E N;
    ELSE        : E L S E;
    ENDIF       : E N D I F;
    PLUS        : '+';
    MINUS       : '-';
    MULT        : '*';
    DIV         : '/';
    EQUALS      : '=';
    GT          : '>';
    LT          : '<';
    GE          : '>=';
    LE          : '<=';
    ULINE       : '_';
    DOT         : '.';
    GETS        : ':=';
    LABEL       : (LETTER | ULINE) (LETTER | DIGIT | ULINE)*;
    INT         : DIGIT+;            // no '-' here, unary_expr handles this
    FLOAT       : DIGIT* DOT DIGIT+; // no '-' here, unary_expr handles this
    HEX         : '0' ('x'|'X') HEXDIGIT+;
    RES_PARM    : DIGIT LABEL;
    VARIABLE    : '$' LABEL;
    
    fragment A:'A'|'a';    fragment B:'B'|'b';    fragment C:'C'|'c';    fragment D:'D'|'d';    
    fragment E:'E'|'e';    fragment F:'F'|'f';    fragment G:'G'|'g';    fragment H:'H'|'h';    
    fragment I:'I'|'i';    fragment J:'J'|'j';    fragment K:'K'|'k';    fragment L:'L'|'l';
    fragment M:'M'|'m';    fragment N:'N'|'n';    fragment O:'O'|'o';    fragment P:'P'|'p';    
    fragment Q:'Q'|'q';    fragment R:'R'|'r';    fragment S:'S'|'s';    fragment T:'T'|'t';    
    fragment U:'U'|'u';    fragment V:'V'|'v';    fragment W:'W'|'w';    fragment X:'X'|'x';
    fragment Y:'Y'|'y';    fragment Z:'Z'|'z';
    
    fragment HEXDIGIT : DIGIT |'a..f'|'A'..'F';
    fragment DIGIT    : '0'..'9';
    fragment LETTER   : A | B | C | D | E | F | G | H | I | J | K | L | M 
                      | N | O | P | Q | R | S | T | U | V | W | X | Y | Z
                      ;
    

    如果您现在使用输入测试您的解析器:

    if $variable1 = 0 then
      if $variable2 > 250 then
        $variable3 := 0
      else
        $variable3 := 42
      endif
    endif
    

    以下内容被打印到控制台:

    if (variable1 == 0)
    {
      if (variable2 > 250)
      {
        variable3 = 0;
      }
      else
      {
        variable3 = 42;
      }
    }
    

    如果语法的其他部分(严重)依赖谓词(回溯),则可以轻松应用与上述相同的策略,但随后在树语法中(因此 after 回溯解析器完成了它的工作并生成了一个 AST)。

    【讨论】:

    • 谢谢,这正是我想要的。
    • 我很抱歉这么快就问了另一个问题,但是我将如何添加功能以在 if 语句中使用多个表达式?例如,如果 $variable1 = 0 和 $variable7 = 0 那么...
    • @almostProgramming,创建一个and_expr 并将其放在rel_expr 上方。现在让expr 使用这个新的and_expr 规则而不是rel_expr。新规则看起来有点像这样(没有自定义代码):and_expr : rel_expr (AND rel_expr)*;。当然,您还需要创建一个AND 令牌:AND : A N D;
    • 再次感谢您!我将对此进行测试并使用相同的策略来实现 OR。
    • 经过几次尝试和错误,我相信我已经成功了。希望我能给你更多的票! expr 返回 [string str] : andor_expr {$str = $and_expr.str;} ; andor_expr 返回 [string str] : r1=rel_expr {$str = $r1.str;} (AND r2=rel_expr {$str += " && " + $r2.str;} | OR r2=rel_expr {$str += " || " + $r2.str;})* ;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2011-09-22
    相关资源
    最近更新 更多