iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
自我挑戰組

Lex & Yacc 學習筆記系列 第 27

[Day27] Yacc 進階 - 使用外部變數

  • 分享至 

  • xImage
  •  

本篇內容

  • 在C++主程式中定義變數,並使用於parser
  • 範例:陣列讀取2

介紹

我們在之前的例子中,主程式在讀取完資料後就結束了。如果要繼續做後續的部分,那就要將讀進來的資料找個地方存起來。因此,在 Yacc 檔案裡定義變數的做法就行不通了。但是,如果我們把變數定義在主程式,那要怎麼讓 yacc 檔案可以使用呢?

在 C++中,若是要讓某個檔案裡的變數可以被其他檔案使用,可以使用 extern 這個方法。我們在這裡就不仔細解說 extern 的用法了,但有一個重點一定要記得:在編譯的時候,定義變數的檔案一定要先編譯,不然引用變數的檔案在編譯時會找不到宣告的源頭,導致編譯失敗。因此,若是我們要將變數定義在 main.cpp 裡面的話,就要先編譯 main.cpp 再編譯 yacc 跟 lex 檔案。

我們直接用下面的範例來說明外部變數在 lex & yacc 運用的情形。

範例 - 陣列讀取2

說明

請在 main.cpp 中宣告一個空的 int array ,接著用 parser 讀取檔案中向量,最後在 parser 與主程式中印出該向量。

程式實作

  • main.cpp
    • 在 main function 定義一個空陣列 arrayMain,以及一個 int arrayMainSize 記錄陣列長度。
    • 定義兩個 global pointer (array, arraySize) ,分別指向主程式的陣列與長度變數位置。
    • 在此定義 Print function,印出 arrayMain 的內容。
  • yacc.y
    • 將主程式的兩個 global pointer extern 進來。
    • 在此定義 Print function,印出 array 的內容。(由於 array 指向 arrayMain,因此印出的內容就是 arrayMain)
    • 在 yacc rule 直接更新 pointer 指向的記憶體位置。
  • Makefile
    • 由於 yacc 要引用 main 宣告的變數,因此編譯順序改為 main → yacc → lex

完整程式碼

Lex

%{
#include "main.h"
#include "yacc.tab.h"
%}

posint      ([0-9]+)
blank_chars ([ \f\r\t\v]+)

%%

{posint}        { yylval = atoi(yytext); return NUMBER; }
{blank_chars}   { ; }
"["             { return yytext[0]; }
"]"             { return yytext[0]; }
\n              { ; }

%%

int yywrap(void) {
    cout << "Finish parsing!" << endl;
    return 1;
}

Yacc

%{
#include "main.h"

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

extern int* array;
extern int* arraySize;

void PrintArrayYacc() {
    cout << "Array in yacc: [ ";
    for (int i = 0; i < (*arraySize); i++) {
        cout << array[i] << " ";
    }
    cout << "]\n";
}

%}

%token NUMBER

%%

func:
      '[' array ']'        { PrintArrayYacc(); }
    ;

array:
    | array NUMBER         { array[(*arraySize)] = $2; (*arraySize)++; }
    ;

%%

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

Main

  • main.h
#ifndef MAIN_H
#define MAIN_H

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

using namespace std;

#define YYSTYPE int
#endif
  • main.cpp
#include "main.h"

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

int *arraySize = nullptr;
int *array = nullptr;

void PrintArrayMain(int *arr, int arrSize) {
    cout << "Array in main: [ ";
    for (int i = 0; i < arrSize; i++) {
        cout << arr[i] << " ";
    }
    cout << "]\n";
}

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

    int arrayMainSize = 0;
    int *arrayMain = new int[64];

    array = arrayMain;
    arraySize = (&arrayMainSize);
    
    yyin = fp;
    yyparse();
    fclose(fp);

    PrintArrayMain(arrayMain, arrayMainSize);

    delete [] arrayMain;

    return 0;
}

Makefile

LEX=flex
YACC=bison
CC=g++
OBJECT=main

$(OBJECT): main.o yacc.tab.o lex.yy.o
		$(CC) main.o lex.yy.o yacc.tab.o -o $(OBJECT)
		@./$(OBJECT)

lex.yy.o: lex.yy.c yacc.tab.h main.h
		$(CC) -c lex.yy.c

yacc.tab.o: yacc.tab.c main.h
		$(CC) -c yacc.tab.c

yacc.tab.c yacc.tab.h: yacc.y
		$(YACC) -d yacc.y

lex.yy.c: lex.l
		$(LEX) lex.l

main.o: main.cpp
		$(CC) -c main.cpp

clean:
		@del -f $(OBJECT) *.o lex.yy.c yacc.tab.h yacc.tab.c main.exe

執行結果

輸入內容

[ 1 2 3 ]

輸出結果

Array in yacc: [ 1 2 3 ]
Finish parsing!
Array in main: [ 1 2 3 ]

可以看到,我們成功將文本的陣列存到 main function 宣告的陣列中了!

結語

今天的內容比較複雜,但了解之後,便可以把 parser 讀進來的資料繼續用 C++ 達成更多目的了。

參考資料

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

上一篇
[Day26] Yacc 進階 - Embedded Actions
下一篇
[Day28] Yacc - Command Line輸入
系列文
Lex & Yacc 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言