在之前所有的範例中,都只有讀取字串或是整數。字串是預設的型別,整數則是透過YYSTYPE
自訂的型別,將數值存入yylval
當中。然而,若是在文本中有多種不同型別的字串呢?在C++的parser中,我們可以使用union
這個架構來處理多種型別的文本輸入。
C++ 中的 union
(聯合體)是一種特殊的結構,允許在同一塊記憶體中儲存不同的數據類型,但同一時間只能訪問其中一個成員。union
可以幫助節省記憶體,因為它僅分配足夠大的內存以容納最大的成員。
以下是 union
的基本語法和特點:
union UnionName {
memberType1 memberName1;
memberType2 memberName2;
// 可以有多個成員
};
UnionName
是 union
的名稱,可以自己命名。memberType1
、memberType2
等是不同成員的數據類型,可以是基本數據類型(如 int
、float
)或自定義數據類型(如struct或class)。memberName1
、memberName2
等是成員的名稱,它們用於訪問 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
的一個重要特點,需要謹慎使用,以確保不會造成數據混淆或錯誤。
當我們需要讀取多種不同類型的字串時,只需要將這些類型包進一個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
到這裡,多型別資料的讀取就完成了。
請實作出一個簡易的計算機,能夠對多個實數(包含整數與小數)做四則運算(加減乘除),符合運算優先順序為小括號→乘除法→加減法,並回傳結果。
NUMBER
token分成INTEGER
跟FLOAT
%{
#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;
}
%{
#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;
}
#ifndef MAIN_H
#define MAIN_H
#include <iostream>
#include <stdio.h>
using namespace std;
#endif
#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的型別定義,避免因為定義不明而影響後續的運用。