iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
自我挑戰組

Lex & Yacc 學習筆記系列 第 21

[Day21] Yacc - 多型別字串的運用 (Union)

  • 分享至 

  • xImage
  •  

本篇內容

  • 介紹Union語法
  • 介紹YYSTYPE的進階應用
  • 範例:簡易計算機6

前言

在之前所有的範例中,都只有讀取字串或是整數。字串是預設的型別,整數則是透過YYSTYPE自訂的型別,將數值存入yylval當中。然而,若是在文本中有多種不同型別的字串呢?在C++的parser中,我們可以使用union這個架構來處理多種型別的文本輸入。

Union

C++ 中的 union(聯合體)是一種特殊的結構,允許在同一塊記憶體中儲存不同的數據類型,但同一時間只能訪問其中一個成員。union 可以幫助節省記憶體,因為它僅分配足夠大的內存以容納最大的成員。

以下是 union 的基本語法和特點:

union UnionName {
    memberType1 memberName1;
    memberType2 memberName2;
    // 可以有多個成員
};
  • UnionNameunion 的名稱,可以自己命名。
  • memberType1memberType2 等是不同成員的數據類型,可以是基本數據類型(如 intfloat)或自定義數據類型(如struct或class)。
  • memberName1memberName2 等是成員的名稱,它們用於訪問 union 內的數值。

以下是一個簡單的範例,展示了如何定義和使用 union

#include <iostream>

union MyUnion {
    int intValue;
    double doubleValue;
    char charValue;
};

int main() {
    MyUnion data;
    data.intValue = 42;

    std::cout << "Value in the union: " << data.intValue << std::endl;

    data.doubleValue = 3.14159;
    std::cout << "Value in the union: " << data.doubleValue << std::endl;

    data.charValue = 'A';
    std::cout << "Value in the union: " << data.charValue << std::endl;

    return 0;
}

注意,由於 union 同時只能儲存一個成員的數值,因此在更改 union 中的成員值時,之前儲存的值將被覆蓋。這是 union 的一個重要特點,需要謹慎使用,以確保不會造成數據混淆或錯誤。

YYSTYPE的進階應用

當我們需要讀取多種不同類型的字串時,只需要將這些類型包進一個union結構中,在YYSTYPE定義這個union即可。不過,字串的型別轉換還是要自己處理。

我們以下面的例子說明,如何讀取一份有整數及小數的文本。

首先,在Yacc檔案的Definition區塊定義union:

%union {
    float   floatNum;
    int     intNum;
}

接著,我們在lex定義regular expressions

integer     (-?([0-9]+))
float       (-?([0-9]*\.?[0-9]+))

%%

{integer}       { sscanf(yytext, "%d", &(yylval.intNum)); return INTEGER; }
{float}         { sscanf(yytext, "%f", &(yylval.floatNum)); return FLOAT; }

最後,若是要對數值進行進一步計算,token要記得定義型別。

%token <intNum>   INTEGER
%token <floatNum> FLOAT

到這裡,多型別資料的讀取就完成了。

範例 - 簡易計算機6

說明

請實作出一個簡易的計算機,能夠對多個實數(包含整數與小數)做四則運算(加減乘除),符合運算優先順序為小括號→乘除法→加減法,並回傳結果。

程式實作

Lex

  • 用regular expression定義整數與小數
  • 將原本的NUMBER token分成INTEGERFLOAT
  • 依據yacc的union定義,自行處理型別轉換
%{
#include "main.h"
#include "yacc.tab.h"
%}

integer     (-?([0-9]+))
float       (-?([0-9]*\.?[0-9]+))
blank_chars ([ \f\r\t\v]+)
expressions ([-+*/()])

%%

{integer}       { sscanf(yytext, "%d", &(yylval.intNum)); return INTEGER; }
{float}         { sscanf(yytext, "%f", &(yylval.floatNum)); return FLOAT; }
{expressions}   { return yytext[0]; }
{blank_chars}   { ; }
"="             { return yytext[0]; }
\n              { ; }

%%

int yywrap(void) {
    return 1;
}

Yacc

  • 將union定義於此
  • 定義value規則,將整數與小數統一轉化為float,避免做四則運算時,operator出問題
  • 將token跟type標上型別,避免assign時出現型別不明的問題。
%{
#include "main.h"

void yyerror(const char *s);
extern int yylex();
extern int yyparse();

%}

%union {
    float   floatNum;
    int     intNum;
}

%token <intNum>   INTEGER
%token <floatNum> FLOAT

%type <floatNum> value expr

%left '+' '-'
%left '*' '/'
%right UMINUS

%%

func:
      expr '='              { printf("Result: %f\n", $1); }
    ;

expr:
      value                 { $$ = $1; }
    | expr '+' expr         { $$ = $1 + $3; }
    | expr '-' expr         { $$ = $1 - $3; }
    | expr '*' expr         { $$ = $1 * $3; }
    | expr '/' expr         { $$ = $1 / $3; }
    | '-' expr %prec UMINUS { $$ = -$2; }
    | '(' expr ')'          { $$ = $2; }
    ;

value:
      FLOAT                 { $$ = $1; }
    | INTEGER               { $$ = (float)$1; }
    ;

%%

void yyerror(const char *s) {
    cerr << s << endl;
}

Main

  • main.h
    • 注意:由於已經使用union定義YYSTYPE,故前個範例中的definition要記得拿掉。
#ifndef MAIN_H
#define MAIN_H

#include <iostream>
#include <stdio.h>

using namespace std;

#endif
  • main.cpp
    • 與前例相同
#include "main.h"
#include "yacc.tab.h"

extern int yyparse(void);
extern FILE* yyin;

int main()
{
    const char* sFile = "file.txt";
    FILE* fp = fopen(sFile, "r");
    if (fp == NULL) {
        printf("cannot open %s\n", sFile);
        return -1;
    }
    
    yyin = fp;
    yyparse();

    fclose(fp);

    return 0;
}

執行結果

輸入內容

-4.5 * (-7) + 28.68 / 4 =

輸出結果

Result: 38.669998

這裡的答案應該是38.67,不過由於float的運算誤差導致這個結果。

結語

為了要能夠做小數的運算,我們使用了union來儲存不同類型的變數數值。

我們也介紹了token與type的型別定義,避免因為定義不明而影響後續的運用。

參考資料

  • Levine, John R., Tony Mason and Doug Brown [1992]. Lex & Yacc. O’Reilly & Associates, Inc. Sebastopol, California.
  • Tom Niemann. Lex & Yacc

上一篇
[Day20] Yacc - 優先級(2) nonassoc prec
下一篇
[Day22] Yacc - Error Handling
系列文
Lex & Yacc 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言