First!
lex程序的結(jié)構(gòu)是這樣的!

定義
%%
規(guī)則
%%
用戶代碼

 

一個 Lex 程序分為三個段:第一段是 C 和 Lex 的全局聲明,第二段包括模式(C 代碼),第三段是補充的 C 函數(shù)。 這些段以%%來分界。 下面是一個行數(shù)與字數(shù)的統(tǒng)計工具。


        int num_lines = 0, num_chars = 0;
 %%
\n      ++num_lines; ++num_chars;
.       ++num_chars;

%%
main()
        {
        yylex();
        printf( "# of lines = %d, # of chars = %d\n",
                num_lines, num_chars );
        }

 

Second!
對First內(nèi)容的回顧
C 和 Lex 的全局聲明
這一段中我們可以增加 C 變量聲明。這里我們將為字數(shù)統(tǒng)計程序聲明一個整型變量,來保存程序統(tǒng)計出來的字數(shù)。我們還將進行 Lex 的標記聲明。

字數(shù)統(tǒng)計程序的聲明

       %{
        int wordCount = 0;
        %}
        chars [A-za-z\_\'\.\"]
        numbers ([0-9])+
        delim [" "\n\t]
        whitespace {delim}+
        words {chars}+
        %%

 

兩個百分號標記指出了 Lex 程序中這一段的結(jié)束和三段中第二段的開始。

Lex 的模式匹配規(guī)則
讓我們看一下 Lex 描述我們所要匹配的標記的規(guī)則。(我們將使用 C 來定義標記匹配后的動作。)繼續(xù)看我們的字數(shù)統(tǒng)計程序,下面是標記匹配的規(guī)則。
字數(shù)統(tǒng)計程序中的 Lex 規(guī)則

       {words} { wordCount++; /*
        increase the word count by one*/ }
        {whitespace} { /* do
        nothing*/ }
        {numbers} { /* one may
        want to add some processing here*/ }
        %%

 
C 代碼
Lex 編程的第三段,也就是最后一段覆蓋了 C 的函數(shù)聲明(有時是主函數(shù))。注意這一段必須包括 yywrap() 函數(shù)。 Lex 有一套可供使用的函數(shù)和變量。 其中之一就是 yywrap。一般來說,yywrap() 的定義如下例。我們將在 高級 Lex 中探討這一問題。
字數(shù)統(tǒng)計程序的 C 代碼段

       void main()
        {
        yylex(); /* start the
        analysis*/
        printf(" No of words:
        %d\n", wordCount);
        }
        int yywrap()
        {
        return 1;
        }

 

Lex 編程的基本元素就這樣搞定了,它將幫助你編寫簡單的詞法分析程序。
Third
高級Lex
Lex 有幾個函數(shù)和變量提供了不同的信息,可以用來編譯實現(xiàn)復(fù)雜函數(shù)的程序。下表中列出了一些變量和函數(shù),以及它們的使用。 詳盡的列表請參考 Lex 手冊。
Lex 變量
   yyin  FILE* 類型。 它指向 lexer 正在解析的當前文件。
   yyout FILE* 類型。 它指向記錄 lexer 輸出的位置。 缺省情況下,yyin 和 yyout 都指向標準輸入和輸出。
   yytext 匹配模式的文本存儲在這一變量中(char*)。
   yyleng 給出匹配模式的長度。
   yylineno 提供當前的行數(shù)信息。(lexer不一定支持。)

Lex 函數(shù)
   yylex() 這一函數(shù)開始分析。 它由 Lex 自動生成。
   yywrap() 這一函數(shù)在文件(或輸入)的末尾調(diào)用。如果函數(shù)的返回值是1,就停止解析。 因此它可以用來解析多個文件。代碼可以寫在第三段,這就能夠解析多個文件。 方法是使用 yyin 文件指針(見上表)指向不同的文件,直到所有的文件都被解析。最后,yywrap() 可以返回 1 來表示解析的結(jié)束。
   yyless(int n) 這一函數(shù)可以用來送回除了前 n? 個字符外的所有讀出標記。
   yymore() 這一函數(shù)告訴 Lexer 將下一個標記附加到當前標記后。
到此為止,可能你看到lex程序還會范暈,沒關(guān)系,下面我們接著來,分析一個類pascal語法的極簡析器!
/* 這個就是注釋了*/
  /* scanner for a toy Pascal-like language */
申明部分開始
%{ 內(nèi)的東西會原封不動地出現(xiàn)在輸出文件中 }%

  %{
     /* need this for the call to atof() below */
     #include <math.h>
  %}
  DIGIT    [0-9]
  ID       [a-z][a-z0-9]*
  %%
模式部分開始
  {DIGIT}+    {
    printf( "An integer: %s (%d)\n", yytext,
    atoi( yytext ) );
  }
  {DIGIT}+"."{DIGIT}*        {
    printf( "A float: %s (%g)\n", yytext,
    atof( yytext ) );
  }
  if|then|begin|end|procedure|function        {
    printf( "A keyword: %s\n", yytext );
  }
  {ID}        printf( "An identifier: %s\n", yytext );
  "+"|"-"|"*"|"/"   printf( "An operator: %s\n", yytext );
  "{"[^}\n]*"}"     /* eat up one-line comments */
  [ \t\n]+          /* eat up whitespace */
  .           printf( "Unrecognized character: %s\n", yytext );
  %%
補充部分開始
  main( argc, argv )
   int argc;
   char **argv;
  {
    ++argv, --argc;  /* skip over program name */
    if ( argc > 0 )
      yyin = fopen( argv[0], "r" );
    else
      yyin = stdin;
    yylex();
  }
想要真正了解lex, [[正則表達式]] 是關(guān)鍵!
Four
yytext 匹配模式的文本存儲變量, 可以通過在申明階段使用%pointer或%array來控制是一個字符指針還是一個字符數(shù)組。指針模式與數(shù)組模式各有特點,導(dǎo)致在yytex申明上也不一樣,具體請參考lex手冊!
在模式階段中

  模式            動作
  [ \t]+        putchar( ' ' );
  [ \t]+$       /* ignore this token */

模式部分是正則表達式,動作部分是處理方法,動作部分如果時{開頭,那么,動作將會持續(xù)到},如果動作中出現(xiàn)了括號{},開始采用 %{ %}來表示動作去區(qū)段。動作部分如果時 |,就表示與下一條規(guī)則執(zhí)行相同的動作。
好的,我們來看一個更為實用一點的lex程序。
我們先定義三個動作:
ECHO 將yytext輸出
BEGIN 開始一個條件處理塊
REJECT 指示簡析器對當前規(guī)則不做處理,而是采用第二匹配規(guī)則。
  int word_count = 0;
  %%
  frob        special(); REJECT;
  [^ \t\n]+   ++word_count;
如果frob沒有REJECT動作,frob將不會被計數(shù),因為解析器在通常情況下,每個被匹配的對象只會對一個動作生效,多個REJECT也是允許的,會尋找下一個最配的規(guī)則來做處理。所以,下面的規(guī)則會把輸入的"abcd"處理后輸出"abcdabcaba".
  %%
  a        |
  ab       |
  abc      |
  abcd     ECHO; REJECT;
  .|\n     /* eat up any unmatched character */

`yymore()' 告訴解析器下一次匹配的規(guī)則,滿足的部分將會添加到當前yytext值得后面而不是替換它。 例如,指定的輸入"mega-kludge"經(jīng)過下面的程序處理后將會輸出"mega-mega-kludge"。
  %%
  mega-    ECHO; yymore();
  kludge   ECHO;
第一個 "mega-" 被滿足并且輸出. 然后 "kludge" 滿足, 但是并沒有替換之前的"mega-"而是"kludge"附加到他的后面,然后輸出的其實是"mega-kludge".
yymore()需要兩件事情需要注意。第一,yymnore()依賴于表現(xiàn)當前匹配項的長度yyleng的值,所以使用yymore不允許改變yyleng的值。第二,yymore()的使用會使解析器付出一點點性能的代價。
有yymore()就有yyless()
yyless(n) 返回當前匹配項除了開始的n個字符內(nèi)的所有的內(nèi)容到輸入緩存區(qū),解析器處理下一個匹配時,它們將會被重新解析。yyless將會導(dǎo)致yytext與yyleng的調(diào)整。(yyleng將會等于=n) 如輸入"foobar"被下面的程序處理后,將會輸出"boobarbar". 因為前n=3個字符foo外的字符bar被重新返回到輸入緩存區(qū)了。
  %%
  foobar    ECHO; yyless(3);
  [a-z]+    ECHO;
參數(shù)0對于yyless將會導(dǎo)致整個當前匹配將會被重新解析。除非你改變了解析器本來的處理流程(如使用begin),這將會導(dǎo)致循環(huán)結(jié)束。需要注意的是,yyless是一個宏,并且在flex輸入文件中使用,不能在其他源文件中使用。
unput(c) 將字符c放回到輸入流中,該字符可以重新被解析。下面的動作將當前的匹配值附上括號后重新進行匹配。
 {
  int i;
  /* Copy yytext because unput() trashes yytext */
  char *yycopy = strdup( yytext );
  unput( ')' );
  for ( i = yyleng - 1; i >= 0; --i )
    unput( yycopy[i] );
  unput( '(' );
  free( yycopy );
 }
注意: 由于每次unput()將指定的字符添加到輸入源的開頭,所以將字符串添加到輸入源開頭必須從后道前處理。一個比較重要的潛在問題是使用unput()的時候,如果采用了%pointer指針模式保存yytext,unput會破壞yytext的內(nèi)容,從最右邊的字符開始將會破壞左邊的一個字符。如果在unput()后要用到y(tǒng)ytext,你首先必須復(fù)制一份yytext,或者用%array模式來保存yytext. 最后你不能放一個EOF去試圖標志輸入流的結(jié)束。
input 從輸入源中讀取下一個字符。例如,下面有的例子將會吃掉C語言注釋

  %%
  "/*"        {
            register int c;
            for ( ; ; )
                {
                while ( (c = input()) != '*' &&
                        c != EOF )
                    ;    /* eat up text of comment */
                if ( c == '*' )
                    {
                    while ( (c = input()) == '*' )
                        ;
                    if ( c == '/' )
                        break;    /* found the end */
                    }
                if ( c == EOF )
                    {
                    error( "EOF in comment" );
                    break;
                    }
                }
            }

注意: 如果簡析器采用用C++編譯,input()被yyinput()的替代,因為input()與C++中的流名稱input沖突。
YY_FLUSH_BUFFER 刷新解析器內(nèi)部緩存以便于下一次的匹配工作,首先它會使用YY_INPUT填充緩存區(qū)。這是通用yy_flush_buffer()的一個特例,將會在多輸入緩存中描述。
yyterminate()可以在動作內(nèi)部返回描述區(qū)域中使用,它將終止解析器并返回0給解析器調(diào)用者,表示操作完成。缺省情況下,到達文件結(jié)束位置也會被調(diào)用,它是一個宏,并且可能重定義。


Lex進階
模式
模式在第一階段或第二個階段使用,也就是在申明或規(guī)則階段中出現(xiàn),模式定義了匹配的目標,目標被匹配后將會執(zhí)行動作。
對于模式不想做太多說明,使用正則表達式定義,可以參看 regex 或 pcre.

開始條件
lex提供了根據(jù)條件激活規(guī)則的機制。在<sc>前綴的規(guī)則將會在解析器在"sc"的開始條件下被匹配。

<STRING>[^"]*        { /* eat up the string body ... */            ...            }
將會在啟動條件"STRING"的情況下被激活。

<INITIAL,STRING,QUOTE>\.        { /* handle an escape ... */            ...            }
將會在 "INITIAL", "STRING", "QUOTE"三者之一的條件下被激活。

開始條件在輸入源的定義(第一)部分被申明,在‘%s' 或 ’%x'后跟隨著名字列表。 %s申明了包含的開始條件,%x申明了排他的開始條件。開始條件被BEGIN動作激活。直到下一個BEGIN動作,滿足開始條件名稱的規(guī)則將會被規(guī)則,不滿足啟動條件的規(guī)則將不會被執(zhí)行。


如果是包含條件,沒有開始條件的規(guī)則也會被激活執(zhí)行,如果時排他條件,只有滿足開始條件的規(guī)則才會被執(zhí)行。

具有相同排他條件的規(guī)則的集合可以使解析器獨立于其他的規(guī)則。因此,排他條件可以容易地創(chuàng)建微型解析器處理輸入源中的獨立與其他部分的一部分(如,注釋)。如果對于包含與排他條件還有混淆,可以看下面的例子。

%s example%%<example>foo   do_something();bar            something_else();
等同于


%x example%%<example>foo   do_something();<INITIAL,example>bar    something_else();
上面的程序中如果沒有<INITIAL,example>,在example條件下bar規(guī)則將永遠不會被激活。如果使用<example>,將會導(dǎo)致只能在exmaple開始條件下激活,而INITIAL條件下不會被激活。而第一個程序中在任何條件下bar都被會激活。因為第一個程序用example時%s,時包含條件。頁可以通過特殊開始條件<*>來配置任何開始條件,上面的程序還可以寫為:

%x example%%<example>foo   do_something();<*>bar    something_else();
缺省規(guī)則(顯示任何未被匹配的字符)在開始條件下仍然生效。等同于:

<*>.|\\n     ECHO;
‘BEGIN(0)’在無開始條件的規(guī)則激活條件下返回原始狀態(tài),這個狀態(tài)同于開始條件下的'INITIAL',所以‘BEGIN(INITIAL)'等同于’BEGIN(0)'。
BEGIN行為在規(guī)則部分的開頭是默認的代碼(BEGIN actions can also be given as indented code at the beginning of the rules section.請翻譯)例如,下面的代碼將會僅需SPECIAL開始條件,不管合適yylex()被調(diào)用并且全局變量enter_special是true。

        int enter_special;%x SPECIAL%%        if ( enter_special )            BEGIN(SPECIAL);<SPECIAL>blahblahblah...more rules follow...
為了說明開始條件,我們用兩種方法處理"123.456".缺省將會被解析為 '123','.','456'三個標記,如果expect-floats后面將會被解析為浮點數(shù) 123.456

%{#include <math.h>%}%s expect%%expect-floats        BEGIN(expect);<expect>[0-9]+"."[0-9]+      {            printf( "found a float, = %f\n",                    atof( yytext ) );            }<expect>\n           {            /* that's the end of the line, so             * we need another "expect-number"             * before we'll recognize any more             * numbers             */            BEGIN(INITIAL);            }[0-9]+      {            printf( "found an integer, = %d\n",                    atoi( yytext ) );            }"."         printf( "found a dot\n" );
下面的代碼能夠是被C語言注釋并且統(tǒng)計行數(shù)。

%x comment%%        int line_num = 1;"/*"         BEGIN(comment);<comment>[^*\n]*        /* eat anything that's not a '*' */<comment>"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */<comment>\n             ++line_num;<comment>"*"+"/"        BEGIN(INITIAL);
實際上,編寫高速解析程序的辦法時在每個規(guī)則中做盡可能多的匹配。

This scanner goes to a bit of trouble to match as much text as possible with each rule. In general, when attempting to write a high-speed scanner try to match as much possible in each rule, as it's a big win.

注意: 開始條件的名字實際上時一個整形值并且能夠被保存,所以,上面的代碼可以擴展為:

%x comment foo%%        int line_num = 1;        int comment_caller;"/*"         {             comment_caller = INITIAL;             BEGIN(comment);             }...<foo>"/*"    {             comment_caller = foo;             BEGIN(comment);             }<comment>[^*\n]*        /* eat anything that's not a '*' */<comment>"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */<comment>\n             ++line_num;<comment>"*"+"/"        BEGIN(comment_caller);
而且,可能易使用YY_START宏來訪問當前的開始條件。如上面的賦值條件可以改寫為

comment_caller = YY_START

YYSTATE是YY_START的別名(因為AT&T lex使用了YYSTATE)。
注意 開始條件沒有他們的名字空間; %s 與 %x 申明與 #define形式一樣。

到這里,時一個使用排他開始條件如何匹配C風(fēng)格的引用字符串的處理。包含的擴展的轉(zhuǎn)義,但不包括檢查,因為代碼太長。

%x str%%        char string_buf[MAX_STR_CONST];        char *string_buf_ptr;\"      string_buf_ptr = string_buf; BEGIN(str);<str>\"        { /* saw closing quote - all done */        BEGIN(INITIAL);        *string_buf_ptr = '\0';        /* return string constant token type and         * value to parser         */        }<str>\n        {        /* error - unterminated string constant */        /* generate error message */        }<str>\\[0-7]{1,3} {        /* octal escape sequence */        int result;        (void) sscanf( yytext + 1, "%o", &result );        if ( result > 0xff )                /* error, constant is out-of-bounds */        *string_buf_ptr++ = result;        }<str>\\[0-9]+ {        /* generate error - bad escape sequence; something         * like '\48' or '\0777777'         */        }<str>\\n  *string_buf_ptr++ = '\n';<str>\\t  *string_buf_ptr++ = '\t';<str>\\r  *string_buf_ptr++ = '\r';<str>\\b  *string_buf_ptr++ = '\b';<str>\\f  *string_buf_ptr++ = '\f';<str>\\(.|\n)  *string_buf_ptr++ = yytext[1];<str>[^\\\n\"]+        {        char *yptr = yytext;        while ( *yptr )                *string_buf_ptr++ = *yptr++;        }
通常,如上面的例子中所看到你,會有許多相同開始條件的處理。開始條件范圍可以簡化重復(fù)操作。


<SCs>{}
SCs 是一個或開始條件的列表。在這個開始條件范圍內(nèi),每個規(guī)則將會自動具有前綴 `<SCs>' 直到 `}' 與開始的 `{' 匹配.  例如

<ESC>{    "\\n"   return '\n';    "\\r"   return '\r';    "\\f"   return '\f';    "\\0"   return '\0';}
等價于

<ESC>"\\n"  return '\n';<ESC>"\\r"  return '\r';<ESC>"\\f"  return '\f';<ESC>"\\0"  return '\0';
開始條件頁可以嵌套,下面時三個管理開始條件堆棧的參數(shù)。

`void yy_push_state(int new_state)'
將當前的開始條件壓棧,切換到 new_state 與使用 `BEGIN new_state'類似。
`void yy_pop_state()'
從棧頂彈出,類似于 BEGIN.
`int yy_top_state()'
返回棧頂值,不改變棧內(nèi)容。
開始條件棧動態(tài)增長,沒有固定限制,如果內(nèi)容用盡,程序竟會終止。

為了使用開始條件棧,需要使用 `%option stack' 指令。

 


多輸入緩存區(qū)

 

一些允許include文件解析器的解析器要求從幾個輸入流中讀取內(nèi)容。YY_INPUT只在結(jié)束緩存時被調(diào)用,碰到 include 后需要切換輸入源,而解析一個描述也許需要很長時間。為了解決此類問題,解析器提供了創(chuàng)建并在多個輸入緩存中創(chuàng)建的機制。輸入緩存可以通過下面的方式創(chuàng)建:

YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )


參數(shù)為與緩存關(guān)聯(lián)的輸入文件指針,以及足夠的可維持size字符(如果不確定,size可以使用YY_BUF_SIZE)。返回一個YY_BUFFER_STATE,可以傳遞到其他的處理過程。YY_BUFFER_STATE是一個不可見結(jié)構(gòu)yy_buffer_state的指針,所以可以安全地使用`((YY_BUFFER_STATE) 0)'來初始化YY_BUFFER_STATE,如果你愿意,你可以在解析器之外的源程序中引用這個不透明結(jié)構(gòu)來正確的申明輸入緩存。可以通過下面的參數(shù)來選擇一個緩存區(qū)。

void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )


切換解析器的輸入緩存將會導(dǎo)致記接下來的匹配項來自于新的緩存中。yy_switch_to_buffer可能出現(xiàn)在yywrap中為繼續(xù)解析做準備,替換打開一個新的文件并執(zhí)行yyin. 通過yy_switch_to_buffer 或 yywrap切換輸入源不改變開始條件。

 

void yy_delete_buffer( YY_BUFFER_STATE buffer )


用于收回與緩存關(guān)聯(lián)的空間。你可以使用下面的函數(shù)清空當前內(nèi)容:

void yy_flush_buffer( YY_BUFFER_STATE buffer )


此函數(shù)廢棄緩存內(nèi)容,下一個解析器試圖匹配一個內(nèi)容時將會使用YY_INPUT來更新緩存區(qū)。

`yy_new_buffer()' 是 `yy_create_buffer()' 的一個別名,用于提供C++使用new 與 delete操作創(chuàng)建與銷毀動態(tài)對象的兼容性。

最后, YY_CURRENT_BUFFER 宏返回 YY_BUFFER_STATE 指針,表示當前的緩存。

這里是一個擴展include使用的一個解析器 (`<<EOF>>' 特性將會在以后討論):


/* "incl" 狀態(tài)用于獲取include的文件名 */
%x incl

%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}

%%
include             BEGIN(incl);

[a-z]+              ECHO;
[^a-z\n]*\n?        ECHO;

<incl>[ \t]*      /* eat the whitespace */
<incl>[^ \t\n]+   { /* got the include file name */
        if ( include_stack_ptr >= MAX_INCLUDE_DEPTH )
            {
            fprintf( stderr, "Includes nested too deeply" );
            exit( 1 );
            }

        include_stack[include_stack_ptr++] =
            YY_CURRENT_BUFFER;

        yyin = fopen( yytext, "r" );

        if ( ! yyin )
            error( ... );

        yy_switch_to_buffer(
            yy_create_buffer( yyin, YY_BUF_SIZE ) );

        BEGIN(INITIAL);
        }

<<EOF>> {
        if ( --include_stack_ptr < 0 )
            {
            yyterminate();
            }

        else
            {
            yy_delete_buffer( YY_CURRENT_BUFFER );
            yy_switch_to_buffer(
                 include_stack[include_stack_ptr] );
            }
        }


提供三個過程來實現(xiàn)內(nèi)存字符串而不是文件輸入緩存的解析。它們都要創(chuàng)建一個輸入緩存來解析字符串,并且返回YY_BUFFER_STATE (可以在完成解析后用 `yy_delete_buffer()' 刪除).,也可以通過`yy_switch_to_buffer()'來切換, 下一次調(diào)用`yylex()' 將會解析字符串。

`yy_scan_string(const char *str)'                             解析0結(jié)尾字符串。
`yy_scan_bytes(const char *bytes, int len)'               解析bytes開始的len個字符(可能包含 0 字符)


注意,上面的兩個函數(shù)會創(chuàng)建字符串或字節(jié)串的副本。(這也許時期望的,因為`yylex()' 會修改被解析緩存的內(nèi)容)  可以使用下面的方式來拒絕使用副本:

`yy_scan_buffer(char *base, yy_size_t size)'

將會從base開始解析,包含size個字節(jié), 最后的兩個字節(jié)必須是 YY_END_OF_BUFFER_CHAR (ASCII NUL)。他們不會被解析, 解析范圍從 `base[0]'  到 `base[size-2]'(包含)。如果你沒能按照這種規(guī)定使用base(如,忘記了最后的兩個YY_END_OF_BUFFER_CHAR字節(jié)), `yy_scan_buffer()' 將會返回空指針而不創(chuàng)建YY_BUFFER_STATE。yy_size_t類型是個整型,可以轉(zhuǎn)化為整數(shù)來反映buffer的長度。

 


文件結(jié)束規(guī)則


特殊規(guī)則 "<<EOF>>" 只是規(guī)則在文件結(jié)束位置發(fā)生且yywrap()返回非0值。(如,沒有更多的文件要處理). 這個動作必須完成下面四件事情之一:

賦值給yyin一個新的文件 (早期版本的flex, 此操作后必須調(diào)用特殊動作 YY_NEW_FILE; 這個操作已經(jīng)不需要了);
執(zhí)行一個返回申明;
執(zhí)行一個特殊的`yyterminate()' 動作;
或者使用`yy_switch_to_buffer()' 切換到一個新的輸入緩存區(qū).


<<EOF>> 不能與其他模式一起使用;它也許僅在開始條件列表申明。如果指定了不合法 <<EOF>> 規(guī)則, 它將會應(yīng)用到所有的開始條件而不僅是 <<EOF>> 動作. 指定 <<EOF>> 規(guī)則僅在 initial 開始條件下匹配,就是用:

<INITIAL><<EOF>>


下面的規(guī)則可以發(fā)現(xiàn)象不關(guān)閉的注釋類的問題。

%x quote
%%

...other rules for dealing with quotes...

<quote><<EOF>>   {
         error( "unterminated quote" );
         yyterminate();
         }
<<EOF>>  {
         if ( *++filelist )
             yyin = fopen( *filelist, "r" );
         else
            yyterminate();
         }