原文链接:yacc
术语
英文 | 中文 |
The lexical analyzer | 词法分析器 |
Literal characters | 原义字符 |
Specification | 规范/标准 |
nonterminal symbol | 非终止符号 |
In either case | 无论发生何种情况 |
be assumed to | 被认定为 |
potential | 潜在的 |
简介
计算机程序输入通常有一些结构,实际上,每一次电脑程序输入可以被认为是定义了一种“输入语言”来接收。一门输入语言可能像一门变成语言那么复杂,也可能像数字序列那么简单。不幸的是,常见输入设施是受限制的,难以使用的,并且经常检查输入的校验是不严格的。
Yacc提供了一种用来描述计算机程序输入的通用工具。yacc的使用者指定了需要的输入结构,跟被调用的代码一起,作为每个这样接收的结构。yacc把这样一种说明转化为子程序来处理输入过程;经常地,通过这样的子程序可以很方便很合适地用来处理用户应用的大多数流控制场景。
yacc生产的输入子程序可以称为一种用户提供的程序来返回下一个基本输入项。因此,用户可以指定他的输入按照个人输入字符,或者按照更高级别的结构比如说名称或者数字。这个用户提供的程序也可以处理惯用的特性,比如说评论和延续惯例,用来对抗简单的文法规范。
yacc是使用portableC编写的。规范类接收一个非常通用的:LALR(1)语法,使用消除规则。
除了C,APL,Pascal,RATFOR等编译器。yacc也已经被使用在量级更小、更不常见的语言,包括照相机语言,多桌面计算器语言,文档检索系统,公式编辑器调试系统。
0:介绍
yacc为利用计算机程序输入的结构提供了一个通用的工具。yacc使用者准备了一种输入处理的规范,包括描述输入结构的规则,这些规则被识别调用的代码,一个低级别程序来执行基本的输入。yacc接着会生成一个函数来控制输入过程。这个函数,被称为一个解析器,调用用户提供的低级别的输入程序(词法分析器)从输入流挑选出基本项(被称为tokens)。这些tokens根据输入结构规则来组成,称为语法规则;当其中一个规则被识别,然后用户代码提供了这种规则,会执行相应的动作;这些动作拥有返回结果和使用其他动作的值的能力。
yacc由编写的方言的C语言编写,动作和输出的子程序也都是用C语言。此外,yacc的很多语法约定也跟随了C语言。
输入规范的主体是一个语法规则集。每一条规则描述了一个可允许的结构并赋予它一个名字。比如说,一个语法规则可能是如下:
date : month_name day ',' year ;
在这里,date,month_name,day,year代表了输入处理关心的结构;可以推测出,month_name,day,year被定义在else的地方。符号","被单引号修饰,这意味着这个符号会在输入中的字面上出现。冒号和分号仅仅是作为规则中的标点符号,并没有意义来控制输入。因此,合理的定义,像这种输入
July 4, 1776
会跟上述规则比较匹配。
输入处理的一个重要部分由词法分析器来实现。这个用户程序读取输入流,识别低级别结构体,把这些tokens传递给解析器。由于历史原因,一个能被词法分析器识别的结构体称之为终端符号,同时被解析器parser识别的结构体被称为非终止符号。为了避免歧义,终端符号通常作为tokens被引用。
选择词法分析器还是语法规则来识别结构体,其差别是值得被考虑的。比如说,这些规则:
month_name : 'J' 'a' 'n' ;
month_name : 'F' 'e' 'b' ;
. . .
month_name : 'D' 'e' 'c' ;
可以被使用在上面的例子中。词法分析器只会识别单独的字母,并且month_name将会是一个非终止符号。这种低级别的规则会导致浪费时间和空间,也会使得规范复杂化,导致超出yacc的处理能力。通常,词法分析器会识别月份名字,然后返回一个指示表名month_name被看见;在这种情况下,month_name将是一个token。
Literal characters(原义字符)比如说","必须也被传递到词法分析器中,也会被考虑成tokens
规范文件是非常灵活的。它非常可靠简单地添加上面的例子,这样的规则:
date : month '/' day '/' year ;
允许7 / 4 / 1776作为下面July 4, 1776的同义词
在大多数情况下,这个新规则可以被使用最小的代价”运送“到工作系统,并且对于破坏已有的输入的危险性是最小的。
读输入可能不符合规范。这些输入错误采用从左到右扫描的方法,理论上可以尽可能早地被检测到;因此,不只是减少了错误的读和计算的输入数据的几率,而且错误数据通常可以被很快地发现。错误处理,作为被提供的输入规范的一部分,允许错误数据的重新进入,或者跳过错误数据之后继续进行输入处理。
在一些情况下,当制定了一系列规范后,yacc无法生成一个解析器。比如说,规范可能自我矛盾,或者他们需要一个更强力的识别机制来保证yacc可用。前者代表涉及错误;后者经常可以修正通过制定更强大的词法分析器,或者通过重写语法规则。因为yacc无法处理所有可能的规范,它的功能相比起来更倾向于在相似的系统中发挥;而且,这些结构对于yacc来说很难处理,对人类来说处理起来也很难。有些用户已经提出,为他们的输入制定有效的yacc规范的原则,揭示了程序发展的早期的概念或设计错误。
1:基本规范
命名适用于tokens和非终止符号。yacc要求命名被声明成这样。另外,基于第三章讨论的原因,通常包含词法分析器作为规范文件的一部分是可取的;并且包含其他程序可能也是有用的。因此,每个规范文件由三个部分组成:声明,语法规则,和程序。这些部分被用两个百分号"%%"分隔开来标记。(百分号”%“通常被用在yacc规范里面作为一种逃逸字符)
也就是说,一个完整的规范文件长这样
declarations
%%
rules
%%
programs
声明(declarations)部分可能是空的。此外,如果程序(programs)部分被省略了,第二个%%可能也被省略掉;
因此,最小的合法yacc规范是
%%
rules
空白页,退格和新行会被忽略掉,除非这些不会出现在命名或者多字符保留符号里面。注释可能出现在任何命名合法的地方;C语言和PL/I语言中,它们会被/* ... */包裹。
规则rules部分由一个或多个语法规则。一个语法规则有这样的格式:
A : BODY;
A代表一个非终止符的命名,BODY代表零值或者其他命名和字面量的顺序。冒号和分号是yacc的标点符号。
命名可能是任意长度的,并且由字母组成,点号".",下划线"_",还有非初始化数字。大写小写字母是独特的。一个语法规则体可能代表tokens或者非终止符号。
一个字面量包含一个被单引号" ' "。就像C语言,反斜杠"\"在字面量里面是一个逃逸字符,并且所有的C语言逃逸符号是公认的。因此
'\n' newline
'\r' return
'\'' single quote ``'''
'\\' backslash ``\''
'\t' tab
'\b' backspace
'\f' form feed
'\xxx' ``xxx'' in octal
因为很多技术原因,NUL字符('\0'或者0)在语法规则中从不应该被使用。
如果在左手边有很多个相同的语法规则,竖线"|"可以被用来避免重复写左手边。此外,一个规则的尾部的分号可以在一个竖线前被丢弃。因此语法规则
A : B C D ;
A : E F ;
A : G ;
可以给yacc提供成这样
A : B C D
| E F
|
没必要所有的左侧相同的语法规则一起出现在语法规则部分,尽管这样可以让输入变得更可读,并且修改更简单。
如果一个非终止符号匹配到空的字符串string,这种可以被表达成明显的方式:
empty : ;
命名代表了tokens必须被声明;这可以在声明(declarations)部分被书写得最简单。(看第三、五、六章)
%token name1 name2 . . .
每一个在声明(declarations)部分未被定义到的命名name会被假定为一个非终止符号。每一个非终止符号必须出现在至少一个规则的左侧。
对所有的非终端符号来说,第一个被称为起始符号,有特别的重要性。解析器parser被设计来识别开始符号;因此,这个符号代表了被语法规则描述的最长,最通用的结构。默认来说,起始符号是在rules规则模块的第一个语法规则的左侧。在声明(declarations)模块明确声明起始符号很可能,事实上也可取,使用%start 关键词:
%start symbol
parser解析器的输入结束事件由一个特殊的token来通知,称为结束标记。如果这些tokens符号指向(但是不包含)结束标记,形成一个结构来匹配开始符号,解析器parser函数在看到结束标记后返回它的调用者caller;它接收输入。如果结束标记出现在任何其他上下文中,这是一个错误error。
用户提供的词法分析器的工作职责是在合适的时候返回结束标记;看下面的第三章。通常结束标记代表一些合理的明显的I/O状态,例如说"end-of-file"或者"end-of-record"
2: Actions动作
对于每一条语法规则,在每一次输入流识别到一条规则的时候,用户可能会关联上一些动作行为被执行。而且,预期词法分析器会返回tokens的值
一个行为是C语言的一个专用描述,像这种可以做输入和输出的,并且修改外部向量和变量的,叫子程序。一个行为动作被一个或多个声明指定,被花括号包围。例如:
A : '(' B ')'
{ hello( 1, "abc" ); }
XXX : YYY ZZZ
{ printf("a message\n");
flag = 25; }
上述这两种就是包含动作的语法规则。
为了帮助actions和解析器parser的容易交流,动作action声明被轻微修改了。美元符号"$"被用来作为一个yacc上下文的信号。
action动作很常见地给一些值设置伪变量"$$"来返回一个值,action动作可能使用伪变量$1和$2,...,指的是被一个规则rule右侧的部分返回的值,变量是从左到右读取的。因此,如果规则是下面这样
A : B C D ;
比如说,