Lex詞法分析器

mist14發表於2019-05-10

LEX/FLEX詞法分析器

CONTENTS:

[TOC]

這篇文章的內容包括:

  • lex語法格式

  • linux下flex的安裝和使用

  • flex例項

  • flex原始碼的編譯和使用


Lex/Flex詞法分析器

Lex是LEXical compiler的縮寫,是Unix環境下非常著名的工具,主要功能是生成一個詞法分析器(scanner)的C原始碼,描述規則採用正規表示式(regular expression)。描述詞法分析器的檔案*.l,經過lex編譯後,生成一個lex.yy.c 的檔案,然後由C編譯器編譯生成一個詞法分析器。詞法分析器,簡單來說,其任務就是將輸入的各種符號,轉化成相應的識別符號(token),轉化後的識別符號 很容易被後續階段處理。 —— [ 百度百科 ]

Flex的安裝和使用

在使用apt軟體包管理器linux系統上我們可以非常方便地安裝並使用flex。在終端中輸入以下程式碼安裝flex:

$> sudo apt-get install flex

flex程式碼的原始檔往往是以.l為字尾名的。
.l檔案通過以下命令編譯(以檔名為scanner.l為例):

$> flex scanner.l

編譯後在原始碼相同目錄下會生成一個lex.yy.c,這就是生成的能夠執行上述scanner.l功能的c語言程式碼。使用gcc編譯即可生成詞法分析程式1

void yywrap() { return 1; }

$> gcc lex.yy.c -o scanner

然後將需要分析的檔案(以input.txt為例)作為引數傳遞給scanner執行分析:

$> ./scanner input.txt

Lex語法格式

flex的語法被分為三個部分:

{definitions}
%%
{rules}
%%
{user subroutines}

definitions:

LABEL REGULAR_EXPRESSION

LABEL是這裡類字串的名稱,REGULAR_EXPRESSION則是匹配這種字串的正規表示式。正規表示式的語法主要包括:

符號 含義
[] 括號中的字元取其一
a-z表示ascii碼中介於a-z包括a.z的字元
\ 轉義(flex不能識別除字母外的字元)
* 0或多個字元
? 0或1個字元
+ 1或多個字元
^ 除此之外的其餘字元
.
外的所有字元,等價於^

示例:

1. INT [1-9][0-9]*|[0]    /*整數型別,0或不以0開頭的由0-9組成的字串*/
2. FLOAT [0-9]*[.][0-9]+([eE][+-]?[0-9]*|[0])?f?    /*浮點數格式*/
3. LP (    /*一個左圓括號*/

注:用%{ %}括起來的語句將被完全寫入編譯後的c語言檔案中。

例如
`%{

#include <stdio.h>
int num_id = 0;

%}`

rules:

規則部分的語法如下:

{LABEL1} |
{LABLE2} |
...
{ 
/*TODO*/
}

TODO部分是告訴編譯器在匹配到字串之後程式需要做些什麼。
例如在匹配到整數後列印這個整數:

{INT} {
    printf("Pick up an integer, value is %d", atoi(yytext));
    printf("Pick up an integer, value is %s", yytext);
}

其中atoi()函式將字串轉換為整數。

user subroutines

此處主要是放置使用者需要執行的c語言程式碼。他們會被原封不動地加入到lex.yy.c檔案的末尾。
這裡一般用來存放main函式,詳細會在後面說明。

FLEX例項

下面通過一個例項來具體展示flex的使用方式,主要功能是掃描並匹配檔案中的字串,並回顯其型別和內容,程式碼如下:

/************************
 * scanner.l
 * @author mist
 * 2015-9-21 23:08
 ************************/
%{
#include "stdio.h"
#include "stdlib.h"
%}

INT_DEX [1-9][0-9]*|[0]
INT_HEX [0][Xx]([1-9][0-9]*|[0])
INT_OCT [0][0-7]
FLOAT [0-9]*[.][0-9]+([eE][+-]?[0-9]*|[0])?f?
SEMI [;]
COMMA [,]
ASSIGNOP [=]
RELOP [>]|[<]|[>][=]|[<][=]|[=][=]|[!][=](^[=])
PLUS [+]
MINUS [-]
STAR [*]
DIV [/]
AND [&][&]
OR [|][|]
DOT [.]
NOT [!]
TYPE int|float
LP (
RP )
LB [
RB ]
LC {
RC }
STRUCT struct
RETURN return
IF if
ELSE else 
WHILE while
SPACE [ 
	]
ID [a-zA-Z_][a-zA-Z_0-9]*
/*end of definition*/

%%
{SEMI} {
    printf("get semmi : %s
", yytext);

}

{COMMA} {
    printf("get comma : %s
", yytext);
}
{ASSIGNOP} {
    printf("get assignop : %s
", yytext);
}

{INT_DEX} |
{INT_HEX} |
{INT_OCT} {
    printf("get an integer: %s
", yytext);
}

{FLOAT} {
    printf("get a float: %s
", yytext);
}

{PLUS} | 
{MINUS} |
{DIV} |
{STAR} {
    printf("get an operator: %s
", yytext);
}

{RELOP} {
    printf("get a relop: %s
", yytext);
}

{AND} |
{OR} |
{NOT} {
    printf("get a logic operator: %s
", yytext);
}

{DOT} {
    printf("get a dot: %s
", yytext);
}
{STRUCT} |
{RETURN} |
{IF} |
{ELSE} |
{WHILE} {
    printf("get keyword: %s
", yytext);
}

{TYPE} {
    printf("get type: %s
", yytext);
}

{LP} |
{RP} |
{LB} |
{RB} |
{LC} |
{RC} {
    printf("get brackets : %s
", yytext);
}

{SPACE} |
. {
/*ABANDON THESE CHARACTORS*/
}

{ID} {
    printf("get an ID: %s
", yytext);
}
%%
int yywrap() {
  return 1;
}

int main(int argc, char** argv) {
   if (argc > 1) {
       if (!(yyin = fopen(argv[1], "r"))) {   
           perror(argv[1]);
           return 1;
       }
   }
   while (yylex());
   return 0;

我們需要為生成的分析程式編寫main函式。首先需要通過yyin來獲取指向被分析檔案的檔案FILE指標,一般檔案的路徑通過控制檯的第二個引數獲得。分析部分的實體在函式yylex()中。
yywrap()用於判斷是否已經掃描完了所有的檔案。如果它在最後一個檔案的末尾被呼叫,則返回值為1。此時程式將停止分析,可以用來掃描多個檔案。

輸入文字:
`int float {}()[] 0
0x0 0x123
123.5
.3e-10f
= >= || && ! ; ,
this_is_an_id
id123
if then else
`
輸出:

get type: int
get type: float
get brackets : {
get brackets : }
get brackets : (
get brackets : )
get brackets : [
get brackets : ]
get an integer: 0
get an integer: 0x0
get an integer: 0x123
get a float: 123.5
get a float: .3e-10f
get assignop : =
get a relop: >=
get a logic operator: ||
get a logic operator: &&
get a logic operator: !
get semmi : ;
get comma : ,
get an ID: this_is_an_id
get an ID: id123
get keyword: if
get an ID: then
get keyword: else

另外附上詞法要求:
INT  / A sequence of digits without spaces1 /
FLOAT  /* A real number consisting of digits and one decimal point. The deci-
mal point must be surrounded by at least one digit2 */
ID  /* A character string consisting of 52 upper- or lower-case alphabetic, 10
numeric and one underscore characters. Besides, an identifier must not start
with a digit3 */
SEMI  ;
COMMA  ,
ASSIGNOP  =
RELOP  > | < | >= | <= | == | !=
PLUS  +
MINUS  –
STAR  *
DIV  /
AND  &&
OR  ||
DOT  .
NOT  !
TYPE  int | float
LP  (
RP  )
LB  [
RB  ]
LC  {
RC  }
STRUCT  struct
RETURN  return
IF  if
ELSE  else
WHILE  while

1) 詞法單元INT表示的是所有(無符號)整型常數。一個十進位制整數由0~9十個數字組
成,數字與數字中間沒有如空格之類的分隔符。除“0”之外,十進位制整數的首位數字
不為0。例如,下面幾個串都表示十進位制整數:0、234、10000。為方便起見,你可以
假設(或者只接受)輸入的整數都在32bits位之內。

2) 整型常數還可以以八進位制或十六進位制的形式出現。八進位制整數由0~7八個數字組成並以
數字0開頭,十六進位制整數由0~9、A~F(或a~f)十六個數字組成並以0x或者0X開頭。
例如,0237(表示十進位制的159)、0xFF32(表示十進位制的65330)。

3) 詞法單元FLOAT表示的是所有(無符號)浮點型常數。一個浮點數由一串數字與一個
小數點組成,小數點的前後必須有數字出現。例如,下面幾個串都是浮點數:0.7、
12.43、9.00。為方便起見,你可以假設(或者只接受)輸入的浮點數都符合IEEE754單
精度標準(即都可以轉換成C語言中的float型別)。

4) 浮點型常數還可以以指數形式(即科學記數法)表示。指數形式的浮點數必須包括基
數、指數符號和指數三個部分,且三部分依次出現。基數部分由一串數字(0~9)和一
個小數點組成,小數點可以出現在數字串的任何位置;指數符號為“E”或“e”;指
數部分由可帶“+”或“”(也可不帶)的一串數字(0~9)組成,“+”或“”(如
果有)必須出現在數字串之前。例如01.23E12(表示1.23  1012)、43.e-4(表示43.0 
10-4)、.5E03(表示0.5  103)。

5) 詞法單元ID表示的是除去保留字以外的所有識別符號。識別符號可以由大小寫字母、數字
以及下劃線組成,但必須以字母或者下劃線開頭。為方便起見,你可以假設(或者只
接受)識別符號的長度小於32個字元。

6) 除了INT、FLOAT和ID這三個詞法單元以外,其它產生式中箭頭右邊都表示具體的字
符串。例如,產生式TYPE  int | float表示:輸入檔案中的字串“int”和“float”都
將被識別為詞法單元TYPE。
2.2
High-level Definitions


  1. 生成詞法分析程式 可能會發生yywrap未定義的錯誤。yywrap必須由使用者親自編寫,一般按如下形式即可

相關文章