V. GETTING STARTED -- A SMALL EXAMPLE
V. 现在开始-一个简单例子
这一章的目的是教您怎样在一个语言开发项目中使用PCYACC,为了达到这个目标,我们假定您熟悉C语言。我们同样假定您有一份ABRAXAS PCYACC和一个C语言编译器(C compiler)。
这一章给你使用PCYACC的程序开发流程的概述。本章的例子是一个简单的计算器,它能够做普通算术操作。这个例子将会向您展示它-我们暂时定名为SACALC(simple arithmetic calculator program)的PCYACC程序清单,同时举例说明怎样用PCYACC和C编译器建立可执行的SACALC,在稍后的一章中我们还会用一个稍微高级一点的例子来详细描述开发过程。
1.SACALC的语法描述文件(Grammar Description File)
下面是这个简单计算器例子的GDF代码文件:SACALC.Y,为了引用的方便,我们在其中加入了行号(请注意,行号不能出现在您的GDL文件中)
01: %{
02:
03: #define YYSTYPE double /* stack data type */
04:
05: %}
06:
07: %token NUMBER
08: %left '+' '-' /* left associative */
09: %left '*' '/' /* left associative */
10: %left UNARYMINUS
11:
12: %%
13:
14: list: /* nothing */
15: | list '\n'
16: | list expr '\n'
17: { printf("\t%.8g\n", $2); }
18: | list error '\n'
19: { yyerrok; }
20: ;
21:
22: expr: NUMBER
23: { $$ = $1; }
24: | '-' expr %prec UNARYMINUS
25: { $$ = -$2; }
26: | expr '+' expr
27: { $$ = $1 + $3; }
28: | expr '-' expr
29: { $$ = $1 - $3; }
30: | expr '*' expr
31: { $$ = $1 * $3; }
32: | expr '/' expr
33: { $$ = $1 / $3; }
34: | '(' expr ')'
35: { $$ = $2; }
36: ;
37:
38: %%
39:
40: #include <stdio.h>
41: #include <ctype.h>
42: char *progname; /* for error messages */
43: int lineno = 1;
44:
45: main(argc, argv)
46: char *argv[];
47: {
48: progname = argv[0];
49: yyparse();
50: }
51:
52: yylex()
53: {
54: int c;
55:
56: while ((c=getchar()) == ' ' || c == '\t' );
57:
58: if (c == EOF)
59: return 0;
60: if (c == '.' || isdigit(c)) { /* number */
61: ungetc(c, stdin);
62: scanf("%lf", &yylval);
63: return NUMBER;
64: }
65: if (c == '\n')
66: lineno++;
67: return c;
68: }
69:
70: yyerror(s) /* called on syntax error */
71: char *s;
72: {
73: warning (s, (char *) 0);
74: }
75:
76: warning(s, t) /* print warning message */
77: char *s, *t;
78: {
79: fprintf(stderr, "%s: %s", progname, s);
80: if (t) fprintf(stderr, " %s", t);
81: fprintf(stderr, " near line %d\n", lineno);
82: }
83:
这个短小的例子,展示了一个PCYACC语法描述程序(grammar description program)的典型结构和组成部分,line1到11称作定义段(declaration section),符号、操作符优先级等等就是定义在这里。line12到37组成了文法规则段(grammar rule section),开发和放置这个语言的文法规则。line38到83是程序段(program section),这里放置这个编译器的C支持代码。就像这个例子中看到的,一个语法描述程序由三部分组成:定义段、文法规则段和程序段。其中的定义段、程序段可以为空(没有定义段时,分隔符"%%"仍然需要,它告诉PCYACC开始处理文法规则段)。
成对出现在line1和line5的符号是一对分隔符,在定义段中,它们用来包含C的声明,例如预处理指令、全局数据结构定义或者变量声明(在本例子中,就是一个预处理指令。)PCYACC不会查看在此分隔符里面的代码。在分隔符里面的任何东西都将毫无变化的传递给C程序(即产生的编译器代码)
line7声明了NUMBER为一个记号(token),token是一种语法符号,它不能用在文法规则的左边。(当yylex被调用的时候,我们假设yylex返回它发现的token的类型。NUMBER要被定义在产生代码里面的一个"#define"语句中。如果yylex不是产生程序的一部分,它还可以被放进一个头文件中。当yylex被调用的时候,产生的分析器(the generated parser)期望收到token的类型,例如NUMBER和标志文件结尾的0。)
8到10行定义了和算子(the arithmetic operators)有关的结合式(associativity)。所有的算子被定义成左结合的(比如,在a+b+c这个式子中,a+b会被首先计算)。这些式子同样传达了以下信息:加(+)和减(-)有同样的优先级;乘(*)和除(/)有同样的优先级并且高于加和减;负号,这个一元算子,有最高的优先级。
12行是一个分隔符,它隔开了定义段和文法规则段,
14行到20行可以简单记做以下的四个规则:
(1) list : ;
(2) list : list '\n' ;
(3) list : list expr '\n' ;
(4) list : list error '\n' ;
这些规则说:一个list不仅可以是空的(1),而且可以是一个list后面跟着一个换行符号('\n')(2),还可以是一个list后面跟着一个表达式(expr)又一个换行符(3),又或者是一个list后面跟着什么错误之类的(error)(4)。在简单符号中,冒号(:)是用来分隔开文法规则的左部的。竖线(|)是用来分隔开一个普通的左边非终结符,或者非终结符的可选项。分号(;)则用来结束这条文法规则。在list文法规则中,17行和19行,都是附加的指令,叫做行为(actions)。类似的,22行到36行定义了expressions的文法规则。
第二个"%%"分隔符隔开了文法规则段(grammar rule section)和程序段(program section).在程序断中的所有东西也被拷贝到PCYACC的输出文件中。这个段定义了三个C函数:main(),yylex(),yyerror().记住在PCYACC生成的支持产生式分析器中,这三个C函数总是需要的。(它们可以在不同的文件中,只是简单地在程序发生时链接)。下面地讨论会帮助你明白:怎样将PCYACC的产生代码和程序员用编写的支持函数结合在一起,组成一个完整的C程序。
PCYACC的产生代码是一个C函数:yyparse(),在技术上,它是一个GDF文件中定义的语言的文法规则(grammar rule)的LALR分析器(LALR parser),用户默认的main函数:main(),是用来激活分析器的,激活的执行前需要初始化,在激活后需要释放。词法分析器:yylex(),是在分析器前后出现的(is the front end of the parser).词法分析器(the lexical analyzer)被认为是分解自然文本字符串为有意义的词汇单元的,这个词汇单元就叫做tokens,同时把这个信息传递给parser.错误处理过程:yyerror(),当parser解析的过程中一个句法错误(syntax error)未被发现时调用此函数。这四个函数的关系在下图中可以看到:
这三个段:定义段、文法规则段、程序段,还会在后面的章节中更多的讨论到。
2.建立能够执行的SACALC
调用PCYACC去解析语法描述文件SACALC.Y,使用以下的命令:
PCYACC SACALC.Y<ENTER>
这个操作的结果是一个C程序文件SACALC.C,它被创建在当前目录下,这个是计算器的C代码,要获得SACALC.C的执行版本,调用C的编译器如下所示(假定这里是MICROSOFT C compiler)
CL SACALC.C<ENTER>
此操作将产生目标文件SACALC.OBJ,现在使用LINK命令产生真正的MSDOS工具:
LINK SACALC.OBJ<ENTER>
(注意:这里的"make"文件,SACALC.MAK,可以在MSDOS下被调用,以自动生成执行码。如果你使用不同的C compiler,你需要遵守你的编译器的使用方法去建立一个应用程序,这里的应用程序有很多含义,可能是一个对话框,接受输入同时显示结果)。
3.SACALC使用的例子
当SACALC的可执行代码成功建立后(在我们的例子中,我们就有了一个SACALC在当前目录下),我们可以使用它去执行简单的算术运算。下面是SACALC工作的例子:
SACALC <ENTER>
1 + 2 <ENTER>
3
2.5 + 4 * 1.5 <ENTER>
8.5
2 / 4 - 3 * 0.5 / 3 + 5 <ENTER>
5