日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

简易编译器实现(二)使用Bison创建语法分析器

發(fā)布時(shí)間:2024/3/26 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简易编译器实现(二)使用Bison创建语法分析器 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

你也可以通過我的獨(dú)立博客 —— www.huliujia.com 獲取本篇文章

簡易編譯器實(shí)現(xiàn)(一)使用Flex創(chuàng)建詞法分析器一文介紹了編譯器的概念和七個(gè)階段,并說明了如何使用Flex創(chuàng)建詞法分析器。本篇文章介紹如何使用Bison創(chuàng)建語法分析器,并實(shí)現(xiàn)基本的運(yùn)算能力。本文繼續(xù)使用簡易編譯器實(shí)現(xiàn)(一)使用Flex創(chuàng)建詞法分析器中提出的集合運(yùn)算語言AlphaGun作為演示的例子。

語法分析

語法分析器使用詞法分析器輸出的token流作為輸入,把token流轉(zhuǎn)換成樹狀的中間表示,通常會轉(zhuǎn)換成語法樹,本文中使用的例子比較簡單,所以會對結(jié)果進(jìn)行直接計(jì)算。復(fù)雜的語言通常會先構(gòu)建語法樹,然后在語法樹的基礎(chǔ)上做一系列的處理。如果輸入的token流不符合語法分析器的規(guī)定的語法,語法分析器還可以報(bào)語法錯(cuò)誤。

和詞法分析器自動生成類似,我們可以利用Bison來自動生成語法分析器,提高開發(fā)速度,降低迭代成本和維護(hù)成本。本文主要介紹Bison的使用。

Bison的語法

Bison語法規(guī)則和Flex一樣分為3個(gè)部分,第一部分是C語言聲明、token聲明、類型聲明。由"%{“和”}%"圍住的C語言部分會被直接拷貝到生成的語法分析器代碼前面。第二部分是使用BNF語法編寫的語法規(guī)則,為了編寫方便,Bison對BNF做了一定的簡化。第三部分是要執(zhí)行的main函數(shù)。

下面是為集合運(yùn)算語言AlphaGun編寫的Bison規(guī)則,代碼量比較大,可以直接翻到下面看解釋

%{#include <stdio.h>#include <string.h>#include <stdlib.h>#include <stdarg.h>#define NAME_SIZE 100#define CHAR_SET_SIZE 26extern int yylineno; /* from lexer */int yylex();void yyerror(char *s, ...){va_list ap;va_start(ap, s);fprintf(stderr, "%d: error: ", yylineno);vfprintf(stderr, s, ap);fprintf(stderr, "\n");}struct Symbol{char name;char value[CHAR_SET_SIZE];};struct Symbol symbol_table[26];char temp_char_set[CHAR_SET_SIZE];char factor_char_set[CHAR_SET_SIZE];char expr_char_set[CHAR_SET_SIZE];struct Symbol* NewSymbol(){struct Symbol* symbol = (struct Symbol*)malloc(sizeof(struct Symbol));symbol->name = 0;memset(symbol->value, 0, sizeof(symbol->value));}void PrintCharSet(char name, const char* char_set){printf("%c: [", name);int need_comma = 0;for(int i=0; i< CHAR_SET_SIZE; i++){if(char_set[i] != 0){if(need_comma == 1){printf(",");}printf("%c", char_set[i]);need_comma = 1;}}printf("]\n");}void PrintSymbol(const struct Symbol* symbol){PrintCharSet(symbol->name, symbol->value);}void Union(char* result_char_set, const char* char_set_1, const char* char_set_2){memcpy(result_char_set, char_set_1, CHAR_SET_SIZE);for(int i=0; i<CHAR_SET_SIZE; i++){if(char_set_2[i] != 0){result_char_set[i] = char_set_2[i];}}}void Intersect(char* result_char_set, const char* char_set_1, const char* char_set_2){for(int i=0;i <CHAR_SET_SIZE; i++){if(char_set_1[i] != char_set_2[i] || char_set_1[i] == 0){result_char_set[i] = 0;}else{result_char_set[i] = char_set_1[i];}}}void Substract(char* result_char_set, const char* char_set_1, const char* char_set_2){for(int i=0;i <CHAR_SET_SIZE; i++){if(char_set_1[i] == 0 || char_set_1[i] == char_set_2[i]){result_char_set[i] = 0;}else{result_char_set[i] = char_set_1[i];}}} %}%union {char name;char element;char* char_set; }%token PRINT %token <name> IDENTIFIER %token <element> CHAR %token COMMA %token LEFT_BRACKET %token RIGHT_BRACKET %token ASSIGN %token UNION %token INTERSECT %token SUBSTRACT %token NEWLINE%type <char_set> char_list init_list factor expr%%language: /* nothing */| language statement NEWLINE| language NEWLINE /*允許空行出現(xiàn)*/statement: PRINT IDENTIFIER { PrintSymbol(&symbol_table[$2 - 'A']); }| IDENTIFIER ASSIGN init_list { symbol_table[$1-'A'].name = $1; memcpy(symbol_table[$1-'A'].value, $3, CHAR_SET_SIZE); }| IDENTIFIER ASSIGN expr { symbol_table[$1-'A'].name = $1; memcpy(symbol_table[$1-'A'].value, $3, CHAR_SET_SIZE); }expr: factor { $$ = expr_char_set; memcpy($$, $1, CHAR_SET_SIZE); }| expr SUBSTRACT factor { Substract($$, $1, $3); }factor: IDENTIFIER { $$ = factor_char_set; memcpy($$, symbol_table[$1-'A'].value, CHAR_SET_SIZE); }| factor UNION IDENTIFIER { Union($$, $1, symbol_table[$3-'A'].value); }| factor INTERSECT IDENTIFIER { Intersect($$, $1, symbol_table[$3-'A'].value); }init_list: LEFT_BRACKET char_list RIGHT_BRACKET { $$ = $2; }char_list: CHAR { $$ = temp_char_set; memset($$, 0, 26); $$[$1-'a'] = $1; }| char_list COMMA CHAR { $$[$3-'a'] = $3; }%%int main(int argc, char ** argv) {yyparse(); }

Bision規(guī)則第一部分

C語言部分,包含了需要的頭文件,聲明了幾個(gè)函數(shù),這些函數(shù)將在BNF語法規(guī)則部分用到。實(shí)現(xiàn)了yyerror,使得生成的語法分析器可以打印語法錯(cuò)誤的相關(guān)信息,另外為了避免編譯錯(cuò)誤,前置聲明了yylex。

token聲明部分,首先定義了yylval的union類型。這里yylval由name、element、char_set三種變量聯(lián)合組成,%token<name> IDENTIFIER 聲明了IDENTIFIER token類型,并且告訴Bison,IDENTIFIER使用union類型的name變量存儲值,這樣在BNF語法規(guī)則部分,$N就會直接指代name變量。類似的CHAR使用element變量,$N指代element。

type聲明部分,聲明了char_list, init_list, facotr, expr這4種非終結(jié)符使用union類型的char_set變量。如果沒有這個(gè)聲明的話,在BNF語法規(guī)則部分,是不能給非終結(jié)符的值變量$$賦值的。

Bison規(guī)則第二部分

第二部分是BNF語法規(guī)則部分,BNF語法規(guī)則如果細(xì)說的話又是一篇長文,這里簡單介紹一下。每個(gè)規(guī)則的最左邊是非終結(jié)符,冒號右邊是非終結(jié)符的推導(dǎo)規(guī)則,一個(gè)非終結(jié)符如果有多個(gè)推到規(guī)則,使用豎線 | 分割。每個(gè)推導(dǎo)規(guī)則都可以對應(yīng)一個(gè)動作,由 { } 包含,使用C語言代碼編寫。第一個(gè)規(guī)則的非終結(jié)符也被稱為起始符,最終語言的全部輸入都會最終匹配到起始符這里。Bison會自動對輸入的token流進(jìn)行解析,對匹配到的推導(dǎo)規(guī)則,執(zhí)行動作代碼,如果沒有動作代碼,會繼續(xù)往下匹配。Bison中的每個(gè)token和非終結(jié)符都可以有一個(gè)值變量,這個(gè)是在上面的%token和%type聲明中定義的。每個(gè)推導(dǎo)規(guī)則中,非終結(jié)符的值保存在$$中,推導(dǎo)規(guī)則中出現(xiàn)的符號的值分別保存在$1、$2、$3、… 中。$$、$1、$2等實(shí)際指向的就是前面提到的yylval的union類型的具體變量。比如:

init_list: LEFT_BRACKET char_list RIGHT_BRACKET

init_list的值變量是$$,而LEFT_BRACKET的值變量就是$1,但是顯然左括號不會有值,所以這里$1實(shí)際上是無用的,char_list的值變量是$2,在動作部分我們把$2賦值給了$1,從而實(shí)現(xiàn)了集合的初始化動作。

Bison規(guī)則第三部分

第三部分是main函數(shù),直接調(diào)用了yyparse函數(shù),yyparse是Bison生成的語法分析器入口,yyparse會不斷地調(diào)用yylex獲取token流去解析,和語法規(guī)則去做匹配,直到token流結(jié)束或者發(fā)現(xiàn)語法錯(cuò)誤。

執(zhí)行Bison

首先把簡易編譯器實(shí)現(xiàn)(一)使用Flex創(chuàng)建詞法分析器中的Flex規(guī)則文件做一點(diǎn)修改,修改結(jié)果如下:

%option noyywrap yylineno %{#include <stdio.h>#include "set_calc.tab.h"int fileno(FILE *stream); %}%%PRINT { return PRINT; } [A-Z] { yylval.name = yytext[0]; return IDENTIFIER; } [a-z] { yylval.element = yytext[0]; return CHAR; } "," { return COMMA; } "[" { return LEFT_BRACKET; } "]" { return RIGHT_BRACKET; } "=" { return ASSIGN; } "∪" { return UNION; } "∩" { return INTERSECT; } "-" { return SUBSTRACT; } \n { return NEWLINE; } "//".* { /* omit comment*/ } [ \t] { /*ignore white space*/ } . { printf("unexpected token: (%s)\n", yytext); }%%

刪除了手動定義的枚舉類型和yylva變量,包含了set_calc.tab.h頭文件,這個(gè)頭文件是由bison生成的,頭文件中定義了枚舉類型和yylval變量。為了避免編譯錯(cuò)誤,聲明了fileno函數(shù)。

保存文件、聯(lián)合Flex編譯

把上面的Bison規(guī)則保存為set_calc.y,把flex規(guī)則保存為set_calc.l,編譯

bison -d set_calc.y # 生成語法分析器 flex set_calc.l # 生成詞法分析器 gcc -std=c99 -o set_calc set_calc.tab.c lex.yy.c # 編譯生成可執(zhí)行文件

編寫AlphaGun語言代碼,保存為test.set

A=[a,b,c,d,z] B=[c,d,e, f ] // test comment C=[e,f,g,h,z] D=[x,y,z]E = A ∪ B ∩ C - A ∩ B ∪ DPRINT A PRINT E

執(zhí)行AlphaGun代碼

./set_calc < test.set

運(yùn)行結(jié)果

A: [a,b,c,d,z] E: [e,f]

語法樹

對于復(fù)雜的語言,直接根據(jù)推導(dǎo)規(guī)則進(jìn)行計(jì)算時(shí)無法實(shí)現(xiàn)的,所以在動作部分中可以構(gòu)建語法樹,最后集中處理、計(jì)算。

總結(jié)

以上是生活随笔為你收集整理的简易编译器实现(二)使用Bison创建语法分析器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。