JFLEX詞法分析
安裝與配置
1. 下載下傳jflex-1.4.3.zip,解壓縮到本地目錄(c:/jflex)。
2. 找到jflex\bin\jflex.bat檔案,配置JAVA HOME和JFLEX HOME
3. 把x:\jflex\bin寫入系統環境變量path中
運作
可視化方式
直接運作jflex\bin\jflex.bat檔案,打開可視化界面操作即可。
指令行方式
配置
把x:\jflex\bin以及x:\jflex\lib\ JFlex.jar配置到系統環境變量的CLASSPATH中。
格式
java JFlex.Main<options> <inputfiles>
運作參數
-d <directory>
<directory>生成檔案的輸出目錄
--skel <file>
使用外部的骨架檔案生成掃描器類,它大多數情況下用于JFLEX的維護和低級别定制。隻有在你知道自己正在作什麼時候才使用它。JFLEX的源碼中帶有一個骨架檔案,預先編寫骨架檔案才能使用此指令。
--nomin
在掃描器生成的過程中,跳過DFA簡化步驟。
--jlex
完全相容jlex
--dot
為NFA, DFA and minimised DFA生成擴充名為.dot的graphviz圖型檔案。該參數還在最初階段,尚未完全實作。
--dump
在控制台顯示NFA轉換表,初始DFA和最簡DFA。
--verbose or –v
顯示生成過程資訊。
--quiet or –q
僅顯示生成錯誤資訊
--time
顯示代碼生成耗時資訊(不十分精确)
--version
列印JFLex版本号
--info
列印系統以及JDK資訊。
--pack
使用%pack代碼生成政策
--table
使用%table代碼生成政策
--switch
使用%switch代碼生成政策
--help or -h
列印幫助資訊,解釋運作參數以及Jflex用法。
JFLEX 配置檔案編寫
配置檔案以.flex為擴充名,整個文檔分為三個部分,使用%%劃分
1. 使用者代碼
2. 選項與聲明
3. 詞法規則
形式形如:
使用者代碼 ……………………………………………………………………………. ……………………………………………………………………………. %% 選項與聲明 ……………………………………………………………………………. …………………………………………………………………………….. %% 詞法規則 ……………………………………………………………………………….. |
使用者代碼
JFLEX直接将這部分代碼拷貝到生成詞法分析器Java源檔案中,通常在這裡我們隻定義一些類注釋資訊以及package和import的引用。
選項與聲明
在這一部分,選項用來定制詞法分析器,聲明則是聲明一些能夠在第三部分(詞法規則定義)使用的宏定義和詞法狀态,其中宏大多由正規表達式定義。
選項
所有選項都要由一個“%”符号開頭,下面來列舉一下所有的選項:
類選項和使用者代碼
%class
定義生成詞法分析器Java檔案的檔案名,如果不定義該選項,則預設生成”Yylex.java”。
例子:%class MyScanner
%implements
使得生成的詞法分析器類實作特定的接口,可以同時實作多個接口。
例子: %implementsinterface1,interface2
%extends
使得生成的詞法分析器類是某個類的子類,至多定義一個%extends選項。
例子: %extendsParentClass
%public
使得生成的類是public的,類似的還有%final和%abstract指令,他們分别生成的類是final和abstract類型的。
例子: %public
%apiprivate
使得生成的類檔案中,所有生成的方法和變量都變為private,隻有該類的構造方法和使用者自定代碼段除外。如果使用了%cup選項,那麼next_token方法也不會被設定為私有。這個方法如果沒有特殊情況不推薦使用。
例子: % apiprivate
%{………使用者代碼…….%} 類代碼指令
其中使用者代碼将被直接複制到生成類檔案中,在這裡你可以定義自己的成員變量和方法。此規範描述中出現多個類代碼指令,那麼JFLEX将根據這些類代碼指令出現的先後順序将他們拼接起來。
例子:
%{
public String name;
public void test(){
System.out.println(“this is a test!”);
}
………………
………………
%}
%init{………初始化代碼………%init}
初始化代碼将被直接複制生成類的構造函數中,我們可以在這裡對類指令代碼中聲明的成員變量進行初始化工作。同類代碼指令一樣,如果出現多個初始化指令定義,那麼JFLEX将根據這些類代碼指令出現的先後順序将他們拼接起來。
例子:
%init{
name=”Benson”;
………………
………………
%init}
%initthrow
使得生成的類的構造器方法抛出某種異常,也就說當我們執行個體化生成的詞法分析器時需要捕獲異常才可以。
例子:
方式1:
%initthrow{
"exception1","exception2"
%initthrow}
方式2:
%initthrow "exception1", "exception2"
%ctorarg
使得生成的類的構造器方法,包含參數,可以設定多個該選項,那麼參數會按順序排列。
例子:
%ctorarg String ss
%scanerror
定義當掃描出現錯誤時抛出的具體異常。
例子:
%scannerror XXException
%buffer
設定預設掃描緩沖區大小,預設是16384
例子:
%buffer 16388
掃描函數設定
%function
用于設定詞法掃描函數的名稱,如果不設定該指令,那麼預設的詞法掃描函數名稱為:yylex;注意該指令優先于%cup指令,因為%cup指令設定後,預設掃描函數被命名為:next token,也就是說當我們使用了%cup指令後,盡量就不要使用%function指令了。
例子:
%function myScanner
%integer,%int
這兩條指令都使掃描函數傳回java語言中的int類型,在這種設定下檔案結尾傳回YYEOF
它是生成類 中的一個public static final int 的常量。
例子:
%interger
%intwrap
這條指令使掃描函數傳回Java語言中的Integer類型,在這種設定下檔案結尾預設值是null.
例子:
%intwrap
%type
這條指令用于設定掃描函數的傳回類型,在這種設定下檔案結尾預設值是null.如果指定的類型不是java.lang.Object的子類那麼應該使用%eofval指令或者《EOF》來指定其他檔案結束值。
例子:
%type MyClassSymbol
%yylexthrow
可以使掃描函數聲明抛出異常,可以抛出多個異常。
例子:
%yylexthrow{
"exception1","exception2"
%yylexthrow}
或者:
%yylexthrow "exception1","exception2"
掃描結束操作
當掃描函數掃描到檔案結束時都有一個預設的傳回值,當然在掃描到檔案結束時你也可以定義一個具體的值被傳回或者一段具體代碼被執行。
%eofval{…使用者代碼…%eofval}
其中使用者代碼部分直接被複制到掃描函數中,并且在每次檔案結束時執行。這個使用者代碼應該傳回表示檔案結束的值。
例子:
%eofval{ return new MySymbol()(sym.EOF, null); % eofval }
%eof{…使用者代碼...%eof}
其中使用者代碼遇到檔案結束時隻執行一次,使用者代碼将被放到void yy do eof()方法中,并且不傳回任何值。如果需要傳回值應該使用%eofval{…%eofval}指令或者《EOF》規則。如果出現多個該指令,則按照出現先後順序被連接配接在一起。
例子:
%eof{
System.out.println(" "+nlines+"\t"+nwords+"\t"+nchars);
%eof}
%eofthrow
可以使函數void yy do eof()聲明抛出異常,可以抛出多個異常。
例子:
%eofthrow{
"exception1","exception2"
%eofthrow}
或者
%eofthrow "exception1","exception2"
%eofclose
這條指令使JFLEX在檔案結束處關閉輸入流,代碼yyclose()被追加到方法void yy do eof()中,并且在這個方法throw子句中聲明java.io.IOException。
例子:
%eofclose
%eofclose false
關閉%eofclose的影響。
例子:
%eofclose false
添加Main主函數
%debug
在生成類中生成一個Main方法,它從指令行獲得輸入檔案名,然後對這個檔案運作文法分析器,并向Java控制台列印每個傳回記号的資訊,直到遇到檔案結束。所輸出的資訊包括:line,column号(如果設定了%line,%column),比對文本,執行動作。
例子:
%debug
%standalone
在生成類中生成一個Main方法,它從指令行獲得輸入檔案名,然後對這個檔案運作文法分析器,掃描器的傳回值将被忽略,任何不比對的文本将被列印在Java控制台之上。應該避免使用額外的标記類,掃描方法将被聲明傳回預設的int類型。
例子:
%standalone
CUP 能力
%cup
該指令等同于以下指令集
%implementsjava_cup.runtime.Scanner
%function next_token
%typejava_cup.runtime.Symbol
%eofval{
return newjava_cup.runtime.Symbol(<CUPSYM>.EOF);
%eofval}
%eofclose
%cupsym
使用在%cup指令之前,定義cup包含token生成類/接口的名字,預設為sym.
例子:
%cupsym MySym
%cupdebug
在生成類中生成一個Main方法,它從指令行獲得輸入檔案名,然後對這個檔案運作文法分析器,列印行号,列号,比對字元以及标準的傳回的CUP symbol名稱。
BYacc/J 能力
%byacc
該指令等同于以下指令集
%integer
%eofval{
return 0;
%eofval}
%eofclose
代碼生成算法
以下這些選項,将使得JFLEX産生詞彙分析代碼。當沒有設定代碼産生選項時,%pack是預設被使用的。
%switch
使用%swith指令可以使JFLEX詞法分析器産生一個嵌套的開關結構的代碼。這個方法可以在保證良好運作的前提下,對編譯.class檔案數量更好的壓縮。如果你的掃描器有過多的狀态(大于200個),你就可以考慮使用%table或者%pack.如果狀态再多(大于300個),則可能Java編譯時産生錯誤代碼,以緻于執行錯誤代碼或者在java虛拟機檢測過程中産生java.lang.VerifyError異常。當出現這種情況是将被強制使用%pack。
%table
%table指令将産生一個經典的表格驅動掃描器,将使用數組編碼DFA表格。JFLEX隻進行數量較小的表格壓縮。
%pack
%pack是預設設定,使用一個或者多個字元串生成DFA裝配表。當沒有使用代碼生成方法的具體規定時預設使用。
字元集
%7bit
支援字元集中0-127#字元,如果超出範圍将抛出ArrayIndexOutofBoundsException異常。
%full,%8bit
支援字元集中0-255#字元,如果超出範圍将抛出ArrayIndexOutofBoundsException異常。
%unicode,%16bit
支援字元集中0-65535#字元,使用該字元集不會出現運作時的溢出現象。
%caseless,%ignorecase
對字元的大小寫忽略的設定。也就是說字母a可以對a進行比對,也可以比對字母A.
行,列,字元設定
%char
字元計數器,yychar記錄從輸入開始到目前記号開始出處字元數(從0開始計數)。
%line
行計數器,yyline記錄目前行數
%column
列計數器,yycolumn記錄目前列數
======================================================================
聲明
狀态
% states
包含狀态------定義可能出現的詞法狀态
% xstates
排除狀态------需要排除的詞法狀态。
示例說明
%states A, B
%xstates C
%%
expr1 { yybegin(A);action }
<YYINITIAL, A>expr2 { action }
<A> {
expr3 { action }
<B,C> expr4 {action }
}
解釋:
1. 首先确認A,B狀态是包含狀态,C是排除狀态,預設狀态YYINITIAL總是隐式的不需要被聲明。
2. expr1不存在狀态清單,他可以比對任何狀态,除了排除狀态C。狀态跳轉到A狀态。
3. expr2隻能比對YYINITIAL和A狀态。
4. expr3隻能比對A狀态
5. expr4能夠比對A,B,C三個狀态
6. 總而言之,包含狀态和排除狀态的隻有在規則前沒有狀态清單時才展現出來。那些狀态清單為空的規則隻能比對除排除狀态以外的所有規則。
宏定義
宏定義規則:
宏标示符=正規表達式
按照這種形式定義的宏辨別符可以再第三部分引用,右邊的正規表達式必須是合式,并且不能包含^, / 或者 $等運算符。
詞法規則
詞法規則部分包括一組正規表達式和動作行為,也就是當正規表達式比對成功後要執行的Java代碼。
文法
了解BNF範式
文法結構使用BNF範式形式給出,是以我們先做一個簡單了解。
在雙引号中的字("word")代表着這些字元本身。而double_quote用來代表雙引号。
在雙引号外的字(有可能有下劃線)代表着文法部分。
尖括号( <> )内包含的為必選項。
方括号( [ ] )内包含的為可選項。
大括号( { } )内包含的為可重複0至無數次的項。
豎線( | )表示在其左右兩邊任選一項,相當于"OR"的意思。
::= 是“被定義為”的意思。
結構定義
LexicalRules::= Rule+
Rule ::=[StateList] [’^’] RegExp [LookAhead] Action
| [StateList]’<<EOF>>’ Action
| StateGroup
StateGroup::= StateList ’{’ Rule+ ’}’
StateList::= ’<’ Identifier (’,’ Identifier)* ’>’
LookAhead ::=’$’ | ’/’ RegExp
Action ::= ’{’JavaCode ’}’ | ’|’
RegExp ::=RegExp ’|’ RegExp
| RegExp RegExp //正規表達式連接配接運算
| ’(’ RegExp ’)’
| (’!’|’~’) RegExp //”~”比對任何文本直到第一次比對RegExp
| RegExp (’*’|’+’|’?’)
| RegExp "{"Number ["," Number] "}" //RegExp重複次數
| ’[’ [’^’] (Character|Character’-’Character)*’]’
| PredefinedClass
| ’{’ Identifier ’}’
| ’"’StringCharacter+ ’"’
| Character
PredefinedClass ::= ’[:jletter:]’ //java.lang.Character.isJavaIdentifierStart( )決定的字元類
| ’[:jletterdigit:]’ //由java.lang.Character.isJavaIdentifierPart()決定的字元類
| ’[:letter:]’ //由java.lang.Character.isLetter( )決定的字元類
| ’[:digit:]’ //由java.lang.Character.isDigit( )決定的字元類
| ’[:uppercase:]’ //由java.lang.Character.isUpperCase()決定的字元類
| ’[:lowercase:]’ //由java.lang.Character.isLowerCase()決定的字元類
| ’.’ //包含除\n 外的所有字元
上述EBNF 文法中使用了以下終結符:
JavaCode:表示Java 語言中的語句序列。
Number:表示一個非負的十進制整數。
Identifier:表示一個辨別符,它是以字母(用[a-zA-Z]表示)開頭,後跟0 個或多
個字母、數字或下劃線(即[a-zA-Z0-9_])。
轉義序列包括:
n、\r、\t、\f 和\b;
\x 後跟兩個十六進制數字(即[a-fA-f0-9]),或者反斜杠後跟從000 到377 的三
個八進制數字,表示标準的ASCII 轉義序列;
\u 後跟四個十六進制數字(即[a-fA-f0-9]),表示unicode 轉義序列;
反斜杠後跟其他任何unicode 字元,代表這個unicode 字元。
Character:是不包含下面字元之一的轉義序列或任何unicode 字元:
| ( ) { } [ ] <> \ . * + ? ^ $ / "
StringCharacter:是不包含下面字元之一的轉義序列或任何unicode 字元:\ "
<<EOF>>規則
[StateList]<<EOF>> { 動作代碼}
這條<<EOF>>規則與%eofval 指令十分類似,其不同在于<<EOF>>規則前可以放可
選的StateList。在遇到檔案結束并且詞法分析器目前處于StateList 中的某一詞法狀
态時,執行動作代碼。
語義
Character:比對這個字元;
‘[’ (Character | Character ‘-’ Character)* ‘]’:比對任意一個出現在方括号内的字元或
者由字元範圍Character ‘-’ Character 所界定的字元。例如,[a0-2\n]可以比對字元a、
0、1、2 或者\n。[]比對空集,而不是空串。
‘[^’ (Character | Character ‘-’ Character)* ‘]’ :比對所有未列在方括号中的字元。[^]
比對任意字元。
‘"’ StringCharacter+ ‘"’:比對包含在雙引号内的文本。
‘{’ Identifier ‘}’:比對由名為Identifier 的宏的RHS 所比對的輸入。
JFlex 在正規式中使用以下标準運算符(優先級從高到低):
一進制字尾運算符(‘*’、‘+’、‘?’、{n}、{n, m}),假設a 是正規式,則
a*---表示比對 0 個或多個由a 比對的輸入;
a+---表示比對 1 個或多個由a 比對的輸入;
a?---表示比對 0 個或1 個由a 比對的輸入;
a{n}---表示比對 n 個由a 比對的輸入;
a{n, m}---表示至少比對 n 個、至多比對m 個由a 比對的輸入。
一進制字首運算符(‘!’、‘~’),假設a 是正規式,則
!a---表示比對除a 所比對的串之外的輸入;
~a---表示比對任何文本直到第一次比對a,它等價于正規式!([^]*a[^]*)。
連接配接(RegExp ::= RegExp RegExp)
聯合(RegExp ::= RegExp ‘|’ RegExp)
正規式 r 前面加上‘^’運算符,表示r 隻在輸入流的每行開始進行比對。
正規式 r 後跟上‘$’運算符,表示r 隻在輸入流的每行結尾進行比對。
假設 r1 和r2 是正規式,則r1/r2 表示r1 比對的文本必須是定長的,或者r2 的内容
的開始不比對r1 的尾部。例如,"abc"/ "a"|"b" 是合法的,因為"abc"是定長的;
"a"|"ab"/ "x"* 也是合法的,因為"x"*的字首不比對"a"|"ab"的字尾;"x"|"xy" / "yx"
是非法的,因為"x"|"xy"的字尾"y"也是"yx"的字首。
在動作代碼中可以通路的應用程式設計接口(API)
生成的詞法分析器類中的方法和成員變量名以“yy”為字首,表示它們是自動生成的,避免與複制到這個類中的使用者代碼有名字沖突。由于使用者代碼也是類中的一部分,JFlex 沒有像private 修飾符這樣的語言手段來訓示哪些方法和成員是内部的,哪些屬于應用程式設計接口(API)。取而代之,JFlex 遵循一種命名約定:以“zz”為名字字首的方法或域将被認為是内部使用的,在JFlex 的各釋出版本之間不會通告對這些方法或域的變化;生成類中不以“zz”為名字字首的方法或域就屬于提供給使用者在動作代碼中使用的API,在JFlex 的各釋出版本之間會盡可能地支援它們并保持穩定不變。
API方法和域組成
以下方法由JFLEX自動生成,并且提供給使用者使用。
String yytext()
傳回比對的輸入文本串
int yylength()
傳回所比對的輸入文本串的長度
char yycharat(int pos)
傳回位于比對的文本中第pos 個字元,這等價于
yytext().charAt(pos),但是執行得會更快一些。pos 的取值範圍是0 到yylength( )-1。
void yyclose()
關閉輸入流
void yyreset(java.io.Readerreader)
關閉目前的輸入流,并複位詞法分析器,使之讀取一個新的輸入流。所有的内部變量将被複位,原先的輸入流不能被重用(内部緩沖區的内容被丢棄)。詞法狀态被設定為YY_INITIAL。
voidyypushStream(java.io.Reader reader)
将目前的輸入流儲存到一個棧中,并從一個新的輸入流中讀取。詞法狀态以及行、字元和列的計數資訊保持不變。可以用yypopstream(通常放在<<EOF>>動作中)恢複目前的輸入流。
"#include" {FILE} {yypushStream(new FileReader(getFile(yytext()))); }
..
<<EOF>> { if(yymoreStreams()) yypopStream(); else return EOF; }
該方法僅當使用--skel <file>生成詞法分析器時可用。
void yypopStream()
關閉目前的輸入流,從輸入流棧出棧,并從彈出的輸入流中繼續讀取。
該方法僅當使用--skel <file>生成詞法分析器時可用。
boolean yymoreStreams()
如果輸入流棧中還有輸入流,則傳回true。
該方法僅當使用--skel <file>生成詞法分析器時可用。
int yystate()
傳回目前的詞法狀态。
void yybegin(intlexicalState)
進入詞法狀态lexicalState。
void yypushback(int number)
将所比對的文本中number 個字元退回到輸入流中。這些被退回的字元将在下次調用掃描方法時被再次讀入。在調用yypushback後,被退回的字元将不會包含在yylength 和yytext( )中。
int yyline
包含輸入檔案的目前行數(從0 開始,隻有在設定了%line 指令時才被激活)。
int yychar
包含輸入檔案的目前字元數(從0 開始,隻有在設定了%char 指令時才被激活)。
int yycolumn
包含輸入檔案的目前列數(從0 開始,隻有在設定了%column 指令時才被激活)。
如何比對輸入流
當對輸入流進行詞法分析時,詞法分析器依據最長比對規則來選擇比對輸入流的正規式,即所選擇的正規式能最長地比對目前輸入流。如果同時有多個滿足最長比對的正規式,則生成的詞法分析器将從中選擇最先出現在詞法規範描述中的那個正規式。在确定了起作用的正規式之後,将執行該正規式所關聯的動作。如果沒有比對的正規式,詞法分析器将終止對輸入流的分析并給出錯誤消息。如果在詞法規範描述中使用了%standalone 指令,則所生成的詞法分析器會把不比對的輸入輸出到java.lang.System.out,然後繼續進行詞法分析。