|
最近又开始做ACM了。原因是,我在学校里面找了份兼职,做ACM兴趣班的助教。 任务是给大一的新生讲一些ACM的入门知识。 虽然讲的东西很基础很简单,但我觉得他们的潜力巨大,很快就能达到跟我一样的水平。。 所以我自己也得补下课啦。 这次我下决心,坚决不做一道水题! 我放弃了原来在POJ账号,转战HOJ,因为这样我才不会为了题目数量而刷水题。 我觉得以前真的很傻逼,老是挑着做一些自己已经会了的题目,还总是抱怨自己的水平一直没提高。 整天刷水题,水平他妈的能提高么。 人都是很懒的,懒得动脑子,只是享受敲完一道水题后ac的感觉,还很有成就感。 但这样子有什么用,遇到难题的时候,始终还是想不出来,还以为是自己脑子不好使。 实际上不是的,只要克服惰性,多思考问题,每个人的脑子都是很好使的。 我挑战的第一道难题,呵呵,对我来说比较难的题,就是这道。 想了一下发现了这个规律: (A*B)%M = ((A%M)*(B%M))%M 演算一下就可以得出这个的。 对与输入中的一个数字,可以分别计算出: A%M A^2 %M A^4 %M ... 然后再相乘取模就行了。 这样二分一下,就很好办了。 这是我自己克服了懒惰动了脑子想出来的!我他妈的很自豪!
#include <stdio.h>
__int64 N, K, A, B, C, d, i, n, ans; #define M 200907
int main() { scanf("%I64d", &N); while (N--) { scanf("%I64d%I64d%I64d%I64d", &A, &B, &C, &K); if (B - A == C - B) { ans = ((A%M) + (((K-1)%M)*((B-A)%M)%M))%M; } else { d = (B/A)%M; n = K - 1; ans = A%M; for (i = 0; (1LL << i) <= n; i++) { if (n & (1LL << i)) ans = (ans*d)%M; d = (d*d)%M; } } printf("%I64d\n", ans); }
return 0; }
apt-get install xfwm4 apt-get install scim-chinese apt-get install roxterm cat >~/.xinitrc <<E roxterm & scim & xfwm4 E xinit
插入买机的时候送的驱动光盘,灰色的那张。 mount /dev/cdrom /media/cdrom cd /tmp find /media/cdrom -name '*.exe' | while read i; do unzip -ao $i; done cd DRIVER_US ndiswrapper -i bcmwl5.inf rmmod b43 rmmod ssb modprobe ndiswrapper iwconfg .....
题意: 给出N条木棍的长度,问能不能用这些木棍拼成一个正方形。
思路: 这题一开始想复杂了,后来发现只要2个最简单的剪枝题目就能79ms通过了。 再加上一个比较屌的剪枝就可以到16ms。 参考了网上的代码,很多份代码如出一辙。思想十分牛逼。
剪枝1: 所有木棍的长度必须能被4整除 剪枝2: 最长的木棍不能长于正方形的边长 这两个是最容易想到的,用上这两个可以79ms通过 剪枝3: 同样长度的木棍的同样数量的组合只搜索一次。 这个剪枝需要将木棍从大到小排列,在搜索的时候加一句代码就行了,代码很巧妙。 由于数据问题,这个剪枝貌似不管用。 剪枝4: 每条边的木棍按照从大到小的顺序拼接 如果某条木棍不能够作为某边的第一条木棍,那比它短的也不行 想一想还是可以理解,后面的边始终会用到这条长的棍子,那时候可供选择的木棍更少 所以在前面的边拼不成,在后面的边更加拼不成 这个剪枝非常牛逼,不知道谁想出来的,代码只需要一句。太牛逼了。 由于数据的N比较小,这个剪枝相当管用。 无法实现的理想化剪枝: 如果在枚举中,发现用一条长的棍子枚举失败,那么几条短的棍子组成同样长度的棍子也必然不用试验。 这个很理想,但是实现的代价很大。
#include <stdio.h> #include <stdlib.h> #include <string.h>
// 1+2 : 79ms // 1+2+3 : 79ms // 1+2+4 : 16ms // 1+2+3+4: 16ms
int part, N; int len[128]; char used[128];
int cmp(const void *a, const void *b) { return *(int *)b - *(int *)a; }
int dfs(int side, int start, int left) { int i;
if (!left) { left = part; side++; start = 0; }
if (side == 4) return 1;
for (i = start; i < N; i++) { if (len[i] > left) continue; if (used[i]) continue;
// 3 if (i && !used[i - 1] && len[i] == len[i - 1]) continue;
used[i] = 1; if (dfs(side, i + 1, left - len[i])) return 1; used[i] = 0;
// 4 if (left == part) return 0; }
return 0; }
int solve() { int i, sum;
scanf("%d", &N); sum = 0; for (i = 0; i < N; i++) { scanf("%d", &len[i]); sum += len[i]; }
// 1 if (sum % 4) return 0;
part = sum / 4; qsort(len, N, sizeof(len[0]), cmp); // 2 if (len[0] > part) return 0;
memset(used, 0, N); return dfs(0, 0, part); }
int main() { int t;
scanf("%d", &t); while (t--) printf("%s\n", solve() ? "yes" : "no");
return 0; }
题目大意: 给出一个玩到一半的汉诺塔。叫你把所有的盘子归到任意一根针上,求最少步数。
由于结果很大,输出它和100000的模。 最基本的思路是动态规划,跟一般的汉诺塔问题类似 位于任意一个状态的汉诺塔。必然有位于最底层的最大的盘子。 最终的目的是把这个盘子移动到某根针上。 所以必然会出现的一个场景就是: 最大的盘子单独在一根针上,其他的盘子在另外一根针上 假设: f(n, idx) = { 将初始状态下盘子 1~n 集中到第idx根针上所需要的最小步数 } g(n) = { 将位于一根针上的盘子 1~n 移动到另外一根针所需要的最小步数 } 那么转移方程就是: f(n, idx) = { 如果盘子n位于idx:f(n - 1, idx) 否则:f(n - 1, idx) } 从普通的汉诺塔问题上可以得出: g(n) = 2^n - 1
这题很难,我只写出了一个TLE的版本。 后来找了解题报告,只找到了一个,就是这位alpc43大牛的版本: http://hi.baidu.com/alpc43/blog/item/95184e03a5fef4e209fa932d.html
这个代码很牛逼! 它的思路是:
1)首先按照常规的方法求出最长公共子序列的长度 也就是用O(MN)的那个动态规划,结果放在二维数组dp里 dp[i][j] = { 字串a的1~i部分与字串b的1~j部分的最长公共子序列的长度 } 2)求辅助数组 last1[i][j] = { 到下标i为止,字符j在字串a中最后一次出现的下标 } last2[i][j] = { 到下标i为止,字符j在字串b中最后一次出现的下标 } 3)枚举最长公共字串的每一个字符 从最后一个字符开始枚举 比如说现在枚举最后一个字符是'C'的情况。 那么 'CDCD' 与 'FUCKC' 这两个字串。 一共有 (0, 2) (0, 4) (2, 2) (2. 4) 这四种可能。 很明显前三个是可以舍弃的,因为第四个优于前三个,为后续的枚举提供了更大的空间。 last数组正好是用来做这个的。 4)排序输出 代码里用了stl的set。 注意,由于刚刚的枚举过程是针对每个字符,所以是不用判重的。 这个思路非常之牛逼!
#include <stdio.h> #include <string.h> #include <math.h> #include <string> #include <set>;
using namespace std;
const int MAXLEN=100; char s1[MAXLEN]; char s2[MAXLEN]; int len1,len2; int dp[MAXLEN][MAXLEN]; int last1[MAXLEN][27]; int last2[MAXLEN][27]; int longest; char temp[MAXLEN]; set<string> SET; void input() { scanf("%s %s",&s1[1],&s2[1]);
} inline int maxab(int a,int b) { if(a>b) return a; return b; }
inline void find(int x,int y,int len) { if(len<=0) { //printf("%s\n",&temp[1]); SET.insert(&temp[1]); return ; } int i,j; if(x>0 && y>0) { for(i=0;i<26;i++) { int t1=last1[x][i]; int t2=last2[y][i]; if(dp[t1][t2]==len) { temp[len]='a'+i; find(t1-1,t2-1,len-1); } } } } void solve() { int i,j,k; len1=strlen(&s1[1]); len2=strlen(&s2[1]); for(i=0;i<=len1;i++) dp[i][0]=0; for(i=0;i<=len2;i++) dp[0][i]=0; for(i=1;i<=len1;i++) for(j=1;j<=len2;j++) { if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=maxab(dp[i-1][j],dp[i][j-1]); } longest=dp[len1][len2];
for(j=0;j<26;j++) for(i=0;i<=len1;i++) last1[i][j]=0; for(j=0;j<26;j++) for(i=0;i<=len2;i++) last2[i][j]=0; for(i=1;i<=len1;i++) { for(j=0;j<26;j++) { if(s1[i]=='a'+j) last1[i][j]=i; else last1[i][j]=last1[i-1][j]; } } for(i=1;i<=len2;i++) { for(j=0;j<26;j++) { if(s2[i]=='a'+j) last2[i][j]=i; else last2[i][j]=last2[i-1][j]; } } temp[longest+1]='\0'; find(len1,len2,longest); set<string>::iterator it; for(it=SET.begin();it!=SET.end();it++) { printf("%s\n",(*it).c_str()); } } int main() { freopen("e:\\in.txt", "r", stdin);
input(); solve(); return 0; }
大学毕业时,爸说:“你一定要念一个硕士学位。不用念博士,可是硕士是一定要的。”
为什么“硕士是一定要的”?我没问。爸爸对我的要求非常少,所以一旦他开口了,我都很“上道”的照单全收,当然,也因为硕士大都很容易念,选个容易的科目,常常可以在九个月内就拿到硕士。
博士就麻烦得多,要是不幸遇上贪图廉价人工的指导教授,想把研究生一直留在身边帮忙,那一个博士学位耗掉你十年以上,也是常有的事。 所以我就很安然的接受了爸的指示。
“没问题,一个硕士。”我很有精神的覆诵一次,好像柜台后的日本料理师傅。
“而且要念一流的学校。”爸进行第二阶段的指示。
“没问题,一流学校。”师傅覆诵客人点的第二道菜。
我当然很同意“念一流学校”的想法。我在大学四年,整天听我有学问的好友阿笔,不断告诉我西方最厉害的几间大学,到底都厉害在什么地方:柏克莱待了多少个得过诺贝尔奖的物理学家、约翰霍普金斯大学的医学院又完成了什么手术、德国的法学博士和美国的有何不同、牛津的研究生吃晚饭时要穿什么、康乃尔的研究生为什么自杀比例最高……聊的都是这一类的事情。
对于在台湾各种烂学校混了十几年的我们来说,没事就把这些知识神殿的名字,在牙齿之间盘弄一番,实在是个方便又悲伤的娱乐。 就像两个台湾的初中男生,翻看着“花花公子”杂志拉页上的金发兔女郎。夹杂着向往和民族的自卑。
爸对学位的指示,已经清楚收到。“一流学校、硕士就好”。
轮到我对爸开出条件了。
有风格的料理师傅,是不会任凭客人想点什么、就做什么的。客人可以要求吃生鱼片,可是有风格的师夫,会决定此刻最适合做生鱼片的,是哪一种鱼。也就是说,你点归你点,未必吃得到。
“爸,我只念我想念的东西喔。”
“可以,不要念太多就好。”
爽快。这是爸跟我随着岁月培养出来的默契。各取所需,互蒙其利。
不过,老实说,“我取我需”的状况,似乎比“爸取爸需”的状况,要多那么一两百次吧。
我想念的东西,对一般的台湾爸妈来说,似乎有点怪。
我想学“舞台剧”。
还好我爸不是“一般的台湾爸妈”。
从小到大,爸从来没问过我:“这有什么用?”
“这有什么用?”几乎是我们这个岛上,最受欢迎的一个问题。每个人都好像上好发条的娃娃,你只要拍他的后脑一下,他就理直气壮的问:“这有什么用?”
“我想学舞台剧。”“这有什么用?”
“我正在读《追忆似水年华》。”“这有什么用?”
“我会弹巴哈了。”“这有什么用?”
“我会辨认楝树了。”“这有什么用?”
这是我最不习惯回答的问题,因为我没被我爸问过这个问题。
从小,我就眼睁睁看着爸妈做很多“一点用也没有”的事情。爸买回家里一件又一件动不动就摔破的瓷器水晶;妈叫裁缝来家里量制一件又一件繁复的旗袍;一桌又一桌吃完就没有的大菜;一圈又一圈堆倒又砌好的麻将,从来没有半个人会问:“这有什么用?”
“漂不漂亮?”“喜不喜欢?”“好不好吃?”这些才是整天会被问到的问题。
长大以后,越来越常被别人问:“这有什么用?”才忽然领悟很多人,是随着这个问题一起长大的。
我不大确定——这是不是值得庆幸的事。一直到,反复确认了“人生最重要的东西,其实都没有什么用”时,才觉得自己运气真好。
人生,并不是拿来用的。
爱情,光荣,正义,尊严,文明,这些一再在灰黯时刻拯救我、安慰我的力量,对很多人来讲“没有用”,我却坚持相信这才都是人生的珍宝,才禁得起反复追求。
Dinic算法是一种比较容易实现的,相对比较快的最大流算法。 今天看了一下它的原理,发现的确很牛逼。
求最大流的本质,就是不停的寻找增广路径。直到找不到增广路径为止。 对于这个一般性的过程,Dinic算法的优化如下:
(1) Dinic算法首先对图进行一次BFS,然后在BFS生成的层次图中进行多次DFS。 层次图的意思就是,只有在BFS树中深度相差1的节点才是连接的。 这就切断了原有的图中的许多不必要的连接。很牛逼! 这是需要证明的,估计证明也很复杂。
(2) 除此之外,每次DFS完后,会找到路径中容量最小的一条边。 在这条边之前的路径的容量是大于等于这条边的容量的。 那么从这条边之前的点,可能引发出别的增广路径。 比如说 S -> b -> c -> d -> T 是一条增广路径,容量最小的边是 b -> c。 可能存在一条 S -> b -> e -> f -> g -> T 这样的增广路径。 这样的话,在找到第一条增广路径后,只需要回溯到 b 点,就可以继续找下去了。 这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。 也就是再次从 S 寻找到 b 的开销。 这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!
(3) 在同一次 DFS 中。如果从一个点引发不出任何的增广路径,就将这个点在层次图中抹去。 而这样一个算法,实现起来居然只需要100行。太吊了。 我的代码是参考别人的代码写的。可以用 POJ 1273 测试。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h>
#define MAX_VETXS 1024 #define MAX_EDGES 1024
int E[MAX_EDGES], next[MAX_EDGES], C[MAX_EDGES], M; int V[MAX_VETXS], L[MAX_VETXS], Q[MAX_VETXS], N; int S, T;
void __insert(int from, int to, int cap) { M++; C[M] = cap; E[M] = to; next[M] = V[from]; V[from] = M; }
void insert(int from, int to, int cap) { __insert(from, to, cap); __insert(to, from, 0); }
int bfs() { int h, t, e, u, v;
h = t = 0; Q[t++] = S; memset(L, 0, N*sizeof(L[0])); L[S] = 1; while (h != t) { u = Q[h++]; for (e = V[u]; e; e = next[e]) { v = E[e]; if (!L[v] && C[e] > 0) { L[v] = L[u] + 1; Q[t++] = v; } } }
return L[T]; }
int dfs() { int t, u, v, e, i, f, r, back;
t = 1; r = 0;
while (t) { u = (t == 1) ? S : E[Q[t - 1]]; if (u == T) { f = INT_MAX; for (i = 1; i < t; i++) { e = Q[i]; if (C[e] < f) { f = C[e]; back = i; } } for (i = 1; i < t; i++) { e = Q[i]; C[e] -= f; C[e^1] += f; } r += f; t = back; } else { for (e = V[u]; e; e = next[e]) { v = E[e]; if (L[v] == L[u] + 1 && C[e] > 0) break; } if (e) Q[t++] = e; else { t--; L[u] = 0; } } }
return r; }
int dinic() { int f = 0;
while (bfs()) f += dfs();
return f; }
int main() { int n, m, a, b, c, i;
freopen("d:\\in.txt", "r", stdin);
while (scanf("%d%d", &n, &m) != EOF) { S = 0; T = m - 1; N = m; memset(V, 0, N*sizeof(V[0])); M = 2; for (i = 0; i < n; i++) { scanf("%d%d%d", &a, &b, &c); insert(a - 1, b - 1, c); } printf("%d\n", dinic()); }
return 0; }
语法分析 - 后台运行、管道、重定向
--- 后台运行 我们从上一节提到的入口点 inputunit 看起。
inputunit: simple_list simple_list_terminator ... ;
simple_list: simple_list1 | simple_list1 '&' | simple_list1 ';' ;
simple_list1: simple_list1 AND_AND newline_list simple_list1 | simple_list1 OR_OR newline_list simple_list1 | simple_list1 '&' simple_list1 | simple_list1 ';' simple_list1 | pipeline_command ;
这几句语法的功能,就是平时很常用的: check_ok && do_sth file_exists || create_it firefox & do_a; do_b; do_c; do_d
--- 管道 来看一下 pipe_command
pipeline_command: pipeline | BANG pipeline ... ;
pipeline: pipeline '|' newline_list pipeline | command ;
newline_list: | newline_list '\n' ;
BANG 对应的符号是 '!' 这里把 BANG 和 pipeline 放到一起并不是说明 '!' 和管道有什么关系。 只是在这里实现 '!' 这个符号的功能而已。
--- command_connect() 我们注意到,在语法的处理函数中,command_connect 这个函数被经常使用。
COMMAND * command_connect (com1, com2, connector) COMMAND *com1, *com2; int connector; { CONNECTION *temp;
temp = (CONNECTION *)xmalloc (sizeof (CONNECTION)); temp->connector = connector; temp->first = com1; temp->second = com2; return (make_command (cm_connection, (SIMPLE_COM *)temp)); } 这个函数的作用就是把两个相关的语法树节点连接起来,并构成一个新的节点。 而 COMMAND 这个数据结构,里面就包含了指向两个孩子的指针,以及跟连接相关的属性。 这里我们先不去详细的看它。
--- 重定向 从 pipeline 引出了 command 。
command: simple_command | shell_command | shell_command redirection_list { COMMAND *tc;
tc = $1; if (tc->redirects) { register REDIRECT *t; for (t = tc->redirects; t->next; t = t->next) ; t->next = $2; } else tc->redirects = $2; $$ = $1; } | function_def ;
redirection_list: redirection | redirection_list redirection ;
这个项应该就是传说中的,单项命令的实体了。 我们暂时不去理会其他的东西,先看一看 redirection_list。 那一段处理函数可以看出,它把一系列的重定向操作加入到 shell_command 的 redirects 链表尾部。 而 redirection_list 包含的内容就比较多了,也就是重定向的所有语法啦。
redirection: '>' WORD // > xxx | '<' WORD // < xxx | NUMBER '>' WORD // 1> xxx | NUMBER '<' WORD // 0< xxx | GREATER_GREATER WORD // >> xxx | NUMBER GREATER_GREATER WORD // 2>> xxx | LESS_LESS WORD // << xxx | NUMBER LESS_LESS WORD // 0<< xxx | LESS_LESS_LESS WORD // <<< xxx | NUMBER LESS_LESS_LESS WORD // 0<<< xxx | LESS_AND NUMBER // <&2 | NUMBER LESS_AND NUMBER // 1<&2 | GREATER_AND NUMBER // >&1 | NUMBER GREATER_AND NUMBER // 2>&1 | LESS_AND WORD // <& xxx | NUMBER LESS_AND WORD // 1<& xxx | GREATER_AND WORD // >& xxx | NUMBER GREATER_AND WORD // 1>& xxx | LESS_LESS_MINUS WORD // <<- xxx | NUMBER LESS_LESS_MINUS WORD // 1 <<- xxx | GREATER_AND '-' // >&- | NUMBER GREATER_AND '-' // 1>&- | LESS_AND '-' // <&- | NUMBER LESS_AND '-' // 1<&- | AND_GREATER WORD // &> xxx | NUMBER LESS_GREATER WORD // 1<> xxx | LESS_GREATER WORD // <> xxx | GREATER_BAR WORD // >| xxx | NUMBER GREATER_BAR WORD // 1>| xxx ;
可见,真的是十分之多阿,每一条后面我都加了注释。 平时常用的基本只有几种了,有一部分是《bash高级编程》里面提到的, 有些就是根本没提到,完全没见过的用法。。 现在我们先不去深究这些用法。
语法分析 - 入口点
--- main() 我们打开shell.c的main函数,大概300来行,其主题都是围绕这xxx_init,做各种初始化操作。 我们可以略过不看,等遇到问题的时候再说。把目光放到最后一句 reader_loop()。这是一个循环读 入并执行命令的函数。
--- reader_loop() 位于eval.c的reader_loop()函数,其中仿佛只有调用read_command()是重点。
--- read_command() 同样位于eval.c的read_command()函数。一开始那一段ALARM信号的处理让人觉得很费解,难道 在bash输入命令还要有时间限制吗?无论如何,这种看似偏门的、非关键性的东西,在代码分析的初期 是不能理会的,如果太深究这些东西,没有把握代码的主线,则会走入死胡同,而且会失去源码分析 的乐趣。 代码主线走入parse_command()函数。
--- parse_command() 同样位于eval.c的parse_command()函数。它调用的yyparse()函数是语法分析的开始。 用过yacc的人很明白这一点了。一开始我们看到文件列表中有y.tab.c这样的文件,就能意识到bash也是 利用yacc生成的代码来完成语法分析的。
--- Yacc的作用 你只要告诉yacc三样东西:语法、每一条语法的处理函数、负责词法分析的函数 yacc就会为你生成y.tab.c文件,只要调用这个文件中的yyparse()函数,就可以完成编译器的 词法分析和语法分析的部分了。在分析的过程中,你刚刚指定的每一条语法对应的处理函数也会 被调用。关于yacc的具体介绍,可以在网上搜搜,很多的。
例子: 告诉yacc:语法和对应的处理函数。 expr : expr '+' expr { $$ = add($1, $3) } | expr '*' expr { $$ = mul($1, $3) } | expr '-' expr { $$ = sub($1, $3) } | NUMBER ; 调用yyparse(),输入 1 + 2 add(1, 2) 就会被回调了 在处理函数中 $$ 代表着处理函数的返回值 $1 代表着该条语法中的第一个元素(expr) $2 代表着该条语法中的第二个元素('+') $3 代表着该条语法中的第三个元素(expr) 至于说这些元素的类型,则会在前面定义。比如 %type<char *> expr 之类。 具体的还是找篇文章看看吧。
--- parse.y 观察Makefile可以发现: y.tab.c y.tab.h: parse.y $(YACC) -d $(srcdir)/parse.y y.tab.c是由parse.y生成的。而parse.y中包含了语法和对应的处理函数,它是语法分析的核心文件。
首先是一个%union定义 %union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT element; PATTERN_LIST *pattern; }
然后是一系列的token定义:
/* Reserved words. Members of the first group are only recognized in the case that they are preceded by a list_terminator. Members of the second group are for [[...]] commands. Members of the third group are recognized only under special circumstances. */ %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION %token COND_START COND_END COND_ERROR %token IN BANG TIME TIMEOPT
/* More general tokens. yylex () knows how to make these. */ %token <word> WORD ASSIGNMENT_WORD %token <number> NUMBER %token <word_list> ARITH_CMD ARITH_FOR_EXPRS %token <command> COND_CMD %token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS %token GREATER_AND SEMI_SEMI LESS_LESS_MINUS AND_GREATER LESS_GREATER %token GREATER_BAR
读入字符串流,返回token是词法分析函数的责任。 以%token定义,表明返回值是int类型 以%token <word>定义,表明返回值是%union中对应的类型
词法分析函数是lex生成的,但这个工程好像把原始的 .lex文件删除了。我们只能看到生成后的yylex()函数。 但有一个表,可以看出token对应的字串内容:
/* Reserved words. These are only recognized as the first word of a command. */ STRING_INT_ALIST word_token_alist[] = { { "if", IF }, { "then", THEN }, { "else", ELSE }, { "elif", ELIF }, { "fi", FI }, { "case", CASE }, { "esac", ESAC }, { "for", FOR }, #if defined (SELECT_COMMAND) { "select", SELECT }, #endif { "while", WHILE }, { "until", UNTIL }, { "do", DO }, { "done", DONE }, { "in", IN }, { "function", FUNCTION }, #if defined (COMMAND_TIMING) { "time", TIME }, #endif { "{", '{' }, { "}", '}' }, { "!", BANG }, #if defined (COND_COMMAND) { "[[", COND_START }, { "]]", COND_END }, #endif { (char *)NULL, 0} };
/* other tokens that can be returned by read_token() */ STRING_INT_ALIST other_token_alist[] = { /* Multiple-character tokens with special values */ { "-p", TIMEOPT }, { "&&", AND_AND }, { "||", OR_OR }, { ">>", GREATER_GREATER }, { "<<", LESS_LESS }, { "<&", LESS_AND }, { ">&", GREATER_AND }, { ";;", SEMI_SEMI }, { "<<-", LESS_LESS_MINUS }, { "<<<", LESS_LESS_LESS }, { "&>", AND_GREATER }, { "<>", LESS_GREATER }, { ">|", GREATER_BAR }, { "EOF", yacc_EOF }, /* Tokens whose value is the character itself */ { ">", '>' }, { "<", '<' }, { "-", '-' }, { "{", '{' }, { "}", '}' }, { ";", ';' }, { "(", '(' }, { ")", ')' }, { "|", '|' }, { "&", '&' }, { "newline", '\n' }, { (char *)NULL, 0} };
/* others not listed here: WORD look at yylval.word ASSIGNMENT_WORD look at yylval.word NUMBER look at yylval.number ARITH_CMD look at yylval.word_list ARITH_FOR_EXPRS look at yylval.word_list COND_CMD look at yylval.command */
这些token在语法中会遇到的。
接下来是对语法中每一项内容(编译原理没学好,不知道这个术语叫什么。。)的定义:
/* The types that the various syntactical units return. */
%type <command> inputunit command pipeline pipeline_command %type <command> list list0 list1 compound_list simple_list simple_list1 %type <command> simple_command shell_command %type <command> for_command select_command case_command group_command %type <command> arith_command %type <command> cond_command %type <command> arith_for_command %type <command> function_def function_body if_command elif_clause subshell %type <redirect> redirection redirection_list %type <element> simple_command_element %type <word_list> word_list pattern %type <pattern> pattern_list case_clause_sequence case_clause %type <number> timespec %type <number> list_terminator
%start inputunit
从名字上来看,大概能知道是作什么的。 %start 表示整个语法分析的入口是 inputunit 那一项。 接着就是语法了,内容就比较多,不直接贴了。 语法是我比较感兴趣的地方,无论看哪本关于bash的书,都不如看代码来的直接,呵呵。 我们以后慢慢看。
|