C 使用Bison实现语法。语法控制流意外

C 使用Bison实现语法。语法控制流意外,c,parsing,grammar,bison,flex-lexer,C,Parsing,Grammar,Bison,Flex Lexer,抽象语法。实际语法如下: start -> t1 V1; V1 --> t2 V1 | t3 V2 ; V2 --> t4 | /* Empty */ ; 当控件位于V2中且遇到令牌t3时。控件返回到V1。好的,说到这一点 但控件不会在V1停止以触发t3,而是返回到启动,这就是问题所在。 begin: MAIN main ; main: OP_BR functn_Decls CL

抽象语法。实际语法如下:

start ->  t1  V1;

V1 -->   t2   V1
       | t3   V2
       ;

V2 -->   t4
       | /* Empty */
       ;
当控件位于V2中且遇到令牌t3时。控件返回到V1。好的,说到这一点

但控件不会在V1停止以触发t3,而是返回到启动,这就是问题所在。

begin:  MAIN main ; 

main:   OP_BR   functn_Decls   CL_BR 
        ; 

functn_Decls:   functn_Decls INT_DT VAR SM_CLN 
              | functn 
              ; 

functn:    functn BBS_ST INT_LIT BBS_END1 
        |  bb 
        ; 

bb:    bb   stmnt 
    |  /* Empty Rule */ 
    ; 

stmnt:   t1  // terminals only. 
       ; 

var_List:   t2 // terminal just for illustration. 
            ;

expr:  t3 // terminal just for illustration.
       ;
实际语法。解释如下

%{
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

void yyerror (char *s); 
extern FILE *yyin;

%}

%token MAIN   

%token INT_DT INT_LIT   /* t_INT_DT --> Integer Data type, i.e. "int"
                         * t_INT_LIT --> An integer, i.e. "5".
                         */

%token VAR              // A variable

%token SCANF  PRINTF    // For the printf and the scanf statements.

%token IF     ELSE      // For the if and else statements.
%token GOTO

%token OPRTR            // For the operator in an expression.

/* Begining of a basic block, BBS, Basic Block Statement.
 * BBS_ST = "<bb "
 * BBS_END1 ">:"
 * BBS_END2   ">;"
 * Example:   
 * <bb 2>:
 *      Statements;
 *      goto <bb 3>;
 */
%token BBS_ST   // "<bb "// it is "<bb ".
%token BBS_END1 // ">:" 
%token BBS_END2 // ">;" 

// Opening and closing Braces, i.e. { & }
%token OP_BR  // "{" 
%token CL_BR  // "}"

// For opening and closing Parantheses, ( & )
%token OP_PR  //"("  
%token CL_PR  //")"

%token EQUAL  // "="
%token COMMA  // ","       
%token NBSP   // "&"
%token SM_CLN // ";"       // For ";" 

%token RETURN           // For the return statement.

%start begin

%%

begin:   MAIN    main   ;

main:    OP_BR    functn   CL_BR   ;

functn:   INT_DT   VAR      SM_CLN   functn  /*Recursion to stay in functn */
        | BBS_ST   INT_LIT  BBS_END1  bb     
        ;

bb:    SCANF    var_List   CL_PR    SM_CLN  bb
    |  PRINTF   var_List   CL_PR    SM_CLN  bb
    |  VAR      EQUAL      expr     SM_CLN  bb
    |  IF       OP_PR      expr     CL_PR   bb
    |  ELSE     bb
    |  GOTO     BBS_ST     INT_LIT  BBS_END2
    |  RETURN   SM_CLN    
    |  /* Empty Rule. */  
    ;  /* bb has can't catch BBS_ST as first token, thus it must return
        * to the calling rule and functn can catch BBS_ST. */

var_List:    COMMA   VAR   var_List 
          |  /* Empty Rule. */ 
          ;

expr:     VAR      expr2 
      |   INT_LIT  expr2  
      ;

expr2:    OPRTR    expr3  
       |  /* Empty Rule */
       ;

expr3:    VAR     
       |  INT_LIT  
       ;

%%
int main (int argc, char *argv[])
{
    #if YYDEBUG == 1
        extern int yydebug;
        yydebug = 1;
    #endif

    if (argc == 2)
    {
        yyin = fopen (argv [1], "r");
        if (yyin == NULL)
            perror (argv [1]); 
    }

    yyparse ();

    fclose (yyin);
    return 0;    
}


void yyerror (char *s)
{
    printf ("Error: %s\n", s);
}

导致输入错误的原因:

main ()
{
  <bb 2>:
  goto <bb 4>;

  <bb 3>:
}

我重新整理了你的一些规则。问题是当
functn
看到一个
bb
时,它只需要一个。因此,当到达您的
GOTO
时,不需要更多令牌。允许
functn
s出现在
bb
语句之后,应该可以解决这个问题,并提供您想要的行为

functn: INT_DT VAR ";" functn
      | bb_stmt functn
      | bb_stmt
      ;

bb_stmt: BBS_ST /*P2*/ INT_LIT ">:" bb
       ;
如果要继续强制语句仅位于函数顶部,除上述操作外,还可以执行以下操作:

main:    "{"     functn_decls /*P1*/   "}"   ;

functn_decls: INT_DT VAR ";" functn_decls
            | functn
            ;

functn: bb_stmt functn
      | bb_stmt
      ;

你的语法和你认为的不一样。(在这里,我只是使用简化语法,因为它比遍历整个语法更简单,并且,正如所指出的,原理是相同的。)

让我们问一个简单的问题:接下来是什么?因为语法中唯一使用
V2
的地方是生产
V1→ t3 V2
,答案只能是:与V1后面的令牌完全相同

那么接下来是什么呢?同样,只有两个地方使用了
V1
,并且都在制作结束时。一个是递归的,因此它不参与follow集合,另一个在
start中→ t1 V1
。因此我们可以得出结论,唯一可以跟在
V1
后面的令牌(因此唯一可以跟在
V2
后面的令牌)是输入结束令牌,通常写在
$
后面

因此
t3
不能跟在
V1
V2
后面,因此,句子:

t1 t3 t3
不是语言的一部分;无法解析第二个
t3


更一般地说,您似乎试图分析生成的解析器的行为,就好像它是一个自顶向下的递归下降解析器一样。Bison不产生自顶向下的解析器;它生成自底向上的LALR(1)解析器(默认情况下),您的“控制流”概念与LR(k)算法中正在进行的任何事情都不匹配

此外,LR(1)语法在左递归方面没有任何问题,因此没有必要像自顶向下的解析器生成器那样破坏语法。您会发现左递归工作得更好,因为它实际上反映了语言的结构。如果你分析

statement1 ; statement2 ; statement3 ;
使用右递归语法:

Program → ε | Statement ; Program
您将发现
语句
的缩减是从右向左应用的,从您的角度来看,这可能是向后的。这是因为LR解析器产生最右边的派生,所以它在到达输入的末尾之前不会开始进行缩减:

  statement1 ; statement2 ; statement3 ;
→ statement1 ; statement2 ; statement3 ; Program (Program → ε)
→ statement1 ; statement2 ; Program              (Program → Statement ; Program)
→ statement1 ; Program                           (Program → Statement ; Program)
→ Program                                        (Program → Statement ; Program)

很可能您真正想要的是以下内容(回到简化语法):


这使得
S
a
t1
后跟任意数(可能为0)的
t2
t3
t3 t4
,每个后跟分号。

我想解析
t1 t3 t3
。它最终不会被野牛解析,而是属于这种语言。我同意你所说的左右递归。我对语法不太熟悉。所以,我使用正确的递归,以性能为代价,因为现在它们对我来说似乎更容易。如果是右递归导致了这个问题,我可以研究左递归并使用它。谢谢你的回答。@KulwantSingh:我最后建议的语法将解析
t1 t3 t4 t3
(用分号,当然你可以删除该要求)。关键是,您需要迭代“语句”(
V1
,在我的语法中),而不是像在原始语法中那样混合使用不同的内容。但是正确的递归也是一个问题,您为使语法可解析所做的事情是不必要的、令人困惑的,最终会适得其反。但它不起作用。在他的回答中,我想补充一点,消除右递归也是必要的。利用“rici”和“Chris Hunt”的建议,我解决了这个问题。谢谢你的时间和建议,我相信不可能接受两个答案。所以,我一个也不接受。如果你想让我上传有效的语法,请发表评论。
statement1 ; statement2 ; statement3 ;
Program → ε | Statement ; Program
  statement1 ; statement2 ; statement3 ;
→ statement1 ; statement2 ; statement3 ; Program (Program → ε)
→ statement1 ; statement2 ; Program              (Program → Statement ; Program)
→ statement1 ; Program                           (Program → Statement ; Program)
→ Program                                        (Program → Statement ; Program)
S  → t1 | S V1 ';'
V1 → t2 | t3 V2
V2 → t4 | /* Empty */