antlr指南 第四章 語法分析

第四章 語法分析

語法分析是編譯過程的第二步,在詞法分析提供的記號流的基礎上,對源代碼的結構做總體的分析。無論分析的內容有多大語法分析總是由一個啓始規則開始的,最後總是生成一棵語法樹。一般情況語法規則是一個文法的主體部分,也是編寫文法的難點。本章用幾個示例來講述如何用ANTLR定義語法規則。

 

4.1語法分析的方法

    在ANTLR中語法分析定義的規則名必須以小寫字母開始大寫如“baseClass”,“subfixSymbol”。如果詞法規則與語法規則寫在同一個文件時,雖然ANTLR中並沒有嚴格定義規則的先後順序,但一般情況下語法規則寫到詞法規則的上面,因爲整個文法的啓始規則是從語法規則開始的,這樣可以從上到下查看整個文法。

    ANTLR中語法定義的方法與詞法基本相同請看下面一個SQL文法的片段示例:

grammar Test;

sqlStatement : selectStatement | insertStatement | deleteStatement;

selectStatement : SELECT (ALL | DISTINCT)?  SelectList FROM tableSource;

SelectList : SelectItem+;

tableSource : TableName | '(' selectStatement ') ';

 

4.2遞歸定義

定義文法時通常出現遞歸的情況,比如上例中tableSource中的子查詢就是一個遞歸定義。在C++,C#,java語言中類名這樣的符號是用“.”分隔的多個標識符組成的,如java.IO,System.Web.UI等這種情況需要使用遞歸的方法來定義,遞歸有左遞歸和右遞歸。

左遞歸:

qualifiedName : qualifiedName '. ' Identifier;

qualifiedName : Identifier;

Identifier : ('a'.. 'z' | 'A'.. 'Z' | '_') ('a'.. 'z' | 'A'.. 'Z' | '_' | '0'.. '9')*;

右遞歸:

qualifiedName : Identifier '.' QualifiedName

qualifiedName : Identifier;

Identifier : ('a'.. 'z' | 'A'.. 'Z' | '_') ('a'.. 'z' | 'A'.. 'Z' | '_'| '0'.. '9')*;

不過在ANTLR中不允許左遞歸定義,ANTLR會提示:“rule is left-recursive”錯誤。ANTLR中還有別一種定義方法,象類名這樣的符號遞歸定義使用這種方法是最好的方案。原因我們會在後面章節講述。

qualifiedName : Identifier ('.' Identifier)*;

Identifier : ('a'.. 'z' | 'A'.. 'Z' | '_') ('a'.. 'z' | 'A'.. 'Z' | '_' | '0'.. '9')*;

 

4.3 java方法的文法示例

下面來看一個定義java方法的文法,些文法是從ANTLR自帶的java.g示例中選出的一段:

methodDeclaration :type Identifier

        (('(' (variableModifier* type formalParameterDeclsRest?)? ')')

('[' ']')*

        ('throws' qualifiedNameList)?

        (   methodBody

        |   ';'

        )) ;

formalParameterDeclsRest :

variableDeclaratorId

(',' (variableModifier* type formalParameterDeclsRest?))?

|   '...' variableDeclaratorId

;

type : Identifier (typeArguments)?

                  ('.' Identifier (typeArguments)? )* ('[' ']')*

|                   primitiveType ('[' ']')*

;

variableModifier : 'final'  |  annotation ;

 

formalParameterDeclsRest :

variableDeclaratorId

(',' (variableModifier* type formalParameterDeclsRest?))?

|   '...' variableDeclaratorId

;

variableDeclaratorId : Identifier ('[' ']')* ;

 

Identifier : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;

 

typeArguments : '<' typeArgument (',' typeArgument)* '>' ;

 

typeArgument : type

           | '?' (('extends' | 'super') type)? ;

 

qualifiedNameList :qualifiedName (',' qualifiedName)* ;

 

qualifiedName :Identifier ('.' Identifier)* ;

methodDeclaration爲方法定義的起始規則,後面type Identifier爲方法的返回值和方法名,( '(' (variableModifier* type formalParameterDeclsRest?)? ')' ) ('[' ']')*中的variableModifier爲參數前的標識符,type爲參數的類型formalParameterDeclsRest是對參數其餘部分的定義,參數表在“()”中,後面是“[]”字符,這是數組類型爲返回值時需要的,因爲數據可以是多維的後是用('[' ']')*來定義。

formalParameterDeclsRest用來定義參數表部分,variableDeclaratorId爲參數名,如何有一個以上參數(',' (variableModifier* type formalParameterDeclsRest?))?部分字義了第二個到第N個參數的規則,這裏使用了右遞歸方法。Java1.5中支持可變參數 '...' variableDeclaratorId爲可變參數的定義。

另外typeArguments配合Identifier來定義泛型類型,使用“<>”括起來一個類型列表,如List<string>。typeArgument與type形成了遞歸定義,如Hashtable<string,List<stirng>>。

 

4.4 | 作用範圍

上例中的typeArgument : type | '?' (('extends' | 'super') type)? 規則無須寫成typeArgument:type |  ('?' (('extends' | 'super') type)?)。在同一規則或子規則中“|”使其兩側的內容爲並列的選擇關係,如果有多個“|”則一起爲並列的選擇關係,需要改變優先順序時才使用“()”。

 

4.5 SELECT語句文法示例

下面給出一個SQL SELECT語句文法片段的例子。

grammar Select;

statement

    : selectStatement (SEMICOLON)?;

selectStatement

    : queryExpression  (computeClause)? (forClause)? (optionClause)?;

queryExpression

    : subQueryExpression (unionOperator subQueryExpression)* (orderByClause)?

    ;

subQueryExpression

    :  querySpecification  |  ‘(‘ queryExpression ‘)’

    ;

querySpecification

    : selectClause (fromClause)? (whereClause)? (groupByClause (havingClause)? )?

    ;

selectClause

    : SELECT (ALL | DISTINCT)? (TOP Integer (PERCENT)?)? selectList

    ;

whereClause

    : WHERE searchCondition

    ;

orderByClause

    : ORDER BY expression (ASC | DESC)? (COMMA expression (ASC | DESC)? )*

    ;

groupByClause

    :GROUP BY (ALL)? expression (COMMA expression)* (WITH (CUBE | ROLLUP) )?

    ;

havingClause

    : HAVING searchCondition

    ;

SEMICOLON : ';';

我們對SELECT語句都比較熟悉,看一下SELECT語句文法的大體結構。selectStatement規則表示整個SELECT語句體系,queryExpression表示查詢語句可能由多個子查詢用UNION到一起unionOperator代表UNION或UNION ALL關鍵字,orderByClause子句出現在SELECT語句的最後,這裏subQueryExpression和queryExpression之間形成了遞歸定義,子查詢中還可以有子查詢。whereClause子句和havingClause子句後面都是查詢過濾表達式後成它們共用searchCondition規則。

 

4.6 HTML文法示例

下面再看一個HTML文法片段示例:

grammar HTML;

options  {language=CSharp; output=AST;}

document :     OHTML  body  CHTML;

body :    obody  (body_content)*  CBODY ; 

body_content :  body_tag | text;

body_tag :    block; // | heading | ADDRESS

text :     text_tag;

text_tag :      form;// phrase | special | font

block :      table;//paragraph | list | preformatted | div | center | blockquote | HR |

table :      otable (tr)+ CTABLE;

tr :      o_tr (th_or_td)* (C_TR)? ;

th_or_td :      o_th_or_td (body_content)* (C_TH_OR_TD )?;

form :      oform (form_field | body_content)* CFORM;//

oform :      '<form' (ATTR)* '>';

form_field :    inputField;// | select | textarea;

inputField :  '<input' (ATTR)* '>';

obody :    '<body' (ATTR)* '>';

 

CBODY :      '</body>';

OHTML :      '<html>';

CHTML :      '</html>';

otable :      '<table' (ATTR)* '>';

CTABLE :      '</table>';

o_tr :      '<tr' (ATTR)* '>';

C_TR :      '</tr>';

o_th_or_td :     ('<th' | '<td') (ATTR)* '>';

C_TH_OR_TD :   '</th>' | '</td>';

CFORM :      '</form>';

ATTR :      WORD ('=' (WORD ('%')? | ('-')? INT | STRING | HEXNUM))?;

HEXNUM :      '#' ('0'..'9' | 'a'..'f')+;

INT :      (DIGIT)+;

DIGIT :      '0'..'9';

WORD :      (LCLETTER | '.' | '/')  (LCLETTER | DIGIT | '.')+;

STRING :      '"' (~'"')* '"' | '\'' (~'\'')* '\'';

LCLETTER :     'a'..'z';

WS : ( ' ' | '\t' | '\n' | '\r' ) + {Skip();} ;

這個可運行的簡化的HTML文法中只支持<form>表單和<table>表格。O開頭的符號如OHTML、otable表示標記開頭<html>和<table>,C開頭的符號是表示結束標記如:CHTML,CTABLE表示</html>和</table>。document規則表示整個HTML文檔其包括body部分。Body主要包括兩種內容text和body_tag,body_tag中包括<table>表格,text中包含<form>表單。詞法規則可以和語法規則混合書寫這是允許的。

文法中的th_or_td規則表示表格中的一個cell,其中又包含了body_content這個遞歸的定義使得表格中的第一個小格都可以嵌套的包含HTML標記。生成分析器後可以對下面的HTML文件進行分析。

<html>

<body id="doc">

<table>

  <tr>

    <td>

        <form action="login.jsp" >

            <input id="txt1" value="" />

        </form>

  </td>

  </tr>

</table>

</body>

</html>

 

4.7 Skip()的效果

在HTML文法示例中的開始標記的定義是用語法規則定義的,而結束是用詞法規則定義的ANTLR中規則可以靈活定義不拘一格,結束標記只是固定的字符串定義成詞法規則就可以了。而開始標記中要包含屬性的定義所以用語法規則來定義。其中有一點要注意的是如果我們把開始標記也定義成詞法規則會出現什麼情況呢?如下面改變一下HTML文法。

OFORM : ‘<form’  (ATTR)*  ‘>’

將<form>表單的開始標記改爲詞法規則,這時分析器不能正確分析上面的html文件。在分析<form ation=””>行時異常。運行這個示例的語句如下:

HTMLLexer lex = new HTMLLexer(new ANTLRFileStream("t.html"));

ITokenStream tokens = new CommonTokenStream(lex);

HTMLParser parser = new HTMLParser(tokens);

HTMLParser.document_return dReturn = parser.document();

這是因爲空白規則WS中的Skip()語句的效果是對語法分析而言的。在詞法分析階段Skip()並沒有去掉空格,我們可以java和.net的開發環境中查看tokens對象瞭解詞法分析階段的結果,這可以幫助我們分析問題。對於輸入的“<form ation="">”來說 form和ation之間有一個空格這時詞法分析器會失敗,因爲我們在這個規則中沒有定義空白,除非我們這樣寫這個詞法規則。

OFORM : ‘<form’  WS  (ATTR)* WS? ‘>’

把所有可能出現空格地方都加下顯示地定義空白才行。這也就是我們在上一章中的FuzzyJava2示例中爲什麼詞法規則中加了很多WS的原因。

 

4.7 語法規則中的字符常量

可能讀者早已發現我們在語法規則中直接寫入了需要匹配的字符。如:

oform : '<form' (ATTR)* '>';

‘<form’和‘>’都屬於詞法範疇,ANTLR爲了使書寫簡單直觀,允許在語法規則中直接寫出需要匹配字符。在生成分析器代碼時這些常量會自動被放入詞法分析程序中,語法分析程序中的字符串會用生成的序號代替。oform : '<form' (ATTR)* '>'與下面的寫法是等價的。

oform : t10 (ATTR)* t11;

t10 :‘<form';

t11 : ‘>';

 

4.8 C-語言示例

C-語言是C語言的一個子集,是ANTLR的一個經典示例。下面看一下它的文法。

grammar CMinus;

program

    :   declaration+

    ;

declaration

    :   variable  |   function

    ;

variable

    :   type ID ';'

    ;

type:   'int'  |   'char'

    ;

function

    :   type ID

        '(' ( formalParameter (',' formalParameter)* )?  ')'

        block

    ;

formalParameter

    :   type ID

    ;

block

    :   '{' variable* stat* '}'

    ;

stat

: forStat  |  ifStat  |  expr ';'  |  block  |  assignStat ';'  |  ';'

    ;

ifStat

    : ‘if’  ‘(‘  expr  ‘)’  stat  (‘else’  stat)?

    ;

forStat

    :  'for' '(' assignStat ';' expr ';' assignStat ')'  block

    ;

assignStat

    :   ID '=' expr

    ;

expr:   condExpr ;

 

condExpr

    :   aexpr ( ('==' | '!=' ) aexpr )?

    ;

aexpr

    :   mexpr ('+'  mexpr)*

    ;

mexpr

    :   atom ('*'  atom)*

    ;

atom:   ID

    |   INT

    |   '(' expr ')'

    ;

ID  :   ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ;

INT :   ('0'..'9')+ ;

WS  :  ( ' ' | '\t' | '\r' | '\n' )+ { $channel = HIDDEN; } ;

啓始規則program 表示整個C-程序,declaration表示語言中的聲明項,有函數和變量兩種聲明variable和function。變量聲明由類型type加標識符ID組成,function函數聲明是由返回類型type函數名ID和參數表'(' ( formalParameter (',' formalParameter)* )? ')',block爲函數體。函數體內可以有變量variable和語句stat。

語句stat包括for語句forStat、表達式expr、語句塊block和賦值語句assignStat。表達式expr和第一章中的HelloWorld示例類似推導順序與操作符優先順序同反,在表達式的末端規則atom有可出現標識符ID、整數INT和嵌套的子表達式,語句塊block與函數體block是同一規則,這是一個遞歸定義使得語句塊可以嵌套書寫。

生成分析器代碼編譯運行,來分析如下的C-代碼。

char c;

int x;

int foo(int y, char d) {

int i;

for (i=0; i!=3; i=i+1) {

x=3;

y=5;

}

     if(5 == 4)

       if(5 == 4)

i = 1;

else

i = 2;

}

 

4.9小結

本章講述了ANTLR的語法分析,語法分析中的遞歸定義的應用,用SQL、java、HTML和C-幾個實際的例子來讓讀者對語法分析有更好的理解。通過對Skip()效果的示例說明了詞法部分的操作與語法分析的關係。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章