ANTLR语法if语句

标签 antlr

我一直在学习ANTLR,以创建特定领域的语言。要求之一就是将DSL转换为C。我已经能够获得识别DSL的基本语法,但是我在将其转换为C时遇到了问题。主要是,我的问题来自尝试将DSL if语句转换为C。 C if语句。我试图在语法中使用打印语句,但无济于事(我正在使用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语句打印两次,但没有按照我希望的顺序打印。我认为这与尝试在print语句中发出statement块有关,但是我不确定如何使它正常工作。我一直在阅读StringTemplate,或者创建AST并使用Tree Walker对其进行遍历,但是无论如何,还是可以将上述输出修复为如下所示吗?
if ($variable1 = 0)
{
    if ($variable2 > 250)
    {
         $variable3 := 0
    }
}

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

最佳答案

如果删除回溯(在您的情况下很容易做到),则可以让解析器立即构建C代码。

请注意,解析器规则可以采用参数(在下面的示例中为缩进级别),并且可以返回自定义对象(示例中为String):

这是您的语法,无需回溯并输出到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;
  }
}

如果语法的其他部分(大量)依赖谓词(回溯),则可以像在树语法中一样轻松地应用与上述相同的策略(因此在回溯解析器完成工作并生成AST之后)。

关于ANTLR语法if语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9314034/

相关文章:

java - antlr4 - 如何实现递归

ANTLR - 允许不完整的语法

c - 用antlr解析评论

java - 如何检查 ANTLR 3 中的数字范围?

ANTLR4 力 LL(1)

parsing - 重写多用途日志文件解析器以使用形式语法会提高可维护性吗?

java - ANTLR : AST, Java

java - 命题逻辑的 BNF 语法 ANTLR

Antlr 词法分析器对出现范围的支持?

c# - 为什么 .NET 异常没有被 try/catch block 捕获?