编译器设计中的句柄和句柄修剪介绍
手柄是编译器设计中自下而上的解析中使用的一个概念。自下而上的解析是一种语法分析方法,在这种方法中,属于某些无上下文语法的句子被分析。
这种分析的结果是构建解析树。这种构建从叶子节点开始,一直到根部的非终端,因此被称为自下而上,有时也被称为移位还原解析法。
每个符号都被移到栈上,直到有一个匹配的右侧非终端。随后,通过用左手边替换生产的右手边来进行还原。这个过程一直持续到最终字符串被减少到起始非终端。
在每个还原步骤中被替换的字符串的集合被称为句柄。
前提条件
为了理解这篇文章,你应该对以下概念有一个预先的了解。
- 编译器设计中的词汇分析。
- 编译器设计中的语法分析,特别是自上而下的解析。
- C编程语言。
虽然一个字符串的句柄可以非正式地描述为等于生产规则右边的子串,但并不是每个与生产规则右边相同的子串都被认为是句柄。
例子。
Expression → Expression + Term | Term
Term → Term * Factor | Factor
Factor → (Expression) | id
id → number
5+6*3的最右派生是
Expression ⇒ Expression + Term ⇒ Expression + Term * Factor ⇒ Expression + Term * id ⇒ Expression + Factor * id ⇒ Term + id * id ⇒ Factor + id * id ⇒ id + id * id → number+numbernumber→ 5+63。这里的句柄是 "id"、"Expression + Term "和 "number"。
什么是句柄修剪?
这描述的是识别句柄并将其减少到适当的最左边的非句柄的过程。它是自下而上解析的基础,负责完成使用自下而上解析的句法分析。
最右派生是使用句柄修剪以相反的顺序实现的。
例如,使用语法。
A → kXYz
X → Xwr | w
Y → g
使用自下而上的反向解析法来解析字符串 "kwwrgz"。
-
找到位置2的句柄 "w",用第二个生产中的非句柄替换它,如下图。
-
用 "X "替换第一个 "w",产生 "kXwrgz"。
-
位置3的 "w "不是一个句柄。"kXXrgz "因此不能被替换。
-
将 "Xwr "替换为第二个产品上的左非终端,即 "X",得到 "kXdz"。
-
将 "g "替换成 "Y",如上一个产品,得到 "kXYz"
-
然后,用起始非术语 "S "替换 "kXYz",成功地完全解析了字符串 "kwrgz"。
为了构建一个最右边的派生,下面的算法就派上用场了。
for j ← n to 1 by -1
Determine handle Aj → βj in γj
Replace βj with Aj in an attempt to generate γj-1
实现句柄修剪
下面是用一个例子来说明。
例子1
对于语法来说。
1. A → Expression
2. Expression → Expression + Term
3. | Expression → Term
4. | Term
5. Term → Term * Fact
6. | Term / Fact
7. | Fact
8. Fact → number
9. | id
以下是用于推导字符串的句柄a – 2 * b
| 句子形式 | 句柄 |
|---|---|
| 生产编号,位置编号 | |
| A | - |
| 表达式 | 1, 1 |
| 表达式-术语 | 3, 3 |
| 表达式 - 术语 * 事实 | 5, 5 |
| 表达式 - 术语 * <id,b | 9, 5 |
| 表达式--事实 * <id,b | 7, 3 |
| 表达式 - <数字,2> * <id,b> | 8, 3 |
| 期限 - <数字,2> * <id,b> | 4, 1 |
| 事实 - <数,2> * <id,b> | 7, 1 |
| <id,a> - <数,2> * <id,b> | 9, 1 |
例2
考虑一下下面的CFG。
S → Term Length
Term → integer
Term → float
Term → character
Length → Length, id
Length → id
| 输入字符串 | 行动 | 处理 |
|---|---|---|
| 整数id,id | 减少术语→整数 | 整数 |
| 术语id,id | 缩短长度→id | id |
| 术语length,id | 缩短长度→长度,id | 长度,id |
| 期限长度 | 缩减S→期限长度 | 期限长度 |
| S | 接受 |
使用C语言通过自下而上的解析实现句柄和句柄修剪的程序。
//Including all the required Libraries
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//Declaration of Variables
int x = 0;
int y = 0;
int k = 0;
int w = 0;
//Array declaration
char r[16];
char rw[20];
char stk[15];
char action[10];
//This function Checks if the stack has a Handle
//This means if any on the production rules can be used to reduce whats on top of the stack
void checking()
{
strcpy(rw,"Reduction to S -> ");
// c is the length of input string
for(x = 0; x < w; x++)
{
//identifying for production with the rule S->6
if(stk[x] == '6')
{
printf("%s6", rw);
stk[x] = 'S';
stk[x + 1] = '\0';
//output the following
printf("\n$%s\t%s$\t", stk, r);
}
}
//Looks for a production that has a rule where S implies 6
for(x = 0; x < w - 2; x++)
{
//checking for a production with S implies 7S7
if(stk[x] == '7' && stk[x + 1] == 'S' && stk[x + 2] == '7')
{
printf("%s7S7", rw);
stk[x] = 'S';
stk[x + 1] = '\0';
stk[x + 2] = '\0';
printf(stk, a);
y = y - 2;
}
}
for(x=0; x<w-2; x++)
{
//checking for a production with S implies 8S8
if(stk[x] == '8' && stk[x + 1] == 'S' && stk[x + 2] == '8')
{
printf("%s8S8", rw);
stk[x]='S';
stk[x + 1]='\0';
stk[x + 1]='\0';
printf( stk, a);
y = y - 2;
}
}
return ; //return to the main program
}
//Main Function
int main()
{
//Prints the grammar
printf("GRAMMAR is -\nS->7S7 \nS->8S8 \nS->6\n");
// r is input string
strcpy(r,"87678");
// This will return the lengths of r to w
c=strlen(r);
strcpy(action,"Shift");
// This will print names of columns
printf("\nstack \t input \t action");
printf("\n$\t%s$\t", r);
//If there is a handle you reduce it using the production rules if not you shift
for(y = 0; k < w; y++, k++)
{
// Printing action
printf("%s", action);
stk[y] = r[k];
stk[y + 1] = '\0';
r[k]=' ';
// Printing action
printf( stk, r);
checking();
}
// Checking for valid productions
checking();
//Checks if the start symbol is on top of the stack accept
if(stk[0] == 'S' && stk[1] == '\0')
printf("Accept");
else
// if fthe start sysmbol is not on top of the stack you do not allow
printf("Rejection");
}
这个程序检查与堆栈顶部的内容相匹配的句柄,并使用生产规则来减少它们。这样做直到堆栈顶部只剩下起始非终端。如果开始符号在堆栈的顶部,它就接受并允许产生一个解析树。
否则,它拒绝输入的字母。该程序通过自下而上的解析对输入字母87678 ,并打印出语法,然后输出解析表,如下图所示。
S → 7S7
S → 8S8
S → 6
| 堆栈 | 输入 | 动作 | 处理 |
|---|---|---|---|
| $ | 87678$ | 移位 | |
| $8 | 7678$ | 挪移 | |
| $87 | 678$ | 挪移 | |
| $876 | 78$ | 减为S→6 | 6 |
| $87S | 78$ | 转移 | |
| $87S7 | 8$ | 减为S→7S7 | 7S7 |
| $8S | 8$ | 转移 | |
| $8S8 | $ | 减为S→8S8 | 8S8 |
| $S | $ | 接受 |
总结
确定语法中的句柄并修剪它们是解析输入字符串的第一步。它负责构建解析表和解析树,因此是编译器设计中自下而上解析的一个关键概念。