1.前言
LL(1)文法是为了语法分析的中自上而下推导而专门打造的一个规范化的文法。给定一个文法,首先要判定是不是LL(1)文法,如果不是,需要转化成LL(1)文法;然后构建LL(1)分析表;最后,构造相应的求解器。
2.算法
2.1 LL(1)文法判定
对于每个产生式(A->α),
定义first(α)为first(α)={t|α=>t...,t∈VT},follow(A)为follow(A)={t|Z=>...At...,t∈VT}。
由上述定义可知,first集求得是该符号串推导时遇到的第一个非终结符,follow集求的是推导时紧随该非终结符其后的第一个非终结符。
算法:
对于每个产生式 A->α
1.求first(α)
2.求follow(A)
3.若first(α)不为{ε},select(A->α)=first(α);否则,select(A->α)=first(α) U follow(A)
4.如果A有多个产生式,比较这些产生式的select集合,如果有集合相交,就不属于LL1文法。
如果所有相同左部的产生式的select集合均没有交集,那么该文法符合LL1文法。
这里要尤其注意把握住first集合和follow集合的定义。 强调一点:first集合是符号串进行多步推导得到的第一个非终结符,follow集合也一样。举例:A->Ba B->b|ε,那么first(Ba)={a,b},first(b)={b},first(ε)={},follow(A)={},follow(B)={a}。
2.2 构建LL(1)分析表
根据之前求得的Select集合构建。
2.3 LL(1)求解器
基本思路:利用判定时求得的select集合信息作为匹配条件,利用栈来进行具体推导。
1.初始化空栈,压入'#'表示栈底。将指针指向字符串第一个字符。
2.开始符号进栈。
3.根据栈顶符号、指针指向的字符在select集合找到对应的产生式。将栈顶符号出栈,将产生式“逆序”压栈。
4.持续3,直到所指字符与栈顶字符相同,此时该字符匹配成功。随后指针移向下一个字符,同时将栈顶符号弹栈。
5.反复执行3、4直到栈为空,字符串字符全部匹配完。
凡是不能推导到5都说明字符串匹配不成功。
(流程示意图)
其中,PUSH(Z)是将Z压入栈内,NEXT(w)表示判断字符串指针移向下一个字符,LL(1)分析表是由select集合构建的。如果算法流程没有看懂没关系,后续我们举例就可以清楚地说明白了。
2.4 递归下降程序算法
begin
stack.push('#');
a:=M[0];//a表示当前符号串读取字符
flag:=true;
while flag do
begin
x=stack.top();
if x∈Vt then //栈顶符号为终结符
if x=a then //对比读入符号串,相同则出栈,不同则报错
a=M[++i];
else error
else if x='#'then //栈顶符号为‘#’(结束符)
flag:=false;
else error
else if M[A,a]={X->X1X2...Xk} then //栈顶符号为非终结符,查分析表,找对应推导式
stack.pop(),stack.push(Xk),stack.push(Xk-1),... //推导右侧符号串逆序压栈
else error
end of while
stop
end
这是一段C与Pascal混合风格的伪代码,描述了利用栈和分析表控制的递归下降程序的运行过程。 上述算法在字符串结尾加入结束符‘#’控制结束,而实际编程上我们可以灵活处理。
3.实例
E->TE’ (1)
E’->+TE’ (2)| -TE’ (3) | ε (4)
T->FT’ (5)
T’->*FT’ (6)| /FT’ (7)| ε (8)
F->i (9)| (E) (10)
给出串"a+b*c"的识别过程。
在求解之前,首先要确定哪些是非终结符、哪个是起始符。一般,我们将第一个产生式的左端非终结符默认是起始符。默认大写的字母是非终结符。凡是出现在产生式左端的,无论大小写均是非终结符。
①求select集合,判定是否属于LL(1)文法
在求之前,要把开始符号右侧产生式最右侧加一个'#',或者自己记住也行。select集合如下:
E: Select((1))={i,(}
E': Select((2))={+} Select((3))={-} Select((4))={#,)}
T: Select((5))={i,(}
T': Select((6))={*} Select((7))={/} Select((8))={+,-,#,)}
F: Select((9))={i} Select((10))={(}
这里省略了求first、follow集合的过程,实际上这部分是必须的。可以看到对于每个非终结符,它的产生式的select集合均不发生相交。得出结论:该文法判定符合LL(1)文法规则。
②若是LL(1)文法,则根据select集合建立LL(1)分析表
| i | + | - |
| * | / | ( | ) | # |
E | (1) |
|
|
|
|
| (1) |
|
|
E’ |
| (2) | (3) |
|
|
|
| (4) | (4) |
T | (5) |
|
|
|
|
| (5) |
|
|
T’ |
| (8) | (8) |
| (6) | (7) |
| (8) | (8) |
F | (9) |
|
|
|
|
| (10) |
|
|
③根据LL(1)分析表,利用栈进行求解
这里SYN[a]表示推导栈,x表示当前栈顶,w表示当前读入符号。
SYN[a] | x | w | 操作 |
#E | E | a | (1) |
#E’T | T | a | (5) |
#E’T’F | F |
| (9) |
#E’T’{i}i | i | a | 匹配 |
#E’T’{i} | {i} | + | PUSH i |
#E’T’ | T’ | + | (8) |
#E’ | E’ | + | (2) |
#E’{+}T+ | + | + | 匹配 |
#E’{+}T | T | b | (5) |
#E’{+}T’F | F | b | (9) |
#E’{+}T’{i}i | i | b | 匹配 |
#E’{+}T’{i} | {i} | * | PUSH b |
#E’{+}T’ | T’ | * | (6) |
#E’{+}T’{*}F* | * | * | 匹配 |
#E’{+}T’{*}F | F | c | (9) |
#E’{+}T’{*}{i}i | i | c | 匹配 |
#E’{+}T’{*}{i} | {i} | # | PUSH c |
#E’{+}T’{*} | {*} | # | EXP |
#E’{+}T’ | T’ | # | (8) |
#E’{+} | {+} | # | EXP |
#E’ | E’ | # | (4) |
# |
| # | End |
4.代码实现
编程过程很多情况我们可以灵活处理。比如把+和-、*和/分别看成同一性质的两种运算符,尽可能地简化工程量。如果我们这么做了,文法就变成了:
E -> T E1
E1-> ω0 T E1 |ε
T -> F T1
T1-> ω1 F T1 |ε
F -> I | ( E )
struct Word {
int kind;//1为自定义标识符,4为常数,6为界符
string name;//串值
int val;
}word_list[1000];
//LL1文法,栈操作
void Op_SYM(int n, int &w_id)
{
switch (n)
{
case 1:
cout << "case:" << n << " pop E push E1,T" << endl;
st.pop();
st.push('G');
st.push('T');
break;
case 2:
cout << "case:" << n << " pop E1 push E1,T,ω0" << endl;
st.pop();
st.push('G');
st.push('T');
st.push('X');
break;
case 3:
cout << "case:" << n << " pop E1" << endl;
st.pop();
break;
case 4:
cout << "case:" << n << " pop T push T1,F" << endl;
st.pop();
st.push('U');
st.push('F');
break;
case 5:
cout << "case:" << n << " pop T1 push T1,F,ω1" << endl;
st.pop();
st.push('U');
st.push('F');
st.push('Y');
break;
case 6:
cout << "case:" << n << " pop T1" << endl;
st.pop();
break;
case 7:
cout << "case:" << n << " pop F push i" << endl;
st.pop();
st.push('i');
break;
case 8:
st.pop();
cout << "case:" << n << " pop F push ),E,(" << endl;
st.push(')');
st.push('E');
st.push('(');
case 9:
cout << "case:" << n << " pop w,x" << endl;
st.pop();
++w_id;
break;
default:
cout << "Wrong!(Analysis Failed): " << st.top() << " " << word_list[w_id].name << endl;
work_f = false;
}
}
//G:E1 U:T1 X:W0 Y:W1 i
int get_sym(char x, Word w)
{
switch (x)
{
case 'E':
if (w.kind == 1 || w.kind == 4)
return 1;
else if (w.name == "(")
return 1;
else
return 0;
break;
case 'G':
if (w.name == "+" || w.name == "-")
return 2;
else if (w.name == ")" || w.name == "#")
return 3;
else
return 0;
break;
case 'T':
if (w.kind == 1 || w.kind == 4 || w.name == "(")
return 4;
else
return 0;
break;
case 'U':
if (w.name == "*" || w.name == "/")
return 5;
else if (w.name == "+" || w.name == "-" || w.name == ")" || w.name == "#")
return 6;
else
return 0;
break;
case 'F':
if (w.kind == 1 || w.kind == 4)
return 7;
else if (w.name == "(")
return 8;
else
return 0;
break;
case 'X':
if (w.name == "+" || w.name == "-")
return 9;
else
return 0;
break;
case 'Y':
if (w.name == "*" || w.name == "/")
return 9;
else
return 0;
break;
case 'i':
if (w.kind == 1 || w.kind == 4)
return 9;
else
return 0;
break;
case '(':
if (w.name == "(")
return 9;
else
return 0;
break;
case ')':
if (w.name == ")")
return 9;
else
return 0;
break;
case '#':
if (w.name == "#")
return 9;
else
return 0;
default:
return 0;
}
}
/**
* LL(1) Analysis
*/
void GA_LL1()
{
while (!st.empty())
{
st.pop();
}
st.push('#');
st.push('E');
int alpha_id, w_id = 0;
while (!st.empty())
{
alpha_id = get_sym(st.top(), word_list[w_id]);
Op_SYM(alpha_id, w_id);
if (!work_f)
break;
}
if (!st.empty())
cout << "Wrong!" << endl;
else
{
printf("Grammar Analysis is Done!\nYES!\n");
}
}