Friday, February 25, 2005

ทบทวน Antlr

วันนี้แกะ code Jakarta Slide ส่วน Client command line tool
พบว่าใช้ Antlr ช่วยเขียน เลยขอ note ทบทวนเกี่ยวกับ Antlr หน่อย
เนื่องจากห่างหายไม่ได้ใช้มา 2-3 ปีแล้ว

เวลาเราเขียน Gramma spec ด้วย Antlr
เราจะเขียนผ่าน file ที่มีนามสกุล .g โดยเราสามารถเขียน Lexer, Parser
แล้วก็ Tree Parser จากนั้นก็จะใช้ Ant ช่วย
compile ให้กลายเป็น java file อีกที

ในส่วน Slide Client command line จะใช้เฉพาะ Lexer และ
Parser เมื่อ parser parse ได้ ก็จะเกิด Action เลย ไม่ได้มีการสร้างเป็น Abstract
Syntax Tree แต่อย่างไร


Lexer Spec


class ClientLexer extends Lexer;

// ------------------------------------------------------------ options section

options {
k = 2;
caseSensitiveLiterals = false;
charVocabulary = '\u0003'..'\uFFFF';
}

// ------------------------------------------------------------- tokens section

tokens {
EXIT = "exit";
QUIT = "quit";
BYE = "bye";
HELP = "help";
....
}
//---------------------------------------------------------------- lexer rules
WS : (
' '
| '\t'
)
{
_ttype = Token.SKIP;
}
;

EOL // the end of line
: "\r\n" // DOS
| '\r' // MAC
| '\n' // UN*X
;

// Backslashes are accepted by CHARS,
// but STRING replace them into slashes !!
STRING
: ( CHARS (CHARS | '-')*
| '"'! ( ~'"' )* '"'!
)
{ String txt = $getText;
txt = txt.replace('\\', '/');
$setText(txt);
}
;

protected
CHARS
: 'a'..'z'
| 'A'..'Z'
| '0'..'9'
| '.'
| ':'
| '/'
| '$'
| '#'
| '%'
| '&'
| '('
| ')'
| '!'
| '+'
| '\\'
| '_'
;

....



ส่วนที่อยู่ในเครื่องหมายปีกกาเรียกว่า action
ซึ่งจะถูกแปลงเป็น java code ตรงๆ ใน Lexer class
และจะถูกเรียกทำงานเมื่อ lexer recognize rule ส่วนนั้น
สังเกตุได้ที่ STRING lexer rule เมื่อ lexer
จับได้ว่าเป็น String token
ก็จะทำการแปลงเครื่องหมาย \\ ให้เป็น /

ส่วน protected ที่นำหน้า Rule นั้น
เนื่องจาก Antlr แปลง rule ทุกตัวให้เป็น java method
ดังนั้นคำว่า protected ก็คือบอกให้ method
ที่ gen ออกมามี scope เป็น protected

k=2 ก็คือ บอกว่า Look ahead เท่ากับ 2


Parser Spec

class ClientParser extends Parser;

// ------------------------------------------------------------ options section

options {
k = 2;
exportVocab = Slide; // call this vocabulary "Slide"
defaultErrorHandler = false; // abort parsing on error
// TODO: use a tree-parser rule
// buildAST = true; // build tree construction
buildAST = false; // uses CommonAST by default
}

// Java code
{

// ------------------------------------------------------------- properties

/**
* The Slide WebDAV client.
*/
protected Client client;

// --------------------------------------------------------- helper methods

/**
* Set a client.
*
* @param client a client
*/
void setClient(Client client) {
this.client = client;
}

// --------------------------------------------------------------- parser rules

commands
: {
client.prompt();
}
( command
{
client.prompt();
}
)+
;
exception catch [ANTLRException ex] {
// XXX bad hack for bug #28100
if (ex.toString().indexOf("null") != -1) System.exit(-1);
// handle parse errors gracefully
client.print("Error: "+ex.toString());
}

command
:
( exit
| help
| invalid
| nothing
| spool
| run
| echo
.... (ตัดออก)
| commit
| abort
)
;

help
: ( HELP
| QUESTION
)
EOL
{
client.help(null);
}
;
exception
catch [RecognitionException ex]
{
printUsage("help");
}

connect
: ( CONNECT
| OPEN
)
uri:STRING
EOL
{
client.connect(text(uri));
}
;
exception
catch [RecognitionException ex]
{
printUsage("connect");
}


จะเห็นได้ว่าเราสามารถ embed method setClient
เพื่อที่เราจะได้ set helper class ลงไปได้
โดยเมื่อ parser recognise syntax ได้
ก็จะมีการทำ action (ส่วนที่อยู่ในเครื่องหมายปีกกา)
ซึ่ง implement ในลักษณะ delegate
ต่อไปยัง Client class อีกที

กฎง่ายๆของการเขียน Parser Rule ก็คือ
ถ้าเขียนตัวใหญ่หมด อันนั้นคือ token
ส่วนถ้าเป็นตัวเล็ก ก็หมายถึง sub rule

action code สามารถอ้างถึง token value
ได้โดยใส่ ชื่อตัวแปรตามด้วย colon
นำหน้า token ดังจะเห็นได้ในตัวอย่างของ
connect rule ซึ่งเขียนกฎ token STRING
-> uri:STRING

ข้อดีของ Antlr คือเมื่อแปลงเป็น java code แล้วก็ยังอ่านง่าย, debug ง่าย
ตัวอย่างของ code ที่ gen ออกมาในส่วนของ rule connect


public final void connect() throws RecognitionException,
TokenStreamException {

Token uri = null;

try { // for error handling
{
switch ( LA(1)) {
case CONNECT:
{
match(CONNECT);
break;
}
case OPEN:
{
match(OPEN);
break;
}
default:
{
throw new
NoViableAltException(LT(1), getFilename());
}
}
}
uri = LT(1);
match(STRING);
match(EOL);

client.connect(text(uri));

}
catch (RecognitionException ex) {

printUsage("connect");

}
}

Related link from Roti

No comments: