1. 좌우선(leftmost) 유도와 우우선(rightmost) 유도
- 문법을 잘못 만들면 상황에 따라 다른 모호한 파스트리가 만들어 질 수 있다.
- 좌우선(leftmost) 유도 : 왼쪽 non terminal부터 유도한다.
- 우우선(rightmost) 유도 : 오른쪽 non terminal부터 유도한다.
- 아래와 같은 문법으로 파스트리를 만들면 항상 같은 파스트리가 만들어진다. (모호성이 없다)
// 좌우선(Leftmost) 유도
<number> → <number><digit>
→ <number><digit><digit>
→ <digit><digit><digit>
→ 1<digit><digit>
→ 12<digit>
→ 123
// 우우선(Rightmost) 유도
<number> → <number><digit>
→ <number>3
→ <number><digit>3
→ <number>23
→ <digit>23
→ 123
2. 모호성제거 (1) - 연산자의 종류에 따른 우선순위
1) 모호한 파스트리의 생성 이유
<assign> → <id> = <expr>
<id> → A | B | C
<expr> → <expr> + <expr>
| <expr> -<expr>
| <expr> * <expr>
| <number>
<number> → <number><digit>
| <digit>
<digit> → 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 눈치챘을 수도 있지만 이 모호성은 대부분 "연산자의 종류에 따른 우선순위"에서 기인된다.
- <expr> + <expr>과 <expr> * <expr>의 우선순위가 같다고 해놓아서 다른 결과가 나오는 것이다
- 우리가 사칙연산을 할 때는 *가 빠르다고 생각하고 연산하지만, +가 빠르다고 생각하면 다른 결과가 나온다
- 다음은 A = 3 + 5 * 2의 좌우선, 우우선 파스트리이다. 두 파스트리가 다르기 때문에 연산 결과도 다르다.
- 위 그림을 보면, 처음에 assign하고나서 id = expr까지는 동일하게 진행된다.
- 그리고 나서 여기에 expr을 더할 것인지, 곱할 것인지 선택하게 되는데 여기에서 문제가 발생한다.
- 결론부터 말하자면 여기에서 더하기를 선택해야한다. 왜냐하면, 우리가 파스트리로 연산을 진행할때는
- 거꾸로부터 타고 올라가기 때문이다. 즉, 곱하기가 늦게 선택되어야 곱하기를 먼저 연산하게 된다.
- 결국 좌우선은 5 * 2를 먼저 하고 3에 더하지만(정답), 우우선은 3 + 5를 하고 거기에 2를 곱한다(오답)
2) 왜 거꾸로 타고 올라가서 연산할까?
private int precedence(char c) {
if (c == '*' || c == '/') return 2;
else if (c == '+' || c == '-') return 1;
else return 0;
}
public String transform(String inFix) {
char[] f = inFix.toCharArray();
ListStack stack = new ListStack();
String postFix = "";
for (char c : f) {
if (c == '(')
stack.push(c);
else if (c == ')') {
while (!stack.isEmpty() && stack.peek() != '(')
postFix += (char) stack.pop();
stack.pop();
} else if (c == '+' || c == '-' || c == '*' || c == '/') {
while (!stack.isEmpty() && precedence((char) stack.peek()) >= precedence(c))
postFix += (char) stack.pop();
stack.push(c);
} else if (c >= '0' && c <= '9')
postFix += c;
}
while (!stack.isEmpty())
postFix += (char) stack.pop();
return postFix;
}
- 왜 거꾸로 타고 올라가야하냐면,,, 사실 이거 알고리즘(후위표기법)때 했던 것과 동일한 원리이다
- 파스트리는 트리구조를 이용했지만, 스택을 써서 파스트리와 똑같이 구현해놓은 것이 위 알고리즘이다.
- 사칙연산을 할때는 먼저 Stack에 연산자들을 push하고 규칙에 따라 pop하면 후위표기법으로 변한다.
- 그러나, 곱하기를 가장 먼저 연산하려면 스택에 가장 마지막에 넣어야하는데 (후입선출이므로),
- 곱하기가 먼저 들어가면 스택 구조상 가장 늦게 나오게 되고, 그럼 더하기 먼저 연산 되는 것이다.
3) 해결방법
- 자, 그럼 해결방법은 무엇일까? 생각보다 엄청 간단하다. 그냥 더 높은 순위의 연산자(곱하기)가
- 무조건 더 나중에 스택에 들어가게만 만들어주면 끝이다. 그렇게 하면, 곱하기가 먼저 Pop된다.
- BNF로 보자. 결과는 <expr>과 * 연산 사이에 하나의 비단말 기호 <term>을 넣어주는 것이다.
- term은 expr과 절대 더할 수 없다. 때문에, term으로 바꾸기 전에 더하기를 스택에 넣어야만 계산할 수 있다
- 즉 더하기 먼저 스택에 넣고 더해지는 하나의 대상을 term을 선택하여 곱하기를 스택에 넣어야만 한다.
<assign> → <id> = <expr>
<id> → A | B | C
<expr> → <expr> + <expr>
| <expr> - <expr>
| <term>
<term> → <term> * <term>
| <number>
<number> → <number><digit>
| <digit>
<digit> → 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 이 파스트리의 경우, 가장 아래쪽에 위치한 연산인 곱하기가 스택에서 나와서 먼저 계산되고,
- 그리고나서 더하기가 나와서 더해지고 연산을 종료한다. 그런데 만약 assign에서 나뉘어 지고,
- expr에서 덧셈보다 먼저 곱하기 먼저 스택에 넣기 위해 비단말 기호를 term으로 바뀌 버리는 순간,
- 그 이후로는 다시는 expr으로 돌아올 수 없게 되고, 더하기 연산을 절대 할 수 없게 된다.
3. 모호성 제거 (2) - 피연산자(Operand) 순서에 의한 우선순위
1) 모호한 파스트리 생성 이유
- 자, 다 끝난 것 같지만 "B = 3 - 5 - 2"를 예제를 보면 또 다시 모호성이 발생한다.
- 두 뺄셈 연산의 우선순위가 동일해서, 둘중 무엇을 먼저 연산해야 하는지 모르기 때문이다.
- 즉, 무슨 말이냐면 3 - 5를 먼저 연산할지, 5 - 2를 먼저 연산할지 몰라서 모호성이 생긴다.
- 일반적으로 우리는 앞 연산자를 먼저 계산하는데, 지금의 문법은 그런 Rule이 없기 때문이다.
- 때문에 뒤쪽 뺄셈 연산이 스택에 먼저 들어가게 해주면 된다. (제일 늦게 Pop되서 늦게 연산된다)
2) 해결방법
<assign> → <id> = <expr>
<id> → A | B | C
<expr> → <expr> + <term>
| <expr> - <term>
| <term>
<term> → <term> * <term>
| <number>
<number> → <number><digit>
| <digit>
<digit> → 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 이렇게 변경하면 동일한 연산자에서 피연산자의 순서에 따라 달라지는 우선순위 문제를 해결할 수 있다.
- expr과 expr은 뺄 수 없게 만든다. 하나의 expr을 expr과 term 사이의 뺄셈이던 덧셈연산으로 바꿀 때
- 앞쪽만 expr으로 살고 뒤쪽은 term으로 죽는다. 그래서 앞쪽에서만 반드시 뺄셈을 전개해야만 하게되고,
- 그러면 앞쪽 피연산자들 사이에서 먼저 뺄셈 연산이 먼저 수행될 수 밖에 없다. (그림을 보면 이해간다)
4. 모호성 제거 결론
- 연산자 높은 우선순위의 연산자가 더 나중에 선택(스택에 push)되게 하기 위해서 높은 우선순위의
- 연산자인 곱셈은 BNF 상에서 아예 따로 떼서 분리해버리고, 다시 덧셈연산을 못하게끔 만들어서
- 무조건 덧셈을 찍고(스택에 Push) 그리고 나서 곱셈을 선택(스택에 Push)할 수 밖에 없게 만든다거나,
- 아니면 피연산자 순서에 의한 모호성을 막기 위해 한쪽에서는 연산이 전개되게 못하게 막아버리는 등
- 몇몇 노벨한 아이디어를 적용하여 모호성을 훌륭하게 제거할 수 있다. 모호성이 제거된 파스트리는
- 구문분석단계를 넘어가서 의미분석후에, 중간코드를 생성하게 되고, 목적프로그램이 생성된다.
'언어이론' 카테고리의 다른 글
EBNF(Extended BNF)와 구문 도표 (0) | 2020.05.19 |
---|---|
파스 트리 (1) - 파스 트리 그리는 방법 (0) | 2020.05.18 |
배커스-나우어 형식 (BNF) (4) | 2020.05.14 |
문맥자유언어 (Context Free Grammar) (1) | 2020.05.14 |
프로그래밍 패러다임 개요 (0) | 2020.04.14 |
인터프리터 & 혼합 기법 (0) | 2020.04.14 |
컴파일 개요 (0) | 2020.04.14 |