로더의 기능 Allocation(주기억장치할당), Linking(연결), Relocation(재배치), Loading(적재)
단항연산 NOT
프로그램에서 변수들이 갖는 속성이 완전히 결정되는 시간 바인딩시간(Binding Time)
연산기호가 오퍼랜드들의 다음에 오는 표기법 Postfix표기법
주기억 장치에서 가장 오랫동안 사용되지 않은 페이지를 교체할 페이지로 선책하는 교체 알고리즘 LRU
OPT (Optimal Replacement, 최적 교체)
․앞으로 가장 오랫동안 사용하지 않을 페이지를 교체하는 기법․각 페이지의 호출 순서와 참조 상황을 미리 예측해야 하므로 실현 가능성이 희박함
FIFO(First In First Out)
․각 페이지가 주기억장치에 적재될 때마다 그때의 시간을 기억시켜 가장 먼저 들어와서 가장 오래 있었던 페이지를 교체하는 기법․이해하기 쉽고, 프로그래밍 및 설계가 간단하며, 벨레이디의 모순(Belady's Anomaly) 현상이 발생함
LRU(Least Recently Used)
․최근에 가장 오랫동안 사용하지 않은 페이지를 교체하는 기법․각 페이지마다 계수기나 스택을 두어 현 시점에서 가장 오랫동안 사용하지 않은 즉, 가장 오래 전에 사용된 페이지를 교체함
LFU(Least Frequently Used)
․사용 빈도가 가장 적은 페이지를 교체하는 기법. 프로그램 실행 초기에 많이 사용된 페이지가 그 후로 사용되지 않을 경우에도 프레임을 계속 차지할 수 있음
NUR(Not UsedRecently)
․최근에 사용하지 않은 페이지를 교체하는 기법․최근의 사용 여부를 확인하기 위해서 각 페이지마다 참조 비트(Reference Bit)와 변형 비트(Modified Dit, Dirty Bit)가 사용됨
SCR(Second Chance Replacement)
가장 오랫동안 주기억장치에 있던 페이지 중 자주 사용되는 페이지의 교체를 방지하기 위한 것으로, FIFO 기법의 단점을 보완하는 기법
Parser(파서)
Parser(파서)란 컴파일러의 일부로서, 원시 프로그램의 명령문이나 온라인 명령문, HTML 문서 등에서 마크업 태그 등을 입력으로 받아들여서 컴파일러 등을 처리전에 구문을 해석할 수 있는 단위로 여러 부분으로 분할 해주는 역할을 한다. 즉, 컴파일러나 인터프리터에서 원시 프로그램을 읽어들여, 그 문장의 구조를 알아내는 구문분석 (parsing) 을 행하는 프로그램을 말합니다.
파서는 필요한 모든 입력이 제공되었는지를 점검하기도 한다.
구문분석 (parsing) 이란, 컴파일러나 인터프리터가 프로그램을 이해해 기계어로 번역하는 과정중의 한단계로, 각 문장의 문법적 구성·구문을 분석하는 과정입니다. 정확하게 이야기하면, 원시 프로그램의 토큰열 (token sequence) 을 받아들여 문법에 맞게 파스 트리 (parse tree) 로 구성하는 것을 말한다. 구문분석의 종류에는 다음과 같은 것들이 있다:
하향식 파싱 (top-down parsing)
상향식 파싱 (bottom-up parsing)
재귀 하강식 파싱 (recursive descendent parsing)
예측형 파싱 (predictive parsing)
픽쳐 파싱 (picture parsing)
연산자 우선순위 파싱 (operator)
참조
5. 상향식 파싱(Bottom-Up Parsing)
5.0 소개
일반적인 상향식 파싱 기법은 3장에서 소개되었다. 이 장에서는 몇 가지 상향식 파싱 알고리즘을 설명한다.
간략히 말하면, 파싱은 한 언어로 된 입력 스트링과 그 언어의 문법이 주어지면 파스 트리를 구성하는 것이다. 상향식 파서는 밑에서 위쪽으로, 즉, 단말 노드로부터 루트까지의 방향으로 파스 트리를 구성한다.
5.1 LR 문법
하향식 파싱을 위한 문법이 LL문법인 것과 마찬가지로, 상향식 파싱을 위한 문법은 LR 문법이다. 여기에서 L은 입력 스트링을 왼쪽에서 오른쪽으로 읽는다는 것을 나타내고, R은 우측 유도를 생성해낸다는 것을 의미한다.(Left to right scanning Right most derivation) 이때 실제 유도는 역순으로 구성된다.
5.1.1 LR(k) Grammars
3장에서 정의되었던 핸들(handle), 문장형태(sentential form), 우측 유도(right derivation) 등의 용어를 써서 LR(k) 문법을 정의하여 보자.
한 문법이 LR(k)라면, 다음 조건을 만족한다.
우측 유도에서 다음과 같은 문장형태가 있을 때,
αβω
여기에서 ω 와 α 가 빈 스트링으로 유도될 수 있고, ω(if non empty) 가 터미널 심볼들로만 이루어진다면, 핸들 β 는 ω의 좌측 k개 심볼들을 보고서 구분될 수 있다.
실제에서는 k = 1 로 문법을 기술하고, 이를 LR(1) 문법이라 한다. 대부분의 프로그래밍 언어들의 문법은 LR(1)문법이다. LR(0) 문법은 핸들 다음의 심볼을 보지 않고도 파싱할 수 있음을 나타내므로 잘 쓰이지 않으며, 대부분의 프로그래밍 언어의 문법을 LR(0)문법으로 기술하기가 어렵다.
예1) 다음 문법은 LR(0)이 아니다.
1. E → E + T
2. E → T
3. T → T * F
4. T → F
5. F → (E)
6. F → Id
문장형태 "E + T * Id"에서 미리보기(lookahead)가 없다면, 핸들을 E + T(즉, rule 1)로 결정할 수 밖에 없다. 따라서 문장형태는 E * Id로 축소된다. 여기에서 다음 핸들은 Id가 되어(즉, rule 6) 문장형태는 E * F로 되고, 다음 핸들은 F(rule 4)여서 문장형태는 E * T가 된다. 이어서 다음 핸들은 T (rule 2)이어서 문장형태는 E * E로 축소된다. 이때, 이 문장형태에서는 핸들을 발견할 수 없어서 이 문장 형태는 오류가 된다. 즉, 다음과 같은 과정을 밟는다.
E + T * Id 핸들 E + T
E * Id 핸들 Id
E * F 핸들 F
E * T 핸들 T
E * E 핸들 없음
따라서, 이 문법은 LR(1) 문법이다.
예2) 다음문법은 LR(0)이다.
S → a S a
| b S b
| c
한 문법이 LR(k)라는 것을 보이는 것은 그것이 LR(k)가 아니라는 것을 보이는 것보다 어려운 일이다.
L(S) = c, aca, bcb, abcba, aabbbcbbbaa ........ , 즉, c를 중앙에 가진 a 와 b의 대칭 스트링(symmetric string)의 언어이다. 이 언어의 어떤 문장형태에서도 지나간 심볼을 보지 않고 핸들을 찾고 축소하는 것이 가능하다.
LR(0)문법과 LR(1)문법 사이에는 한 가지 관심이 가는 문법이 있는데, 이것이 SLR(1) 문법이다. 여기에서 S는 간단함(Simple)을 뜻한다. 이 문법으로 많은 프로그래밍 언어들의 구성 요소들을 기술할 수 있다. 이들 문법들 사이의 차이는 우리가 파서를 구성할 때 파싱 테이블을 만드는 방법에 차이가 있다.
5.2 LR 파싱
LR 파서들은 효율적(빠름)이고 오류의 발견이 빠르다. LR 파서의 파싱테이블을 손으로 만들기는 매우 어렵지만, 오늘날에는 주어진 언어의 문법으로 LR 파싱 테이블을 구성해주는 도구들이 많다. 우리는 이들 파서 생성 도구들이 어떻게 동작하는지 알기 위해 LR 파싱 기법을 공부한다.
LL 파서와 마찬가지로, LR 파서도 두부분, 즉, 파싱 테이블과 드라이브 루틴으로 구성할 수 있다.
LR파싱 테이블의 구성하는 과정은 다음의 두 단계이다.
1) item set들로 구성되는 파싱 상태들을 구한다.
2) 구해진 파싱 상태로부터 테이블을 구성한다.
파싱 상태들을 구할 때 미리보기(lookahead)를 쓰지 않으면, LR(0) 파서를 구성한 것이며, 이것은 구성이 쉬우나 언어의 수용 범위가 작게 된다.
상태들을 구할 때 한 개의 미리보기( lookahead)를 쓰면, 이는 LR(1) 파서를 구성한 것이며, 이는 구성하기 어렵고 테이블의 크기가 너무 커지나 수용 언어의 범위가 크게 된다.
따라서, 절충형인 SLR(1)을 씀 -- 상태 구할 때는 lookahead를 쓰지 않고, 테이블을 구할 때만 lookahead 를 쓴다. SLR 파서는 대부분의 프로그래밍 언어를 수용할 수 있다.
LR 파싱의 개념을 익히기 위해서, 이장에서는 SLR(1) 파서를 설명한다. 파싱 테이블을 구성하는 알고리즘을 설명하기 전에, 파싱 테이블을 어떻게 사용하는지를 보인다. 파싱 알고리즘은 shift-reduce 파싱 알고리즘이다.
5.2.1 Shift-Reduce 파싱
LR파서는 Shift-Reduce라 부르는 파싱 형식을 사용한다. 이 방식은 이전에 파싱된 상황을 기록하고 있는 파싱 스택을 사용한다. 이 파서는 스택의 내용과 입력을 참고하여, 다음에 취해야 할 행동을 결정한다.
파싱 스택은 이미 만들어져 있다고 가정한다.
파싱 테이블 구조는 상태 X 문법 심볼의 이차원 테이블이다. 테이블은 두 부분으로 구성되는데, 행동(action) 부분과 GOTO부분이다. 행동 부분은 "shift-reduce"행동을 나타내는데, 상태들이 행을 구성하고, 터미날 심볼들이 열을 구성한다. GOTO부분의 열도 상태들로 구성되고, 행은 난터미널 심볼들로 구성된다.
LR 파싱 테이블:
상태
행동(action)
t1 t2 t3 ... tm $
GOTO
A1 A2 ... Ak
0
Error/Accept/Shift/Reduce
상태번호들
1
...
n
Shift-Reduce 파서의 4 가지 행동:
Error : 테이블에서 빈자리, 문법 오류를 말함.
Accept: 입력 스트링이 문법적으로 맞음. 파싱 종료 상태
Shift: S#가 가리키는 상태로 전이, 입력 심볼과 S#를 스택에 Push
Reduce: R#는 문법의 생성규칙 번호 임. 스택의 top은 이 생성규칙의 rhs를 가지고 있으므로 이를 pop하여 lhs로 대치함. 다음 상태는 GOTO 에서 가리키는 상태임.
다음과 같이 파싱 테이블이 이미 구성되어 있다고 간주하자. 파싱 테이블은 상태 수 X 문법 심볼 수의 크기로 구성된다.
상태
Action
GOTO
id
+
*
(
)
$
E
T
F
0
S5
S4
1
2
3
1
S6
Accept
2
R2
S7
R2
R2
3
R4
R4
R4
R4
4
S5
S4
8
2
3
5
R6
R6
R6
R6
6
S5
S4
9
3
7
S5
S4
10
8
S6
S11
9
R1
S7
R1
R1
10
R3
R3
R3
R3
11
R5
R5
R5
R5
위의 문법과 주어진 LR파싱 테이블을 가지고 다음의 입력 문장을 파싱해 보자
id * ( id + id )
다음은 파싱 과정을 보인다. 이어서 각 단계를 설명한다. 스택의 탑은 오른쪽이다.
스택(Stack)입력(Input)행동(Action)
(1) 0 id * ( id + id ) $ S5
(2) 0id5 * ( id + id ) $ R6
(3) 0F3 * ( id + id ) $ R4
(4) 0T2 * ( id + id ) $ S7
0T2*7 ( id + id ) $ S4
0T2*7(4 id + id ) $ S5
0T2*7(4id5 + id ) $ R6
0T2*7(4F3 + id ) $ R4
0T2*7(4T2 + id ) $ R2
0T2*7(4E8 + id ) $ S6
0T2*7(4E8+6 id ) $ S5
0T2*7(4E8+6id5 ) $ R6
0T2*7(4E8+6F3 ) $ R4
0T2*7(4E8+6T9 ) $ R1
0T2*7(4E8 ) $ S11
0T2*7(4E8)11 $ R5
0T2*7F10 $ R3
0T2 $ R2
0E1 $ Accept
과정 1
파싱은 스택에 상태 0를 넣고, 입력의 끝에는 $를 추가하여 시작한다.
스택 입력 스트링 행동
(1) 0 id * (id + id) $
파싱 알고리즘에 따라 테이블을 참조하면, 상태 0에서 입력 심볼 id는 행동 S5, 즉, 입력 심볼을 스택에 넣고 상태 5로 전이하라는 것이다.
과정 2
스택 입력 스트링 행동
(1) 0 id * (id + id) $ S5
(2) 0 id 5 * (id + id) $
이때, 테이블을 참조하면, 상태 5에서 입력 심볼 *는 행동 R5로 생성규칙 6번의 오른쪽이 축소할 핸들임을 말한다. 따라서, 스택에서 핸들에 해당하는 부분을 모두 지우게 되는데, 이 부분이 id 5이다. 따라서 스택에는 0만이 남는다. 생성 규칙 6의 왼쪽 부분(F)이 스택에 넣어지기 때문에, 테이블에서 상태 0과 F에 해당하는 GOTO부분을 참조하여야 한다. 이 엔트리는 3이므로 이것을 스택에 넣는다.
과정 3
스택 입력 스트링 행동
(1) 0 id * (id + id) $ S5
(2) 0 id 5 * (id + id) $ R6
(3) 0 F 3 * (id + id) $
이제, 스택의 탑은 상태 3이고, 현재 입력 심볼은 * 이므로, 테이블은 생성규칙 4번으로 축소하라는 행동 R4이다. 따라서 생성규칙 4번의 오른쪽은 스택의 탑 부분인 핸들이다. 알고리즘은 스택의 탑을 제거하고 생성규칙 4번의 왼쪽인 T를 넣고, 상태 0에서 T 일 때의 GOTO테이블에서 다음 상태인 상태 2를 얻어 스택에 넣는다.
이와 같은 방법으로 나머지 과정을 수행하고 나면, 우리는 마지막 과정인 과정 19에 다다른다.
과정 19
스택의 탑은 상태 1이고 입력 심볼은 $이다. 이때, 테이블은 행동 Accept를 표시하고 있으므로, 파싱은 완저히 성공적으로 끝나게 된다. R2부터 R6까지의 파싱 과정의 축소 행동을 역으로 따라가면, 우리는 파스 트리를 만들 수 있다.
파싱 과정 중, 우리는 항상 다음과 같은 스택과 입력 스트링의 상태를 유지했다.
StackInput
s0x1s1 ... xmsmaiai+1 .... $}
여기서 s는 상태를 표시하고, x는 터미널과 난터미널의 나열을 표시하며, a는 입력 심볼들이다. 이것은 스택의 탑이 지금까지의 파싱 과정에 대한 정보를 누적하고 있는 유한 상태 기계와 같이 보인다.
우리는 무엇을 하여야 하는지 알기 위해서 단지 스택의 탑과 입력 심볼을 보아야 한다.
5.2.2 SLR 테이블 만들기
우리는 문법의 생성규칙들로부터 각각의 상태가 아이템(item)들의 집합인 위와 같은 유한 상태 기계를 구성할 수 있다.
Items: 포지션 마크("·")가 있는 생성규칙, 즉 LR(0) Item
ex) E → E · + T
즉, 파싱 과정 중 E로부터 유도될 수 있는 스트링 중 E는 이미 확인했고, 앞으로 + T를 보려한다.
Item들을 모아서 Item set을 구성하고, 한 Item set이 한 상태가 된다.
알고리즘:
Constructing States
(0) 새 nonterminal S'를 만들어 S' → S인 새로운 생성규칙을 문법에 추가한다.
(1) S가 시작 심볼이면, S' → ·S를 시작상태 상태0에 추가한다.
(2) Closure: 상태에 A → x ·Xα가 있으면, 문법에 있는 모든 X → ω에 대하여 Item X → ·ω를 상태에 추가한다.
(3) 새로운 상태를 생성하기: 현재 상태에 있는 모든 A → x ·zω에 대하여 A → xz ·ω를 가지는 새 상태를 만든다. 새 상태는 서로 다른 모든 z에 대하여 만든다.
(4) 더 이상 새로운 상태가 만들어지지 않을 때까지 (2)와 (3)을 반복한다.
예4) 다음 문법의 상태들을 만들어 보자.
1. E → E + T
2. E → T
3. T → T * F
4. T → F
5. F → (E)
6. F → Id
Step 0
E' → E를 문법에 추가한다.
0. E' → E
1. E → E + T
2. E → T
3. T → T * F
4. T → F
5. F → (E)
6. F → Id
Step 1
State 0
E' → ·E
Step 2
Sttate 0의 Closure를 구하여 State 0에 추가한다.
State 0
E' → ·E
E → ·E + T
E → ·T
T → ·T * F
T → ·F
F → ·(E)
F → ·Id
Step 3
State 0에 대하여 알고리즘의 (3)을 적용하여 새로운 상태들을 만든다.
State 1
E' → E·
E → E ·+ T
State 2 State 3 State 4 State 5 State 6
E → T· T → F· F → (·E) F → Id· E → E +· T
T → T·* F E → ·E + T T → ·T * F
E → ·T T → ·F
T → ·T * F F → ·( E )
T → ·F F → ·Id
F → ·( E )
F → ·Id
State 7 State 8 State 9 State 10 State 11
T → T *·F F → ( E·) E → E + T· T → T * F· F → ( E )·
F → ·( E ) E → E·+ T T → T·* F
F → ·Id
SLR(1)테이블 구성
이 상태들을 이용하여 LR(0) 파싱테이블을 구성할 수 있다. 그러나 이 문법이 LR(0)문법이 아니기 때문에(예 1과 같이), 이들을 SLR Item으로 보고 lookahead를 동원하여 SLR(1)테이블을 구성한다.
알고리즘
Constructing of an SLR(1) Parsing Table
(1) Shift:
상태 m에 A → x·aω가 있고, 입력 a 에 대하여 상태 n에 A → xa·ω가 있으면, Table[m,a] = Sn 이다.
(2) Reduce:
상태 n에 A → ω·가 있으면 Table[n,a] = Ri이다. 이때 I 는 생성규칙 A → ω의 번호이고, a 는 FOLLOW(A)이다.
(3) Accept:
상태 n에 S' → S·가 있으면, Table[n,$] = Accept이다.
(4) GOTO's:
상태 m에 A → x·Bω가 있고, 상태 n에 A → xB·ω가 있으면, Table[m,B] = n 이다.
예5) 예4에서 구해진 상태들을 이용하여 SLR(1)파싱 테이블을 구성하자.
상태
Action
GOTO
id
+
*
(
)
$
E
T
F
0
S5
S4
1
2
3
1
2
3
4
5
6
7
S5
S4
10
8
9
R1
S7
R1
R1
10
11
5.2.3 Shift-Reduce 충돌
때때로, 파서는 한 생성규칙에 대하여 쉬프트를 계속할 것인지, 또는, 다른 생성규칙으로 축소할 것인지를 결정할 수 없는 때가 있다.
shift-reduce충돌은 한 상태에서 하나의 lookahead에 대하여 Shift해야 할지, 아니면, 스택의 핸들을 Reduce해야 할지 결정할 수 없는 상태로, 파싱테이블을 구성할 때 한 엔트리에 두 개 이상의 행동이 기록된다. 이것은 문법이 SLR(1)문법이 아님을 (혹은 LR(1)테이블이라면, LR(1)문법이 아님을)말하고 있다.
한 가지 해결책은 문법을 다시 기술하는 것이고, 다른 방법은 가능하다면, 상황을 분석하여 어느 행동이 적합한지를 결정하는 것이다.
대개의 경우, 축소되는 생성규칙과 입력 심볼의 순위를 비교하여, 생성규칙의 순위가 높으면 축소(reduce)를, 그렇지 않으면 쉬프트(shift)를 선택한다. 같은 우선순위의 경우는 결합법칙을 이용하여, 좌측 결합을 만족하면 축소(reduce)를, 우측 결합을 만족하면 쉬프트(shift)한다.
5.2.4 Reduce-Reduce충돌
때때로, 파서는 여러 개의 생성규칙에 대하여 축소하여야 하는 상황이 발생할 수 있다. 즉, reduce-reduce충돌은 스택의 핸들이 한 개 이상의 생성규칙에 대하여 축소(reduce)를 표시하는 경우이다. shift-reduce충돌 때와 마찬가지로, 문법이 SLR(1) 문법이 아니면, 테이블의 한 엔트리에 축소할 한 개 이상의 생성규칙이 표시된다.
이 경우의 충돌 해결은 축소되는 생성규칙들의 우선순위를 비교하여, 우선 순위가 높은 것을 선택한다.
5.2.5 LR파서의 종류
LR(0)파서
SLR(1)파서
LR(1)파서
LALR(1)파서
5.2.6 LR 파서 생성기
지금까지의 하향식 파싱 알고리즘을 논의한 결과로, 실제의 프로그래밍 언어를 위한 LR 파서를 손으로 만든다는 것은 매우 어려운 일임을 알게 되었을 것이다. 따라서, 수많은 프로그래머들이 LR 파서를 생성하는 파서 생성기들을 제작하였고, 우리는 이들을 선택하여 사용할 수 있게 되었다. 이들중 가장 유명한 것은 아마도 유닉스 운영체제에서 LALR(1) 파서를 생성하는 YACC(Yet Another Compiler Compiler)일 것이다.