编译原理–自顶向下分析类型的题目(纪念本题拿下首刀的题解)
先纪念一下,写了2小时终于AC了。
首先关于普通的四则运算中缀表达式,我们知道有很多方法求解,包括中缀表达式+栈(顺便转RPN) 递归下降法(构造可贪心的文法)。虽然递归下降也是一种自顶向下分析法,但是构造出来的语法树并不能代表调用的先后顺序。
所以我们需要通过文法内部的左递归或者右递归的性质,来进行递归下降分析。
首先先放一个普通的四则运算求值的代码:
#include<stdio.h>
#include<string.h>
char str[10101];
int len;
int getnum(int l,int r)
{
int i,ret=0;
for(i=l;i<=r;i++)
{
ret=ret*10+str[i]-'0';
}
return ret;
}
int isnum(int l,int r)
{
int i;
for(i=l;i<=r;i++)
{
if(str[i]<'0'||str[i]>'9')
{
return 0;
}
}
return 1;
}
int issub(int l,int r)
{
int i,in=0;
if(str[l]!='('||str[r]!=')')
{
return 0;
}
for(i=l;i<r;i++)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in==0)
{
return 0;
}
}
return 1;
}
int getlst(int L,int R)
{
int i,ret=-1;
int in=0;
for (i=R;i>=L;i--)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in!=0)
{
continue;
}
if(str[i]=='+'||str[i]=='-')
{
return i;
}
if((str[i]=='*'||str[i]=='/')&&ret==-1)
{
ret=i;
}
}
return ret;
}
int c(int L,int R)
{
if(L>R)
{
return 0;
}
if(isnum(L,R))
{
return getnum(L,R);
}
if(issub(L,R))
{
return c(L+1,R-1);
}
int mid=getlst(L,R);
if(str[mid]=='+')
{
return c(L,mid-1)+c(mid+1,R);
}
if(str[mid]=='-')
{
return c(L,mid-1)-c(mid+1,R);
}
if(str[mid]=='*')
{
return c(L,mid-1)*c(mid+1,R);
}
if(str[mid]=='/')
{
return c(L,mid-1)/c(mid+1,R);
}
return 0;
}
int main()
{
while(~scanf("%s",str+1))
{
len=strlen(str+1);
printf("%d\n",c(1,len));
}
}
那么在这里同样的,我们人为的根据运算的优先级,优先级越低在调用计算的时候越应该在顶层,所以我们可以得到类似的思路进行判断。
首先构造对应的语法树需要记录的东西
struct parse_tree_node {
string content;
int dfs_time;
char key_link;
vector<int> child_index;
void add_child(int _index);//增加一个儿子结点
//0代表空节点 如果是false 则需要标号dfs序 表示为非叶子结点
bool is_leaf();
//每个非叶子结点的格式化输出
void print();
};
parse_tree_node memory_pool[maxn];
int node_cnt;//在内存池中开设的节点个数
int root;//语法树根节点的下标
然后需要对应的构造语法树的递归代码,在这之前,我们需要如下函数(暂时只给出函数接口)
假设我们输入的字符串变量名是input
int get_plus_or_minus(int l, int r);//是否能在input[l, r]中找到加减符号
int get_multi_or_div(int l, int r);//是否能在input[l, r]中找到乘除符号
int get_member_func(int l, int r);//是否能找到input[l, r]作为成员函数调用的'.'的位置
int is_stadard_func(int l, int r);//input[l, r]是否是普通函数调用
vector<int> get_all_comma(int l, int r);//找到input[l, r]作为普通/成员函数调用,参数表中的所有逗号
对应的构造语法树的代码如下:
int build(int l, int r) {
if (l > r) return 0;
//pre-treat : 去掉所有括号
while (is_duplicate_bracket(l, r)) l++, r--;
int node = ++node_cnt, pos = -1;
//特判 priority 5 : 常量
if (l == r) {
memory_pool[node].content = input.substr(l, r - 1 + 1);
memory_pool[node].key_link = input[l];
return node;
}
//priority 1 : 加减符
pos = get_plus_or_minus(l, r);
if (pos != -1) {
memory_pool[node].content = input.substr(l, r - l + 1);
memory_pool[node].key_link = input[pos];
memory_pool[node].add_child(build(l, pos - 1));
memory_pool[node].add_child(build(pos + 1, r));
return node;
}
//priority 2 : 乘除符
pos = get_multi_or_div(l, r);
if (pos != -1) {
memory_pool[node].content = input.substr(l, r - l + 1);
memory_pool[node].key_link = input[pos];
memory_pool[node].add_child(build(l, pos - 1));
memory_pool[node].add_child(build(pos + 1, r));
return node;
}
//priority 3 : 成员函数
pos = get_member_func(l, r);
if (pos != -1) {
memory_pool[node].content = input.substr(l, r - l + 1);
memory_pool[node].key_link = input[pos + 1];//注意字母在'.'后面
memory_pool[node].add_child(build(l, pos - 1));
vector<int> commas = get_all_comma(pos + 3, r - 1);
commas.push_back(r);
//if (commas.empty())
//memory_pool[node].add_child(build(pos + 3, r - 1));
int start = pos + 3;
for (int i = 0; i < commas.size(); ++i) {
memory_pool[node].add_child(build(start, commas[i] - 1));
start = commas[i] + 1;
}
return node;
}
//priority 4 : 普通函数
pos = is_stadard_func(l, r);
if (pos) {
memory_pool[node].content = input.substr(l, r - l + 1);
memory_pool[node].key_link = input[l];
vector<int> commas = get_all_comma(l + 2, r - 1);
commas.push_back(r);
int start = l + 2;
for (int i = 0; i < commas.size(); ++i) {
memory_pool[node].add_child(build(start, commas[i] - 1));
start = commas[i] + 1;
}
return node;
}
return 0;
}
接下来则需要进行后序遍历,来获得每个节点的dfs序,注意只有非叶子结点才能获得dfs序
//后序遍历dfs序部分
void back_trace(int _index) {
for (int i = 0; i < memory_pool[_index].child_index.size(); ++i)
back_trace(memory_pool[_index].child_index[i]);
if (!memory_pool[_index].is_leaf())
memory_pool[_index].dfs_time = ++dfs_cnt, time_table[dfs_cnt] = _index;
};
然后根据后序遍历的dfs序,调用print()函数依次输出。
AC代码
Sol.1 自顶向下
char input[maxn];
int len;
//语法树部分
typedef struct parse_tree_node {
int dfs_time;
char key_link;
int child_index[maxn];
int child_cnt;
}node, *node_ptr;
node memory_pool[maxn];
int node_cnt;//在内存池中开设的节点个数
int root;//语法树根节点的下标
int dfs_cnt;//dfs序的时间戳
int time_table[maxn];//dfs_time memory_pool_index
void clear(node_ptr x) {
int i = 0;
for (i = 0; i < x->child_cnt; ++i)
x->child_index[i] = 0;
x->dfs_time = 0;
x->key_link = '\0';
x->child_cnt = 0;
}
void add_child(node_ptr x, int a) {
x->child_index[x->child_cnt] = a;
x->child_cnt++;
}
int is_leaf(node_ptr x) {
int i = 0;
for (i = 0; i < x->child_cnt; ++i)
if (x->child_index[i])
return 0;
return 1;
}
void print(node_ptr x) {
putchar(x->key_link), putchar(' ');
for (int i = 0; i < x->child_cnt; ++i) {
int ch = x->child_index[i];
if (memory_pool[ch].dfs_time) printf("%d ", memory_pool[ch].dfs_time);
else putchar(memory_pool[ch].key_link), putchar(' ');
}
putchar('\n');
}
//后序遍历dfs序部分
void back_trace(int _index) {
for (int i = 0; i < memory_pool[_index].child_cnt; ++i)
back_trace(memory_pool[_index].child_index[i]);
if (!is_leaf(&memory_pool[_index]))
memory_pool[_index].dfs_time = ++dfs_cnt, time_table[dfs_cnt] = _index;
};
void init() {
for (int i = 1; i <= node_cnt; ++i)
clear(&memory_pool[i]);
node_cnt = root = 0;
dfs_cnt = 0;
}
int is_duplicate_bracket(int l, int r) {
int s[maxn], cnt = 0;
memset(s, 0, sizeof(s));
int ret = 0;
for (int i = l; i < r; ++i) {
if (input[i] == '(') s[cnt] = i, cnt++;
if (input[i] == ')') cnt--, s[cnt] = 0;
}
if (cnt == 1 && s[0] == l) ret = 1;
return ret;
}
int get_plus_or_minus(int l, int r) {
int in = 0;//在多少层括号内,无论正负,只有0才是真的需要的
for (int i = r; i >= l; --i) {
in += input[i] == '(';
in -= input[i] == ')';
if (in) continue;
if (input[i] == '+' || input[i] == '-') return i;
}
return -1;
}
int get_multi_or_div(int l, int r) {
int in = 0;//在多少层括号内,无论正负,只有0才是真的需要的
for (int i = r; i >= l; --i) {
in += input[i] == '(';
in -= input[i] == ')';
if (in) continue;
if (input[i] == '*' || input[i] == '/') return i;
}
return -1;
}
int get_member_func(int l, int r) {
int in = 0;//在多少层括号内,无论正负,只有0才是真的需要的
for (int i = r; i >= l; --i) {
in += input[i] == '(';
in -= input[i] == ')';
if (in) continue;
if (input[i] == '.') return i;
}
return -1;
}
int is_stadard_func(int l, int r) {
if (r - l + 1 <= 3) return 0;
return islower(input[l]) && input[l + 1] == '(' && input[r] == ')';
}
int* get_all_comma(int l, int r) {
int* ret;
ret = (int*)malloc(sizeof(int) * maxn);
memset(ret, -1, sizeof(int) * maxn);
int cnt = 0;
int in = 0;
for (int i = l; i <= r; ++i) {
in += input[i] == '(';
in -= input[i] == ')';
if (in) continue;
if (input[i] == ',') ret[cnt] = i, cnt++;
}
return ret;
}
int build(int l, int r) {
if (l > r) return 0;
//pre-treat : 去掉所有括号
while (is_duplicate_bracket(l, r)) l++, r--;
int node = ++node_cnt, pos = -1;
//特判 priority 5 : 常量
if (l == r) {
memory_pool[node].key_link = input[l];
return node;
}
//priority 1 : 加减符
pos = get_plus_or_minus(l, r);
if (pos != -1) {
memory_pool[node].key_link = input[pos];
add_child(&memory_pool[node], build(l, pos - 1));
add_child(&memory_pool[node], build(pos + 1, r));
return node;
}
//priority 2 : 乘除符
pos = get_multi_or_div(l, r);
if (pos != -1) {
memory_pool[node].key_link = input[pos];
add_child(&memory_pool[node], build(l, pos - 1));
add_child(&memory_pool[node], build(pos + 1, r));
return node;
}
//priority 3 : 成员函数
pos = get_member_func(l, r);
if (pos != -1) {
memory_pool[node].key_link = input[pos + 1];//注意字母在'.'后面
add_child(&memory_pool[node], build(l, pos - 1));
int* commas = get_all_comma(pos + 3, r - 1);
int commas_size = 0;
while (commas[commas_size] >= 0)++commas_size;
commas[commas_size] = r;
commas_size++;
int start = pos + 3, i = 0;
for (i = 0; i < commas_size; ++i) {
add_child(&memory_pool[node], build(start, commas[i] - 1));
start = commas[i] + 1;
}
free(commas);
return node;
}
//priority 4 : 普通函数
pos = is_stadard_func(l, r);
if (pos) {
memory_pool[node].key_link = input[l];
int* commas = get_all_comma(l + 2, r - 1);
int commas_size = 0;
while (commas[commas_size] >= 0)++commas_size;
commas[commas_size] = r;
commas_size++;
int start = l + 2, i = 0;
for (i = 0; i < commas_size; ++i) {
add_child(&memory_pool[node], build(start, commas[i] - 1));
start = commas[i] + 1;
}
free(commas);
return node;
}
return 0;
}
int main() {
while (scanf("%s", input) != EOF) {
init();
len = strlen(input);
root = build(0, len - 1);
back_trace(root);
int i = 0;
for (i = 1; i <= dfs_cnt; ++i)
print(&memory_pool[time_table[i]]);
}
}
Sol.2 自顶向下+栈
char str[114514];
int issub(int l,int r)
{
int i,in=0;
if(str[l]!='('||str[r]!=')')
{
return 0;
}
for(i=l; i<r; i++)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in==0)
{
return 0;
}
}
return 1;
}
int isfun(int l,int r)
{
int ret=0,in=0;
if(islower(str[l])&&str[l+1]=='('&&str[r]==')')
{
ret=1;
}
int i;
for(i=l+1; i<r; i++)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in==0)
{
return 0;
}
}
return ret;
}
int getlst(int L,int R)
{
int i,ret=-1;
int in=0;
for (i=R; i>=L; i--)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in!=0)
{
continue;
}
if(str[i]=='+'||str[i]=='-')
{
return i;
}
if((str[i]=='*'||str[i]=='/')&&ret==-1)
{
ret=i;
}
}
if(ret!=-1)
{
return ret;
}
for (i=R; i>=L; i--)
{
in+=str[i]=='(';
in-=str[i]==')';
if(in!=0)
{
continue;
}
if(str[i]=='.')
{
return i;
}
}
return ret;
}
int q[100005];
int t;
int cnt;
int c(int L,int R)
{
int x,y;
if(L>R)
{
return 0;
}
if(L==R&&islower(str[L]))
{
return -str[L];
}
if(issub(L,R))
{
return c(L+1,R-1);
}
if(isfun(L,R))
{
int num=0,las=L+2,in=0;
int i;
for(i=las; i<=R-1; i++)
{
if(str[i]=='(')
{
in++;
}
if(str[i]==')')
{
in--;
}
if(in!=0)
{
continue;
}
if(str[i]==',')
{
q[++t]=c(las,i-1);
las=i+1;
num++;
}
}
q[++t]=c(las,R-1);
num++;
printf("%c ",str[L]);
for(i=t-num+1; i<=t; i++)
{
if(q[i]<0)
{
printf("%c ",-q[i]);
}
else
{
printf("%d ",q[i]);
}
}
printf("\n");
while(num--)
{
q[t]=0;
t--;
}
return ++cnt;
}
int mid=getlst(L,R);
if(str[mid]=='.')
{
q[++t]=c(L,mid-1);
int num=1,las=mid+3,in=0;
int i;
for(i=las; i<=R-1; i++)
{
if(str[i]=='(')
{
in++;
}
if(str[i]==')')
{
in--;
}
if(in!=0)
{
continue;
}
if(str[i]==',')
{
q[++t]=c(las,i-1);
las=i+1;
num++;
}
}
q[++t]=c(las,R-1);
num++;
printf("%c ",str[mid+1]);
for(i=t-num+1; i<=t; i++)
{
if(q[i]<0)
{
printf("%c ",-q[i]);
}
else
{
printf("%d ",q[i]);
}
}
printf("\n");
while(num--)
{
q[t]=0;
t--;
}
return ++cnt;
}
q[++t]=c(L,mid-1);
q[++t]=(y=c(mid+1,R));
printf("%c ",str[mid]);
if(q[t-1]<0)
{
printf("%c ",-q[t-1]);
}
else
{
printf("%d ",q[t-1]);
}
if(q[t]<0)
{
printf("%c ",-q[t]);
}
else
{
printf("%d ",q[t]);
}
q[t]=0;
t--;
q[t]=0;
t--;
printf("\n");
return ++cnt;
}
int main()
{
scanf("%s",str+1);
int len;
len=strlen(str+1);
c(1,len);
}
Sol.3 爬升法
该做法对应这篇题解,代码也出自这位大佬。
int LINE;//当前行号
struct expr
{
int valid;
int isline;//是行号
int value;//名字或行号
};
struct expr express();
struct expr func(char name,struct expr lhs)//传入函数名,已知的左值,默认左括号也被读了。左值仅当valid的时候才会输出
{
struct expr mem[100];
int top=0;
char next=0;
while(next!=')')//回退时并不改变next。执行函数的参数列表项
{
mem[top]=express();
top++;
next=getchar();//右小括号或逗号
}//结束的时候右小括号已经读过了
printf("%c",name);
if(lhs.valid==1)
{
if(lhs.isline==1)//是行号
{
printf(" %d",lhs.value);
}
else
{
printf(" %c",(char)lhs.value);
}
}
int i;
for(i=0;i<top;i++)
{
if(mem[i].isline==1)//是行号
{
printf(" %d",mem[i].value);
}
else
{
printf(" %c",(char)mem[i].value);
}
}
printf("\n");
LINE++;
struct expr temp;
temp.valid=1;
temp.isline=1;
temp.value=LINE;
return temp;
}
struct expr parse_primary()//处理常量、括号、普通函数。返回行号
{
char next=getchar();
if(next=='(')//左括号
{
struct expr temp=express();
next=getchar();//右括号
return temp;//直接无脑弹出temp
}
else//标识符。函数也在这部分
{
char temp=next;
next=getchar();
if(next=='(')//左括号
{
struct expr nul;
nul.valid=0;
return func(temp,nul);
}
else//一个普通的标识符
{
ungetc(next,stdin);//退回左括号
struct expr ans;
ans.isline=0;
ans.valid=1;
ans.value=temp;
return ans;
}
}
}
struct expr combine(struct expr lhs,char op,struct expr rhs)//处理加减乘除。输出语句,返回行号expr
{
printf("%c ",op);
if(lhs.isline==1)//是行号
{
printf("%d ",lhs.value);
}
else
{
printf("%c ",(char)lhs.value);
}
if(rhs.isline==1)//是行号
{
printf("%d\n",rhs.value);
}
else
{
printf("%c\n",(char)rhs.value);
}
LINE++;
struct expr temp;
temp.valid=1;
temp.isline=1;
temp.value=LINE;
return temp;
}
int prec(char op)//返回指定算符的优先级
{
if(op=='+'||op=='-')
{
return 1;
}
else if(op=='*'||op=='/')
{
return 2;
}
}
int isop(char op)
{
if(op=='+'||op=='-'||op=='*'||op=='/')
{
return 1;
}
else
{
return 0;
}
}
struct expr parse_opg(struct expr lhs,int cerp)//cerp是左部优先级。每次调用这个的时候,下一个总应该是运算符。点运算应该在这里特判。所有的lhs rhs代表行号
{
char peek=getchar();//运算符1,prec是优先级
while(peek=='.')//是个点运算
{
char temp=getchar();
getchar();//左括号
lhs=func(temp,lhs);
peek=getchar();//再来
}
while(isop(peek)&&prec(peek)>=cerp)//peek是二元运算符,peek优先级大于等于左部旧优先级就要规约
{
char op=peek;
struct expr rhs=parse_primary();//右部读一个并解析某个东西3
peek=getchar();//再预读下一个运算符2。比较1和2两个运算符的优先级
while(peek=='.')//是个点运算
{
char temp=getchar();
getchar();//左括号
rhs=func(temp,rhs);
peek=getchar();//再来
}
while(isop(peek)&&prec(peek)>prec(op))//peek是二元运算符且peek优先级大于op优先级。因为赋值只能出现一次,且优先级最低,故无需考虑右结合
{
ungetc(peek,stdin);//这里必须退回运算符2
rhs=parse_opg(rhs,prec(peek));//做右边
peek=getchar();
while(peek=='.')//是个点运算
{
char temp=getchar();
getchar();//左括号
rhs=func(temp,rhs);
peek=getchar();//再来
}
}
lhs=combine(lhs,op,rhs);//表示规约
}
ungetc(peek,stdin);//这里必须退一个后面的字符
return lhs;
}
struct expr express()
{
struct expr lhs=parse_primary();
struct expr ans=parse_opg(lhs,0);
return ans;
}
int main()
{
express();
return 0;
}
Sol.4 自建BNF范式的递归下降法
课下写的时候可以这么做,但是考场上不建议这样。该做法出自另一位不在acwing的大佬。
/********** Formula ***********
* <EXPR> ::= <TERM> {"+"|"-" <TERM>}
* <TERM> ::= <FACTOR> {"*"|"/" <FACTOR>}
* <FACTOR> ::= (<NAME> [<FUNC> | <METHOD> {<METHOD>}]) | ("(" <EXPR> ")" {<METHOD>})
* <FUNC> ::= "(" <EXPR> {"," <EXPR>} ")"
* <METHOD> ::= "." <NAME> <FUNC>
* <NAME> ::= [IDENTIFIER]
*******************************/
#define WF printf("panic at line %d:^^^^^^WRONG FORMAT^^^^^^^^^\n", __LINE__);
#define MAX_EXPR_LENGTH 125
char buf[MAX_EXPR_LENGTH];
char *p_str = buf;
static int count = 0;
#define SAVE (++count)
typedef struct Val {
unsigned char type; // 0: variable, 1: temporary result
union {
char var;
int res;
} val;
} Val;
void disp(const Val *v) {
if (v->type == 0) {
putchar(v->val.var);
} else {
printf("%d", v->val.res);
}
}
Val ValVar(char var) {
Val r;
r.type = 0;
r.val.var = var;
return r;
}
Val ValRes(int res) {
Val r;
r.type = 1;
r.val.res = res;
return r;
}
Val Expr();
Val Term();
Val Factor();
Val Func(char name, const Val* self);
Val Method(const Val *self);
Val Name();
int main()
{
gets(buf);
Expr();
return 0;
}
Val Expr(){
if (!(*p_str)) WF;
Val term = Term();
Val res = term;
while ((*p_str) && ((*p_str) == '+' || (*p_str) == '-')) {
char op = *(p_str++);
term = Term();
printf("%c ", op);
disp(&res); putchar(' ');
disp(&term);
res = ValRes(SAVE);
putchar('\n');
}
return res;
}
Val Term(){
if (!(*p_str)) WF;
Val factor = Factor();
Val res = factor;
while ((*p_str) && ((*p_str) == '*' || (*p_str) == '/')) {
char op = *(p_str++);
factor = Factor();
printf("%c ", op);
disp(&res); putchar(' ');
disp(&factor);
res = ValRes(SAVE);
putchar('\n');
}
return res;
}
Val Factor(){
if (!(*p_str)) WF;
Val ret;
if ((*p_str) == '(') {
p_str++;
Val expr = Expr();
if (!(*p_str) || (*p_str) != ')') WF;
p_str++; // ')'
ret = expr;
} else {
Val name = Name();
if (*p_str && (*p_str) == '(') {ret = Func(name.val.var, NULL);}
else ret = name;
}
while ((*p_str) && (*p_str) == '.') {
ret = Method(&ret);
}
return ret;
}
Val Func(char name, const Val* self){
// self has already calculated
Val *args = malloc(121 * sizeof(Val));
int argc = 0;
if (!(*p_str) || (*p_str) != '(') WF;
p_str++; // '('
if (self != NULL) {
args[argc++] = *self;
}
Val expr = Expr();
args[argc++] = expr;
while (*p_str && (*p_str) == ',') {
p_str++;
expr = Expr();
args[argc++] = expr;
}
if (!(*p_str) || (*p_str) != ')') WF;
p_str++;
printf("%c", name);
for (int i = 0; i < argc; i++) {
putchar(' ');
disp(&args[i]);
}
putchar('\n');
free(args);
return ValRes(SAVE);
}
Val Method(const Val *self){
if (!(*p_str) || (*p_str) != '.') WF;
p_str++;
Val name = Name();
return Func(name.val.var, self);
}
Val Name(){
if (!(*p_str) || (*p_str) < 'a' || (*p_str) > 'z') WF;
char name = *(p_str++);
return ValVar(name);
}
虽然我看不懂,但我大为震撼
虽然我看不懂,但我大为震撼