470 likes | 1.17k Views
구문분석기 생성기 YACC. 문법표현 + C 코드. Yacc. 원시 프로그램. 어휘분석기. y.tab.c. C 코드의 실행결과. Yacc. Yacc Yet Another Compiler Compiler 1975 년 , Johnson LALR(1) 파서 생성기. Lex 와의 결합. 어휘분석기와 구문분석기의 관계 구문분석기 : 토큰을 요구 Yacc: yylex() 를 호출 어휘분석기 : 입력 프로그램을 잘라서 토큰으로 전달 Lex: 토큰 값을 return UNIX 명령어
E N D
문법표현 + C 코드 Yacc 원시 프로그램 어휘분석기 y.tab.c C 코드의 실행결과 Yacc • Yacc • Yet Another Compiler Compiler • 1975년, Johnson • LALR(1) 파서 생성기
Lex와의 결합 • 어휘분석기와 구문분석기의 관계 • 구문분석기: 토큰을 요구 • Yacc: yylex()를 호출 • 어휘분석기: 입력 프로그램을 잘라서 토큰으로 전달 • Lex: 토큰 값을 return • UNIX 명령어 % vi simple.l simple.y % lex simple.l % yacc -d simple.y % cc -o sim lex.yy.c y.tab.c -ly -ll
Lex Source • 수식문법을 위한 lex source %{ #include "y.tab.h" %} %% [0-9]+ return(NUMBER); [ \t] ; \n return(0); \+ return('+'); \* return('*'); . { printf("'%c': illegal character\n", yytext[0]); exit(-1); }
Yacc의 입력 • 입력의 구성 • 선언 부분 • y.tab.c에서 사용할 자료구조, 변수, 상수를 정의 • 생성규칙 부분에서 사용되는 토큰의 이름을 정의 • 생성규칙 부분 • 문법의 생성규칙을 기술 • 생성규칙이 reduce될 때 처리할 행위(C 코드)를 기술 선언 부분 %% 생성규칙 부분 %% 사용자 부프로그램 부분
선언 부분 • 형태 • 정의 예 %token NUMBER %{ /* y.tab.c에 복사될 내용 */ %} %token 토큰이름1 토큰이름2 ... %start 시작문법기호 %left 모호한 문법에서 사용 %right ' ' %nonassoc ' '
생성규칙 부분 • 형태 • ::= 기호 대신에 : 기호를 사용 • 문자토큰은 '와 '사이에 둠 (대소문자 구별은 없음) • 생성규칙의 끝에는 ; 기호를 첨가 • 정의 예 %% Exp : Exp '+' Term { printf("rule 1\n"); } | Term { printf("rule 2\n"); } ; %% 생성규칙1 C 코드1 생성규칙2 C 코드 2 ... 생성규칙n C 코드 n
Yacc 입력의 예 • 수식문법의 예 %token NUMBER %% Exp : Exp '+' Term { printf("rule 1\n"); } | Term { printf("rule 2\n"); } ; Term : Term '*' Num { printf("rule 3\n"); } | Num { printf("rule 4\n"); } ; Num : NUMBER { printf("rule 5\n"); } ;
ToyPL 문법 검사기 • 문제: ToyPL로 작성된 어떤 프로그램이 문법에 적합한지 검사하자. • 입력: ToyPL로 작성되었다고 여겨지는 어떤 프로그램 • 출력: 문법에 맞지 않으면 “syntax error”를 출력 • 방법: ToyPL 문법을 yacc으로 작성
Yacc 실습 • 괄호문법 S ::= ( S ) S | • 괄호문법을 위한 lex source • 괄호 이외의 문자는 모두 무시함 • 괄호문법을 위한 입력 예 ( a + b ) + ( 4 * ( ( 33 - sum ) / ( sum + 21 ) ) )
Yacc의 상세 설명 • yyerror() • 문법에 맞지 않는 문장에 대한 오류 메세지 • 사용자 부 프로그램 부분에 정의 %% yyerror() { printf("틀린수식입니다\n"); } • yyparse() • lex의 yylex()에 해당 • 응용 예: 파싱이 시작됨과 끝났음을 출력 • yacc -v • 파싱테이블을 y.output에 출력
main() yyparse() yylex() yyerror() Yacc 실행체계 main() { . . . yyparse(); . . . } yyparse() { . . . while (1) { . . . t = yylex(); . . . yyerror(); . . . } . . . } yylex() { . . . return(T); . . . }
구문지향 번역 • 구문지향 번역 (syntax directed translation) • 문법의 각 생성규칙에 대응하여 코드를 생성 • 트리를 만들지 않음 • 비교 • 트리를 사용한 번역에 비하여 간단하다 • 1-pass 번역에만 적용 가능
수식 값의 계산 • 수식 값의 계산 • 1+2*3의 값? • 일반적인 방법 • 파스트리를 만든 후에 값을 계산 • 다른 방법 • Yacc에서 문법 기호의 값을 이용 • 문법 기호의 값 • 생성규칙 A : B C D • 좌측 기호의 값 = $$ • 우측 첫 기호의 값 = $1, 두번째의 값 = $2
Exp Exp Term + Term Num * 3 $$ Term Term Num * 3 $1 $2 $3 Num $$ $1 3 문법 기호의 값 • 1+2*3의 유도 과정 Exp=> Exp + Term (규칙 1) => Exp + Term * Num (규칙 3) => Exp + Term * 3 (규칙 5) • 문법규칙 1. Exp ::= Exp + Term 3. Term ::= Term * Num 5. Num ::= 3
예제 • Yacc의 예1 A : B C D { printf("%d %d %d => %d", $1, $2, $3, $$); } • Yacc의 예2 E : E '+' T { printf("%d + %d = %d\n", $1, $3, $$); } • Yacc의 예3 %token NUMBER %% N : NUMBER { printf("%d\n", $1); $$ = $1; } • 예3의 Lex [0-9]+ { yylval = atoi(yytext); return NUMBER; }
토큰의 값 • 토큰의 값 • yylval을 통하여 파서에게 전달 • 토큰 값의 타입 • yylval의 타입 • YYSTYPE • 여러 타입의 값을 허용하려면 • C: union을 사용 • Yacc: %union을 사용
%union • 토큰 값의 종류 • 정수, 실수, 스트링, … • 여러 타입의 토큰을 파서에게 전달 • Yacc에서 토큰의 타입을 정의 %union { double dval; int vblno; } • 각 토큰의 타입을 지정 %token <vblno> NAME %token <dval> NUMBER
문법 기호의 타입 • 문법 기호의 타입 • %union에서 정의 • %type을 이용하여 지정 • 예 %union { double dval; struct symtab *symp; } %token <symp> NAME %token <dval> NUMBER %type <dval> expression
평가용 실습 • 문제 "입력되는 수식의 값을 계산하는 lex와 yacc의 source을 작성하시오" • 허용하는 연산자: + * - / ( ) • 우선순위: ( ) > * / > + - • 결합순위: 모두 좌측 결합 • 입력 예 (1+2+3)*(2*(51-47)/(1+2*3))
실습용 문법 • 덧셈과 곱셈 식을 생성하는 문법 Exp : Exp ‘+’ Term | Exp ‘-’ Term | Term Term : Term ‘*’ Fact | Term ‘/’ Fact | Fact Fact : ‘(’ Exp ‘)’ | NUMBER
Pretty Printer • Pretty printer란? • 입력된 프로그램을 보기 좋게 출력하는 프로그램 • 입력: 문법에 따라 쓰여진 프로그램 • 출력: 들여쓰기(indentation)된 프로그램 • 예 • UNIX의 cb(C beautifier)
포함된 행동 • 포함된 행동 (embedded action) • 생성규칙의 중간에 있는 행동 • 예 • 기존: A : B C D { printf(); } • 포함: A : B { printf(); } C D • 실제의 의미는? A : B { printf(); } C D ==> A : B xx C D xx : { printf(); }
PP용 입력 문법 • 입력문법 S : If | While | ‘stmt’ If : ‘if’ Cond ‘then’ S While : ‘while’ Cond ‘do’ S ‘end’ Cond : ‘c0’
입력과 출력의 예 • 입력 예 while c0 do while c0 do if c0 then stmt end end • 출력 예 while c0 do while c0 do if c0 then stmt end end
Yacc 입력의 일부 • Yacc 입력의 일부 %{ int i, k; %} %token ii tt ... %% ... If : ii {printf("\n"); for(k=0;k<i;k++) printf(" "); printf("if");} Cond tt {printf("then"); i=i+4;} S {i=i-4;} ; ... %% main() { i = 0; yyparse(); }
ToyPL의 PP • 문제 • ToyPL을 위한 Pretty Printer를 lex와 yacc를 이용하여 작성하시오 • 입력 예 program Sample; proc Fact(n:long) var m:int; begin ... end var a,b: int; begin ... end .
ToyPL의 PP 출력 • 출력 예 program Sample; proc Fact(n: long) var m: int; begin ... end var a,b: int; begin ... end.
파서를 만들 수 없는 문법 • 모호한 문법 %% Exp : Exp '-' Exp | Exp '*' Exp | 'a' ; • yacc에서는… • 항상 shift/reduce conflict가 발생 • yacc에서는 억지로 파서를 만듬
Lookahead가 많이 필요한 문법 • 2개의 미리보기가 필요 %% Exp : ZeroOne '+' '2' | OneTwo '+' '3' ; ZeroOne: '0' | '1' ; OneTwo : '1' | '2' ; • 비교: 미리보기가 1개만 있어도 됨 %% Exp : ZeroOne '2' | OneTwo '3' ; ZeroOne: '0' | '1' ; OneTwo : '1' | '2' ; Yacc은 LALR(1)파서를 생성
수식문법 • 뺄셈과 곱셈 식을 생성하는 문법 1. Exp ::= Exp - Exp 2. | Exp * Exp 3. | Num 4. Num ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
1-2*3의 유도 • 좌측유도 E 1 E - E 3 N - E 4 1 - E 2 1 - E * E 3 1 - N * E 4 1 - 2 * E 3 1 - 2 * N 4 1 - 2 * 3 E 2 E * E 1 E - E * E 3 N - E * E 4 1 - E * E 3 1 - N * E 4 1 - 2 * E 3 1 - 2 * N 4 1 - 2 * 3 • 우측유도 E 1 E - E 2 E - E * E 3 E - E * N 4 E - E * 3 3 E - N * 3 4 E - 2 * 3 3 N - 2 * 3 4 1 - 2 * 3 E 2 E * E 3 E * N 4 E * 3 1 E - E * 3 3 E - N * 3 4 E - 2 * 3 3 N - 2 * 3 4 1 - 2 * 3 12343434
Exp Exp Exp - Exp Exp * Exp Num Exp * Exp Exp - Exp Num 1 Num Num Num Num 3 2 3 1 2 1-2*3의 유도트리
1-2-3의 유도 • 좌측유도 E 1 E - E 3 N - E 4 1 - E 1 1 - E - E 3 1 - N - E 4 1 - 2 - E 3 1 - 2 - N 4 1 - 2 - 3 E 1 E - E 1 E - E - E 3 N - E - E 4 1 - E - E 3 1 - N - E 4 1 - 2 - E 3 1 - 2 - N 4 1 - 2 - 3 • 우측유도 E 1 E - E 1 E - E - E 3 E - E - N 4 E - E - 3 3 E - N - 3 4 E - 2 - 3 3 N - 2 - 3 4 1 - 2 - 3 E 2 E - E 3 E - N 4 E - 3 1 E - E - 3 3 E - N - 3 4 E - 2 - 3 3 N - 2 - 3 4 1 - 2 - 3 11343434
Exp Exp Exp - Exp Exp - Exp Num Exp - Exp Exp - Exp Num 1 Num Num Num Num 3 2 3 1 2 1-2-3의 유도트리
Yacc 실습 • 다음의 yacc 입력으로 파서를 만드시오. %token Num %% E : E ‘-’ E { printf(“rule 1\n”); } | E ‘*’ E { printf(“rule 2\n”); } | N { printf(“rule 3\n”); } ; N : Num { printf(“rule 4\n”); } ; • 다음 입력에 대하여 적용된 생성규칙을 확인하고 파스 트리를 그리시오. 1 - 2 * 3 1 * 2 - 3 1 - 2 - 3
모호한 문법 • 모호한 문법 (ambiguous grammar) • 어떤 문장에 대하여 유도트리가 두개이상 존재할 때 • 수식문법은 모호 • 예: If-문 <Stmt> ::= <AsgnStmt> | <IfStmt> | <WhileStmt> | <ForStmt> | <CallStmt> | <CompStmt> | s <IfStmt> ::= if ( <Cond> ) then <Stmt> | if ( <Cond> ) then <Stmt> else <Stmt> <Cond> ::= c
유도트리 그리기 • 다음 문장에 대한 유도트리를 모두 그리시오. if ( c ) then if ( c ) then s else s • 2개의 유도트리가 존재
모호한 문법의 문제점 • 문장에 대한 여러 해석이 가능 • 수식문법 • 1 - 2 * 3의 값은? • If-문 • 다음의 문장에서 j++가 실행되는 조건은? • if (i < 0) then if (j < 0) then i++ else j++
Exp Exp + Term Term Term * Num Num Num 3 1 2 해결책 • 해결책 • 모호하지 않은 문법으로 변환 • yacc의 모호성 해결 규칙의 사용 • 모호하지 않은 문법으로 변환 Exp ::= Exp + Term | Term Term ::= Term * Num | Num Num ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Yacc의 모호성 해결 • yacc에서 conflict 해결 • 사용자가 명시하지 않을 경우=> 기본(default) 규칙 • 사용자가 명시=> 사용자가 우선순위/결합순위를 명시 • 기본 규칙 • shift/reduce conflict: shift 우선 • reduce/reduce conflict: 문법 상에서 먼저 나타나는 규칙을 reduce
우선순위 • 사용자가 순서를 명시 • 지정 위치: 정의 부분 • 순위 지정의 대상: 토큰 • 우선순위 • 높은 우선순위의 연산자를 먼저 적어 준다. • 1 - 2 * 3 • 지정 예:%left ‘*’ %left ‘-’ %%
결합순위 • 결합순위 • 같은 우선 순위의 연산자 사이의 순서 • 1 - 2 - 3 • 좌측결합 • (1 - 2) - 3 • %left ‘-’ • 우측결합 • 1 - (2 - 3) • %right ‘-’
결합/우선순위로 해결한 예 • 결합/우선순위로 해결한 예 %left ‘*’ %left ‘-’ %token Num %% E : E ‘-’ E { printf(“rule 1\n”); } | E ‘*’ E { printf(“rule 2\n”); } | N { printf(“rule 3\n”); } ; N : Num { printf(“rule 4\n”); } ;
Yacc 실습 • 결합/우선순위를 다음과 같이 지정하여 각각의 파서를 만들자. %left ‘*’ %right ‘*’ %left ‘-’ %right ‘-’ %left ‘-’ %right ‘-’ %left ‘*’ %right ‘*’ %% %% %% %% • 각 파서에 대하여 다음의 입력으로 실행하여 적용된 생성규칙을 확인하고 파스 트리를 그리자. 1 - 2 * 3 1 * 2 - 3 1 - 2 - 3