JLex specification은 %%로 구분된 세 section으로 구성된다. 형식은 다음과 같다
사용자 코드 %% JLex 지시문 %% 정규식 rules |
specification 파일의 첫 section인 사용자 코드 section은 출력 파일에 그대로 복사되는 부분이다. 이 영역은 유틸리티 클래스나 리턴 type을 기술할 수 있도록 제공되는 공간이다.
두 번 째인 JLex 지시문 section에서는, 매크로가 정의되고 상태 이름이 선언된다.
세 번째 section에서는 lexical analyzer의 rule이 정의된다. : 각 rule은 option 상태 list, 정규식, action의 세 부분으로 구성된다.
사용자 코드는 첫 번째 %%의 앞부분에 해당되며 이곳의 내용은, JLex의 출력물인 lexical analyzer 소스코드의 처음에 그대로 복사된다. 따라서, lexer 소스가 package 선언이나 중요 외부 클래스를 필요로 하는 경우, 사용자 코드 section 첫 부분에 선언할 수 있다.
JLex 지시문 section은 첫 번째 %%에서부터 두 번째 %%까지 계속된다. 각 JLex 지시문은 줄의 처음부터 시작되어 그 줄에서 안에서 끝나야 한다.
%{...%} 지시문안에는 lexical analyzer 클래스에 복사될 자바 코드를 작성할 수 있다. 형식은 다음과 같다.
%{ <code> %} |
class Yylex { ... <code> ... } |
%init{ ... %init} 지시문안에는 lexical analyzer 클래스의 생성자에 복사될 자바 코드를 작성할 수 있다.
%init{ <code> %init} |
class YYlex { Yylex( ) { ... <code> ... } } |
%init{ ... %init} 지시문안에서는 exception을 throw하거나 다른 함수로부터의 것을 전파시킬 수 있다. 이들 exception을 선언하려면 %initthrow{ ... %initthrow} 지시문을 사용하면 된다.
%initthrow{ <exception[1]>[,<exception[1]>,...] %initthrow} |
Yylex() throws <exception[1]>[,<exception[1]>,...] { ... <code> ... } |
%eof{ ... %eof} 지시문안에는 lexical analyzer 클래스가 end-of-file을 만난 이후에 수행할 자바 코드를 작성할 수 있다.
%eof{ <code> %eof} |
%eof{ ... %eof} 지시문안의 code들은 exception을 throw하거나 다른 함수로부터의 것을 전파시킬 수 있다. 이들 exception을 선언하려면 %eofthrow{ ... %eofthrow} 지시문을 사용하면 된다.
%eofthrow{ <exception[1]>[,<exception[1]>,...] %eofthrow} |
private void yy_do_eof() throws <exception[1]>[,<exception[1]>,...] { ... <code> ... } |
매크로 정의는 specification 파일의 JLex 지시문 section에서 이루어진다. 각각의 매크로 정의문은 매크로 이름, =, 정의부가 들어간 하나의 줄로 작성된다. 다음과 같이 형식을 요약할 수 있다.
<name>=<definition> |
매크로 이름은 알파벳이나 밑줄로 시작하여 알파벳, 아라비아 숫자, 밑줄의 나열이 따라오는 형태의 적법한 identifier여야 한다.
매크로 정의문은 적법한 정규식이어야 한다. 자세한 내용은 아래 section을 참고하라.
매크로 정의문은 정규식 중간에 {<name>}과 같은 형식으로 다른 매크로 정의가 포함되는 것을 허용한다. 하지만, 함수나 nonterminal과 같이 상호 재귀적으로 호출되는 것을 허용하지 않는 '매크로'라는 것을 명심해야 한다. 매크로 정의문에서 발생하는 순환 문제는 예상치 못한 결과를 발생시킬 수 있다.
Lexical 상태들은 주어진 입력에 어떤 정규식을 적용해야 할지를 판단할 때 사용된다. 다음과 같은 형태로 JLex 지시문안에 선언된다.
%상태 state[0][,state[1],state[2],...] |
상태명은 알파벳이나 밑줄로 시작하여 알파벳, 아라비아 숫자, 밑줄의 나열이 따라오는 형태의 적법한 identifier여야 한다.
JLex에 의해 하나의 lexical 상태가 암시적으로 선언된다. 생성된 lexical analyzer가 시작될 때 YYINITIAL이라 불리는 상태를 갖는다.
lexical analyze의 규칙들은 optional 상태 리스트로 시작된다. 상태 list가 주어진 경우 lexical analyzer가 명시된 상태 중의 하나일 경우에만 lexical 규칙이 적용된다. 상태 list가 없는 경우 lexical 규칙은 현재의 lexical analyzer의 상태에 무관하게 적용된다.
JLex specification이 상태도 선언하지 않고 lexical 규칙 앞에 상태 list를 두지도 않는 경우, 생성되는 lexer의 상태는 YYINITIAL로 계속 유지된다. Lexical 규칙 앞에 상태 list가 없기 때문에, 암시적으로 선언되는 YYINITAIL을 포함한 모든 상태에 대해 규칙이 적용된다. 따라서, specification에서 상태가 전혀 사용되지 않은 경우라도 예상치 못한 결과가 발생하진 않는다.
State들은 생성될 lexical analyzer 클래스안에서 상수로 선언된다. 선언된 상태에 대응되는 상수는 상태명과 같은 이름을 갖는다. 따라서, 규칙의 action 부분에 선언된 변수명이 상태명과 중첩되는 것을 조심해야 한다. 관례적으로 상태명을 전부 대문자로 하여 상수라는 것을 상기시키게끔 한다.
문자수 세기 기능은 default로 꺼져있지만, %char 지시문를 사용하여 동작시킬 수 있다.
%char |
줄번호 세기는 default로 꺼져있지만, %line 지시문를 사용하여 동작시킬 수 있다.
%line |
Java CUP은 Georgia Tech University의 Scott Hudson이 처음 만들기 시작하여, Frank Flannery, Dan Wang, C.Scott Ananian에 의해 관리, 확장된 parser generator이다. 자세한 설명은 다음 URL에서 찾아볼 수 있다. http://www.cs.princeton.edu/~appel/modern/java/CUP/. Java CUP과의 호환기능은 default로 꺼져있지만, 다음과 같은 JLex 지시문를 사용하여 동작시킬 수 있다.
%cup |
%implements java_cup.runtime.Scanner %function next_token %type java_cup.runtime.Symbol |
여기서 설명할 지시문들을 사용하여 생성될 lexical analyzer 클래스, 토큰화 함수, 토큰 리턴형을 바꿀 수 있다. Lexical analyzer 클래스의 이름을 Yylex으로부터 변경할 때는 %class 지시문을 사용한다.
%class <name> |
%function <name> |
%type <name> |
Scoping 충돌을 피하기 위해 lexical analyzer의 함수와 변수에 예약된 이름들은 yy로 시작한다.
32-bit primitive 정수형인 int를 토큰화 함수의 리턴형으로 하기 위하여 %integer 지시문을 사용한다.
%integer |
class Yylex { ... public Yytoken yylex() { ... } |
class Yylex { ... public int yylex() { ... } |
{ ... return 7; ... } |
integer 리턴형을 사용하려면 end of file 코드도 바꿔줘야 한다. 기본값으론, java.lang.Object 클래스나 자식 클래스의 인스턴스들이 Yylex.yylex()로부터 리턴된다. 생성된 Yylex lexer가 수행되는 과정 중에서 end-of-file을 만나게 되면 Yylex.yylex()로부터 null값이 리턴되어야 한다.
Yylex.yylex()의 리턴 값이 int로 바뀌면, null이 더 이상 리턴될 수 없다. 대신, -1의 값을 갖는 Yylex.YYEOF 상수가 리턴된다. %integer 지시문에는 %yyeof가 내포되어 있다
java.lang.Integer를 토큰화 함수의 리턴형으로 하기 위하여 %intwrap 지시문을 사용한다.
%intwrap |
class Yylex { ... public Yytoken yylex() { ... } |
class Yylex { ... public java.lang.Integer yylex() { ... } |
{ ... return new java.lang.Integer(0); ... } |
%intwrap 지시문은 다음과 같이 %type 지시문을 사용한 것과 동일한 효과를 갖는다.
%type java.lang.Integer |
%yyeof 지시문은 Yylex.YYEOF 상수를 선언하게 한다. %integer 지시문이 선언된 경우, Yylex.YYEOF가 end-of-file에서 리턴된다.
%yyeof |
public final int YYEOF = -1; |
UNIX 운영체제에서는 개행문자로 '\n' 단일 character를 사용한다. 반면, DOS 기반 운영체제에서는 "\r\n"(carriage return + newline)이 개행문자로 사용된다. %notunix 지시문은 carriage return이나 newline이 개행문자로 인식되도록 한다.
%notunix |
Default로는 문자 code 중 0에서 127사이를 사용한다. 이 범위를 벗어나는 문자가 들어오면 lexer가 제대로 동작을 하지 못한다.
%full 지시문은 범위를 8 bit으로 나타낼 수 있는 모든 값으로 확장한다.
%full |
%unicode 지시문은 범위를 16 bit으로 나타낼 수 있는 모든 유니코드로 확장한다.
%unicode |
%ignorecase 지시문은 대소문자 구별을 하지 않는 lexer를 생성하게 한다.
%ignorecase |
현재 JLex 버젼에서 생성되는 lexical analyzer에서는 한 문자 당 한 바이트가 할당되는 아스키 형식의 텍스트 파일만을 지원한다. 하지만, 앞으로의 확장성을 위하여 비록 16 비트의 전체 표현 범위를 모두 지원하지는 않지만, 모든 JLex의 내부 문자 처리에 16 비트 자바 문자형을 사용하고 있다.
JLex의 3번째 section에 해당되는 정규식 규칙의 action부 코드들은 exception을 throw하거나 다른 함수로부터의 것을 전파시킬 수 있다. 이들 exception을 선언하려면 %yylexthrow{ ... %yylexthrow} 지시문을 사용하면 된다.
%yylexthrow{ <exception[1]>[,<exception[1]>,...] %yylexthrow} |
public Yytoken yylex() throws <exception[1]>[,<exception[1]>,...] { ... } |
%eofval{ ... %eofval} 지시문은 end-of-file의 리턴값을 명시하는 역할을 한다. 이 지시문은 lexical analyzer가 end-of-file을 만났을 때 수행되는 Yylex.yylex()안에 넣을 코드를 작성할 수 있게 해준다. 이 코드에서는 토큰화 함수 Yylex.yylex()의 리턴 타입과 일치하는 값을 리턴해야 한다.
%eofval{ <code> %eofval} |
다음은 %eofval{ ... %eofval} 지시문의 사용 예이다. End-of-file의 리턴 값으로 기본값인 null이 아닌 (new token(sym.EOF))를 원하는 경우 다음과 같이 specification 파일에 추가하면 된다.
%eofval{ return (new token(sym.EOF)); %eofval} |
public Yytoken yylex(){ ... return (new token(sym.EOF)); ... } |
JLex에서는 Yylex 클래스가 특정 interface를 implement하게 할 수 있다. 다음과 같은 선언문을 추가하면 된다.
%implements <classname> |
class Yylex implements classname { ... |
%public 지시문을 선언하면 JLex가 lexical analyzer 클래스를 public 클래스로 만든다.
%public |
JLex specification 파일의 세 번째 부분에는 입력 스트림을 토큰으로 분해하는 규칙들을 열거한다. 이들 규칙들은 정규식과 이에 연결된 action을 설명하는 자바 코드로 구성되어 있다.
하나의 규칙은 Optional 상태 list, 정규식, action의 세 부분으로 나누어지며, 다음과 같은 형식을 갖는다.
[<상태s>]<정규식>{<action>} |
입력 문자열이 하나 이상의 규칙과 일치하는 경우, lexer는 길이가 가장 긴 문자열과 일치하는 규칙을 선택한다. 그래도, 길이가 같은 문자열이 여러 개인 경우 JLex specification에 먼저 명시된 규칙을 선택한다. 결국, specification에서 더 앞에 있는 규칙들이 더 높은 우선권을 갖는다고 볼 수 있다.
JLex specification 파일에 선언된 규칙들은 가능한 모든 입력에 적용될 수 있어야 한다. 만약, lexical anaylzer가 이들 규칙에 적용되지 못하는 입력을 받는 경우 에러가 발생한다.
결국, 모든 입력들은 적어도 하나의 규칙에 적용될 수 있어야 하며, 이것은 다음과 같은 규칙을 제일 마지막에 추가하는 방법으로 보장할 수 있다.
. { java.lang.System.out.println("Unmatched input:" + yytext()); } |
Optional lexical 상태 리스트는 규칙의 앞 부분에 위치하며, 다음과 같은 형식을 따른다.
<상태[0][,state[1],stateg[2],...]> |
예를 들어, yylex()가 상태 A일때 호출된 경우, lexer는 상태 list에 A가 포함된 규칙들만을 적용시킬 수 있다.
상태 list가 없는 것은, 모든 상태에서 해당 규칙이 적용 가능함을 나타낸다.
White space는 정규식의 종료로 해석되기 때문에 정규식 중에 white space가 포함되면 안된다. 한가지 예외로 개행문자를 제외한 white space는 따옴표로 둘러쌈으로써 자기 자신으로 표현될 수 있다. 예를 들어, " "는 black space로 해석된다.
JLex는 정규식에 0~127사이의 Ascii code를 사용한다.
다음 문자들은 JLex의 정규식에서 특별한 의미를 가지는 metacharacter들이다.
? * + | ( ) ^ $ . [ ] { } " \
다른 문자들은 자기 자신을 나타낸다.
ef 연속된 정규식은 그들의 연쇄를 나타낸다.
e|f 수직 바(|)는 양쪽의 정규식을 선택할 수 있음을 나타내므로, e 또는 f를 뜻한다.
Backslash 뒤에 따라오는 문자열은 다음과 같이 해석된다.
\b Backspace
\n newline
\t Tab
\f Formfeed
\r Carriage return
\ddd 3자리 8진수 숫자에 해당하는 character code
\xdd 2자리 16진수 숫자에 해당하는 character code
\udddd 4자리 16진수 숫자에 해당하는 Unicode character code
\^C Control character
\c 이외의 다른 문자가 backslash 뒤에 오는 경우 그 문자 자신을 나타낸다
$ 달라 기호는 줄의 마지막을 의미한다. 정규식의 마지막에 달라 기호가 있는 경우 그 정규식은 줄의 마지막에서만 적용됨을 의미한다.
. 개행문자를 제외한 모든 문자. [^\n]과 동일하다.
"..." 따옴표 안에 metacharacter가 있는 경우, 원래 의미를 잃고 자기 자신을 표현한다. \"는 예외적으로 따옴표 문자 "를 가리킨다.
{name} 매크로 expansion.
* Kleen closure로 앞의 정규식의 0번 또는 그 이상의 반복.
+ 앞의 정규식의 한번 이상의 반복. 따라서, e+는 ee*와 동일하다
? 앞의 정규식의 0번 또는 한번의 반복.
(...) 정규식을 묶는다.
[...] 대괄호는 문자 집합을 나타내며, 집합에 포함된 어느 하나의 문자에 대해 정규식이 적용된다. 대괄호 안의 첫 문자가 ^인 경우 여집합이 되어 대괄호 안에 포함된 것들 이외의 문자에 대해 정규식이 적용된다. 대괄호 안에서는 다음과 같은 다른 metacharacter 규칙이 적용된다.
{name} 매크로 expansion
"..." 따옴표 안에 metacharacter가 있는 경우, 원래 의미를 잃고 자기 자신을 표현한다. \"는 예외적으로 따옴표 문자 "를 가리킨다.
\ backslash뒤의 metacharacter는 원래의 특별한 의미를 잃는다.
- 대괄호 안의 첫 글자나 끝 글자가 -인 경우 -는 원래의 특별한 의미를 잃는다.
Lexical 규칙에 연관된 Action은 중괄호로 묶여진 자바 코드로 작성한다.
{ action } |
action 부분의 문자열이나 주석을 제외한 부분에 있는 열고 닫는 중괄호들의 숫자는 일치해야 한다.
Action에서 아무런 값도 리턴되지 않는 경우, lexical analyzer는 입력 스트림으로부터 다음 토큰을 찾고 리턴할 때까지 루프를 돈다.
다음과 같이 하여 yylex를 명시적으로 재귀호출 할 수 있다.
{ ... return yylex(); ... } |
앞의 코드는 호출하는 함수의 끝에서 재귀호출이 일어나기 때문에 tail recursion이다. 아래 코드는 tail recursion이 아닌 재귀호출의 예이다.
{ ... next = yylex(); ... } |
JLex 지시문 section에 lexical 상태들이 선언된 경우, 정규식 action에 상태 전환을 다음과 같은 형태로 지시할 수 있다.
yybegin(상태); |
yybegin의 parameter는 JLex 지시문 section에서 선언되어야 하며, 그렇지 않은 경우 생성된 소스의 컴파일 과정에서 에러가 발생한다. 예외적으로 YYINITIAL은 암시적으로 JLex에 의해 선언되어 있기 때문에 추가의 선언문이 필요 없다. 생성된 lexer는 YYINITIAL 상태로 부터 시작되며 상태 전환이 있기 전까지 이 상태에 머문다.
아래의 변수들은 Yylex 클래스에 내부적으로 선언되는 것들로 lexical 규칙의 action 부에서 사용 가능하다.