IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

error トークンの挙動

error トークンがどうゆう感じの挙動をするのか細かく知りたい

ということで、

bison で生成されたコードを読む

エラーが検出される箇所
yybackup:

  /* Do appropriate processing given the current state.  Read a
     look-ahead token if we need one and don't already have one.  */

  /* First try to decide what to do without reference to look-ahead token.  */
  yyn = yypact[yystate];
  if (yyn == YYPACT_NINF)
    goto yydefault;

  /* Not known => get a look-ahead token if don't already have one.  */

  /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol.  */
  if (yychar == YYEMPTY)
    {
      YYDPRINTF ((stderr, "Reading a token: "));
      yychar = YYLEX;
    }

  if (yychar <= YYEOF)
    {
      yychar = yytoken = YYEOF;
      YYDPRINTF ((stderr, "Now at end of input.\n"));
    }
  else
    {
      yytoken = YYTRANSLATE (yychar);
      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
    }

  /* If the proper action on seeing token YYTOKEN is to reduce or to
     detect an error, take that action.  */
  yyn += yytoken;
  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
    goto yydefault;
  yyn = yytable[yyn];
  if (yyn <= 0)
    {
      // ■■■ ここ! ■■■
      if (yyn == 0 || yyn == YYTABLE_NINF)
        goto yyerrlab;
      yyn = -yyn;
      goto yyreduce;
    }
:
:
yydefault:
  yyn = yydefact[yystate];
  // ■■■ ここ! ■■■
  if (yyn == 0)
    goto yyerrlab;
  goto yyreduce;

lookahead トークンを読み込んで、yytable[yypact[yystate] + yytoken] が 0 の場合(つまり、 lookahead トークンを読んでどうアクションすべきかというところで、 reduce (マイナス値)でも shift (プラス値)でもなく、エラー(0)の場合)に、 yyerrlab に飛んでいるというのが分かる。
あと、 yydefault の箇所でも error に行っている

yyerrlab ラベル
yyerrlab:
  /* If not already recovering from an error, report this error.  */
  if (!yyerrstatus)
    {
      ++yynerrs;
#if ! YYERROR_VERBOSE
      yyerror (YY_("syntax error"));
#else
     // (略)
#endif
    }

  // (略)

  /* Else will try to reuse look-ahead token after shifting the error
     token.  */
  goto yyerrlab1;

基本的には、エラー状態に入ったら yyerror を呼び出して、 yyerrlab1 に飛ぶだけ
もともとエラー状態の時に、ここに来てもほとんど何もしない。

yyerrlab1 ラベル
yyerrlab1:

  // ■ ここでエラーフラグをたてている
  yyerrstatus = 3;        /* Each real token shifted decrements this.  */
                      
  for (;;)
    {   
      // ■ この yyn にトークン番号を足すと、アクションか分かる
      yyn = yypact[yystate];
      if (yyn != YYPACT_NINF)
        {
          // ■ error トークンのアクションがあるかを調べる
          yyn += YYTERROR;
          if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
            {
              // ■ 今の状態に error トークンを shift する(yyn がプラス値だと shift)アクションがあったら break
              yyn = yytable[yyn];
              if (0 < yyn)
                break;
            }
        }

      /* Pop the current state because it cannot handle the error token.  */
      if (yyssp == yyss)
        YYABORT;


      yydestruct ("Error: popping",
                  yystos[yystate], yyvsp);

     // ■ error トークンを shift するアクションがなければ、一個状態を戻す
      YYPOPSTACK (1);
      yystate = *yyssp;
      YY_STACK_PRINT (yyss, yyssp);
    }

  if (yyn == YYFINAL)
    YYACCEPT;

  // ■ 値スタックに最後の yylval を積む
  *++yyvsp = yylval;

  /* Shift the error token.  */
  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);

  // ■ error トークンで shift した場合の状態に遷移する
  yystate = yyn;
  goto yynewstate;

error で shift できる状態まで状態をさかのぼっていき shift する。

yyerrstatus フラグは、いつ下ろされるか(yyerrstatus が 0 になるのはどこか)

yyerrlab ラベルに飛ぶところにもう一度戻ってみると、その直後で以下のようにコードが実行されている。

  /* Count tokens shifted since error; after three, turn off error
     status.  */
  if (yyerrstatus)
    yyerrstatus--;

つまり、 error を shift してから、 3 トークン自動で読み込まれるとエラー状態じゃなくなる。

エラー状態とエラーじゃない状態の違い

エラー状態から一旦回復しないと、エラーが報告されない。見た感じそのくらい(?)

エラー状態を手動で回復する場合は

以下のように yyerrok と書くだけでいい。

statment: error ';' { yyerrok; }

yyerrok は

#define yyerrok (yyerrstatus = 0)

と定義されている。