編譯原理與javacc初探
1、前序
真是書到用時方恨少啊,在大學的時候,雖然學過編譯原理,但當時真是不懂啊,只是為了應付考試,死記硬背了一點點。現在呢,由於工作上的需要,不得不彌補一下啊。 這兩天把編譯原理的書又看了一遍,其實也就是主要看了文法,詞法分析,語法分析而已,為了備忘,趕緊先記一下吧。
2、定義
詞法分析,就是把原始碼中的一行行程式碼按照事先規定好的格式分隔成一個個單詞符號(token),比如數字,變數名稱,函式等等。
語法分析呢,主要就是分析詞法分析後的一個個token,是否能夠拼裝,組成事先規定好的語法中的一個。
文法呢,就是對上面語法的規定的集合(程式裡面用到的主要是上下文無關文法)。
3、實現
3.1詞法分析
主要用到的技術有正規表示式,狀態轉換圖,DFA(確定有限自動機),NFA(非確定有限自動機)以及超前搜尋。
3.2語法分析
主要分自上而下分析和子下而上分析。
3.2.1自上而下分析
主要面臨的問題有左遞迴和左因子。
3.2.1.1左遞迴的消除
可以參考一下公式
P->Pα|β可以轉換成一下語句
P->βP'
P'->αP'|ε (ε為空子)
3.2.1.2消除回溯,提取公因子
可以參考一下公式
A->δβ1|δβ2|δβ3|γ1|γ2|γ3
轉換成
A->δA'|γ1|γ2|γ3
A'->β1|β2|β3
3.2.1.3 LL(1)文法
意思就是從左向右掃描輸入串,最左推導,每一步只需向前看一個符號。
3.2.1.4實現方法
當一個文法滿足 LL(1)文法條件時,我們就可以為它構造一個不帶回溯的自上而下分析程式,這個分析程式是由一組遞迴過程組成的,每個過程對應文法的一個非終結符。這樣的一個分析程式稱為遞迴下降分析器。
另外一種實現方法為預測分析程式。就是使用一張分析表和一個棧進行聯合控制。
3.2.2自下而上分析
從輸入串開始,逐步進行規約,直至規約到文法的開始符號。
由於與我下文說的javacc沒有關係,因此我就不再敘述了。
4、javacc概述
JavaCC是一個詞法分析器和語法分析器的生成器,用於java領域,根據指定格式的輸入檔案生成java原始碼的詞法、語法分析程式(比lex和yacc要人性化)。
是一個免費的開源的工具,可以從網上下載。
javacc採用的是自頂向下的分析方法,而且沒有回溯功能,因此如何解決衝突的問題,是程式設計師的責任。
必須要解決兩個問題:左遞迴和公因子
關於左遞迴的問題,一般採用經典的修改文法的方法解決。
關於公因子的問題,可以改寫演算法(提取公因子),也可以通過展望(look ahead)更多符號的方法來解決。 但是因為javacc擴充套件了經典的BNF,因此它面臨更多的問題。總之,當在編譯時碰到衝突問題時,就說到了一個choice point。
可以將javacc中choice point歸結為以下幾類:
l . 由選擇運算元 | 引入的衝突,
2. 由可省略運算元 [] 或 ?引入的衝突
3. 由重複運算元 * 引入的衝突
4. 由重複運算元 + 引入的衝突
當在javacc中碰到衝突時,可以採用下面的方法之一解決問題
1.修改語法,使之成為LL(1)語法。
2.只要將LOOKAHEAD=k寫到Options塊中即可,javacc產生的分析器就可以分析LL(K)語法生成的語言
採用第一種方法的好處是效率非常高,易於維護。採用第二種方法的好處是語法更加直觀,但是卻不易維護。有時候採用第一種方法是無法解決衝突的,第二種方法是唯一的選擇。
5、javacc入門
首先先從官網上面下載相應的javacc檔案,我這裡下載的是javacc-5.0.zip。裡面包含一些入門文件(英文)和例子。
addr.jj
expression: Start->NUMBER(PLUS Primary)*
options {
STATIC = false ; //生成非靜態類
LOOKAHEAD=2;//向前看2個字母,消除歧義用的
DEBUG_PARSER = true;//以debug形式生成,便於除錯
}
PARSER_BEGIN(Adder)
class Adder {
public static void main( String[] args ) throws ParseException, TokenMgrError
{
Adder parser = new Adder( System.in ) ;
parser.Start() ;
}
}
PARSER_END(Adder)
SKIP : { " " }
SKIP : { "\n" | "\r" | "\r\n" }
TOKEN : { < PLUS : "+" > }
TOKEN : { < NUMBER : <DIGITS> |<DIGITS> "." <DIGITS> |<DIGITS> "." | "." <DIGITS>> }
TOKEN : { < #DIGITS : (["0"-"9"])+ > }
void Start() :
{}
{
<NUMBER>
(
<PLUS>
<NUMBER>
)*
<EOF>
}
把上面程式碼儲存到javacc bin目錄下,執行以下語句。
相信能夠看懂怎麼使用吧。
6、javacc與eclipse整合
從網上下載easy-javacc-1.5.7-eclipse plugin.exe,並安裝到eclipse的安裝目錄裡面。然後重啟eclipse,看下圖。
編譯adder.jj,生成java原始檔(詞法語法分析程式)
7、擴充套件語法分析器
在上面的例子中的start函式中,我們僅僅通過語法分析器來判斷輸入的語句是否正確。
我們可以擴充套件BNF表示式,加入Java程式碼,使得經過語法分析後,得到我們想要的結果或者物件。
我們將start函式改寫為:
注意:t.kind表示單詞的類別,t.image表示單詞值。
8、javacc例項一(計算器)
expression:
Start -> ( Expression EOL )* <EOF>
Expression -> Term(PLUS Term|MINUS Term)*
Term -> Primary(TIMES Primary | DIVIDE Primary)*
Primary -> NUMBER
Calculator.jj
options
{
static = false;
}
PARSER_BEGIN(Calculator)
import java.io.PrintStream ;
public class Calculator {
double previousValue = 0.0 ;
public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException {
Calculator parser = new Calculator( System.in ) ;
parser.Start( System.out ) ;
}
}
PARSER_END(Calculator)
SKIP:{ " " }
TOKEN:{< EOL : "\n"|"\r"|"\r\n" >}
TOKEN:{< PLUS : "+">}
TOKEN :{ < MINUS : "-" > }
TOKEN:{< TIMES : "*" > }
TOKEN:{< DIVIDE : "/" > }
TOKEN:{< NUMBER : <DIGITS>| <DIGITS>"." <DIGITS> | <DIGITS>"."|"." <DIGITS> >}
TOKEN : {< #DIGITS : (["0"-"9"])+ >}
void Start(PrintStream printStream) throws NumberFormatException :
{}
{
(
previousValue = Expression()
<EOL>
{ printStream.println(previousValue); }
)*
<EOF>
}
double Expression() throws NumberFormatException :
{
double i;
double value;
}
{
value = Term()
(
<PLUS>
i = Term()
{ value += i;}
|
<MINUS>
i = Term()
{ value -= i; }
)*
{ return value; }
}
double Term() throws NumberFormatException :
{
double i;
double value;
}
{
value = Primary()
(
<TIMES>
i = Primary ()
{ value *= i;}
|
<DIVIDE>
i = Primary ()
{ value /= i; }
)*
{ return value; }
}
double Primary() throws NumberFormatException :
{
Token t;
double d;
}
{
t = <NUMBER>
{ return Double.parseDouble( t.image ); }
}
9、javacc例項一(計算器擴充套件)
在8的基礎上,增加括號支援和負數計算
expression:
Start -> ( Expression EOL )* <EOF>
Expression -> Term(PLUS Term|MINUS Term)*
Term -> Primary(TIMES Primary | DIVIDE Primary)*
Primary -> NUMBER | PREVIOUS | MINUS Primary | OPEN_PAR Expression CLOSE_PAR
Calculator.jj
options
{
static = false;
}
PARSER_BEGIN(Calculator)
import java.io.PrintStream ;
public class Calculator {
double previousValue = 0.0 ;
public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException {
Calculator parser = new Calculator( System.in ) ;
parser.Start( System.out ) ;
}
}
PARSER_END(Calculator)
SKIP:{ " " }
TOKEN:{< EOL : "\n"|"\r"|"\r\n" >}
TOKEN:{< PLUS : "+">}
TOKEN :{ < MINUS : "-" > }
TOKEN:{< TIMES : "*" > }
TOKEN:{< DIVIDE : "/" > }
TOKEN:{< OPEN_PAR : "(" > }
TOKEN:{< CLOSE_PAR : ")" > }
TOKEN:{< PREVIOUS : "$" > }
TOKEN:{< NUMBER : <DIGITS>| <DIGITS>"." <DIGITS> | <DIGITS>"."|"." <DIGITS> >}
TOKEN : {< #DIGITS : (["0"-"9"])+ >}
void Start(PrintStream printStream) throws NumberFormatException :
{}
{
(
previousValue = Expression()
<EOL>
{ printStream.println(previousValue); }
)*
<EOF>
}
double Expression() throws NumberFormatException :
{
double i;
double value;
}
{
value = Term()
(
<PLUS>
i = Term()
{ value += i;}
|
<MINUS>
i = Term()
{ value -= i; }
)*
{ return value; }
}
double Term() throws NumberFormatException :
{
double i;
double value;
}
{
value = Primary()
(
<TIMES>
i = Primary ()
{ value *= i;}
|
<DIVIDE>
i = Primary ()
{ value /= i; }
)*
{ return value; }
}
double Primary() throws NumberFormatException :
{
Token t;
double d;
}
{
t = <NUMBER>
{ return Double.parseDouble( t.image ); }
|
<PREVIOUS>
{ return previousValue; }
|
<OPEN_PAR> d = Expression() <CLOSE_PAR>
{ return d; }
|
<MINUS> d = Primary()
{ return -d; }
}
相關文章
- 初探JsonCpp - 編譯與基本使用JSON編譯
- 編譯原理編譯原理
- javacc-LOOKAHEAD MiniTutorial 翻譯Java
- Flutter 編譯原理Flutter編譯原理
- 編譯原理概述編譯原理
- Quill編輯器實現原理初探UI
- Typescript編譯原理(一)TypeScript編譯原理
- 編譯原理概覽編譯原理
- Vue 模板編譯原理Vue編譯原理
- 白話編譯原理編譯原理
- 前端與編譯原理——用 JS 寫一個 JS 直譯器前端編譯原理JS
- 前端與編譯原理——用JS寫一個JS直譯器前端編譯原理JS
- Java編譯與反編譯Java編譯
- 深入理解flutter的編譯原理與優化Flutter編譯原理優化
- 模板函式編譯原理函式編譯原理
- 《編譯原理》學習心得編譯原理
- 初探 Go 的編譯命令執行過程Go編譯
- Javacc sampleJava
- gcc 編譯器與 clang 編譯器GC編譯
- 編譯領域裡程碑之作:龍書《編譯原理》編譯原理
- Android反編譯和微信機器人初探Android編譯機器人
- OpenStack容器服務Zun初探與原理分析
- vue模板編譯(原理篇)Vue編譯
- 深入分析 Javac 編譯原理Java編譯原理
- 編譯原理讀書筆記編譯原理筆記
- 【編譯原理】語法分析(三)編譯原理語法分析
- 0909 編譯原理1編譯原理
- 編譯原理作業小結編譯原理
- ZOMI的AI編譯原理1AI編譯原理
- ZOMI的AI編譯原理2AI編譯原理
- TIR 的概念和編譯原理編譯原理
- vue原理初探Vue
- Javacc的例子Java
- 深入瞭解Java JIT編譯器:原理與效能最佳化Java編譯
- 孟巖:編譯領域裡程碑之作 龍書《編譯原理》編譯原理
- Rust 交叉編譯與條件編譯總結Rust編譯
- 走進Golang之編譯器原理Golang編譯
- JavaScript預編譯原理, 引擎,作用域JavaScript編譯原理