\input texinfo @c -*-texinfo-*-
@comment %**start of header
@setfilename bison.info
@include version.texi
@settitle Bison @value{VERSION}
@setchapternewpage odd

@finalout

@c SMALL BOOK version
@c This edition has been formatted so that you can format and print it in
@c the smallbook format.
@c @smallbook

@c Set following if you have the new `shorttitlepage' command
@c @clear shorttitlepage-enabled
@c @set shorttitlepage-enabled

@c Set following if you want to document %default-prec and %no-default-prec.
@c This feature is experimental and may change in future Bison versions.
@c @set defaultprec

@c ISPELL CHECK: done, 14 Jan 1993 --bob

@c Check COPYRIGHT dates.  should be updated in the titlepage, ifinfo
@c titlepage; should NOT be changed in the GPL.  --mew

@c FIXME: I don't understand this `iftex'.  Obsolete? --akim.
@iftex
@syncodeindex fn cp
@syncodeindex vr cp
@syncodeindex tp cp
@end iftex
@ifinfo
@synindex fn cp
@synindex vr cp
@synindex tp cp
@end ifinfo
@comment %**end of header

@copying

这个手册是针对@acronym{GNU} Bison (版本@value{VERSION},@value{UPDATED}),
@acronym{GNU}分析器生成器.

Copyright @copyright{} 1988, 1989, 1990, 1991, 1992, 1993, 1995, 1998,
1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.

Chinese(zh_CN) translation:Xiao Wang

@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the @acronym{GNU} Free Documentation License,
Version 1.1 or any later version published by the Free Software
Foundation; with no Invariant Sections, with the Front-Cover texts
being ``A @acronym{GNU} Manual,'' and with the Back-Cover Texts as in
(a) below.  A copy of the license is included in the section entitled
``@acronym{GNU} Free Documentation License.''

(a) The @acronym{FSF}'s Back-Cover Text is: ``You have freedom to copy
and modify this @acronym{GNU} Manual, like @acronym{GNU} software.
Copies published by the Free Software Foundation raise funds for
@acronym{GNU} development.''
@end quotation
@end copying

@dircategory GNU programming tools
@direntry
* bison: (bison).       @acronym{GNU} parser generator (Yacc replacement).
@end direntry

@ifset shorttitlepage-enabled
@shorttitlepage Bison
@end ifset
@titlepage
@title Bison
@subtitle The Yacc-compatible Parser Generator
@subtitle @value{UPDATED}, Bison Version @value{VERSION}

@author by Charles Donnelly and Richard Stallman
@author translated(zh_CN) by Xiao Wang

@page
@vskip 0pt plus 1filll
@insertcopying
@sp 2
Published by the Free Software Foundation @*
59 Temple Place, Suite 330 @*
Boston, MA  02111-1307  USA @*
Printed copies are available from the Free Software Foundation.@*
@acronym{ISBN} 1-882114-44-2
@sp 2
Cover art by Etienne Suvasa.
@end titlepage

@contents

@ifnottex
@node Top
@top Bison
@insertcopying
@end ifnottex

@menu
* 介绍(Introduction):Introduction.
* 使用条件(Conditions):Conditions.
* 版权(Copying):Copying.           @acronym{GNU} General Public License
                             说明了你如何使用和共享Bison

教学章节:
* 概念(Concepts):Concepts.          理解Bison的基本概念.
* 例子(Examples):Examples.          三个详细解释的使用Binson的例子.

参考章节:
* 语法文件(Grammar File):Grammar File.      编写Bison声明和规则.
* 接口(Interface):Interface.         分析器函数@code{yyparse}的C语言接口.
* 算法(Algorithm):Algorithm.         Bison分析器运行时如何工作.
* 错误恢复(Error Recovery):Error Recovery.    编写错误恢复规则.
* 上下文依赖(Context Dependency):Context Dependency.  如果你的语言的语法过于凌乱以至于Bison不能直接处理,你该怎么做.
* 调试(Debugging):Debugging.         理解或调试Bison分析器.
* 调用(Invocation):Invocation.        如何运行Bison(来生成分析源文件).
* 符号表(Table of Symbols):Table of Symbols.  解释所有Bison语言的关键字.
* 词汇表(Glossary):Glossary.          解释基本概念.
* 问题与答案(FAQ):FAQ.               常见问题
* 复制这个手册(Copying This Manual):Copying This Manual.  复制这个手册的许可
* 索引(Index):Index.             文本交叉索引

@detailmenu
 --- 详细节点列表 ---

Bison的基本概念-The Concepts of Bison

* 语言与文法(Language and Grammar):Language and Grammar.  从数学的概念来介绍语言和上下文无关文法.
* Bison中的语法(Grammar in Bison):Grammar in Bison.  怎样用Bison的语法表示各种文法.
* 语义值(Semantic Values):Semantic Values.   每个记号或者语法组可有一个语义值(例如:整数的数值,标识符的名称等等)
* 语义动作(Semantic Actions):Semantic Actions.  每个规则可有一个包含C代码的动作
* GLR分析器(GLR Parsers):GLR Parsers.       为普通的上下文无关文法编写分析器
* 位置概述(Locations Overview):Locations Overview.    追踪位置
* Biosn的分析器(Bison Parser):Bison Parser.      Bison的输入和输出是什么,怎样使用Bison的输出
* 流程:Stages.            编写和运行Bison语法分析程序的流程.
* 语法文件的布局(Grammar Layout):Grammar Layout.    Bison语法文件的整体布局

编写@acronym{GLR}分析器-Writing @acronym{GLR} Parsers

* 简单的GLR分析器(Simple GLR Parsers):Simple GLR Parsers.          使用@acronym{GLR}分析器分析非歧义文法
* 可合并的GLR分析器(Merging GLR Parsers):Merging GLR Parses.          使用@acronym{GLR}分析器解决歧义
* 对编译器的要求(Compiler Requirements):Compiler Requirements.       @acronym{GLR}分析器需要一个现代的C编译器

实例-Examples

* RPN计算器(RPN Calc):RPN Calc.          逆波兰记号计算器,我们的第一个例子,并不带有操作符优先级
* 中缀计算器(Infix Calc):Infix Calc.        中缀代数符号计算器,简单地介绍操作符优先级.
* 简单的错误恢复(Simple Error Rcovery):Simple Error Recovery.  在出现语法错误后继续分析
* 位置追踪计算器(Location Tracking Calc):Location Tracking Calc. 展示@@@var{n}和@@$的用法
* 多功能计算器(Multi-function Calc):Multi-function Calc.  带有存储和触发功能的计算器.它使用了多种数据类型来表示语义值.
* 练习(Exercises):Exercises.         一些改进多功能计算器的方案

逆波兰记号计算器-Reverse Polish Notation Calculator

* 声明(Decls): Rpcalc Decls.  rpclac的Prologue(声明)部分.
* 规则(Rules): Rpcalc Rules.  带注释的rpcalc语法规则
* 词法分析器(Lexer): Rpcalc Lexer.  词法分析器
* 主函数(Main): Rpcalc Main.    控制函数
* 错误(Error): Rpcalc Error.  错误报告的规则
* 生成(Gen): Rpcalc Gen.      使用Bison生成分析器
* 编译(Comp): Rpcalc Compile. 使用C编译器编译得到最终结果

@code{rpcalc}的语法规则-Grammar Rules for @code{rpcalc}

* Rpcalc Input::
* Rpcalc Line::
* Rpcalc Expr::

位置追踪计算器:@code{ltcalc}-Location Tracking Calculator: @code{ltcalc}

* Decls: Ltcalc Decls.  ltcalc的Bison和C语言的声明
* Rules: Ltcalc Rules.  详细解释ltcalc的语法规则
* Lexer: Ltcalc Lexer.  词法分析器

多功能计算器:@code{mfcalc}-Multi-Function Calculator: @code{mfcalc}

* Decl: Mfcalc Decl.      多功能计算器的Bison声明
* Rules: Mfcalc Rules.    计算器的语法声明
* Symtab: Mfcalc Symtab.  符号表管理规则

Bison语法文件-Bison Grammar Files

* 语法提纲(Grammar Outline):Grammar Outline.   语法文件的整体布局
* 符号(Symbols):Symbols.           终结符与非终结符
* 规则(Rules):Rules.             如何编写语法规则
* 递归(Resursion):Recursion.         编写递归规则
* 语义(Semantics):Semantics.         语义值和动作
* 位置(Locations):Locations.         位置和动作
* 声明(Declarations):Declarations.      所有种类的Bison声明在这里讨论
* 多个分析器(Multiple Parsers):Multiple Parsers.  将多个Bison分析器放在一个程序中

Bison语法提纲-Outline of a Bison Grammar

* Prologue::          Prologue部分的语法和使用
* Bison Declarations::  Bison declarations部分的语法和使用
* Grammar Rules::     Grammar Rules部分的语法和使用
* Epilogue::          Epilogue部分的语法和使用

定义语言的语义-Defining Language Semantics

* 值类型(Value Type):Value Type.        为所有的语义值指定一个类型.
* 多种类型(Multiple Types):Multiple Types.    指定多种可选的数据类型.
* 动作(Actions):Actions.           动作是一个语法规则的语义定义.
* 动作类型(Action Types):Action Types.      为动作指定一个要操作的数据类型.
* 规则中间的动作(Mid-Rule Actions):Mid-Rule Actions.  多数动作在规则之后,
                                           这一节讲述什么时候以及为什么要使用规则中间动作的特例.

追踪位置-Tracking Locations

* 位置类型:Location Type.               描述位置的数据类型.
* 动作和位置:Actions and Locations.       在动作中使用位置.
* 位置的默认动作:Location Default Action.     定义了一个计算位置的通用方法.

Bison声明-Bison Declarations

* 符号声明(Token Decl):Token Decl.        声明终结符
* 优先级声明(Precedence Decl):Precedence Decl.   声明终结符的优先级和结合性
* 联合体声明(Union Decl):Union Decl.        声明一组语义值类型
* 类型声明(Type Decl):Type Decl.         声明非终结语义值的类型
* 初始动作声明(Initial Action Decl):Initial Action Decl.  在分析开始前执行的代码
* 析构声明(Destructor Decl):Destructor Decl.   声明如何释放符号
* 期望声明(Expect Decl):Expect Decl.       消除分析冲突时的警告
* 开始符号声明(Start Decl):Start Decl.        指明开始符号
* Pure Decl:Pure Decl.         请求一个可重入的分析器
* 声明的总结(Decl Summary):Decl Summary.      一个所有Bison声明的总结

分析器C语言接口-Parser C-Language Interface

* 分析器函数(Parser Function):Parser Function.   如何调用@code{yyparse}以及它的返回值.
* 词法(Lexical):Lexical.           你必提供一个读入记号的函数@code{yylex}.
* 错误报告(Error Reporting):Error Reporting.   你必须提供一个函数@code{yyerror}.
* 动作特征(Action Feature):Action Features.   在动作中使用的特殊特征.

词法分析器函数@code{yylex}-The Lexical Analyzer Function @code{yylex}

* 调用惯例(Calling Convention):Calling Convention.  @code{yyparse}如何调用@code{yylex}.
* 记号值(Token Values):Token Values.      @code{yylex}是如何返回它已经读入的记号的语义值.
* 记号位置(Token Locations):Token Locations.   如果动作需要,@code{yylex}是如何返回记号的文字位置(行号,等等).
* 纯调用(Pure Calling):Pure Calling.      调用惯例如何区分一个纯分析器
                                          (@pxref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}).

Bison分析器算法-The Bison Parser Algorithm

* 超前扫描记号(Look-Ahead):Look-Ahead.        当分析器决定做什么的时候它查看的一个记号.
* 移进/归约(Shift/Reduce):Shift/Reduce.      冲突:移进和归约均有效.
* 优先级(Precedence):Precedence.        由于解决冲突的操作符优先级.
* 上下文优先级(Contextual Precedence):Contextual Precedence.  当一个操作符的优先级依赖上下文.
* 分析器状态(Parser States):Parser States.     分析器是一个带有栈的有限状态机.
* 归约/归约(Reduce/Reduce):Reduce/Reduce.     在同意情况下可以应用两个规则.
* 令人迷惑的冲突(Mystery Conflicts):Mystery Conflicts.  看起来不平等的归约/归约冲突.
* 通用LR分析(Generalized LR Parsing):Generalized LR Parsing.  分析arbitrary上下文无关文法.
* 栈溢出(Stack Overflow):Stack Overflow.    当栈溢出时发生的事情以及如何避免它.

操作符优先级-Operator Precedence

* 为什么需要优先级(Why Precedence):Why Precedence.    一个展示为什么需要优先级的例子
* 使用优先级(Using Precedence):Using Precedence.  在Bison的语法中如何指定优先级
* 优先级的例子(Precedence Example):Precedence Examples.  这些特性在前面的例子中是怎样使用的
* 优先级的工作方式(How Precedence):How Precedence.    它们如何工作

处理上下文依赖-Handling Context Dependencies

* 语义记号(Semantic Tokens):Semantic Tokens.   对记号的分析可能依赖于语义上下文.
* 词法关联(Lexcial Tie-ins):Lexical Tie-ins.   对记号的分析可能依赖上下文.
* 关联恢复(Tie-in Recovery):Tie-in Recovery.   词法关联含有如何编写错误恢复规则的暗示.

调试你的分析器-Debugging Your Parser

* 理解(Understanding):Understanding.     理解你的分析器的结构
* 跟踪(Tracing):Tracing.           跟踪你的分析器的执行

调用Bison-Invoking Bison

* Bison选项(Bison Options):Bison Options.     按简写选项的字母顺序详细描述所有选项
* 选项交叉键(Option Cross Key):Option Cross Key.  按字母顺序列出长选项
* Yacc库(Yacc Library):Yacc Library.      与Yacc兼容的@code{yylex}和@code{main}

常见问题-Frequently Asked Questions

* 分析器栈溢出(Parser Stack Overflow):Parser Stack Overflow.      突破栈限制
@c bad translation
* 我如何复位分析器(How Can I Reset the Parser):How Can I Reset the Parser. @code{yyparse}保持一些状态
@c up
* 销毁字符串(Strings are Destroyed):Strings are Destroyed.      @code{yylval}丢掉了字符串的追踪
* C++分析器(C++ Parsers):C++ Parsers.                使用C++编译器编译分析器
* 实现Gotos/Loops(Imlementing Gotos/Loops):Implementing Gotos/Loops.   在计算器中控制流

复制这个文档-Copying This Manual

* GNU Free Documentation License::  复制这个手册的许可.

@end detailmenu
@end menu

@node Introduction
@unnumbered Bison简介-Introduction
@cindex 简介(introduction)

@dfn{Bison}是一种通用目的的分析器生成器.
它将@acronym{LALR}(1)上下文无关文法的描述转化成分析该文法的C程序.
一旦你精通@acronym{Bison},
你可以用它生成从简单的桌面计算器到复杂的程序设计语言等等许多语言的分析器.

Bison向上兼容Yacc:所有书写正确的Yacc语法都应该可以不加更改地与Bison一起工作.
熟悉Yacc的人能毫不费力地使用Bison.
你应该熟练地掌握C程序设计语言,这样你才能使用Bison和理解这个手册.

我们会在这个教程的最初几章解释BIson的基本概念,并且展示三个详细解释的例子,
这些例子将在教程的最后被构建.
如果你对Bison或者Yacc一无所知,
你应该首先阅读这些章节.
接下来的参阅章节详细地阐述了Bison的各个方面.

Bison主要由Rovert Corbett编写.Richard Stallman使它与Yacc兼容.
Carnegie Mellon大学的Wilfred Hansen为Bison添加了
多字符字符串文字(multi-character string literals)和其它一些特性.

这个版本的手则主要与Bison@value{VERSION}相一致.
@node Conditions
@unnumbered 使用Bison的条件-Conditions for Using Bison

为了允许非自由程序(nonfree programs)使用Bison生成的LALR分析器C代码,
我们在Bison版本1.24的时已经修改了@code{yyparse}的发布条款(distribution terms for yyparse).
在这之前,这些分析器只能用于自由软件(free software)的程序.

其它@acronym{GNU}编程工具,例如@acronym{GNU} C编译器从未有过类似的要求.
它们总能用于非自由软件的发布.
Bison与它们不同的原因并不是出于特殊策略的决定,
而是由于应用通用许可证(General Public License)到所有的Bison源代码造成的结果.

Bison工具的输出-也就是Bison分析器文件(the Bison parser file)包括大小不固定的Bison代码片段.
这些代码片段来源于yyparse函数.(你的语法的动作被Bison插入到这个函数的某个位置,
但是函数的其余部分并未更改).当我们应用GPL条款到yyparse的代码的时候,
它产生的效果就是Bison的输出只能到自由软件.

我们并未更改条款使出于对那些希望是软件私有化的人的同情.
@strong{所有的软件都应该是自由软件.}但是我们的结论是:
使Bison只能用于自由软件,这对鼓励人们使其它软件变为自由软件作用甚微.
所以我们决定将使用Bison的条件与使用其它@acronym{GNU}工具的条件相一致.
(注:即和@acronym{GCC}一样可以用于非自由软件).

这个特例仅仅在Bison生成@acronym{LALR}(1)分析器的C代码时适用.
否则,@acronym{GPL}条款仍然正常的执行.
你可以通过查看代码,看其是否由这样的说明``As a special execption, when
this file is copied by Bison into a Bison output file, you may use that
output file without restriction.''来分辩这个特例是否适用于你的@samp{.c}输出文件.
@include gpl.texi

@node Concepts
@chapter 和Bison相关的一些基本概念-The Concepts of Bison

这一章介绍了许多基本概念,但没有提及一些感觉不到的细节.
如果你并不了解如何使用Bison/Yacc,我们建议你仔细阅读这一章.

@menu
* 语言与文法(Language and Grammar):Language and Grammar.  从数学的概念来介绍语言和上下文无关文法.
* Bison中的语法(Grammar in Bison):Grammar in Bison.  怎样用Bison的语法表示各种文法.
* 语义值(Semantic Values):Semantic Values.   每个记号或者语法组可有一个语义值(例如:整数的数值,标识符的名称等等)
* 语义动作(Semantic Actions):Semantic Actions.  每个规则可有一个包含C代码的动作
* GLR分析器(GLR Parsers):GLR Parsers.       为普通的上下文无关文法编写分析器
* 位置概述(Locations Overview):Locations Overview.    追踪位置
* Biosn的分析器(Bison Parser):Bison Parser.      Bison的输入和输出是什么,怎样使用Bison的输出
* 流程:Stages.            编写和运行Bison语法分析程序的流程.
* 语法文件的布局(Grammar Layout):Grammar Layout.    Bison语法文件的整体布局
@end menu

@node Language and Grammar
@section 语言与上下文无关文法-Languages and Context-Free Grammars

@cindex 上下文无关文法(context-free grammar)
@cindex 文法(grammar), 上下文无关(context-free)
为了使Bison能分析语言,这种语言必须由@dfn{上下文无关文法(context-free grammar)}描述.
也就是说,你必须指出一个或者多个@dfn{语法组(syntactic groupings)}以及从语法组的部分构它的建整体的规则.
例如,在C语言中,有一种我们称之为`表达式(expression)'的语法组.
一个生成表达式的规则可能是``一个表达式由一个减号和另一个表达式构成''.
另外一个规则可能是``一个表达式可以是一个整数''.
就象你看到的一样,规则经常是递归定义的,但是必须有一个结束递归的规则.

@cindex @acronym{BNF}
@cindex Backus-Naur 范式(Backus-Naur form)
用于表示这些规则的最普遍的系统是@dfn{Backus-Naur 范式(Backus-Naur Form)}或者``@acronym{BNF}''.
发明这种语言的目的是用来阐述Algol 60.
任何用@acronym{BNF}表示的文法都是一种上下文无关文法.
Bison要求它的的输入必须用@acronym{BNF}表示.

@cindex @acronym{LALR}(1) 文法(@acronym{LALR}(1) grammars)
@cindex @acronym{LR}(1) 文法(@acronym{LR}(1) grammars)
上下文无关文法有许多重要的子集.尽管Bison可以处理几乎所有的上下文无关文法,
但Bison针对@acronym{LALR}(1) 文法(@acronym{LALR}(1) grammars)做了优化.
简而言之,在这些文法中(注:指LALR(1)),我们可以告之如何分析仅代有一个超前扫描记号的输入字符串的任意部分.
严格的说,这是一个@acronym{LR}(1)文法的描述.
@acronym{LALR}(1)包括了与多难以分析的额外限制.
幸运的是,在实际中,我们很难找到一个是@acronym{LR}(1)文法而不是@acronym{LALR}(1)文法的例子.
参阅@ref{Mystery Conflicts, ,神秘的归约/归约冲突-Mysterious Reduce/Reduce Conflicts},以获取更多信息.

@cindex @acronym{GLR} 分析(@acronym{GLR} parsing)
@cindex 通用@acronym{LR}(@acronym{GLR})分析(generalized @acronym{LR} (@acronym{GLR}) parsing)
@cindex 歧义文法(ambiguous grammars)
@cindex 非确定性分析(non-deterministic parsing)

@acronym{LALR}(1)文法分析器具有@dfn{确定性(deterministic)},
这就意味着应用于输入的下一个文法规则取决于之前的输入和确定的部分剩余输入(我们称之为一个@dfn{超前扫描记号(look-ahead)}.
一个上下文无关文法可能是有@dfn{歧义的(ambiguous)},即可能可以应用多种规则来获取某些输入.
即使非歧义性文法也可能使@dfn{不确定(non-deterministic)}的,
即没有总能足以决定下一个应用的文法规则的确定的超前扫描记号。
使用孰知的@acronym{GLR}技术,Bison的@acronym{GLR}就可以分析这些更为普通的上下文无关文法.
当任意给定字符串的可能的分析是确定的情况下,Bison可以处理任意上下文无关文法.

@cindex 符号(symbols (abstract))
@cindex 记号(token)
@cindex 语法组(syntactic grouping)
@cindex 组合(grouping),语法的(syntactic)
在正式的语言语法规则中,每一种语法单元或组合被称之为@dfn{符号(symbol)}.
那些可以通过语法规则被分解成更小的结构的符号叫做@dfn{非终结符(nonterminal symbols)}.
那些不能被再分的符号叫做@dfn{终结符(terminal symbols)}或者@dfn{记号类型(token types)}.
我们把同终结符相对应的输入片段叫做@dfn{记号(token)},
把同单个非终结符相对应的输入片段叫做@dfn{组(grouping)}.

我们可以使用C语言做为例子来解释什么是符号,以及终结符和非终结符的含义.
C语言的记号包括标识符,常量(数字或者字符串)以及各种关键字,数学操作符和标点符号.
所以C语言语法的终结符包括`identifier',`number',`string',加上每个关键字的符号,操作符或者标点符号.
例如:`if',`return',`const',`static',`int',`char',`plus-sign',`open-brace',`close-brace',`comma'以及更多.
(这些记号可以再分为字符,但是这些是词法学而不是语法学的事情)

这是一个如何将C函数分解成记号的例子:

@ifinfo
@example
int             /* @r{关键字 `int'} */
square (int x)  /* @r{identifier, open-paren, 关键字 `int',}
                   @r{identifier, close-paren} */
@{               /* @r{open-brace} */
  return x * x; /* @r{关键字 `return', identifier, asterisk,
                   identifier, semicolon} */
@}               /* @r{close-brace} */
@end example
@end ifinfo
@ifnotinfo
@example
int             /* @r{关键字 `int'} */
square (int x)  /* @r{identifier, open-paren, 关键字 `int', identifier, close-paren} */
@{               /* @r{open-brace} */
  return x * x; /* @r{关键字 `return', identifier, asterisk, identifier, semicolon} */
@}               /* @r{close-brace} */
@end example
@end ifnotinfo

C语言的语法组包括表达式,语句,声明和函数定义.
这些由C语言语法中的非终结符`expression',`statement',`declaration'和`funcation definition'表示.
完整的语法还使用了许多额外的语言结构,每种结构都有自己的非终结符来表示上述四种的含义.
上面的例子是一个函数的定义,它包括了一个声明和一个语句.
每一个@samp{x}一个表达式,而且@samp{x * X}也是一个表达式.

每一个非终结符必须有一个描述如何由更简单结构组成这个非终结符的语法规则.
例如,一种C的语句是@code{return}语句的非正式表达将由如下的语法规则描述:

@quotation
一种语句可以由一个`return'关键字,一个`expression'何以个`semicolon'组成.
@end quotation

@noindent
还有许多其它对应`statement'的规则,每一种规则对应一种C语句.

@cindex 开始符号(start symbol)
我们必须注意到一种特殊的非终结符,这种非终结符定义了语言的一个完整表述.
我们称之为@dfn{开始符号(start symbol)}.
在编译器中,这意味着一个完整的输入程序.
在C语言中,非终结符`sequence of definitions and declarations'扮演了这个角色.

例如,@samp{1+2}是以个有效的C表达式---一个有效的C程序的部分---但它不能做为一个C程序的@emph{全部(entire)}.
在C语言的上下文无关文法中,这遵循了`expression'不是开始符号的事实.

Bison分析器读取一个记号序列做为它的输入并使用语法规则将记号组合.
如果输入是有效的,最终的结果是将整个的输入序列分析整理到开始符号.
如果我们使用C语言的语法,整个的输入必须是一个`sequence of definitions and declarations',
否则分析器会报告一个语法错误.

@node Grammar in Bison
@section 从正规文法转换到Bison的输入-From Formal Rules to Bison Input
@cindex Bison语法(Bison grammar)
@cindex 语法(grammar), Bison
@cindex 正规文法(formal grammar)

正规文法是一种数学结构.为了定义Bison要分析的语言,
你必须用Bison语法编写一个表达该语言的文件,即一个@dfn{Bison 语法(Bison grammar)}文件.
@xref{Grammar File, ,Bison的语法文件-Bison Grammar Files}.

就象C语言中的标识符一样,在Bison的输入中,一个正规文法的非终结符由一个标识符表示.
根据惯例,非终结符应该用小写子母表示,例如@code{expr},@code{stmt}或者@code{declaration}.

在Bison中,终结符也被称为@dfn{符号类型(token type)}.
符号类型也可以由类似C语言标识符来表示.
根据惯例,这些标识符因改用大写子母表示以区分它和非终结符.
例如,@code{INTEGER},@code{INDENTIFIER},@code{IF}或者@code{RETURN}.
一个表示某语言的特定关键字的终结符应该由紧随该关键字之后的它的大写表示来命名.
终结符@code{error}保留用作错误恢复之用.
@xref{Symbols, ,符号-Symbols}.

一个终结符也可以由一个像C中的字符常量一样的一个字符来表示.
当一个记号就是一个字符(括号,加号等等)的时候,你可以这样做:
使用同一个字符做为那个记号的终结符.

第三种表示终结符的方法是使用包含一些字符的C字符串常量.
获取更多这方面的信息可以@xref{Symbols, ,符号-Symbols}.

语法规则在Bison语法中也有相应的表示.例如,下面有一个C语言@code{return}语句的规则.
在引号中的分号,是一个字符记号,它是用来表示C语言部分语法的.
没有在引号中的分号和冒号是Bison用来表示每一条规则的标点符号.

@example
stmt:   RETURN expr ';'
        ;
@end example

@noindent
获取这方面更多信息,@xref{Rules, ,语法规则的语法-Syntax of Grammar Rules}.

@node Semantic Values
@section 语义值-Semantic Values
@cindex 语义值(semantic value)
@cindex 值(value), 语义(semantic)

正规文法仅仅靠类别来选择记号:例如,如果一个规则提到了终结符`integer constant',
这就意味着@emph{任何}整数常量在那个位置上都是语法有效的.
常量的精确值与如何分析输入不相关:如果@samp{x+4}符合语法,那么@samp{x+1}或者@samp{x+3989}也符合语法.

但是,当分析输入时,它的精确值是非常重要的,通过精确值可以了解输入的含义.
如果一个编译器不能区别程序中的4,1和3989等常量,毫无疑问,这个编译器是没有用的!
因此,每一个Bison语法的记号既含有一个符号类别也有一个@dfn{语义值(semantic value)}.
获取这方面的更多信息,@xref{Semantics, ,定义语言的语义-Defining Language Semantics}.

符号类型是在语法中定义的终结符,例如@code{INTEGER},@code{IDENTIFIRE}或者@code{','}.
它告诉你决定记号可能有效出现的位置以及如何将它组合成其它记号的信息.
语法规则只知道符号的类型,其它的什么都不知道.

语义值包括了记号的所有剩余信息.例如整数的数值,标识符的名称.
(一个如@code{','}的记号只是一个标点,并不需要语义值.)

例如,一个分类为@code{INTEGER}的记号包含语义值4.
另一个也被分类为@code{INTEGER}的记号的语义值却是3989.
当一个语法规则表明@code{INTEGER}是允许的,任意的这些记号都是可接受的,因为它们都是@code{INTEGER}.
当一个分析器接受了记号,它会跟踪这个记号的语义值.

每一个语法组和它的非终结符也可已有语义值.
一个很典型的例子,在计算器中,一个表达式含有一个数值做为它的语义值,
在程序语言编译器中.一个典型的表达式含有一个用于描述它含义的树型结构做为语义值.

@node Semantic Actions
@section 语义动作
@cindex 语义动作(semantic actions)
@cindex 动作(actions), 语义(semantic)

为了更加实用,一个程序不仅仅要分析输入而且必须做的更多.
它应该可以在输入的基础上产生一些输出.
在Bison语法中,一个语法规则可以有一个包括多个C语句的@dfn{动作(action)}.
分析器每次识别一个规则的匹配,相应的动作就会被执行.
获取这方面的更多信息,@xref{Actions, ,动作-Actions}.

大多数时候,动作的目的是从部分结构的语义值计算整个结构的语义值.
例如,加入我们有一个规则表明一个表达式可以由两个表达式相加而成.
当分析器识别了一个加法和,每一个子表达式都有一个描述其如何构建的语义值.
这个规的动做就是为了新识别的大表达式建立一个类似的语义值.

例如,这里的一个规则表明一个表达式可由两个表达式相加而成.

@example
expr: expr '+' expr   @{ $$ = $1 + $3; @}
        ;
@end example

@noindent
这个动作表明了如何从子表达式的语义值产生加法和表达式的语义值.

@node GLR Parsers
@section 编写@acronym{GLR}分析器-Writing @acronym{GLR} Parsers
@cindex @acronym{GLR}分析(@acronym{GLR} parsing)
@cindex 通用@acronym{LR}分析(generalized @acronym{LR} (@acronym{GLR}) parsing)
@findex %glr-parser
@cindex 冲突(conflicts)
@cindex 移进/归约 冲突(shift/reduce conflicts)
@cindex 归约/归约 冲突(reduce/reduce conflicts)

在一些文法中,Bison标准的@acronym{LALR}(1)分析算法,
不能针对给定的输入应用一个确定的语法规则.
这就是说,Bison可能不能决定(在当前输入的基础上)应该使用两个可能的归约中的那一个,
或者不能决定到底应该应用一个归约还是先读取一些输入稍后再进行归约.
以上两种冲突分别被称为@dfn{归约/归约(reduce/reduce)}冲突(@pxref{Reduce/Reduce, ,归约/归约-Reduce/Reduce})和
@dfn{移进/归约(shift/reduce)}冲突(@pxref{Shift/Reduce, ,移进/归约-Shift/Reduce}).

有些时候,
为了使用一个很难被修改成@acronym{LALR}(1)文法的文法做为Bison的输入,
Bison需要使用通用的分析算法.
如果你在你的文件中加入了这样的声明@code{%glr-parser}(@pxref{Grammar Outline, ,语法大纲-Grammar Outline}),
Bison会产通用的@acronym{LR}(@acronym{GLR})分析器.
这些分析器(例如,在应用了先前所述的声明之后)
在处理那些不包含未解决的冲突的文法时,
采用与@acronym{LALR}(1)分析器一样的处理方式.
但是当面临未解决的移进/归约冲突和归约/归约冲突的时候,
@acronym{GLR}分析器权宜地同时处理这两个可能,
即有效地克隆分析器自己以便于追踪这这两种可能性.
每一个克隆出来的分析器还可以再次被克隆,
这就保证在任意给定的时间,可以处理任意个可能的分析.
分析器逐步地进行分析,即所有的分析器它们进入到下一个输入之前,
都会消耗(归约)给定的输入符号.
每一个被克隆的分析器最终只有两个可能的归宿:
或者这个分析器因进入了一个分析错误而最终被销毁,
会这它和其它的分析器合并,因为它们把输入归约到了一个相同的符号集.

在有多个分析器并存的时刻,Bison只记录它们的语义动作而不是执行它们.
当一个分析器消失的时候,相应的语义动作记录也消失并且永远不会被执行.
当一个规约使得两个分析器等价而合并的时候,
Bison会记录下它们两个的语义动作集.
每当最后两个分析器合并成为一个单独的分析器的时候,
Bison执行所有未完成的动作.这些动作既可能依靠语法规则的优先级被执行也可能均被Bison执行.
在执行完动作之后,Bison调用指定的用户定义求值函数来产生一个独立的合并值.


@menu
* 简单的GLR分析器(Simple GLR Parsers):Simple GLR Parsers.          使用@acronym{GLR}分析器分析非歧义文法
* 可合并的GLR分析器(Merging GLR Parsers):Merging GLR Parses.          使用@acronym{GLR}分析器解决歧义
* 对编译器的要求(Compiler Requirements):Compiler Requirements.       @acronym{GLR}分析器需要一个现代的C编译器
@end menu

@node Simple GLR Parsers
@subsection 使用@acronym{GLR}分析器分析非歧义文法
@cindex @acronym{GLR}分析(@acronym{GLR} parsing), 非歧义文法(unambiguous grammars)
@cindex 通用@acronym{LR}(@acronym{GLR})分析(generalized @acronym{LR} (@acronym{GLR}) parsing), 非歧义文法(unambiguous grammars)
@findex %glr-parser
@findex %expect-rr
@cindex 冲突(conflicts)
@cindex 归约/归约冲突(reduce/reduce conflicts)
@cindex 移进/归约冲突(shift/reduce conflicts)

在最简单的情况下,你可以使用@acronym{GLR}算法分析那些非歧义但不是@acronym{LALR}(1)文法的文法.
典型地,这些文法需要不止一个的超前扫描记号,
或者(在极少数情况下)由于@acronym{LALR}(1)算法丢弃太多的信息
而不属于@acronym{LALR}(1)的文法(它们却属于@acronym{LR}(1),参阅@ref{Mystery Conflicts, ,冲突-Mystery Conflicts}).

考虑一个产生于Pascal语言枚举和子界类型声明中的问题,.
这里有一些例子:

@example
type subrange = lo .. hi;
type enum = (a, b, c);
@end example

@noindent
最初的语言标准只允许数字和常量标识符做为子界的范围(@samp{lo}和@samp{hi}).
但是扩展的Pascal(@acronym{ISO}/@acronym{IEC}10206)以及许多以它的Pascal的实现还允许独立的表达式.
这导致了如下一个包含过量括号的情形.

@example
type subrange = (a) .. b;
@end example

@noindent
考虑如下这个仅含一个值的枚举类型的声明.

@example
type enum = (a);
@end example

@noindent
(在这里的这些例子是人为制造的,但它们是语法有效的.
在实际的程序中可能会出现更复杂的例子)

这两个例子看起来相同(注:指语法上)直到@samp{..}记号的出现.
对于只含有一个超前扫描记号的普通@acronym{LALR}(1)分析,
当@samp{a}被分析的时,分析器候并不能两个形式中(注:指枚举或者子界)那一个是正确的.
因为在后一个例子中,@samp{a}必须成为一个新的标识符来表示枚举;
而在前一个例子中必须使用@samp{a}和它的含义来估计它可能是一个常量或者函数调用,
所以我们当然希望分析器可以对此作出正确的决定.

你可将@samp{(a)}分析成``在括号中为说明的标识符''以便稍后进行分析.
但当括号嵌套在表达式的递归规则中的时候,
这种方式需要对语义动作和大部分语法做出相应的调整.

你可能会想到使用词法分析器通过返回已定义和未定地标识符来区别这两种形式.
但是如果这些声明在局部出现,
而@samp{a}在外部定义的时候,这两种形式都是可能的--
既可能是局部重定义的@samp{a}.也可能是使用外部定义的@samp{a}的值.
所以这种方法不可行.

解决这个问题由一个简单的办法,那就是使用@acronym{GLR}算法.
当@acronym{GLR}分析器到达关键区域的时候,
它会同时沿着两个分支进行分析.
其中的一个分支迟早要进入分析错误.
如果有一个@samp{..}记号在下一个@samp{;}之前的化;
由于枚举类型的规则不能接受@samp{..},所以它应用失败;
否则,子界类型规则应用失败,因为它要求一个@samp{..}记号.
所以,一个分支无声地失败而另一个分支正常地进行,
并且执行所有的在拆分期间被推迟执行的中间动作.

如果输入不符和语法,则这两个分支的分析都会失败
并且如常地报告一个语法错误.

分析器的功能似乎是在``猜测''正确的语法分支,换句话说,
它使用了比@acronym{LALR}(1)算法允许使用的更多的超前扫描记号.
@acronym{LALR}(2)有能力分析这个句子,
但是有些不符和@acronym{LALR}(@math{k})的例子,
即使任意多的@math{k}可以这样地处理.

一般而言,一个@acronym{GLR}分析器在最坏情况下会花费二次方或者三次方的时间复杂度,
当前的Bison甚至会为了某些文法而花费指数的时间.
在实际应用中,这些几乎不会发生,
并且对于许多文法来说,证明它不能发生也是可能的.
当前讨论的例子在两个规则中只有一个冲突,
并且含有冲突的类型声明不能嵌套使用.
所以在任意时刻的分支数量被限制在常量2以下,
这时候分析时间仍然是线性的.

这是一个同上面描述相对应的例子.
它由Pascal类型声明大大简化而来.
如果输入不符和语法,两个分支都会失败并且如常地报告一个语法错误.

@example
%token TYPE DOTDOT ID

@group
%left '+' '-'
%left '*' '/'
@end group

%%

@group
type_decl : TYPE ID '=' type ';'
     ;
@end group

@group
type : '(' id_list ')'
     | expr DOTDOT expr
     ;
@end group

@group
id_list : ID
     | id_list ',' ID
     ;
@end group

@group
expr : '(' expr ')'
     | expr '+' expr
     | expr '-' expr
     | expr '*' expr
     | expr '/' expr
     | ID
     ;
@end group
@end example

当使用平常的@acronym{LALR}(1)文法的时候,Bison会报告一个归约/归约冲突.
在冲突的时候,分析器在会众多选择中选取一个--随意地选择那个先声明的.
所以下面的正确输入不能被识别.

@example
type t = (a) .. b;
@end example

在Bison输入文件中,
加入这两个声明(在第一个@samp{%%}之前)分析器可以将分析器编成一个@acronym{GLR}分析器,
并且Bison不会报告一个归约/归约冲突.

@example
%glr-parser
%expect-rr 1
@end example

@noindent
并不需要对语法本身进行修改.
分析器现通过上面的限制语法后,可以认识所有有效的声明.
用户实际上并不能查觉分析器的拆分.

这就是我们使用@acronym{GLR}而几乎没有坏处的例子.
即使像这样简单的例子,至少两个潜在的问题值得我们注意.
第一,我们总应该分析Bison的冲突报告来确定@acronym{GLR}拆分总发生在我们想要的时候.
一个@acronym{GLR}分析器的拆分会不经意地产生比@acronym{LALR}分析器在冲突中静态的错误选择
更加不明显的问题.
第二,要仔细考虑与词法分析器的互动(@pxref{Semantic Tokens, ,语义记号-Sematic Tokens}).
由于在拆分期间的分析器消耗记号时并不产生任何动作,
词法分析器并不能通过分析动作获得信息.
一些与词法分析器的互动在使用@acronym{GLR}来消除从词法分析器到语法分析器的复杂读时可以忽略.
你必须监察其余情况下时的正确性.

在我们的例子中,因为并没有新的符号在类型声明中间被定义.
词法分析器基于记号当前在符号表的含意被返回是安全的.
@c bad translation
即使在分析枚举常量时定义它们是可能的,
@c up
由于它们不能在相同的枚举类型声明中使用,
所以这实际上并没有什么不同.


@node Merging GLR Parses
@subsection 使用@acronym{GLR}解决歧义-Using @acronym{GLR} to Resolve Ambiguities
@cindex @acronym{GLR} parsing, ambiguous grammars
@cindex generalized @acronym{LR} (@acronym{GLR}) parsing, ambiguous grammars
@findex %dprec
@findex %merge
@cindex conflicts
@cindex reduce/reduce conflicts

让我们考虑由C++语法大大简化而来的例子.

@example
%@{
  #include <stdio.h>
  #define YYSTYPE char const *
  int yylex (void);
  void yyerror (char const *);
%@}

%token TYPENAME ID

%right '='
%left '+'

%glr-parser

%%

prog :
     | prog stmt   @{ printf ("\n"); @}
     ;

stmt : expr ';'  %dprec 1
     | decl      %dprec 2
     ;

expr : ID               @{ printf ("%s ", $$); @}
     | TYPENAME '(' expr ')'
                        @{ printf ("%s <cast> ", $1); @}
     | expr '+' expr    @{ printf ("+ "); @}
     | expr '=' expr    @{ printf ("= "); @}
     ;

decl : TYPENAME declarator ';'
                        @{ printf ("%s <declare> ", $1); @}
     | TYPENAME declarator '=' expr ';'
                        @{ printf ("%s <init-declare> ", $1); @}
     ;

declarator : ID         @{ printf ("\"%s\" ", $1); @}
     | '(' declarator ')'
     ;
@end example

@noindent
这个例子模拟了有问题的C++语法---某些声明和语句中的歧义.例如,

@example
T (x) = y+z;
@end example

@noindent
既可以被分析为一个@code{expr}也可被分析为一个@code{stmt}
(假定@samp{T}被识别为一个@code{TYPENAME}并且@samp{x}被识别为@code{ID}).
Biosn检测到了这个在规则@code{expr : ID}和规则@code{delarator : ID}之间的归约/归约冲突.
分析器遇到@code{x}的时候还不能解决冲突.
由于这是一个@acronym{GLR}分析器,所以它会把问题拆分成两个分析器,
每一个用于解决归约/归约冲突中的一个.
与上一节的例子不同(@pxref{Simple GLR Parsers, ,简单的GLR分析器}),两个分析器中的任一个都不``死亡,''
因为这个语法本身就是歧义的.
其中的一个分析最终归约到@code{stmt : expr ';'}而另一个归约到@code{stmt : decl},
在这之后,两个分析器进入同一个状态: 它们都已经看到@samp{prog stmt}并且有相同的未处理剩余输入.
我们说这些分析器已经@dfn{合并(merged)}.

在这个时候,@acronym{GLR}分析器需要一个关于如何在两个竞争的分析中做出选择的说明.
在上面的例子中,两个@code{%dprec}声明说明了Bison给予@code{decl}的解释以优先级.
这就表明@code{x}是一个声明符(declarator).
因此分析器会打印出:

@example
"x" y z + T <init-declare>
@end example

@code{%deprc}声明仅在多于一个分析幸存的情况下起作用.
考虑这个分析器的一个不同的输入字符串:

@example
T (x) + y;
@end example

@noindent
像在上一节展示的一样(@pxref{Simple GLR Parsers, ,简单的GLR分析器-Simple GLR Parsers}),
这是另外一个使用@acronym{GLR}分析非歧义结构的例子.
在这里并没有歧义(这个并不能被分析成一个声明).
但是在Bison分析器遇到@code{x}的时候,
它没有足够的信息解决归约/归约冲突(还是不能决定@code{x}是一个@code{expr}还是一个@code{declarator}).
在这种情况下,没有优先级可供使用.
分析器再次被拆分成两个,一个假定@code{x}是一个@code{expr}
而另一个假定@code{x}是一个@code{declarator}.
两个分析器中的第二个在遇到@code{+}的时候被销毁,并且分析器打印出


@example
x T <cast> y +
@end example

假设你想查看所有的可能性而不是解决歧义.
你必须合并两个可能的分析器的语义动作而不是选择其中的一个.
为了达到这个目的,你需要像如下那样地改变@code{stmt}的声明:

@example
stmt : expr ';'  %merge <stmtMerge>
     | decl      %merge <stmtMerge>
     ;
@end example

@noindent
并且定义@code{stmtMerge}函数如下:

@example
static YYSTYPE
stmtMerge (YYSTYPE x0, YYSTYPE x1)
@{
  printf ("<OR> ");
  return "";
@}
@end example

@noindent
并且在文件的开头要伴随一个前置声明在C声明中:

@example
%@{
  #define YYSTYPE char const *
  static YYSTYPE stmtMerge (YYSTYPE x0, YYSTYPE x1);
%@}
@end example

@noindent
使用这些声明以后,产生的的分析器将第一个例子既分析成一个@code{expr}又分析成一个@code{decl},
并且打印

@example
"x" y z + T <init-declare> x T <cast> y z + = <OR>
@end example

Bison要求所有参加任何特定合并的产品(productions)拥有相同的@samp{%merge}语句.
否则,歧义不能被解决而且分析器会在任何导致违反规定的合并时候报告一个错误.

@node Compiler Requirements
@subsection 编译@acronym{GLR}分析器时需要考虑的问题-Considerations when Compiling @acronym{GLR} Parsers
@cindex @code{inline}
@cindex @acronym{GLR}分析器和@code{inline}(@acronym{GLR} parsers and @code{inline})

@acronym{GLR}分析器需要支持C89或更新标准的编译器.
额外地,Bison使用关键字@code{inline},这个关键字不是C89标准但却是C99标准,
并且是许多C99之前编译器支持的扩展.
使用它是为了分析器的用户可以处理移植性问题.
例如,如果使用Autoconf和Autoconf宏@code{AC_C_INLINE},一个单单的

@example
%@{
  #include <config.h>
%@}
@end example

@noindent
就足够用了,否则我们建议使用

@example
%@{
  #if __STDC_VERSION__ < 199901 && ! defined __GNUC__ && ! defined inline
   #define inline
  #endif
%@}
@end example

@node Locations Overview
@section 位置-Locations
@cindex 位置(location)
@cindex 原文位置(textual location)
@cindex 位置(location), 原文的(textual)

许多应用程序,如解释器和编译器,需要产生一些有用信息或者出错的信息.
为了达到这个目的,我们必须追踪每个语法结构的@dfn{原文位置(textual location)}或@dfn{位置(location)}.
Bison提供了追踪这些位置的机制.

每一个记号有一个语义值.类似地,每个记号也有一个位置,
对于所有记号和组来说,它们的位置的类型是相同的.
此外,输出的分析器也带有默认的存储位置的数据结构
(获取更多信息,@pxref{Locations, ,位置-Locations}).

像语义值一样,位置可以在动作中使用特定的一套结构来访问.
在上面个的例子中,这个组的的位置是@code{@@$},而子表达式的位置是@code{@@1}和@code{@@3}.

当一个规则被匹配,一个默认的动作用于计算左侧的语义值(@pxref{Actions, ,动作-Actions}).
类似地,另外一个默认的动作用于计算位置.
然而,这个默认动作对于对于大多数情况已经足够永,
即经常没有必要为每个规则描述@code{@@$}应该是如何形成的.
当为一个给定的组建立一个新的位置的时候,
输出的分析器的默认行为是取第一个符号的开头和最后一个符号的末尾.

@node Bison Parser
@section Bison的输出:分析器文件-Bison Output: the Parser File
@cindex Bison分析器(Bison parser)
@cindex Bison工具(Bison utility)
@cindex 词法分析器(lexical analyzer), 目的(purpose)
@cindex 分析器(parser)

当你运行Bison的时候,你需要给Bison一个语法文件做为其输入.
Bison的输出是一个分析这个语法文件描述的语言的C源代码文件.
这个文件叫做@dfn{Bison分析器(Bison parse)}.
我们要记住Bison工具和Bison分析器是两个明显不同的程序:
Bison工具是一个以Bison分析器为输出的程序.
这个Bison分析器应是你程序的一部分.

Bison分析器的工作是依照语法规则组合记号---例如,将标识符和操作符构建成表达式.
在组合的过程中它还要执行相应的语法规定的动作.

记号是来源于称为@dfn{词法分析器(lexical analyzer)}的程序.
你必须以某种形式提供词法分析器(如用C编写).
Bison分析器每当需要一个新的记号的时候就会调用词法分析器.
Bison分析器并不之道记号``中''有什么东西(即使它们的语义值可能反映这个).
典型的词法分析器靠分析字符来产生记号,但是Bison并不依靠这个.
获取更多细节,@xref{Lexical, ,词法分析函数@code{yylex}-The Lexical Analyzer Function @code{yylex}}.

Bison分析器文件是定义了名为@code{yyparse}并且实现了那个语法的函数的C代码.
这个函数并不能成为一个完成的C程序:你必须提供额外的一些函数.
其中之一是词法分析器.另外的一个是一个分析器报告错误时调用的错误报告函数.
另外,一个完整的C程序必须以名为@code{main}的函数开头;
你必须提供这个函数.并且安排它调用@code{yyparse}.
否则分析器永远都不会运行.
@xref{Interface, ,分析器C语言接口-Parser C-Language Interface}.

除了你编写的动作中的记号类型名称和符号以外
,所有Bison分析器文件自己定义的符号都以@samp{yy}或者@samp{YY}开头.
这些符号包括了接口函数例如词法分析函数@code{yylex},错误报告函数@code{yyerror}
和分析器函数@code{yyparse}.
这些符号也包括了许多内部目的的标识符.
所以你要在Bison语法文件中避免使用除了本手册定义的以外的以@samp{yy}或者@samp{YY}开头的C标识符.

在一些情况下,Bison分析器文件包含系统头文件.
在这中情况下,你的代码注意被这些文件保留的标识符.
在意些非@acronym{GNU}系统,@code{<alloca.h>},@code{<stddef.h>}以及@code{<stdlib.h>}
被包含在内用于声明内存分配器及相关类型.
如果你定义@code{YYDEBUG}为非零值,其它的系统头文件也可能被包括进内.
(@pxref{Tracing, ,跟踪你的分析器-Tracing Your Parser})

@node Stages
@section 使用Bison的流程-Stages in Using Bison
@cindex 使用Bison的流程(stages in using Bison)
@cindex 使用Bison(using Bison)

实际使用Bison设计语言的流程,从语法描述到编写一个编译器或者解释器,有三个步骤:

@enumerate
@item
以Bison可识别的格式正式地描述语法.(@pxref{Grammar File, ,Bison语法文件})
对每一个语法规则,描述当这个规则被识别时相应的执行动作.
动作由C语句序列描述.

@item
编写一个词法分析器处理输入并将记号传递给语法分析器.
词法分析器既可是手工编写的C代码(@pxref{Lexical, ,词法分析函数@code{yylex}}),
也可以由lex产生,但是lex的使用并未在这个手册中讨论.

@item
编写一个调用Bison产生的分析器的控制函数.

@item
编写错误报告函数.
@end enumerate

将这些源代码转换成可执行程序,你需要按以下步骤进行.

@enumerate
@item
按语法运行Bison产生分析器.

@item
同其它源代码一样编译Bison输出的代码.

@item
链接目标文件以产生最终的产品.
@end enumerate

@node Grammar Layout
@section Bison语法文件的整体布局-The Overall Layout of a Bison Grammar
@cindex 语法文件(grammar file)
@cindex 文件格式(file format)
@cindex 语法文件的格式(format of grammar file)
@cindex Bison语法文件的布局(layout of Bison grammar)

Bison工具的输入文件是以个@dfn{Bison语法文件(Bison grammar file)}.
通常的Bison语法文件格式如下:

@example
%@{
@var{Prologue}
%@}

@var{Bison declarations}

%%
@var{Grammar rules}
%%
@var{Epilogue}
@end example

@noindent
@samp{%%},@samp{%@{} 和@samp{%@}}是Bison在每个Bison语法文件中用于分隔部分的标点符号.

@var{prologue}可用来定义在动作中使用类型和变量.
你可以使用预处理器命令在那里来定义宏,
或者使用@code{#include}包含干这些事情的头文件.
你需要声在那里与许多要在语法规则的动总中使用的全局标识符一起
声明词法分析器@code{yylex}和错误打印程序@code{yyerror}.

@var{Bison declarations}声明了终结符和非终结符以及操作符的优先级和各种符号语义值的各种类型.

@var{Grammar rules}定义了如何从每一个非终结符的部分构建其整体的语法规则.

@var{Epilogue}可以包括任何你想使用的代码.
在@var{Prologue}中声明的函数经常定义在这里.
在简单的程序里,剩余的所有程序可以放在这里.

@node Examples
@chapter 实例-Examples
@cindex 简单实例(simple examples)
@cindex 实例(examples), 简单(simple)

现在开始我们展示并解释三个使用Bison编写的示范程序:
一个逆波兰记号计算器,一个代数符号(中缀)计算器和一个多功能计算器.
这些程序在BSD Unix 4.3上测试无误;它们每一个虽然功能有限,
但确都是实用的互动桌面计算器.

这些例子虽然简单,但是用Bison语法编写真正的程序设计语言的道理与这相同.
@ifinfo
你可以将这些例子由info文件复制为源代码文件,并尝试编译运行它们
@end ifinfo

@menu
* RPN计算器(RPN Calc):RPN Calc.          逆波兰记号计算器,我们的第一个例子,并不带有操作符优先级
* 中缀计算器(Infix Calc):Infix Calc.        中缀代数符号计算器,简单地介绍操作符优先级.
* 简单的错误恢复(Simple Error Rcovery):Simple Error Recovery.  在出现语法错误后继续分析
* 位置追踪计算器(Location Tracking Calc):Location Tracking Calc. 展示@@@var{n}和@@$的用法
* 多功能计算器(Multi-function Calc):Multi-function Calc.  带有存储和触发功能的计算器.它使用了多种数据类型来表示语义值.
* 练习(Exercises):Exercises.         一些改进多功能计算器的方案
@end menu

@node RPN Calc
@section 逆波兰记号计算器-Reverse Polish Notation Calculator
@cindex 逆波兰记号(reverse polish notation)
@cindex 波兰记号计算器(polish notation calculator)
@cindex @code{rpcalc}
@cindex 计算器(calculator), 简单(simple)

我们的第一个例子是双精度@dfn{逆波兰记号(reverse polish notation)}计算器(一个使用后缀操作符的计算器).
这个例子是一个很好的起点,因为没有操作符优先级的问题.
第二个例子会说明如何处理运算符优先级.

这个计算器的源代码文件叫@file{rpcalc.y}.
@samp{.y}是Bison惯用的输入文件的扩展名.

@menu
* 声明(Decls): Rpcalc Decls.  rpclac的Prologue(声明)部分.
* 规则(Rules): Rpcalc Rules.  带注释的rpcalc语法规则
* 词法分析器(Lexer): Rpcalc Lexer.  词法分析器
* 主函数(Main): Rpcalc Main.    控制函数
* 错误(Error): Rpcalc Error.  错误报告的规则
* 生成(Gen): Rpcalc Gen.      使用Bison生成分析器
* 编译(Comp): Rpcalc Compile. 使用C编译器编译得到最终结果
@end menu

@node Rpcalc Decls
@subsection @code{rpclac}的声明部分-Declarations for @code{rpcalc}

这就是逆波兰记号计算器的C和Bison声明部分.
像C语言一样,注释部分在@samp{/*@dots{}*/}中.

@example
/* Reverse polish notation calculator.  */
/* 逆波兰记号计算器 */

%@{
  #define YYSTYPE double
  #include <math.h>
  int yylex (void);
  void yyerror (char const *);
%@}

%token NUM

%% /* Grammar rules and actions follow.  */
@end example

声明部分(@pxref{Prologue, ,@var{Prologue}部分-The prologue})包括了两个预处理指令和两个前置声明.

@code{#define}指令定义了@code{YYSTYPE}宏,
它指明了记号和组(@pxref{Value Type, ,语义值的数据类型-Data Types of Semantic Values})语义值的C数据类型.
Bison分析器会使用任何@code{YYSTYPE}定义的数据类型;
如果你没有定义它,@code{int}则是默认的类型.
因为我们指明了@code{double},所以每个记号和表达式已经关联了一个浮点数值.

@code{#include}用来声明幂函数@code{pow}.

由于C语言要求函数必须在使用之前声明,
所以前置声明@code{yylex}和@code{yyerror}是必须的.
这些函数将在@var{epilogue}部分定义,但是分析器会调用它们,
所以它们必须在@var{prologue}部分声明.

第二部分-@var{Bison declarations},提供了有关记号类型的信息(@pxref{Bison Declarations, ,@var{Bison Declarations}部分-The Bison Declarations Section}).
每一个不只一个字符组成的终结符必须在这里声明.(通常没有必要声明单一字符.)
在这个例子中,所有的算术操作符都是单一字符的,
所以我们在这里仅需要声明的终结符是@code{NUM}---数字常量的记号类型.

@node Rpcalc Rules
@subsection @code{rpcalc}的语法规则-Grammar Rules for @code{rpcalc}

这就是逆波兰记号计算器的语法规则:

@example
input:    /* empty */
        | input line
;

line:     '\n'
        | exp '\n'      @{ printf ("\t%.10g\n", $1); @}
;

exp:      NUM           @{ $$ = $1;           @}
        | exp exp '+'   @{ $$ = $1 + $2;      @}
        | exp exp '-'   @{ $$ = $1 - $2;      @}
        | exp exp '*'   @{ $$ = $1 * $2;      @}
        | exp exp '/'   @{ $$ = $1 / $2;      @}
         /* Exponentiation */
        | exp exp '^'   @{ $$ = pow ($1, $2); @}
         /* Unary minus    */
        | exp 'n'       @{ $$ = -$1;          @}
;
%%
@end example

在这里定义的rpcalc``语言''的组是:表达式(名称是@code{exp}),
输入行(@code{line}),整个的输入脚本(@code{input)}.
这些非终结符每个都有多个可选择的规则.
这些规则由@samp{|}连接而成.
我们可以把@samp{|}读做``或者''.
接下来的部分解释了规则的含义.

语法的语义由当组组被识别时执行的动作决定.
动作由在大括号中C代码组成.
@pxref{Actions, ,动作-Actions}.

你必须用C语言来指定这些动作,
但是Bison也提供了在规则之间传递语义值的方法.
在每个动作中,伪变量@code{$$}代表着将要构建的组的语义值.
赋值给@code{$$}是大多数动作的工作.
规则的组成部分的语义值由@code{$1},@code{$2}等等指定.

@menu
* Rpcalc Input::
* Rpcalc Line::
* Rpcalc Expr::
@end menu

@node Rpcalc Input
@subsubsection 解释@code{input}-Explanation of @code{input}

考虑@code{input}的定义:

@example
input:    /* empty */
        | input line
;
@end example

这个定义可以被解释为:``一个完整的输入己可能是一个空字符串,
也可能是一个完整输入后紧跟一个输入行''.
我们应该注意到``完整输入''定义在它自己的条款中.
由于@code{input}总是出现在符号序列的最左端,
我们称这种定义为@dfn{左递归(left recursive)}.
(@xref{Recursion, ,递归规则-Recursive Rule}.)

第一个可选择的规则为空是由于在冒号和第一个@samp{|}之间没有任何符号;
这意味着@code{input}可以匹配一个空字符串的输入(没有符号).
我们这样编写规则是因为在你启动计算器后,在右端输入@kbd{Ctrl-d}是合法的.
把空规则放在最开始并伴随注释@samp{/* empty */}是使用惯例.

第二个可选的规则(@code{input line})处理了所有非平凡的输入.
它的含义是``在读取任意个数的line后,如果可能的化读取更多的line.''
作递归使得这个规则进入循环.
由于第一个可选的规则与空输入匹配,循环可能被执行零次或多次.

分析器函数@code{yyparse}继续处理输入直到发现一个语法错误或者
词法分析器表明没有更多的输入记号为止;
我们会安排后者在输入结束的时候发生.

@node Rpcalc Line
@subsubsection 解释@code{line}-Explanation of @code{line}

现在我们考虑@code{line}的定义:

@example
line:     '\n'
        | exp '\n'  @{ printf ("\t%.10g\n", $1); @}
;
@end example

第一个选择是一个换行符号;
这意味着rpcalc接受一个空白行(并且忽略它,因为没有相关的动作).
第二个选择是一个表达式后紧跟着一个换行符.
这个选择使得rpcalc变得实用.
@code{$1}的值是@code{exp}组的语义值,
这是因为@code{exp}是诸多选择中的第一个符号.
这个值就是用户需要的计算结果.
相关的动作打印了这个这个值.

这个动作非同寻常,因为它并没有向@code{$$}赋值.
这样做的结果就是与@code{line}相关联的语义值并未被初始化(它的值是不可预期的).
如果那个值被使用的话,这会成为一个bug,但是我们并未使用它.
rpcalc一旦已经打印了用户输入行的值,就不再需要那个值了.

@node Rpcalc Expr
@subsubsection 解释@code{expr}-Explanation of @code{expr}

@code{exp}组有很多规则,每一个都是一种表达式.
第一个规则处理最简单的表达式:仅仅是数字的表达式.
第二个处理看起来像两个表达式后紧跟一个假发符号的加法表达式.
第三个处理减法等等.

@example
exp:      NUM
        | exp exp '+'     @{ $$ = $1 + $2;    @}
        | exp exp '-'     @{ $$ = $1 - $2;    @}
        @dots{}
        ;
@end example

我们已经使用@samp{|}将@code{exp}的规则连接起来.
但是我们也可以将它们分开来写:

@example
exp:      NUM ;
exp:      exp exp '+'     @{ $$ = $1 + $2;    @} ;
exp:      exp exp '-'     @{ $$ = $1 - $2;    @} ;
        @dots{}
@end example

大部分规则都有从其部分值来计算表达式值的动作.
例如,在加法的规则中,@code{$1}指的是第一个部件@code{exp},
而@code{$2}指的是第二个部件.
第三个部件@code{'+'}并没有相关联的语义值.
但是如果它有语义值的话,你可以用@code{$3}来代表.
当@code{yyparse}使用这个规则识别了一个加法和表达式,
两个自表达式值的相加而得到了整个表达是的值.
@xref{Actions, ,动作-Actions}.

你不必为每一个规则都指定动作.
当一个规则没有动作时,Bison会默认地将@code{$1}的值复制给@code{$$}.
这就是在第一规则被(使用@code{NUM}的规则)识别时发生的事情.

在这里展示的是推荐的惯用格式,
但是Bison并没有要求一定要这么做.
你可以增加或者更改任意你想要的数量的空白.
例如,这个:

@example
exp   : NUM | exp exp '+' @{$$ = $1 + $2; @} | @dots{} ;
@end example

@noindent
与这个的意义相同

@example
exp:      NUM
        | exp exp '+'    @{ $$ = $1 + $2; @}
        | @dots{}
;
@end example

@noindent
然而,后一种写法明显更可读.

@node Rpcalc Lexer
@subsection @code{rpcalc}的词法分析器-The @code{rpcalc} Lexical Analyzer
@cindex 编写一个词法分析器(writing a lexical analyzer)
@cindex 词法分析器(lexical analyzer), 编写(writing)

词法分析器的工作是低级别的分析:
将字符或者字符序列转化成记号.
Bison分析器靠调用词法分析器来获取它的记号.
@xref{Lexical, ,词法分析函数@code{yylex}-The Lexical Analyzer Function @code{yylex}}.

即使最简单的词法分析器也是@acronym{RPN}计算器所必须的.
这个词法分析器跳过了空白和制表符,
然后将数字读取为@code{double}并且以@code{NUM}记号返回它们.
任何不是数字的字符都是一个分隔记号.
注意到一个单个字符的记号是这个字符本身.


词法分析器的返回值是一个代表记号类型的数字码.
相同的文字(在Biosn规则中使用,代表这个记号类型)
也是一个代表这个类型数字码的C表达式.
它以两种方式工作.
如果记号类型是一个字符,那么它的数字码就是那个字符;
你可以在词法分析器中使用相同字符表达那个数字码.
如果记号类型是一个标识符,
那个标识符是一个由Bison定义为一个恰当的数字的宏,
因此在这个例子中,@code{NUM}是一个给@code{yylex}使用的宏.

记号的语义值(如果有的话)被存储在全局变量@code{yylval}中.
Bison分析器会在需要语义值适当的时候找到它.
(@code{yylval}的C数据类是@code{YYSTYPE},它在语法的开始被定义;
@pxref{Rpcalc Decls, ,@code{rpcalc}的声明部分-Declarations for @code{rpcals}}.

符号类型码0在输入结束的时候被返回.
(Bison将任何不正确的值识别为输入结束)

这就是词法分析器的代码:

@example
@group
/* The lexical analyzer returns a double floating point
   number on the stack and the token NUM, or the numeric code
   of the character read if not a number.  It skips all blanks
   and tabs, and returns 0 for end-of-input.  */
/* 词法分析起在栈上返回一个双精度浮点数(注:指yylval)并且返回记号NUM,
   或者返回不是数字的字符的数字码.它跳过所有的空白和制表符,
   并且返回0作为输入的结束. */
#include <ctype.h>
@end group

@group
int
yylex (void)
@{
  int c;

  /* Skip white space.  */
  /* 处理空白. */
  while ((c = getchar ()) == ' ' || c == '\t')
    ;
@end group
@group
  /* Process numbers.  */
  /* 处理数字 */
  if (c == '.' || isdigit (c))
    @{
      ungetc (c, stdin);
      scanf ("%lf", &yylval);
      return NUM;
    @}
@end group
@group
  /* Return end-of-input.  */
  /* 返回输入结束 */
  if (c == EOF)
    return 0;
  /* Return a single char.  */
  /* 返回一个单一字符 */
  return c;
@}
@end group
@end example

@node Rpcalc Main
@subsection 控制函数-The Controlling Function
@cindex 控制函数(controlling function)
@cindex 简单例子的main函数(main function in simple example)

为了保证这个例子的精巧,控制函数也保持了最小化。
它仅仅要求调用@code{yyparse}来开始分析的处理.

@example
@group
int
main (void)
@{
  return yyparse ();
@}
@end group
@end example

@node Rpcalc Error
@subsection 错误报告的规则-The Error Reporting Routine
@cindex 错误报告的规则(error reporting routine)

当@code{yyparse}侦测到语法错误的时候,
它会调用错误报告函数@code{yyerror}来打印一个错误信息(经常但不总是@code{"syntax error"}).
@code{yyparse}需要程序员来提供@code{yyerror}(@pxref{Interface, ,分析器C语言接口-Parser C-Language Interface}),
所我我们使用这样的定义:

@example
@group
#include <stdio.h>

/* Called by yyparse on error.  */
void
yyerror (char const *s)
@{
  fprintf (stderr, "%s\n", s);
@}
@end group
@end example

在@code{yyerror}返回之后,
如果语法包括了适当的错误规则(@pxref{Error Recovery, ,错误恢复-Error Recovery})
Bison分析器可以从错误中恢复并且继续分析.
否则,@code{yyparse}返回非零值.
我们在这个例子中并没有编写任何错误规则,
所以任何无效的输入会导致计算器程序退出.
这种做法显然对于这正的计算器是不够用的,
但是足够用于这个例子.

@node Rpcalc Gen
@subsection 运行Bison来产生分析器-Running Bison to Make the Parser
@cindex 运行Bison(简介)(running Bison (introduction))

(@pxref{Grammar Layout, ,Bison语法文件的布局-The Overall Layout of a Bison Grammar}).
在运行Bison制造分析器之前,我们要决定如何把源代码安排在一个或多个源文件中.
对于这样一个简单的例子来说,
最简单的方法就是把所有的东西放到一个文件中.
@code{yylex},@code{yyerror}和@code{main}放在末尾的@var{epilogue}部分中.

对于一个大工程来说,你可能会有很多的源代码文件,
这时候你需要使用@code{make}安排它们的重编译.

因为所有的代码都在一个文件中,
你使用下面的命令将它转化成一个分析器文件:

@example
bison @var{file_name}.y
@end example

@noindent
在这个例子中,这个文件是@file{rpcalc.y}(代表 ``Reverse Polish @sc{calc}ulator'').
Bison产生一个名为@file{@var{file_name}.tab.c}的文件.
并且将原文件的@samp{.y}的扩展名移去.
在输入中的额外函数(@code{yylex},@code{yyerror}和@code{main})被逐字地复制到输出文件.


@node Rpcalc Compile
@subsection 编译分析器文件-Compiling the Parser File
@cindex 编译分析器(compiling the parser)

这就是如何编译并运行分析器文件:

@example
@group
# @r{列出当前目录的文件.}
$ @kbd{ls}
rpcalc.tab.c  rpcalc.y
@end group

@group
# @r{编译Bison分析器.}
# @r{@samp{-lm} 告诉编译器搜索与@code{pow}匹配的库.}
$ @kbd{cc -lm -o rpcalc rpcalc.tab.c}
@end group

@group
# @r{再次列文件.}
$ @kbd{ls}
rpcalc  rpcalc.tab.c  rpcalc.y
@end group
@end example

文件@file{rpcalc}现在已经包含了可执行代码.
这就是使用@code{rpcalc}的会话的例子:

@example
$ @kbd{rpcalc}
@kbd{4 9 +}
13
@kbd{3 7 + 3 4 5 *+-}
-13
@kbd{3 7 + 3 4 5 * + - n}              @r{注意负号操作符, @samp{n}}
13
@kbd{5 6 / 4 n +}
-3.166666667
@kbd{3 4 ^}                            @r{幂运算}
81
@kbd{^D}                               @r{文件结束标识符}
$
@end example

@node Infix Calc
@section 中缀符号计算器:@code{calc}-Infix Notation Calculator: @code{calc}
@cindex 中缀符号计算器(infix notation calculator)
@cindex @code{calc}
@cindex 计算器(calculator), 中缀符号(infix notation)

现在我们可以通过修改rpcalc使其处理中缀操作符.
中缀操作符涉及到了操作符优先级的概念以及任意深度的括号嵌套.
这里是@file{calc.y},一个中缀桌面计算器的代码.


@example
/* Infix notation calculator.  */
/* 中缀符号计算器 */

%@{
  #define YYSTYPE double
  #include <math.h>
  #include <stdio.h>
  int yylex (void);
  void yyerror (char const *);
%@}

/* Bison declarations.  */
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG     /* negation--unary minus */ /* 负号 */
%right '^'    /* exponentiation */ /* 幂运算 */

%% /* The grammar follows.  */ /*　下面是语法 */
input:    /* empty */
        | input line
;

line:     '\n'
        | exp '\n'  @{ printf ("\t%.10g\n", $1); @}
;

exp:      NUM                @{ $$ = $1;         @}
        | exp '+' exp        @{ $$ = $1 + $3;    @}
        | exp '-' exp        @{ $$ = $1 - $3;    @}
        | exp '*' exp        @{ $$ = $1 * $3;    @}
        | exp '/' exp        @{ $$ = $1 / $3;    @}
        | '-' exp  %prec NEG @{ $$ = -$2;        @}
        | exp '^' exp        @{ $$ = pow ($1, $3); @}
        | '(' exp ')'        @{ $$ = $2;         @}
;
%%
@end example

@noindent
@code{yylex},@code{yyerror}和@code{main}可以与上一个例子一样.

这段代码展示了两个重要的新特征.

在第二部分中(@var{Bison declarations}),
@code{%left}声明了记号类型并且指明它们是左结合操作符.
@code{%left}和@code{%right}(右结合)的声明代替了@code{%token}.
@code{%token}是用来声明没有结合性的记号类型的.
(这些记号(注:是指@samp{'+'},@samp{'-'},@samp{'*'},@samp{'/'},@samp{'NEG'})
是原本并不用声明的单字符记号.我们声明它们的目的是指出它们的结合性.)

操作符优先级是由声明所在行的顺序决定的.
行号越大的操作符(在一页或者屏幕底端)具有越高的优先级.
因此,幂运算具有最高优先级,负号(@code{NEG})其次,
接这是@samp{*}和@samp{/}等等.
@xref{Precedence, ,操作符优先级-Operator Precedence}.

另外一个重要的特征是在语法部分的负号操作符中使用了@code{%prec}.
语法中的@code{%prec}只是简单的告诉Bison规则@samp{| '-' exp}与@code{NEG}有相同的优先级---在前述的优先级规则中.
@xref{Contextual Precedence, ,依赖上下文的优先级-Context-Dependent Precedence}.

这就是一个运行@file{calc.y}的例子:

@need 500
@example
$ @kbd{calc}
@kbd{4 + 4.5 - (34/(8*3+-3))}
6.880952381
@kbd{-56 + 2}
-54
@kbd{3 ^ 2}
9
@end example

@node Simple Error Recovery
@section 简单的错误恢复-Simple Error Recovery
@cindex 错误恢复(error recovery), 简单(simple)

直到这一章之前,这个手册并未提及@dfn{错误恢复(error recovery)}的内容---
即在分析器侦测到错误之后如何继续分析.
所有我们需要做的事情就是编写@code{yyerror}函数.
我们可以回忆一下以前的例子,
@code{yyparse}在调用@code{yyerror}后返回.
这就意味着任一个错误的输入会导致计算机程序的退出.
现在我们就展示如何改正这个不足.

Bison语言自己包括了可以插入到语法规则中去的保留字@code{error}.
在这个例子中,它被添加到@code{line}的一个可选的规则中.

@example
@group
line:     '\n'
        | exp '\n'   @{ printf ("\t%.10g\n", $1); @}
        | error '\n' @{ yyerrok;                  @}
;
@end group
@end example

这个添加的规则允许在语法错误发生的时候有简单的错误恢复动作.
如果一个读入一个无法求值的表达式,
这个错误会被识别成@code{line}的第三个规则并且分析会继续执行.
(@code{yyerror}函数仍会被调用来打印它的信息).
执行这个动作的语句@code{yyerrok}是一个被Bison自动定义的宏.
它的含义是错误恢复已经完成(@pxref{Error Recovery, ,错误恢复-Error Recovery}).
我们应当注意到@code{yyerror}和@code{yyerrok}的区别,
它们的印刷都没有错误.

这种形式的错误恢复用于处理语法错误.
还有很多其它形式的错误;
例如,除数为0,这会产生一个通常致命的异常信号(an exception signal).
一个真正的计算器必须处理这种信号并且使用@code{longjmp}返回到@code{main}并且继续分析输入行;
它(注:真正的计算器)也可以丢弃剩余的输入行.
我们并不深入地讨论这个问题,
因为这与Bison程序无关.

@node Location Tracking Calc
@section 带有位置追踪的计算器:@code{ltcalc}-Location Tracking Calculator: @code{ltcalc}
@cindex 位置追踪(location tracking calculator)
@cindex @code{ltcalc}
@cindex 计算器(calculator), 位置追踪(location tracking)

这个例子将中缀符号计算器扩展以使其带有位置追踪功能.
这个特征将被应用于改进错误消息的显示.
为了把保持简洁性,
这个例子是一个简单的整数计算器.
为了使用位置,大多数工作将在词法分析器中完成.

@menu
* 声明(Decls): Ltcalc Decls.  ltcalc的Bison和C语言的声明
* 规则(Rules): Ltcalc Rules.  详细解释ltcalc的语法规则
* 词法分析器(Lexer): Ltcalc Lexer.  词法分析器
@end menu

@node Ltcalc Decls
@subsection @code{ltcalc}的@var{Declarations}-Declarations for @code{ltcalc}

位置追踪计算器的C和Bison声明部分与中缀符号计算器的声明部分相同.

@example
/* Location tracking calculator.  */
/* 位置追踪计算器 */

%@{
  #define YYSTYPE int
  #include <math.h>
  int yylex (void);
  void yyerror (char const *);
%@}

/* Bison declarations.  */
/* Bison 声明 */
%token NUM

%left '-' '+'
%left '*' '/'
%left NEG
%right '^'

%% /* The grammar follows.  */ /* 下面是语法 */
@end example

@noindent
注意到并没有位置的特别声明.
并不需要定义一个存储位置的数据类型:
我们使用Bison提供的默认的类型(@pxref{Location Type, ,位置的数据类型-Data Types of Locations}).
这种数据类型是带有四个整数域的结构体:
@code{first_line},@code{first_column},@code{last_line}和@code{last_column}.

@node Ltcalc Rules
@subsection @code{ltcalc}的语法规则-Grammar Rules for @code{ltcalc}

是否处理位置对于你的语言的语法明没有影响.
因此,这个语言的语法规则与前一个例子的非常接近;
我们仅仅修改它们以获取新的信息.

在这里,我们使用位置来报告除数为0并且对错误的表达式或子表达式进行定位.

@example
@group
input   : /* empty */
        | input line
;
@end group

@group
line    : '\n'
        | exp '\n' @{ printf ("%d\n", $1); @}
;
@end group

@group
exp     : NUM           @{ $$ = $1; @}
        | exp '+' exp   @{ $$ = $1 + $3; @}
        | exp '-' exp   @{ $$ = $1 - $3; @}
        | exp '*' exp   @{ $$ = $1 * $3; @}
@end group
@group
        | exp '/' exp
            @{
              if ($3)
                $$ = $1 / $3;
              else
                @{
                  $$ = 1;
                  fprintf (stderr, "%d.%d-%d.%d: division by zero",
                           @@3.first_line, @@3.first_column,
                           @@3.last_line, @@3.last_column);
                @}
            @}
@end group
@group
        | '-' exp %preg NEG     @{ $$ = -$2; @}
        | exp '^' exp           @{ $$ = pow ($1, $3); @}
        | '(' exp ')'           @{ $$ = $2; @}
@end group
@end example

这段代码展示了如何对规则部件使用伪变量@code{@@@var{n}}
以及对组使用伪变量@code{@@$}在语义动作中确定位置.

我们不需要向@code{@@$}赋值:输出分析器会自动这样作.
默认地,在执行每个动作的C代码之前,
对于一个具有@var{n}个部件的规则,
@code{@@$}被设定为从@code{@@1}的开始到@code{@@@var{n}}的结束.
我们可以重新定义这个动作(@pxref{Location Default Action, ,位置的默认动作-Default Action for Locations}),
对于一些特殊的规则,@code{@@$}可以手动计算.

@node Ltcalc Lexer
@subsection @code{ltcalc}的词法分析器-The @code{ltcalc} Lexical Analyzer.

直到现在,我们仍然在依靠Bison的默认来激活位置追踪.
下一步就我们重写词法分析器,
并且使它与语法分析器的记号位置相适合,
就像它已经为语义值做的那样.

在最后,为了避免计算的位置出错,我们必须对每个输入字符进行计数.

@example
@group
int
yylex (void)
@{
  int c;
@end group

@group
  /* Skip white space.  */
  /* 跳过空白 */
  while ((c = getchar ()) == ' ' || c == '\t')
    ++yylloc.last_column;
@end group

@group
  /* Step.  */
  yylloc.first_line = yylloc.last_line;
  yylloc.first_column = yylloc.last_column;
@end group

@group
  /* Process numbers.  */
  /* 处理数字 */
  if (isdigit (c))
    @{
      yylval = c - '0';
      ++yylloc.last_column;
      while (isdigit (c = getchar ()))
        @{
          ++yylloc.last_column;
          yylval = yylval * 10 + c - '0';
        @}
      ungetc (c, stdin);
      return NUM;
    @}
@end group

  /* Return end-of-input.  */
  /* 返回输入结束 */
  if (c == EOF)
    return 0;

  /* Return a single char, and update location.  */
  /* 返回一个单字符,并且更新位置 */
  if (c == '\n')
    @{
      ++yylloc.last_line;
      yylloc.last_column = 0;
    @}
  else
    ++yylloc.last_column;
  return c;
@}
@end example

词法分析器基本上做出了与前一个例子相同的处理:
它跳过了空格和制表符,并且读入了许多单字符记号.
而外地,它更新了含有记号位置的全局变量(类型是@code{YYLTYPE})@code{yyloc}.

现在,每当这个函数返回一个记号,与语义值一样,
分析器就有了这个记号的编号和它的文字位置.
最后需要改变的就是初始化@code{yyloc}.
例如在控制函数中:

@example
@group
int
main (void)
@{
  yylloc.first_line = yylloc.last_line = 1;
  yylloc.first_column = yylloc.last_column = 0;
  return yyparse ();
@}
@end group
@end example

要记住:计算位置并不是语法的事情.
每个字符必须关联一个位置更新,
不论它是在一个有效的输入中还是在注释中或者字符串中等等.

@node Multi-function Calc
@section 多功能计算器:@code{mfcalc}-Multi-Function Calculator: @code{mfcalc}
@cindex 多功能计算器(multi-function calculator)
@cindex @code{mfcalc}
@cindex 计算器(calculator), 多功能(multi-function)

到现在为止,我们已经讨论了Bison的基础部分.
是时间去转移到一些高级的问题中去.
上述的计算器仅仅提供了五个功能,
@samp{+},@samp{-},@samp{*},@samp{/}和@samp{^}.
让我们的计算器提供其它数学函数如@code{sin},@code{cos}等等是一个不错的想法.

一旦新的操作符只是单字符,将它们添加到中缀计算器是很简单的事情.
词法分析器@code{yylex}将所有非数字字符返回成记号,
所以新的语法规则对于新的操作符来说足够用.
但是我们需要一些更灵活的东西:采用这种格式的内建函数:

@example
@var{function_name} (@var{argument})
@end example

@noindent
同时,我们靠建立命名变量来为计算器添加记忆功能.
我们可以在它们中存储数值,并在稍后使用它们.
这就是多功能计算器的一个会话的例子:

@example
$ @kbd{mfcalc}
@kbd{pi = 3.141592653589}
3.1415926536
@kbd{sin(pi)}
0.0000000000
@kbd{alpha = beta1 = 2.3}
2.3000000000
@kbd{alpha}
2.3000000000
@kbd{ln(alpha)}
0.8329091229
@kbd{exp(ln(beta1))}
2.3000000000
$
@end example

注意到多重赋值和嵌套函数是允许使用的.

@menu
* 声明(Decl): Mfcalc Decl.      多功能计算器的Bison声明
* 规则(Rules): Mfcalc Rules.    计算器的语法声明
* 符号表(Symtab): Mfcalc Symtab.  符号表管理规则
@end menu

@node Mfcalc Decl
@subsection @code{mfcalc}的声明-Declarations for @code{mfcalc}

这就是多功能计算器的C和Bison声明部分:

@smallexample
@group
%@{
  #include <math.h>  /* For math functions, cos(), sin(), etc.  */ /* 为了使用数学函数, cos(), sin(), 等等 */
  #include "calc.h"  /* Contains definition of `symrec'.  */ /* 包含了 `symrec'的定义 */
  int yylex (void);
  void yyerror (char const *);
%@}
@end group
@group
%union @{
  double    val;   /* For returning numbers.  */ /* 返回的数值 */
  symrec  *tptr;   /* For returning symbol-table pointers.  */ /* 返回的符号表指针 */
@}
@end group
%token <val>  NUM        /* Simple double precision number.  */ /* 简单的双精度数值 */
%token <tptr> VAR FNCT   /* Variable and Function.  */ /* 变量和函数 */
%type  <val>  exp

@group
%right '='
%left '-' '+'
%left '*' '/'
%left NEG     /* negation--unary minus */ /* 负号 */
%right '^'    /* exponentiation */ /* 幂 */
@end group
%% /* The grammar follows.  */ 
@end smallexample

上述的语法仅仅引进了两个Bison语言的信特征.
这些特征允许语义值拥有多种数据类型.
(@pxref{Multiple Types, ,多种值类型-More Than One Value Type}).

@code{%union}声明了所有可能类型清单;
这是用来取代@code{YYSTYPE}的.
现在允许的类型是双精度(为了@code{exp}和@code{NUM})和指向符号表目录项的指针.
@xref{Union Decl, ,值类型集-The Collection of Value Types}.

由于语义值值现在可以有多种类型,
对每一个使用语义值的语法符号关联一个语义值类型是很必要的.
这些符号是@code{NUM},@code{VAR},@code{FNCT},和@code{exp}.
它们的在声明的时候已经指明了语义值类型(在中括号之间).

@code{%type}用来声明非终结符,就像@code{%token}用来声明符号类型(注:终结符)的一样.
我们之前并没有@code{%type}是因为非终结符经常在定义它们的规则中隐含地声明.
但是@code{exp}必须被明确地声明以便我们使用它的语义值类型.
@xref{Type Decl, ,非终结符-Nonterminal Symbols}.

@node Mfcalc Rules
@subsection @code{mfcalc}的语法规则-Grammar Rules for @code{mfcalc}

这就是多功能计算器的语法规则.
它们之中的大多数都是从@code{calc}复制过来的;
三个提到@code{VAR}或者@code{FNCT}的规则是新增的.

@smallexample
@group
input:   /* empty */
        | input line
;
@end group

@group
line:
          '\n'
        | exp '\n'   @{ printf ("\t%.10g\n", $1); @}
        | error '\n' @{ yyerrok;                  @}
;
@end group

@group
exp:      NUM                @{ $$ = $1;                         @}
        | VAR                @{ $$ = $1->value.var;              @}
        | VAR '=' exp        @{ $$ = $3; $1->value.var = $3;     @}
        | FNCT '(' exp ')'   @{ $$ = (*($1->value.fnctptr))($3); @}
        | exp '+' exp        @{ $$ = $1 + $3;                    @}
        | exp '-' exp        @{ $$ = $1 - $3;                    @}
        | exp '*' exp        @{ $$ = $1 * $3;                    @}
        | exp '/' exp        @{ $$ = $1 / $3;                    @}
        | '-' exp  %prec NEG @{ $$ = -$2;                        @}
        | exp '^' exp        @{ $$ = pow ($1, $3);               @}
        | '(' exp ')'        @{ $$ = $2;                         @}
;
@end group
/* End of grammar.  */
%%
@end smallexample

@node Mfcalc Symtab
@subsection @code{mfcalc}的符号表-The @code{mfcalc} Symbol Table
@cindex 符号表实例(symbol table example)

多功能计算器需要一个符号表来追踪变量和符号的名称和意义.
这并不影响语法规则(除了动作以外)或者Biosn声明.
但是它要求一些额外的C函数支持.

符号表本身由记录的链表组成.
它的定义在头文件@file{calc.h}中,如下.
它提供了将函数或者变量插入符号表的功能.

@smallexample
@group
/* Function type.  */
/* 函数类型 */
typedef double (*func_t) (double);
@end group

@group
/* Data type for links in the chain of symbols.  */
/* 链表节点的数据类型 */
struct symrec
@{
  char *name;  /* name of symbol */ /* 符号的名称 */
  int type;    /* type of symbol: either VAR or FNCT */ /* 符号的类型: VAR 或 FNCT */
  union
  @{
    double var;      /* value of a VAR */ /* VAR 的值 */
    func_t fnctptr;  /* value of a FNCT */ /* FNCT 的值 */
  @} value;
  struct symrec *next;  /* link field */ /* 指针域 */
@};
@end group

@group
typedef struct symrec symrec;

/* The symbol table: a chain of `struct symrec'.  */
/* 符号表: `struct symrec'的链表 */
extern symrec *sym_table;

symrec *putsym (char const *, func_t);
symrec *getsym (char const *);
@end group
@end smallexample

新版本的@code{main}包含了一个@code{init_table}的调用,
这个函数用来初始化符号表.
这就是@code{main}和@code{init_table}的代码:

@smallexample
#include <stdio.h>

@group
/* Called by yyparse on error.  */
/* 出错时被yyparse调用 */
void
yyerror (char const *s)
@{
  printf ("%s\n", s);
@}
@end group

@group
struct init
@{
  char const *fname;
  double (*fnct) (double);
@};
@end group

@group
struct init const arith_fncts[] =
@{
  "sin",  sin,
  "cos",  cos,
  "atan", atan,
  "ln",   log,
  "exp",  exp,
  "sqrt", sqrt,
  0, 0
@};
@end group

@group
/* The symbol table: a chain of `struct symrec'.  */
/* 符号表: `struct symrec'链表 */
symrec *sym_table;
@end group

@group
/* Put arithmetic functions in table.  */
/* 将数学函数放入符号表(注:保留字的实现方式) */
void
init_table (void)
@{
  int i;
  symrec *ptr;
  for (i = 0; arith_fncts[i].fname != 0; i++)
    @{
      ptr = putsym (arith_fncts[i].fname, FNCT);
      ptr->value.fnctptr = arith_fncts[i].fnct;
    @}
@}
@end group

@group
int
main (void)
@{
  init_table ();
  return yyparse ();
@}
@end group
@end smallexample

靠简单地编辑初始化列表和添加必要的头文件,
你可以为计算器添加额外的功能.

两个重要的函数允许对符号表进行搜索和安置操作.
@code{putsym}函数需要传递要安置对象的名称和类型(@code{VAR}或@code{FNCT}).
对象被连接到链表的头部,一个指向这个对象的指针最后被返回.
@code{getsym}函数要传递需要查找的符号的名称.
如果找到,指向那个符号的指针回返回,否则返回0.

@smallexample
symrec *
putsym (char const *sym_name, int sym_type)
@{
  symrec *ptr;
  ptr = (symrec *) malloc (sizeof (symrec));
  ptr->name = (char *) malloc (strlen (sym_name) + 1);
  strcpy (ptr->name,sym_name);
  ptr->type = sym_type;
  ptr->value.var = 0; /* Set value to 0 even if fctn.  */ /* 置0即使是fctn */
  ptr->next = (struct symrec *)sym_table;
  sym_table = ptr;
  return ptr;
@}

symrec *
getsym (char const *sym_name)
@{
  symrec *ptr;
  for (ptr = sym_table; ptr != (symrec *) 0;
       ptr = (symrec *)ptr->next)
    if (strcmp (ptr->name,sym_name) == 0)
      return ptr;
  return 0;
@}
@end smallexample

@code{yylex}函数现在必须能识别出变量,数字值和单字符算术操作符.
开头为非字数字的字符串被识别为变量还是函数依赖于符号表对它们的描述.

字符串被传递到@code{getsym}用于在符号表中查找.
如果名称出现在表格中,指向它的位置的指针和它的类型会返回给@code{yyparse}.
如果尚未出现在符号表中,它会被安置为一个@code{VAR}使用函数@code{putsym}.
同样地,一个指针和它的类型(必须是@code{VAR})被返回给@code{yyparse}.

@code{yylex}中处理数字制和算术运算符的代码并不需要更改.

@smallexample
@group
#include <ctype.h>
@end group

@group
int
yylex (void)
@{
  int c;

  /* Ignore white space, get first nonwhite character.  */
  /* 忽略空白,获取第一个非空白的字符 */
  while ((c = getchar ()) == ' ' || c == '\t');

  if (c == EOF)
    return 0;
@end group

@group
  /* Char starts a number => parse the number.         */
  /* 以数字开头 => 分析数字 */
  if (c == '.' || isdigit (c))
    @{
      ungetc (c, stdin);
      scanf ("%lf", &yylval.val);
      return NUM;
    @}
@end group

@group
  /* Char starts an identifier => read the name.       */
  /* 以标识符开头 => 读取名称 */
  if (isalpha (c))
    @{
      symrec *s;
      static char *symbuf = 0;
      static int length = 0;
      int i;
@end group

@group
      /* Initially make the buffer long enough
         for a 40-character symbol name.  */
      /* 在开始的时候使缓冲区足够容纳40字符长的符号名称*/
      if (length == 0)
        length = 40, symbuf = (char *)malloc (length + 1);

      i = 0;
      do
@end group
@group
        @{
          /* If buffer is full, make it bigger.        */
          /* 如果缓冲区已满,使它大一点 */
          if (i == length)
            @{
              length *= 2;
              symbuf = (char *) realloc (symbuf, length + 1);
            @}
          /* Add this character to the buffer.         */
          /* 将这个字符加入缓冲区 */
          symbuf[i++] = c;
          /* Get another character.                    */
          /* 获取另外一个字符 */
          c = getchar ();
        @}
@end group
@group
      while (isalnum (c));

      ungetc (c, stdin);
      symbuf[i] = '\0';
@end group

@group
      s = getsym (symbuf);
      if (s == 0)
        s = putsym (symbuf, VAR);
      yylval.tptr = s;
      return s->type;
    @}

  /* Any other character is a token by itself.        */
  /* 其余的字符是自己为记号的字符 */
  return c;
@}
@end group
@end smallexample

这个程序既有效又灵活.
你可以轻松地加入新的函数.
而且加入于预定义数值如@code{pi}或者@code{e}也是很简单的事情.

@node Exercises
@section 练习-Exercises
@cindex 练习(exercises)

@enumerate
@item
向文件@file{math.h}中的初始列表添加新的函数.

@item
添加另外一个包括常量和它们数值的数组.
然活修改@code{init_table}将这些常量添加到符号表.
使这些常量类型为@code{VAR}最简单.

@item
使这个程序当用户引用一个未初始化的变量时报告一个错误.
@end enumerate

@node Grammar File
@chapter Biosn的语法文件-Bison Grammar Files

Bison使用一个描述上下文无关文法的文件做为输入,
并制造一个实别改文法的正确实例的C语言函数.

Bison语法输入文件通常以@samp{.y}结尾.@xref{Invocation, ,调用Bison-Invoking Bison}.

@menu             
* 语法提纲(Grammar Outline):Grammar Outline.   语法文件的整体布局
* 符号(Symbols):Symbols.           终结符与非终结符
* 规则(Rules):Rules.             如何编写语法规则
* 递归(Resursion):Recursion.         编写递归规则
* 语义(Semantics):Semantics.         语义值和动作
* 位置(Locations):Locations.         位置和动作
* 声明(Declarations):Declarations.      所有种类的Bison声明在这里讨论
* 多个分析器(Multiple Parsers):Multiple Parsers.  将多个Bison分析器放在一个程序中
@end menu

@node Grammar Outline
@section Bison语法的提纲-Outline of a Bison Grammar

一个Bison语法文件有四个主要的部分,
就像如下所示,由恰当的分隔符分隔.

@example
%@{
  @var{Prologue}
%@}

@var{Bison declarations}

%%
@var{Grammar rules}
%%

@var{Epilogue}
@end example

注释包含在@samp{/* @dots{} */}之中,并且可以在任意部分出现.
做为一个@acronym{GNU}扩展,@samp{//}引进了一个直到这行末尾的注释.

@menu
* Prologue::          Prologue部分的语法和使用
* Bison Declarations::  Bison declarations部分的语法和使用
* Grammar Rules::     Grammar Rules部分的语法和使用
* Epilogue::          Epilogue部分的语法和使用
@end menu

@node Prologue
@subsection @var{Prologue}部分-The prologue
@cindex 声明部分(declarations section)
@cindex Prologue
@cindex 声明(declarations)

@var{Prologue}部分包括宏定义和在语法规则动作中使用的函数和变量的声明.
这些将复制到分析器文件的开头以便先于@code{yyparse}的定义.
你可以使用@samp{#include}来从头文件获取声明.
如果你不需要任何的C声明,你可以省略这个部分的括号分隔符@samp{%@{}和@samp{%@}}.

可以有多个与@var{Bison declarations}混合的@var{Prologue}部分.
这种做法允许你拥有相互引用的C和Bison声明.
例如,@code{%union}可以使用定义在头文件的数据类型,
并且你希望使用带有@code{YYSTYPE}类型做为参数的函数.
可以通过两个@var{Prologue}块来实现这个,
一个在@code{%union}之前,另一个在之后.

@smallexample
%@{
  #include <stdio.h>
  #include "ptypes.h"
%@}

%union @{
  long int n;
  tree t;  /* @r{@code{tree} is defined in @file{ptypes.h}.} */ 
           /* @r{@code{tree} 在@file{ptypes.h}中定义.} */
@}

%@{
  static void print_token_value (FILE *, int, YYSTYPE);
  #define YYPRINT(F, N, L) print_token_value (F, N, L)
%@}

@dots{}
@end smallexample

@node Bison Declarations
@subsection @var{Bison Declarations}部分-The Bison Declarations Section
@cindex Bison声明(简介)(Bison declarations (introduction))
@cindex 声明(declarations), Bison(简介)(Bison (introduction))

@var{Bison declatations}部分包含了定义终结符和非终结符的声明,优先级等等.
在一些简单的语法中,可以不需要任何声明.
@xref{Declarations, ,@var{Bison Declarations}部分-Bison Declarations}.

@node Grammar Rules
@subsection 语法规则部分-The Grammar Rules Section
@cindex 语法规则部分(grammar rules section)
@cindex 语法的规则部分(rules section for grammar)

@dfn{Grammar Rules}部分包含了一个或多个Bison语法规则.
@xref{Rules, ,用来表示语法规则的语法-Syntax of Grammar Rules}.

在这里至少应该有一个语法规则,并且第一个@samp{%%}(先于语法规则的那个)绝对不能省略,解释它在文件的最开头.

@node Epilogue
@subsection @var{Epilogue}部分-The epilogue
@cindex 额外C代码部分(additional C code section)
@cindex epilogue
@cindex C代码(C code), 额外部分(section for additional)

就像@var{Prologue}部分被复制到开头一样,@var{Epilogue}部分被逐字地复制到分析器文件的结尾.
如果你想放一些代码却没必要放在@code{yyparse}的定义之前,这里是最方便的地方.
例如,@code{yylex}和@code{yyerror}的定义就经常放在这里.
因为C语言要求函数在使用之前必须声明.
你经常需要在@var{Prologue}部分声明类似@code{yylex}和@code{yyerror}的函数,
即使你在@var{Epilogue}部分已经定义了它们.
@xref{Interface, ,分析器C语言接口-Parser C-Language Interface}.

如果最后一部分为空,你可以省略分隔它的分隔符@samp{%%}.

Bison分析器自己包含了许多以@samp{yy}和@samp{YY}开头宏和标识符的定义.
所以在@var{Epilogue}部分避免使用这种类型的名字(出了这个文档讨论的之外)是一个好主意.

@node Symbols
@section 符号,终结符和非终结符-Symbols, Terminal and Nonterminal
@cindex 非终结符(nonterminal symbol)
@cindex 终结符(terminal symbol)
@cindex 符号类型(token type)
@cindex 符号(symbol)

Bison语法中的@dfn{符号(Symbols)}代表着语言的语法类型.

一个@dfn{终结符(terminal symbol)}(也被称做@dfn{符号类型(token type)}代表了一类从构造上等价的记号.
你在语法中使用符号的意思就是一个这种类型的记号是允许的.
Bison分析器将符号表示为数字码.
@code{yylex}返回一个记号类型来指明一个被读入的记号是什么类型的.
你不需要了解那个码的数值是多少;
你使用代表它的符号就可以了.

一个@dfn{非终结符(noterminal symbol)}代表一类从构造上等价的组.
符号名称用于编写语法规则.
按照惯例,所有的非终结符都应该是小写的.

符号名称可以是字母,数字(不在开头),下划线和句点.
句点只在非终结符中有意义.

在语法中书写终结符有三种方法:

@itemize @bullet
@item
一个@dfn{命名符号类型(named token type)}用类似C语言的标识符书写.
按照惯例,它们应该是大写字母.
每一个这种名称必须由一个Bison声明@code{%token}定义.
@xref{Token Decl, ,符号类型的名称-Token Type Names}.

@item
@cindex 字符记号(character token)
@cindex 文字记号(literal token)
@cindex 单字符文字(single-character literal)
一个@dfn{字符记号类型(character token type}(或者@dfn{文字字符记号(literal character token)}
用如同C语言字符常量相同的语法书写;
例如,@code{'+'}是一个字符记号类型.
除非你要指明字符记号类型的语义值类型(@pxref{Value Type, ,语义值的数据类型-Data Types of Semantic Values}),
结合性或优先级(@pxref{Precedence, ,操作符优先级-Operator Precedence}),
否则没有必要声明它们.

按照惯例,一个字符记号类型只用于表示一个由特定字符组成的记号.
因此,记号类型@code{'+'}用于将字符@samp{+}表示为一个记号.
没有对此惯例的强制要求,
但是如果你不按照惯例做,
你的程序会使其它的读者感到困惑.

所有常用的C语言的字符转义序列都可以在Bison中使用,
但是你不能使用一个空字符作为一个字符文字.
因为它的数字码是0,这表示输入结束.(@pxref{Calling Convention, ,@code{yylex}的调用惯例-Calling Convention for @code{yylex}}).
并且,不像标准C@
三字符词(trigraphs)(注:由``??''开头的九种转义,为了在缺少标准C标点的宿主上使用标准C,可参考标准C文档)
在Bison中并没有特殊意义并且反斜杠换行也是不允许的.

@item
@cindex 字符串记号(string token)
@cindex 文字串记号(literal string token)
@cindex 多字符文字(multicharacter literal)
一个@dfn{文字串记号(literal string token)}用类似C语言中的字符串常量来书写;
例如,@code{"<="}是一个文字串记号.
除非你要指明文字串记号的语义值类型(@pxref{Value Type, ,值类型-Value Type}),
结合性或优先级(@pxref{Precedence, ,优先级-Precedence}),你没有必要声明它们.

你可以使用@code{%token}(@pxref{Token Decl, ,符号声明-Token Declarations})将文字串记号关联一个符号名称作为别名.
如果你不这样做,词法分析器必须从@code{yytname}表中重新找到文字串记号的代码.
(@pxref{Calling Convention, ,调用惯例-Calling Convention}).

@strong{警告}: 文字串记号在Yacc中不能工作.

按照惯例,一个文字串记号只用于表示一个由特定串构成的记号.
因此,你用该使用类型@code{"<="}表示作为记号的字符串@samp{<=}.
Bison并没有强制要求这种管理,但是如果你偏离了惯例,
阅读你程序的人会感到困惑.

所有常用的C语言的字符转义序列都可以在Bison中使用,
但是你不能使用一个空字符作为一个字符文字.
因为它的数字码是0,这表示输入结束.
(@xref{Calling Convention, ,@code{yylex}的调用惯例-Calling Convention for @code{yylex}}.)
并且,不像标准C,
三字符词(trigraphs)(注:由``??''开头的九种转义,为了在缺少标准C标点的宿主上使用标准C,可参考标准C文档)
在Bison中并没有特殊意义并且反斜杠换行也是不允许的.
一个文字串记号必须包括两个或更多个字符;
对于进包含一个字符的记号,你应该使用字符记号(参考以上).
@end itemize

怎样选择终结符的写法对于它的语法意义没有影响.
它只依赖于它出现在规则的什么地方以及什么时候分器起函数返回那个符号.

@code{yylex}的返回值通常是终结符,除非返回一个代表结束输入的0或者负值.
无论你采用那种方法在语法规则中书写符号类型,
你应该在@code{yylex}的定义中采用相同的写法.
单字符符号类型的数字码简单地就是那个字符的正数编码,
所以,即使当@code{char}是有符号时你需要将它转换成@code{unsigned char}来避免主机上符号扩展
@code{yylex}仍可以使用相同的值来产生必要的代码.
每一个命名记号类型在分析器文件中变为一个C宏,
所以@code{yylex}可以使用名称代表那个编码.
(这就是为什么句点在终结符中不起作用.)
@xref{Calling Convention, ,@code{yylex}的调用惯例-Calling Convention for @code{yylex}}.

如果@code{yylex}是在另外的文件中定义的,
你需要安排符号类型的宏定义在那里是可见的.
在你运行Bison的时候使用@samp{-d}选项
以便让它将这些宏定义写入一个另外的头文件@file{@var{name}.tab.h}.
你可以将它加入其它需要它的文件.
@xref{Invocation, ,调用Bison-Invoking Bison}.

如果你要编写一个可以移植到任何标准C宿主上的语法,
你必须只能从基本标准C字符集中选择使用非零字符记号类型.
这个字符集由10个数字,52个大小写英文字母,和在下列C语言字符串中的字符构成的:

@example
"\a\b\t\n\v\f\r !\"#%&'()*+,-./:;<=>?[\\]^_@{|@}~"
@end example

@code{yylex}函数和Bison必须为字符记号使用一个一致的字符集和编码.
例如,如果你在@acronym{ASCII}环境中运行Bison,
但是在不兼容的例如@acronym{EBCDIC}的环境中编译和运行最终的程序,
最终程序可能不会工作.因为Bison产生的表格将字符记号假定为@acronym{ASCII}数字值.
发布带有Bison在@acronym{ASCII}环境中产生的C源文件的软件是标准的做法,
所以在与@acronym{ASCII}不兼容的平台的安装器必须在编译它们之前重新构建这些文件.

符号@code{error}是一个保留用作错误恢复的终结符(@pxref{Error Recovery, ,错误恢复-Error Recovery});
你不应该为了其它的目的而使用它.
特别地,@code{yylex}永远不应该返回这个值(注:@code{error}).
除非你明确地在声明中赋予一个你的记号值为256,否则error记号的默认值是256.

@node Rules
@section 描述语法规则的语法-Syntax of Grammar Rules
@cindex 规则语法(rule syntax)
@cindex 语法规则的语法(grammar rule syntax)
@cindex 用于语法规则的语法(syntax of grammar rules)

一个Bison语法规则通常有如下的下形式:

@example
@group
@var{result}: @var{components}@dots{}
        ;
@end group
@end example

@noindent
@var{reault}所在是这个规则描述的非终结符而@var{components}
是被这个规则组合在一起的多种终结符和非终结符.(@pxref{Symbols, ,符号-Symbols})

例如:

@example
@group
exp:      exp '+' exp
        ;
@end group
@end example

@noindent
表明两组@code{exp}类型和一个@samp{+}记号在中间,
可以结合成一个更大的@code{exp}类型组.

规则中的空白只用来分隔符号.你可以在你希望的地方添加额外的空白.

决定规则的语义的@var{动作}可以分散在部件中.一个动组看起来是这样:

@example
@{@var{C statements}@}
@end example

@noindent
通常只有一个动作跟随着部件.
@xref{Actions, ,动作-Actions}.

@findex |
@var{result}的多种规则可以分别书写或者由垂直条@samp{|}按如下的方法连接起来:

@ifinfo
@example
@var{result}:   @var{rule1-components}@dots{}
        | @var{rule2-components}@dots{}
        @dots{}
        ;
@end example
@end ifinfo
@iftex
@example
@group
@var{result}:    @var{rule1-components}@dots{}
        | @var{rule2-components}@dots{}
        @dots{}
        ;
@end group
@end example
@end iftex

@noindent
在这种方式下依然有我们之特考虑的特殊规则.

如果一个规则的@var{components}为空,它意味着@var{result}可以匹配空字符串.
例如,这就是一个定一个由逗号分隔的0个或多个@code{exp}组:

@example
@group
expseq:   /* empty */ /* 空 */
        | expseq1
        ;
@end group

@group
expseq1:  exp
        | expseq1 ',' exp
        ;
@end group
@end example

@noindent
我们通常对每个没有部件的规则加上一个@samp{/* empty */}的注释.

@node Recursion
@section 递归规则-Recursive Rules
@cindex 递归规则(recursive rule)

一个规则被称为@dfn{递归的(recursive})当它的@var{result}非终结符也出现在它的右手边.
因为这种方法是唯一可以定义一个特定事物的任意数字序列的方法,
几乎所有的Bison语法都需要使用递归.
考虑这个逗号分隔的一个或者多个表达式的递归定义:

@example
@group
expseq1:  exp
        | expseq1 ',' exp
        ;
@end group
@end example

@cindex 左递归(left recursion)
@cindex 右递归(right recursion)
@noindent
由于@code{expseq1}的递归使用是在有手端的最左符号,
我们称这种递归为@dfn{左递归(left recursion)}.
相反地,这里有一个相同地使用@dfn{右递归(right recursion)}的定义.

@example
@group
expseq1:  exp
        | exp ',' expseq1
        ;
@end group
@end example

@noindent
任意序列都可以使用作递归或者右递归定义,
但是通常你应该使用左递归,
因为它可以使用空间固定的栈来分析任意个数的元素序列.
由于即使规则只应用一次,在这之前,所有元素也都必须被移进到栈中,
右递归使用的Bison栈空间与序列中的元素个数成正比.
@xref{Algorithm, ,Bison分析器算法-The Bison Parser Alogorithm}.获得这方面的深入解释.

@cindex 相互递归-mutual recursion
@dfn{间接(Indirect)}或者@dfn{相互(mutual)}递归当规则的结果没有在它的右手端出现
但是出现在其它右手端的非终结符规则的右手端时候发生.

例如:

@example
@group
expr:     primary
        | primary '+' primary
        ;
@end group

@group
primary:  constant
        | '(' expr ')'
        ;
@end group
@end example

@noindent
定义了两个相互递归的非终结符,因为它们互相引用.

@node Semantics
@section 定义语言的语义-Defining Language Semantics
@cindex 定义语言的语义(defining language semantics)
@cindex 语言的语义(language semantics), 定义(defining)

语言的语法规则之决定了语言的语法.
语义值却是由与多种记号和组相关联的语义值和当各种组被识别时执行的动作决定的.

例如,正是由于对每个表达式关联了正确的数值,计算器才可以正确的计算.
因为组@w{@samp{@var{x} + @var{y}}}的动作是将@var{x}和@var{y}相关联的值相加,
所以计算器能正确地计算加法

@menu
* 值类型(Value Type):Value Type.        为所有的语义值指定一个类型.
* 多种类型(Multiple Types):Multiple Types.    指定多种可选的数据类型.
* 动作(Actions):Actions.           动作是一个语法规则的语义定义.
* 动作类型(Action Types):Action Types.      为动作指定一个要操作的数据类型.
* 规则中间的动作(Mid-Rule Actions):Mid-Rule Actions.  多数动作在规则之后,
                                           这一节讲述什么时候以及为什么要使用规则中间动作的特例.
@end menu

@node Value Type
@subsection 语义值的数据类型-Data Types of Semantic Values
@cindex 语义值类型(semantic value type)
@cindex 值类型(value type), 语义(semantic)
@cindex 语义值的数据类型(data types of semantic values)
@cindex 默认数据类型(default data type)

在一个简单的程序中,对所有的语言结构的语义值使用同一个数据类型就足够用了.
在@acronym{RPN}和中缀计算器的例子中的确是这样.(@pxref{RPN Calc, ,逆波兰记号计算器-Reverse Polish 
Notation Calculator}).

Bison默认是对于所有语义值使用@code{int}类型.
如果要指明其它的类型,可以像这样将@code{YYSTYPE}定义成一个宏:

@example
#define YYSTYPE double
@end example

@noindent
这个宏定义比喻在语法文件的@var{Prologue}部分.
(@pxref{Grammar Outline, ,Bison语法大纲-Outline of a Bison Grammar})

@node Multiple Types
@subsection 多种值类型-More Than One Value Type

在大多数程序中,你需要对不同种类的记号和组使用不同的数据类型.
例如,一个数字常量可能需要类型@code{int}或@code{long int},
而一个字符常量可能许需要类型@code{char *},
并且一个标识符需要一个指向符号表项的指针做为其语义值的类型.

为了在一个分析器中使用多种语义值类型,
Bison要求你做两件事情:

@itemize @bullet
@item
使用Bison声明@code{%union}指明全部可能的数据类型集.
(@pxref{Union Decl, ,值类型集-The Collections of Value Types}).

@item
从这些类型中为每个符号(终结符或者非终结符)选择一个做为其语义值类型.
要做到这些,可以对记号使用Bison声明@code{%token}(@pxref{Token Decl, ,符号类型的名称-Token Type Names});
并且对组使用Bison声明@code{%type}(@pxref{Type Decl, ,非终结符-Nonterminal Symbols})
@end itemize

@node Actions
@subsection 动作-Actions
@cindex 动作(action)
@vindex $$
@vindex $@var{n}

当一个规则的实例被识别的时候,
同这个规则关联的包含C代码的动作会被执行.
大多数动作是用来从记号的语义值或者更小组的语义值计算整个组的语义值.

一个动作由包含在大括号之内的几个C语句构成,很像一个C语句块.
一个动作可以包含任意数量的C语句.
然而,Bison并不搜索三字符词(trigraphs),
所以如果你的代码中使用了三字符词,你要保证它不影响嵌套的括号或者注释的边界,字符串或单个字符.

一个动作可以安放在规则的任意部分;
它就在那个位置执行.
大多数规则仅有一个在规则所有部件最后的动作.
在规则之中的动作是富有技巧性的并且仅用于特殊的目的
(@pxref{Mid-Rule Actions, ,在规则中间的动作-Actions in Mid-Rule}).

动作中的C代码可以使用结构@code{$@var{n}}来引用匹配规则的部件的语义值.
这个结构代表了第@var{n}个部件的值.
正在被构建的组的语义值为@code{$$}.
当这些被复制到分析器文件的时候,Bison负责将这些结构翻译成适当类型的表达式.
@code{$$}被翻译成一个可以修改的左值以便可以对它赋值.

这里是一个典型的例子:

@example
@group
exp:    @dots{}
        | exp '+' exp
            @{ $$ = $1 + $3; @}
@end group
@end example

@noindent
这个规则从两个由加号连接的稍小的@code{exp}组构建一个@code{exp}.
在这个动作中,@code{$1}和@code{$3}代着两个部件@code{exp}组的语义值.
它们是规则右手端第一个和第三个符号.
和被存储到@code{$$}以便成为刚刚被规则识别的加法表达式的语义值.
如果@samp{+}记号有一个有用的语义值,可以通过@code{$2}引用它.

注意到垂直杠字符@samp{|}的确是一个分隔符,
并且动作只被附加到一个单一的规则上.
这是一点和Flex工具不同的地方.
在Flex中,@samp{|}既代表``或者''也能代表``与下一个规则有着相同的动作''.
在下面的例子中,动作仅在@samp{b}被发现的时候触发.

@example
@group
a-or-b: 'a'|'b'   @{ a_or_b_found = 1; @};
@end group
@end example

@cindex 默认动作(default action)
如果你并没有指明一个规则的动作,Bison提供了默认的动作:
@w{@code{$$ = $1}.} 因此,第一个符号的值变成了整个规则的值.
当然,仅当它们的数据类型相同时,默认动作才是有效的.
对于一个空规则,并没有有意义的默认动作;
除非这个规则的值无关紧要,否则每个空规则必须有明确的动作.

在@code{$@var{n}}的@var{n}中使用0或负值是允许的.
这使用来引用在匹配当前规则@emph{之前}的栈中记号或组.
这是一个十分冒险的尝试.
你必须明确当前应用的规则处于的上下文才能可靠地使用它.
这里有一个可靠地使用这种方法的例子:

@example
@group
foo:      expr bar '+' expr  @{ @dots{} @}
        | expr bar '-' expr  @{ @dots{} @}
        ;
@end group

@group
bar:      /* empty */
        @{ previous_expr = $0; @}
        ;
@end group
@end example

只要@code{bar}仅仅以上面展示的形式使用,
@code{$0}就总是引用先前于@code{bar}的@code{foo}的定义中的@code{expr}.

@node Action Types
@subsection 动作中值的数据类型-Data Types of Values in Actions
@cindex 动作数据类型(action data types)
@cindex 动作中的数据类型(data types in actions)

如果你为语义值只选择了一种数据类型,
那么@code{$$}和@code{$@var{n}}结构总是那种类型.

如果你已经使用了@code{%union}指定了多种数据类型,
那么你必须从这些类型中为每一个可以有语义值的终结符或非终结符声明一种.
之后当你每次使用@code{$$}或者@code{$@var{n}}的时时侯,
它的数据类型由它引用的符号的类型决定.
在这个例子中:

@example
@group
exp:    @dots{}
        | exp '+' exp
            @{ $$ = $1 + $3; @}
@end group
@end example

@noindent
@code{$1}和@code{$3}引用了@code{exp}的实例,
所以它们都有为非终结符@code{exp}声明的数据类型.
如果使用了@code{$2},它就会拥有为终结符@code{'+'}声明数据类型,
无论它可能是什么.

另外,在你引用数值的时候,
在引用的开始@samp{$}之后插入@samp{<@var{type}>},
你还可以指定数据类型,
例如,如果你已经定义了这里展示的数据类型:

@example
@group
%union @{
  int itype;
  double dtype;
@}
@end group
@end example

@noindent
那么你可以用@code{$<itype>1}作为一个整数来引用规则的第一个子单元,
或者用@code{$<dtype>1}作为一个双精度数来引用.

@node Mid-Rule Actions
@subsection 规则中的动作-Actions in Mid-Rule
@cindex 在规则中的动作(actions in mid-rule)
@cindex 规则中动作(mid-rule actions)

偶尔地,将一个动作放到规则之中是很用用处的.
这些规则的写法如同在规则之后的动作一样,
但是它们却在分析器识别之后的部件之前执行.

一个规则中动作可以通过@code{$@var{n}}来引用在它之前的部件,
但是不能引用接下来的部件,因为这个动作在它们被分析之前执行.

规则中的动作自己也作为这个规则的一个部件.
这与在同一个规则的另一个动作有些不同(另一个动作通常在结尾):
当在@code{$@var{n}}使用序号@var{n}的时候,
你必须把则个动作和符号一样也计算在内.

规则中的动作也可以拥有语义值.
这个动作可以通过向@code{$$}复制来设置它的值,
并且规则之中的后一个动作可应使用@code{$@var{n}}引用这个值.
由于没有符号可以命名这个动作,
所以没有办法为这个值事先声明一个数据类型.
每当你引用这个值的时候
你必须使用@samp{$<@dots{}>@var{n}}结构来指明一个数据类型.

没有办法在规则中动作中为整个规则设定一个值,
因为对@code{$$}的赋值并没有那个效果(注:看上一段).
为整个规则赋值的唯一方法是通过规则末尾的普通动作实现.

这理有一个来自假想编译器的例子,用于处理@code{let}语句.
格式为@samp{let (@var{variable}) @var{statement}}
并在@var{statement}生存期创建一个名为@var{variable}的临时变量.
为了分析这个结构,
当@var{statement}被分析的时候,我们必须将@var{varible}放入符号表,
并在稍后移除它.
这就是这个规则如何工作的:

@example
@group
stmt:   LET '(' var ')'
                @{ $<context>$ = push_context ();
                  declare_variable ($3); @}
        stmt    @{ $$ = $6;
                  pop_context ($<context>5); @}
@end group
@end example

@noindent
一旦@samp{let (@var{variable})}已经被识别,第一个动作就执行.
它使用了数据类型联合中的@code{context},
并保存了一个当前语义上下文(可访问变量列表)的副本作为它的语义值.
然后调用@code{declare_variable}来向那个列表添加新的变量.
一旦第一个动作执行完毕,
嵌入的语句@code{stmt}就可以被分析了.
注意到规则中动作的编号是5,所以@samp{stmt}的编号为6.

在嵌入的语句@code{stmt}被分析之后,它的语义值边变为了整个@code{let}-语句的值.(注:@code{$$=$6;})
之后,前一个动作的语义值(注@code{$<context>5})被用来存储先前的变量列表.(注:@code{pop_context ($<context>5)})
这将临时的@code{let}-变量从列表中删除.
这样作的目的是让它不出现在分析程序的其余部分的时候.

由于分析器为了执行动作而进行强制分析,
在一个规则没有完全被识别之前执行动作经常会导致冲突.
例如,如下的两个规则,并没有规则中的动作,可以在分析器中共存.
因为分析器可以移进一个左大括号
并且查看之后跟随的符号来确定这是否是一个声明.

@example
@group
compound: '@{' declarations statements '@}'
        | '@{' statements '@}'
        ;
@end group
@end example

@noindent
但是当我们向下面这样添加一个规则中的动作时,规则就失效了:

@example
@group
compound: @{ prepare_for_local_variables (); @}
          '@{' declarations statements '@}'
@end group
@group
        | '@{' statements '@}'
        ;
@end group
@end example

@noindent
现在,当分析器还没有读到左到括号的时候,它就被迫决定是否执行规则中的动作.
换句话说,当分析器没有足够的信息作出正确选择的时候,
它就会强制使用一个或者其它的规则.
(这个时候由于分析器仍在决定怎么办,
左大括号记号被称之为@dfn{超前扫描记号(look-ahead)}.
@xref{Look-Ahead, ,超前扫描记号-Look-Ahead Token}.)
(注:这个时候两个规则的超前扫描记号都是@samp{@{})

你可能认为给这两个规则放置相同的动作可以解决这个问题,就像这样:

@example
@group
compound: @{ prepare_for_local_variables (); @}
          '@{' declarations statements '@}'
        | @{ prepare_for_local_variables (); @}
          '@{' statements '@}'
        ;
@end group
@end example

@noindent
但是这种方法不可行,因为Bison并没有意识到两个动作是完全相同的.
(Bison永远不会尝试理解动作中的C代码.)

如果语法是这样的:可以依靠第一个记号(C语言就是这样)识别出一个声明.
那么将动作放到左大括号后是一个可行的解决方法.
就像这样:

@example
@group
compound: '@{' @{ prepare_for_local_variables (); @}
          declarations statements '@}'
        | '@{' statements '@}'
        ;
@end group
@end example

@noindent
现在接下来的声明或者语句的第一个记号(注:超前扫描记号)
在任何情况下都会告知Bison该使用哪一个规则.

另外一种解决方法是将动作放入一个做为子规则的非终结符.

@example
@group
subroutine: /* empty */
          @{ prepare_for_local_variables (); @}
        ;

@end group

@group
compound: subroutine
          '@{' declarations statements '@}'
        | subroutine
          '@{' statements '@}'
        ;
@end group
@end example

@noindent
现在Bison不用却确定最终使用的规则就可执行规则@code{subroutine}中的动作.
注意到:现在动作已经在规则的结尾.
任何规则中间的动作都可以由这种方法转化为一个规则结尾的动作,
并且这也是Bison处理规则中间的动作的方法.

@node Locations
@section 追踪位置-Tracking Locations
@cindex 位置(location)
@cindex 文字位置(textual location)
@cindex 位置(location), 文字的(textual)

虽然语法规则和语义值对于编写功能完善的分析器来说足够用了,
但是处理一些额外的信息特别是符号位置的信息也是非常有用的.

处理位置的方法由一个数据类型和规则被匹配时执行的动作定义的.

@menu
* 位置类型:Location Type.               描述位置的数据类型.
* 动作和位置:Actions and Locations.       在动作中使用位置.
* 位置的默认动作:Location Default Action.     定义了一个计算位置的通用方法.
@end menu

@node Location Type
@subsection 位置的数据类型-Data Type of Locations
@cindex 位置的数据类型(data type of locations)
@cindex 默认位置类型(default location type)

由于所有的记号和组总是使用相同的类型,
定义一个位置的数据类型要比定义语义值的简单的多.

位置的类型由一个名为@code{YYLTYPE}的宏定义.
当@code{YYLTYPE}没有被定义的时候,
Bison使用含有四个成员的结构体作为默认的定义:

@example
typedef struct YYLTYPE
@{
  int first_line;    /* 第一行   */
  int first_column;  /* 第一列   */
  int last_line;     /* 最后一行 */
  int last_column;   /* 最后一列 */
@} YYLTYPE;
@end example

@node Actions and Locations
@subsection 动作和位置-Actions and Locations
@cindex 位置动作(location actions)
@cindex 动作(actions), 位置(location)
@vindex @@$
@vindex @@@var{n}

动作不仅仅是为了定义语言的语义,
它还可以使用位置描述输出的分析器的行为.

最明显的为语法组建立位置的方法与计算语义值的方法相似.
在一个给定的规则中,
多种结构可以用于访问被匹配元素的位置.
右手端第@var{n}个部件的位置是@code{@@@var{n}}
而左边的组的位置是@code{@@$}.

这是一个基本的使用位置的默认数据类型的例子:

@example
@group
exp:    @dots{}
        | exp '/' exp
            @{
              @@$.first_column = @@1.first_column;
              @@$.first_line = @@1.first_line;
              @@$.last_column = @@3.last_column;ip
              @@$.last_line = @@3.last_line;
              if ($3)
                $$ = $1 / $3;
              else
                @{
                  $$ = 1;
                  fprintf (stderr,
                           "Division by zero, l%d,c%d-l%d,c%d",
                           @@3.first_line, @@3.first_column,
                           @@3.last_line, @@3.last_column);
                @}
            @}
@end group
@end example

就像对语义值一样,有一个每当规则被匹配时的位置的默认动作.
它将@code{@@$}的开始设置为第一个符号的开始并将@code{@@$}的末尾设为最后一个符号的末尾.

可以通过使用这个默认的动作来实现位置追踪的全自动.
上面的例子可以简单地这样重写:

@example
@group
exp:    @dots{}
        | exp '/' exp
            @{
              if ($3)
                $$ = $1 / $3;
              else
                @{
                  $$ = 1;
                  fprintf (stderr,
                           "Division by zero, l%d,c%d-l%d,c%d",
                           @@3.first_line, @@3.first_column,
                           @@3.last_line, @@3.last_column);
                @}
            @}
@end group
@end example

@node Location Default Action
@subsection 位置的默认动作-Default Action for Locations
@vindex YYLLOC_DEFAULT

实际上,动作并不是计算位置的最好地方.
由于位置比语义值更普遍,
所以在输出的分析器里有用来重定义每个规则的默认动作(注:对位置的)的空间.
@code{YYLLOC_DEFAULT}宏每当一个规则被匹配而关联的动作尚未执行之前被调用.
当处理一个语法错误要计算错误的位置的时候,它也会被调用.

大多数时候,
这个宏足以胜过语义动作中专注于位置的代码.

@code{YYLOC_DEFAULT}宏带有三个参数.
第一个是组(计算结果)的位置.
当匹配一个规则时,
第二个参数标识了正在匹配的规则的所有右端元素的位置,
第三个参数是规则右端元素的大小;
当处理一个语法错误的时候,
第二个参数标识了在错误处理中丢弃的符号的位置,
第三个参数是丢弃符号的数量.

@code{YYLOC_DEFAULT}默认地被这样定义:

@smallexample
@group
# define YYLLOC_DEFAULT(Current, Rhs, N)                                \
    do                                                                  \
      if (N)                                                            \
        @{                                                               \
          (Current).first_line   = YYRHSLOC(Rhs, 1).first_line;         \
          (Current).first_column = YYRHSLOC(Rhs, 1).first_column;       \
          (Current).last_line    = YYRHSLOC(Rhs, N).last_line;          \
          (Current).last_column  = YYRHSLOC(Rhs, N).last_column;        \
        @}                                                               \
      else                                                              \
        @{                                                               \
          (Current).first_line   = (Current).last_line   =              \
            YYRHSLOC(Rhs, 0).last_line;                                 \
          (Current).first_column = (Current).last_column =              \
            YYRHSLOC(Rhs, 0).last_column;                               \
        @}                                                               \
    while (0)
@end group
@end smallexample

当@var{k}是正数的时候,
函数@code{YYRHSLOC (rhs,k)}返回的是第@var{k}个符号的位置.
当@var{k}和位置@var{n}都为零的时候,
@code{YYRHSLOC(rhs,k)}返回的是刚刚被归约的符号的位置.

当要自己定义宏@code{YYLLOC_DEFAULT}的时候,你要考虑以下几点:

@itemize @bullet
@item
@c bad translated
所有的参数应该不受左端/右端的限制.
然而,只有第一个(结果)应该被@code{YYLOC_DEFAULT}修改.

@item
出于与语义动作一致性的考虑,
右手端有效索引的范围应该是从1到@var{n}.
当@var{n}是0的时候,只有0是有效索引并且它引用的是归约前的那个符号.
在错误处理中,@var{n}总是正数.

@item
因为实际的参数可能不在括号内,
如果需要的话,你应该加参数放在括号内.
同样地,当你的宏后紧跟一个分号时,
它应该被展开成可作为单独语句使用的东西.


@end itemize

@node Declarations
@section Bison声明-Bison Declarations
@cindex 声明(declarations), Bison
@cindex Bison声明(Bison declarations)

@dfn{Bison声明(Bison declarations)}部分定义了用来描述语法的符号和语义值的数据类型.
@xref{Symbols, ,符号-Symbols}.

所有符号类型名称(但不是如@code{'+'}和@code{'*'}的单字符记号)必须被声明.
如果你要指明非终结符语义值的数据类型的话,那么它们也必须被声明
(@pxref{Multiple Types, ,多种值类型-More Than One Value Type}).

默认地,文件的第一个规则也指明了开始符号.
如果你要其它的符号作为开始符号,你必须显示显式地声明它.
(@pxref{Language and Grammar, ,语言与上下文无关文法-Language and Context-Free Grammars})

@menu
* 符号声明(Token Decl):Token Decl.        声明终结符
* 优先级声明(Precedence Decl):Precedence Decl.   声明终结符的优先级和结合性
* 联合体声明(Union Decl):Union Decl.        声明一组语义值类型
* 类型声明(Type Decl):Type Decl.         声明非终结语义值的类型
* 初始动作声明(Initial Action Decl):Initial Action Decl.  在分析开始前执行的代码
* 析构声明(Destructor Decl):Destructor Decl.   声明如何释放符号
* 期望声明(Expect Decl):Expect Decl.       消除分析冲突时的警告
* 开始符号声明(Start Decl):Start Decl.        指明开始符号
* 纯分析器声明(Pure Decl):Pure Decl.         请求一个可重入的分析器
* 声明的总结(Decl Summary):Decl Summary.      一个所有Bison声明的总结
@end menu

@node Token Decl
@subsection 符号类型名称-Token Type Names
@cindex 声明符号类型名称(declaring token type names)
@cindex 符号类型名称(token type names), 声明(declaring)
@cindex 声明文字串记号(declaring literal string tokens)
@findex %token

声明符号类型(终结符)的最基本的方法如下:

@example
%token @var{name}
@end example

Bison会在分析器中将这个声明转换成@code{#define}指令
以便@code{yylex}(如果在这个文件中使用了它)
可以是用名称@var{name}代表这个记号类型码.

另外,如果你要指明结合性和优先级,
你可以使用@code{%left},@code{%right}或者@code{%nonassoc}
代替@code{%token}.
@xref{Precedence Decl, ,操作符优先级-Operator Precedence}.

你可以依靠附加一个十进制活十六进制整数紧跟着记号名称来显式地指定
一个符号类型的数字码.

@example
%token NUM 300
%token XNUM 0x12d // a GNU extension 一个GNU扩展
@end example

@noindent
然而,我们通常最好让Bison选择所有记号类型的数字码.
Bison会自动地选择互不冲突或不与其它字符冲突的码.

当栈类型是一个联合体的时候,
你必须使用@code{%token}或者其它记号声明来指明记号的语义值类型.
语义值类型由中括号分隔.
(@pxref{Multiple Types, ,多种值类型-More Than One Value Type}).

例如:

@example
@group
%union @{              /* define stack type */ /* 定义栈类型 */
  double val;
  symrec *tptr;
@}
%token <val> NUM      /* define token NUM and its type */ /* 定义一个记号NUM和它的数据类型 */
@end group
@end example

将文字串写在@code{%token}声明的结尾,
你可以把一个符号类型名称关联到文字串记号上.
例如:

@example
%token arrow "=>"
@end example

@noindent
例如,一个C语言语法可以用同等的文字串记号指明这些名称

@example
%token  <operator>  OR      "||"
%token  <operator>  LE 134  "<="
%left  OR  "<="
@end example

@noindent
一旦你使文字串和符号名称等价,
你可以在以后的声明或者语法规则中交替地使用它们.
@code{yylex}函数可以使用符号名称或者文字串来获得符号类型数字码.
(@pxref{Calling Convention, ,调用惯例-Calling Convention}).

@node Precedence Decl
@subsection 操作符优先级-Operator Precedence
@cindex 优先级声明(precedence declarations)
@cindex 声明操作符优先级(declaring operator precedence)
@cindex 操作符优先级(operator precedence), 声明(declaring)

使用@code{%left},@code{%right}或者@code{%nonassoc}
可以一次声明一个记号并指明它的优先级和结合性.
这些被称做@dfn{优先级声明(precedence declarations)}.
获取更多这方面的信息,@xref{Precedence, ,操作符优先级-Operator Precedence}.

优先级的声明与@code{%token}的声明相同,或者是

@example
%left @var{symbols}@dots{}
@end example

@noindent
或者是

@example
%left <@var{type}> @var{symbols}@dots{}
@end example

这些声明都与@code{%token}的目的相同.
但是额外地,它们还指明了@var{symbols}的结合性和相对优先级:

@itemize @bullet
@item
操作符的结合性决定了如何重复使用嵌套的操作符:
@samp{@var{x} @var{op} @var{y} @var{op} @var{z}}
是先组合@var{x}和@var{y},还是先组合@var{y}和@var{z}.
@code{%left}指明左结合性(先组合@var{x}和@var{y})而
@code{%right}指明了有结合性(先组合@var{y}和@var{z}).
@code{%nonassoc}指明了无结合性,即@samp{@var{x} @var{op} @var{y} @var{op} @var{z}}
被认为是一个语法错误.

@item
一个操作符的优先级决定了它如何与另外的操作符嵌套使用.
在一个优先级声明中声明的所有记号有相同的优先级,
如何嵌套使用它们取决于它们的结合性.
当两个记号在不同的优先级声明中,稍晚声明的拥有更高的优先级,并且先被组合.
@end itemize

@node Union Decl
@subsection 值类型集-The Collection of Value Types
@cindex 声明值类型(declaring value types)
@cindex 值类型(value types), 声明(declaring)
@findex %union

@code{%union}声明指明了语义值全部可能的数据类型集.
关键字@code{%union}后紧跟包含了C语言@code{union}同样的东西的一对大括号.

例如:

@example
@group
%union @{
  double val;
  symrec *tptr;
@}
@end group
@end example

@noindent
这说明了两个可选择的类型是@code{double}和@code{symrec*}.
它们被赋予了名称@code{val}和@code{tptr};
这些名称用于在@code{%toke}和@code{%type}声明中为终结符或非终结符选择一个类型.
(@pxref{Type Decl, ,非终结符-Nonterminal Symbols}).

作为一个@acronym{POSIX}扩展,一个标志被允许紧跟在@code{union}后.
例如:

@example
@group
%union value @{
  double val;
  symrec *tptr;
@}
@end group
@end example

指明了联合题标志@code{value},所以相应的C类型为@code{union value}.
如果你不知名一个标志,它默认地就为@code{YYSTYPE}.

我们应该注意到,并不像C语言中的@code{union}声明一样,
在Bison中,你不需要在大括号结束的时候写上分号.

@node Type Decl
@subsection 非终结符-Nonterminal Symbols
@cindex 声明值类型(declaring value types), 非终结符(nonterminals)
@cindex 值类型(value types), 非终结符(nonterminals), 声明(declaring)
@findex %type

@noindent
当你使用@code{%union}指明多种值类型的时候,
你必须为每个要使用其语义值的非终结符指明一个值类型.
通过@code{%type}可以做到这一点,像这样:

@example
%type <@var{type}> @var{nonterminal}@dots{}
@end example

@noindent
这里的@var{nonterminal}是非终结符的名称,
@var{type}是在@code{%union}中给定的名称来指定该非终结符的语义值类型.
(@pxref{Union Decl, ,值类型集-The Collection of Value Types}).
你可以给任意多数量的非终结符以相同的数据类型,
如果它们有相同的值类型的话.
这时我们要使用空白来分隔符号名称.

你也可以声明一个终结符的值类型.
为终结符使用相同的@code{<@var{type}>}结构可以做到这一点.
所有种类的记号声明都允许使用@code{<@var{type}>}.

@node Initial Action Decl
@subsection 在分析执行前执行一些动作-Performing Actions before Parsing
@findex %initial-action

有些时候,你的分析器需要在分析之前执行一些初始化.
通过使用@code{%initial-action}指令指定这种代码.

@deffn {指令} %initial-action @{ @var{code} @}
@findex %initial-action
声明了@var{code}必须在每次调用@code{yyparse}之前被调用.
@var{code}可以使用@code{$$}和@code{@@$} --- 超前扫描记号的初始值和位置 --- 和
@code{%parse-param}.
@end deffn

例如,如果你的位置需要使用一个文件名,你可以使用

@example
%parse-param @{ const char *filename @};
%initial-action
@{
  @@$.begin.filename = @@$.end.filename = filename;
@};
@end example


@node Destructor Decl
@subsection 释放被丢弃的符号-Freeing Discarded Symbols
@cindex 释放被丢弃的符号(freeing discarded symbols)
@findex %destructor

分析器可能会丢弃一些符号.
@c bad translatd
例如,在错误恢复中(@pxref{Error Recovery, ,错误恢复-Error Recovery}),
分析器丢弃已经压入栈中为难符号,
以及来自剩余文件的为难记号直到脱离错误恢复状态。
如果这些符号带有堆信息,这些内存就会出现丢失.
@c up
然而这种行为对于例如编译器一样的批分析器是可以容忍的,
但不适用于可能``没有终点''的分析器如shells或者通信协议的实现.

@code{%destructor}指令允许定义当一个符号被丢弃时调用的代码.

@deffn {指令} %destructor @{ @var{code} @} @var{symbols}
@findex %destructor
声明了@var{code}必须在每次分析器丢弃@var{symbols}时调用.
@var{code}应该使用@code{$$}来指明与@var{symbols}关联的语义值.
其余的分析器参数也是可用的.
(@pxref{Parser Function, ,分析器函数@code{yyparse}-The Parser Funcation @code{yyparse}}).

@strong{警告:}对于版本1.875来说,这个特征仍然是实验性的.
主要原因是没有足够的用户反馈.
相应的语法仍可能会改变.
@end deffn

例如：

@smallexample
%union
@{
  char *string;
@}
%token <string> STRING
%type  <string> string
%destructor @{ free ($$); @} STRING string
@end smallexample

@noindent
保证了当一个@code{STRING}或者一个@code{string}将被丢弃时,
相关的内存也会被释放.

@c not yet understanded
注意到在将来,Bison会认为在动作中没有提及的右端成员也可以被销毁.
例如,在:

@smallexample
comment: "/*" STRING "*/";
@end smallexample

@noindent
分析器有权销毁@code{string}的语义值.
当然,这不适用于默认动作:
比较:

@smallexample
typeless: string;  // $$ = $1 does not apply; $1 is destroyed.
typefull: string;  // $$ = $1 applies, $1 is not destroyed.
@end smallexample
@c up

@sp 1

@cindex 被丢弃的符号(discarded symbols)
@dfn{被丢弃的符号(Discarded symbols)} 是如下几种:

@itemize
@item
在第一阶段的错误恢复中栈弹出符号.
@item
在第二阶段错误恢复中要到达的终结符.
@item
当分析器异常终止时(或者通过显式地调用@code{YYABORT},或者一系列失败的错误恢复),
当前的超前扫描记号.

@end itemize


@node Expect Decl
@subsection 消除冲突警告-Suppressing Conflict Warnings
@cindex 消除冲突警告(suppressing conflict warnings)
@cindex 阻止有关冲突的警告(preventing warnings about conflicts)
@cindex 警告(warnings), 阻止(preventing)
@cindex 冲突(conflicts), 消除警告(suppressing warnings of)
@findex %expect
@findex %expect-rr

通常情况下,当出现任何的冲突的时候,Bison会作出警告
(@pxref{Shift/Reduce, ,移进/归约冲突-Shift/Recude Conflicts}),
但是大多数真正的语法含有的是可以通过预测的方法解决并且很难消除的无害的移进/归约冲突.
除非冲突的数量改变,我们渴望消除关于这些冲突的警告.
你可以使用@code{%expect}声明做到这一点.

声明看起来是这样:

@example
%expect @var{n}
@end example

这里的@var{n}是一个十进制整数.
这个声明表明:如果有@var{n}个移进/归约冲突并且没有归约/归约冲突,
Bison并不会作出警告.
如果有更多或更少的冲突或者有归约/归约冲突,Bison仍会作出警告.

对于通常的@acronym{LALR}(1)分析器,归约/归约冲突更加棘手并且应该完全消除.
Bison对于这些分析器总会报告归约/归约冲突.
对于@acronym{GLR}分析器来说,移进/归约冲突和归约/归约冲突都是很平常的情况(否则,就没有必要使用@acronym{GLR}分析).
因此,在@acronym{GLR}分析器中使用如下声明指定一个预期数目的归约/归约冲突也是可以的:

@example
%expect-rr @var{n}
@end example

通常来说,使用@code{%expect}包括了这些步骤:

@itemize @bullet
@item
不使用@code{%expect}而编译你的代码.
使用@samp{-v}选项获取冲突发生的列表.
Bison也会打印冲突的个数.

@item
检查每一个冲突来确定Bison默认的解决方法是你真正想要的.
如果不是,重写语法并回到开始.(注:第一步)

@item
添加一个@code{%expect}声明,从Bison打印的列表复制一个冲突的数目.
@end itemize

现在,如果你不更改冲突的数目,Bison会停止打扰你.
但是如果你改变了语法导致了更多或更少的冲突,
Bison仍会警告你.

@node Start Decl
@subsection 开始符号-The Start-Symbol
@cindex 声明开始符号(declaring the start symbol)
@cindex 开始符号(start symbol), (声明)declaring
@cindex 默认开始符号(default start symbol)
@findex %start

Bison默认地认为在语法叙述部分指明的第一个非终结符为语法的开始符号.
程序员可以使用如下的@code{%start}声明来克服这个约束

@example
%start @var{symbol}
@end example

@node Pure Decl
@subsection 纯(可重入)分析器-A Pure (Reentrant) Parser
@cindex 可重入分析器(reentrant parser)
@cindex 纯分析器(pure parser)
@findex %pure-parser

一个@dfn{可重入(reentrant)}程序是在执行过程中不变更的程序;
换句话说,它全部由@dfn{纯(pure)}(只读)代码构成.
当可异步执行的时候,可重入特性非常重要.
例如,从一个句柄调用不可重入程序可能是不安全的.
在带有多线程控制的系统中,
一个非可重入程序必须只能被互锁(interlocks)调用.

通常地,Bison生成不可重入的分析器.
这对大多数情况足够用了,并且这种分析器提供了与Yacc的兼容性.
(由于在@code{yylex},@code{yylval}和@code{yyloc}通信中使用了静态分配的变量,
标准Yacc界面是不可重入的).

作为另外一个选择,你可以声称一个纯,可重入的分析器.
Bison声明@code{%pure-parse}表明你要产生一个可重入的分析器.
这个声明是这样:

@example
%pure-parser
@end example

这样做的结果是@code{yylval}和@code{yylloc}的通信变量变为一个@code{yyparse}中的局部变量,
并且对词法分析器函数@code{yylex}使用了不同的调用惯例.
@xref{Pure Calling, ,纯分析器的调用惯例-Calling Convention for Pure Parsers}.以获取更多信息.
变量@code{yynerrs}也变为在@code{yyparse}中的局部变量
(@pxref{Error Reporting, ,错误报告函数@code{yyerror}-The Error Reporting Funcation @code{yyerror}}).
@code{yyparse}自己的调用惯例并没有改变.

分析器是否为纯分析器与语法规则毫不相关.
你可以从任何有效的语法产生一个纯分析器或者不可重入分析器.

@node Decl Summary
@subsection Bison声明总结-Bison Declaration Summary
@cindex Bison声明总结(Bison declaration summary)
@cindex 声明总结(declaration summary)
@cindex 总结(summary), Bison声明(Bison declaration)

这是一个用来定义语法的声明的总结:

@deffn {指令} %union
声明了语义值可能拥有的数据类型集.
(@pxref{Union Decl, ,值类型集-The Collection of Value Types}).
@end deffn

@deffn {指令} %token
声明一个未指定优先级和结合性的终结符(符号类型名称)
(@pxref{Token Decl, ,符号类型名称-Token Type Names}).
@end deffn

@deffn {指令} %right
声明一个右结合的终结符(符号类型名称)
(@pxref{Precedence Decl, ,操作符优先级-Operator Precedence}).
@end deffn

@deffn {指令} %left
声明一个左结合的终结符(符号类型名称)
(@pxref{Precedence Decl, ,操作符优先级-Operator Precedence}).
@end deffn

@deffn {指令} %nonassoc
声明一个没有结合性的终结符(符号类型名称).
(@pxref{Precedence Decl, ,操作符优先级-Operator Precedence}).
按结合性的方法使用它是一个语法错误.
@end deffn

@ifset defaultprec
@deffn {指令} %default-prec
为缺乏显式@code{%prec}修饰符的规则指定优先级.
(@pxref{Contextual Precedence, ,上下文依赖优先级-Context-Dependent Precedence})
@end deffn
@end ifset

@deffn {指令} %type
声明非终结符的语义值类型.
(@pxref{Type Decl, ,非终结符-Nonterminal Symbols}).
@end deffn

@deffn {指令} %start
指明了语法的开始符号
(@pxref{Start Decl, ,开始符号-The Start-Symbol}).
@end deffn

@deffn {指令} %expect
声明了预期的移进/归约冲突的个数.
(@pxref{Expect Decl, ,消除冲突警告-Suppressing Conflict Warnings}).
@end deffn


@sp 1
@noindent
为了改变@command{bison}的行为,使用如下指令

@deffn {指令} %debug
在分析器文件中,如果@code{YYDEBUG}未定义,将其定义为1,
以便调式机制被编译.
@end deffn
@xref{Tracing, ,追踪你的分析器}.

@deffn {指令} %defines
编写一个包括记号类型定义和其它声明的宏定义头文件.
如果分析器输出文件是@file{@var{name}.c},
那么这个头文件就是@file{@var{name}.h}.

除非@code{YYSTYPE}已经被定义成了一个宏,
否则输出头文件会声明@code{YYSTYPE}.
因此,如果你使用了需要其它定义的@code{%union}部件
(@xref{Multiple Types, ,多种值类型-More Than One Value Type},)
或者你已经定义了宏@code{YYSTYPE}
(@pxref{Value Type, ,语义值的数据类型-Data Types of Semantic Values}),
你需要安排这些定义使它们在所有模块的前页,
例如,把它们放入一个你的分析器和任何其它模块都包含的头文件中.

除非你的分析器是一个纯分析器,
否则输出的头文件将@code{yylval}声明为一个外部变量.
@xref{Pure Decl,一个纯(可重入)分析器-A Pure (Reentrant) Parser}.

如果你也使用了位置,
输出头文件使用声明与@code{YYSTYPE}和@code{yylval}类似的协议声明@code{YYLTYPE}和@code{yylloc}.
@xref{Locations, ,追踪位置-Semantic Values of Tokens}.

如果你希望将@code{yylex}的定义放在一个另外的源文件中的话,
这个输出的头文件是通常必须的,
因为@code{yylex}需要引用头文件中提供的声明和记号类型码.
@xref{Token Values, ,记号的语义值-Semantic Values of Tokens}.
@end deffn

@deffn {指令} %destructor
指明了分析器如何回收同丢弃的符号相关联的内存.
@xref{Destructor Decl, ,释放丢弃的符号-Freeing Discarded Symbols}.
@end deffn


@deffn {指令} %file-prefix="@var{prefix}"
指定一个所有Bison输出文件的前缀,就好像输入文件名为@file{@var{prefix}.y}.
@end deffn

@deffn {指令} %locations
产生处理位置的代码(@pxref{Action Features, ,使用动作的特殊特征-Special Features for Use in Actions}).
一旦语法使用了@samp{@@@var{n}}记号,这种模式就会被激活.
但是如果你的语法没有用到它,使用@samp{%locations}可以获得更精确的语法错误信息.
@end deffn

@deffn {指令} %name-prefix="@var{prefix}"
重命名分析器使用的外部符号以便它们以@var{prefix}开始,而不是@samp{yy}.
符号被重命名的精确列表是:
@code{yyparse}, @code{yylex}, @code{yyerror}, @code{yynerrs},
@code{yylval}, @code{yylloc}, @code{yychar}, @code{yydebug}, 和可能使用的
@code{yylloc}.
例如, 如果你使用@samp{%name-prefix="c_"}, 名称就会变为 @code{c_parse}, @code{c_lex}等等.
@xref{Multiple Parsers, ,同一个程序中的多个分析器-Multiple Parsers in the Same Program}.
@end deffn

@ifset defaultprec
@deffn {指令} %no-default-prec
对缺少显式的@code{%prec}修饰符规则不指定优先级.
(@pxref{Contextual Precedence, ,上下文依赖优先级-Context-Dependent Precedence}).
@end deffn
@end ifset

@deffn {指令} %no-parser
在分析器文件中不包含任何C代码,仅仅声称表格.
分析器文件仅仅包括@code{#define}指令和静态变量声明.

这个选项也告诉Bison将语法动作的C代码以@code{switch}语句的形式
写入一个名为@file{@var{filename}.act}的文件.
@end deffn

@deffn {指令} %no-lines
在分析器文件中不生成任何@code{#line}预处理指令.
Bison通常将这些指令写入分析器文件以便
C编译器和调式器(debugger)可以将错误和目标代码与你的源文件(语法文件)关联起来.
这个指令使它们关联错误到分析器文件并将它视为一个独立的源文件.
@end deffn

@deffn {指令} %output="@var{filename}"
将分析器文件指定为@var{filename}.
@end deffn

@deffn {指令} %pure-parser
请求一个纯(可重入)分析器程序(@pxref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}).
@end deffn

@deffn {指令} %token-table
在分析器文件中生成一个记号名称数组.
数组的名称是@code{yytname};
@code{yytname[@var{i}]}是Bison内部数字码为@var{i}的记号的名称.
前三个@code{yytname}的元素与预定义记号@code{"$end"},@code{"error"},
和@code{"$undefined"}相对应;
在这几个符号之后便是语法文件中定义的符号.

对于单字符记号和文字串记号,
表格中的名称包含单引号或者双引号字符:
例如,@code{"'+'"}是一个单字符记号而 @code{"\"<=\""}是一个文字串记号.
字符串记号的所有字符一字不差地出现在符号表中;
即使双引号字符也不跳过.
例如,如果记号包含了三个字符@samp{*"*},在@code{yytname}的字符串为@samp{"*"*"}.
(在C语言中,那应被写成@code{"\"*\"*\""}).

当你指定了@code{%token-table},Bison也生成了@code{YYNTOKENS}, @code{YYNNTS}, and
@code{YYNRULES}, 和@code{YYNSTATES}的宏定义:

@table @code
@item YYNTOKENS
最高记号数字加1
@item YYNNTS
非终结符的数量
@item YYNRULES
语法规则的数量
@item YYNSTATES
分析器状态的数量(@pxref{Parser States, ,分析器状态-Parser States}).
@end table
@end deffn

@deffn {指令} %verbose
向一个额外的输出文件写入包括分析器状态和对在那个状态的每一种超前扫描记号做了些什么的详细描述.
@xref{Understanding, ,理解你的分析器-Understanding You Parser},以获取更多信息.
@end deffn

@deffn {指令} %yacc
假定给定了@option{--yacc}选项,也就是模拟Yacc,包括它的命名惯例.
@xref{Bison Options, ,Bison选项-Bison Options},获得更多信息.
@end deffn


@node Multiple Parsers
@section 在同一个程序中使用多个分析器-Multiple Parsers in the Same Program

大多数程序使用Bison仅分析一种语言,因此仅包含一个Bison分析器.
但是如果你要在程序中分析多种语言该怎么办?
这样的话,你需要避免在不同的@code{yyparse},@code{yylval}等等
不同定义的名称之间的冲突.

要做到这一点,最简单的方法就是使用@samp{-p @var{prefix}}选项
(@pxref{Invocation, ,调用Bison-Invking Bison}).
这个选项重命名了接口函数和Bison分析器变量,
使它们以@var{prefix}开头而不是@samp{yy}.
你可以使用这个选项给予每个分析器互不冲突的独特的名称.

重命名符号的精确列表为: @code{yyparse}, @code{yylex},
@code{yyerror}, @code{yynerrs}, @code{yylval}, @code{yylloc},
@code{yychar}和@code{yydebug}.
例如,如果你使用了@samp{-p c},
名称就变为@code{cparse},@code{clex}等等.

@strong{所有其它与Bison相关的变量和宏定义并没有被重命名.}
这些其它的东西并不是全局的;
所以在不同的分析器中使用相同的名称不不会产生冲突.
例如,@code{YYSTYPE}并未被重命名,
但是在不同的分析器中以不同的方式不冲突地定义
(@pxref{Value Type, ,语义值的数据类型-Data Types of Semantic Values}).

@samp{-p}选项靠向分析器文件的开头添加宏定义的方式工作.
定义@code{yyparse}为@code{@var{prefix}parse},等等.
这种方式高效地在整个分析器文件中相互替代名称.

@node Interface
@chapter 分析器C语言接口-Parser C-Language Interface
@cindex 从语言接口(C-language interface)
@cindex 接口(interface)

Bison分析器实际上是一个名为@code{yyparse}的C语言函数.
这里我们描述一下@code{yyparse}和它需要用到的函数的接口惯例.

你应该记住,分析器使用了很多以@samp{yy}和@samp{YY}开头的标识符.
如果你在动作或者@var{epilogue}部分使用了这样一个标识符(不在这个手册之中),
你的程序可能会遇到麻烦.

@menu
* 分析器函数(Parser Function):Parser Function.   如何调用@code{yyparse}以及它的返回值.
* 词法(Lexical):Lexical.           你必提供一个读入记号的函数@code{yylex}.
* 错误报告(Error Reporting):Error Reporting.   你必须提供一个函数@code{yyerror}.
* 动作特征(Action Feature):Action Features.   在动作中使用的特殊特征.
@end menu

@node Parser Function
@section 分析器函数@code{yyparse}-The Parser Function @code{yyparse}
@findex yyparse

你通过调用函数@code{yyparse}开始进行分析.
这个函数读入记号,执行动作,
并且最后如果它遇到输入结束或者不能恢复的错误就会返回.
你也可以编写一个让@code{yyparse}立即返回不再读入的动作.


@deftypefun int yyparse (void)
如果分析成功,@code{yyparse}返回值为0(当遇到输入结束的时候).

如果分析失败,返回值则为1.(当遭遇语法错误的时候).
@end deftypefun

在动作中,你可以使用这些宏使@code{yyparse}立即返回:

@defmac YYACCEPT
@findex YYACCEPT
立即返回0(来报告分析成功).
@end defmac

@defmac YYABORT
@findex YYABORT
立即返回1(来报告分析失败).
@end defmac

如果你使用一个可重入的分析器,
你还可用可重入的方式以向它传送额外的信息.
为了做到这一点,使用@code{%parse-param}声明:

@deffn {指令} %parse-param @{@var{argument-declaration}@}
@findex %parse-param
表明由@code{argument-declaration}声明的参数
是一个额外的@code{yyparse}参数.
@var{argument-declaration}在声明函数或者原型时使用.
@var{argument-declaration}中最后一个标识符必须为参数名称.

@end deffn

这里有一个例子.将这些写入分析器:

@example
%parse-param @{int *nastiness@}
%parse-param @{int *randomness@}
@end example

@noindent
然后向这样调用分析器

@example
@{
  int nastiness, randomness;
  @dots{}  /* @r{Store proper data in @code{nastiness} and @code{randomness}.}  */ 
  value = yyparse (&nastiness, &randomness);
  @dots{}
@}
@end example

@noindent
在语法动作中,用类似这样的表达式来引用数据:

@example
exp: @dots{}    @{ @dots{}; *randomness += 1; @dots{} @}
@end example


@node Lexical
@section 词法分析器函数@code{yylex}-The Lexical Analyzer Function @code{yylex}
@findex yylex
@cindex 词法分析器(lexical analyzer)

@dfn{词法分析器(lexical analyzer)}函数,@code{yylex},
从输入流中识别记号并将它们返回给分析器(注:语法分析器).
Bison并不自动生成这个函数;
你必须编写它以备@code{yyparse}调用.
这个函数有时候也被成为词法扫描器.

在简单的程序中,@code{yylex}经常定义在Bison语法文件的末尾.
如果@code{yylex}定义在另外的文件中,
你需要安排符号类型宏定义在那里是可见的.
为了做到这一点,在运行Bison的时候使用@samp{-d}选项以便它
将这些宏定义写入到另外的名为@file{@var{name}.tab.h}的头文件中.
你可以将它包含在需要它的其它源文件中.
@xref{Invocation, ,调用-Bison-Invoking Bison}.

@menu
* 调用惯例(Calling Convention):Calling Convention.  @code{yyparse}如何调用@code{yylex}.
* 记号值(Token Values):Token Values.      @code{yylex}是如何返回它已经读入的记号的语义值.
* 记号位置(Token Locations):Token Locations.   如果动作需要,@code{yylex}是如何返回记号的文字位置(行号,等等).
* 纯调用(Pure Calling):Pure Calling.      纯分析器的调用惯例有何不同
                                          (@pxref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}).
@end menu

@node Calling Convention
@subsection @code{yylex}的调用惯例-Calling Convention for @code{yylex}

@code{yylex}的返回值必须是它刚刚发现的记号类型的正值数字码;
0或负值代表着输入的结束.

当一个记号在语法规则中由它的名称引用时,
这个名称在语法文件中是一个宏,
这个宏定义了那个记号类型的恰当的数字码.
所以@code{yylex}可是使用这个名称来指明那个记号类型.
@xref{Symbols, ,符号-Symbols}.

当一个记号在语法文件中由一个字符引用时,
那个字符的数字码同样也是那个记号类型的数字码.
所以@code{yylex}可以简单地返回那个字符码,
并且可能转换为@code{unsigned char}以避免符号扩展.
但空字符绝对不能这样使用,
因为它的数字吗为0,
这意味这输入的结束.

这里是一个展示这些东西的例子:

@example
int
yylex (void)
@{
  @dots{}
  if (c == EOF)    /* Detect end-of-input.  */ /* 检测到输入结束 */
    return 0;
  @dots{}
  if (c == '+' || c == '-')
    return c;      /* Assume token type for `+' is '+'.  */ /* 认定`+'的记号类型就是'+' */
  @dots{}
  return INT;      /* Return the type of the token.  */ /* 返回记号的类型 */
  @dots{}
@}
@end example

@noindent
设计这种接口的目的是为了可以不加更改地使用@code{lex}工具的输出@code{yylex}.

如果语法使用了文字串记号,
@code{yylex}决定记号类型马的方法有两种:

@itemize @bullet
@item
如果语法定义了文字串记号的符号名称别名,
@code{yylex}可以像其它符号名称一样使用这些符号名称.
这种情况下,
语法文件中使用文字串记号对@code{yylex}没有影响.


@item
@code{yylex}可以在@code{yytname}表中找到多字符记号.
这个记号的索引是这个记号的类型码.
多字符记号的名称由一个双引号,记号的字符和另外一个双引号记录在@code{yytname}中.
无论如何,记号(注:多字符记号)的字符不能是转义的;
它一字不差地出现在表格字符串的内容里.

这里有在@code{yytname}中搜索记号的代码.
这个代码假定记号的字符存储在@code{token_buffer}中.

@smallexample
for (i = 0; i < YYNTOKENS; i++)
  @{
    if (yytname[i] != 0
        && yytname[i][0] == '"'
        && ! strncmp (yytname[i] + 1, token_buffer,
                      strlen (token_buffer))
        && yytname[i][strlen (token_buffer) + 1] == '"'
        && yytname[i][strlen (token_buffer) + 2] == 0)
      break;
  @}
@end smallexample

@code{yytname}表格只在你使用了@code{%token-table}声明才会生成.
@xref{Decl Summary, ,声明总结-Decl Summary}.
@end itemize

@node Token Values
@subsection 记号的语义值-Semantic Values of Tokens

@vindex yylval
在一个普通的(不可重入)的分析器中,
记号的语义值必须被存放在全局变量@code{yylval}中.
当你只使用一种语义值数据类型时,
@code{yylval}就是那个类型.
因此,如果类型为@code{int}(默认的),
你可以这样编写你的@code{yylex}:

@example
@group
  @dots{}
  yylval = value;  /* Put value onto Bison stack.  */ /* 将值放入Bison栈中 */
  return INT;      /* Return the type of the token.  */ /* 返回记号类型 */
  @dots{}
@end group
@end example

当你使用多种数据类型时,
@code{yylval}的类型是一个由@code{%union}声明组成的联合体.
(@pxref{Union Decl, ,值类型集-The Collections of Value Types}).
所以,当你存储一个记号的语义值的时候,
你必须使用恰当的联合体成员.
如果@code{%union}声明是这样的:

@example
@group
%union @{
  int intval;
  double val;
  symrec *tptr;
@}
@end group
@end example

@noindent
那么@code{yylex}中的代码应该是这样:

@example
@group
  @dots{}
  yylval.intval = value; /* Put value onto Bison stack.  */ /* 将值放入Bison栈中. */
  return INT;            /* Return the type of the token.  */ /* 返回记号的类型 */
  @dots{}
@end group
@end example

@node Token Locations
@subsection 记号的文字位置-Textual Locations of Tokens

@vindex yylloc
如果你在动作中使用了@samp{@@@var{n}}-特征(@pxref{Locations, ,
追踪位置-Tracking Locations})来追踪记号和组的文字位置,
那么你必须在@code{yylex}中提供这些信息.
@code{yyparse}预期在全局变量@code{yyloc}中找到刚刚分析的记号的文字位置.
所以@code{yylex}必须在那个变量里存放正确的数据.

默认地,@code{yyloc}的值是一个结构体并且你只需要初始化将被动作使用的成员.
四个成员分别是@code{first_line}, @code{first_column},
@code{last_line}和 @code{last_column}.
注意到:使用这个特征会使分析器的性能显著下降.

@tindex YYLTYPE
@code{yyloc}的数据类型为@code{YYLTYPE}.

@node Pure Calling
@subsection 纯分析器的调用惯例-Conventions for Pure Parsers

当你使用Bison声明@code{%pure-parser}要求得到一个纯,可重入的分析器,
全局通信变量@code{yylval}和@code{yylloc}不能继续使用.
(@xref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant Parser}.)
在这种分析器中,两个全局变量由传递给@code{yylex}的指针参数取代.
你必须如下你声明它们,并通过这些指针存储数据最后将它们传回.


@example
int
yylex (YYSTYPE *lvalp, YYLTYPE *llocp)
@{
  @dots{}
  *lvalp = value;  /* Put value onto Bison stack.  */ /* 将值放入Bison栈 */
  return INT;      /* Return the type of the token.  */ /* 返回记号类型 */
  @dots{}
@}
@end example

如果语法们文件没有使用@samp{@@}结构引用文字位置,
那么类型@code{YYLTYPE}就不会被定义.
在这种情况下,省略第二个参数;
仅用一个参数调用@code{yylex}.

如果你希望传递额外的数据到@code{yylex},
可以是用@code{%lex-param},就像@code{%parse-param}一样 
(@pxref{Parser Function, ,分析器函数-Parser Function}).

@deffn {指令} lex-param @{@var{argument-declaration}@}
@findex %lex-param
声明@code{argument-declaration}是一个额外的@code{yylex}参数.
@end deffn

例如:

@example
%parse-param @{int *nastiness@}
%lex-param   @{int *nastiness@}
%parse-param @{int *randomness@}
@end example

@noindent
导致了如下的结果:

@example
int yylex   (int *nastiness);
int yyparse (int *nastiness, int *randomness);
@end example

如果添加了@code{%pure-parser}:

@example
int yylex   (YYSTYPE *lvalp, int *nastiness);
int yyparse (int *nastiness, int *randomness);
@end example

@noindent
最后,如果@code{%pure-parser}和@code{%locations}都被使用:

@example
int yylex   (YYSTYPE *lvalp, YYLTYPE *llocp, int *nastiness);
int yyparse (int *nastiness, int *randomness);
@end example

@node Error Reporting
@section 错误报告函数@code{yyerror}-The Error Reporting Function @code{yyerror}
@cindex 错误报告函数(error reporting function)
@findex yyerror
@cindex 分析错误(parse error)
@cindex 语法错误(syntax error)

Bison分析器侦测到一个@dfn{语法错误(syntax error)}
或者一个@dfn{分析错误(parse error)}
每当它读入了一个不能满足任何规则的记号.
一个语法动作也可以使用宏@code{YYERROR}显式地声明一个错误
(@pxref{Action Features, ,使用动作的特殊特征-Special Features for Use in Actions}).

Bison分析器期望靠调用一个名为@code{yyerror}的错误处理报告函数报告错误.
这个函数必须由你提供.
每当@code{yyparse}发现一个语法错误的时候,
@code{yyparse}就会调用它.
@code{yyparse}只接受一个参数.
对于一个语法错误,
显示的字符串通常是@w{@code{"syntax error"}}.

@findex %error-verbose
如果你在@var{Bison declarations}部分
(@pxref{Bison Declarations, ,@var{Bison Declarations}部分-The Bison Declarations Section})
使用了@code{%error-verbose}指令,
那么Bison会提供更加详细而明确的错误信息而不是仅有@w{@code{"syntax error"}}.

分析器可以侦测到另外一种错误:栈溢出.
这在输入包含非常深层次的嵌套结构时发生.
你很难遇到这种情况,
因为Bison会自动将栈容量扩展到一个很大的极限.
但是如果溢出发生,
@code{yyparse}会以通常的格式调用@code{yyerror}并带有字符串@w{@code{"parser stack overflow"}}.

下面的定义对于简单的程序足够用了:

@example
@group
void
yyerror (char const *s)
@{
@end group
@group
  fprintf (stderr, "%s\n", s);
@}
@end group
@end example

当@code{yyerror}返回到@code{yyparse}后,
如果你以已经写好了恰当的错误恢复语法规则(@pxref{Error Recovery, ,错误恢复-Error Recovery}),
@code{yyparse}会尝试进行错误恢复.
如果恢复是不可能的,@code{yyparse}会立即返回1.

显然,在带有错误追踪的纯分析器中,
@code{yyerror}应该会访问当前的位置.
由于历史原因,这些的确是@acronym{GLR}分析器的事情而不是Yacc分析器的事情.
例如,如果传递了@samp{%locations %pure-parser},那么@code{yyerror}的原型是:

@example
void yyerror (char const *msg);                 /* Yacc parsers.  */ /* Yacc 分析器 */
void yyerror (YYLTYPE *locp, char const *msg);  /* GLR parsers.   */ /* GLR 分析器 */
@end example

如果使用了@samp{%parse-param @{int *nastiness@}},那么原型是:

@example
void yyerror (int *nastiness, char const *msg);  /* Yacc parsers.  */ /* Yacc 分析器 */
void yyerror (int *nastiness, char const *msg);  /* GLR parsers.   */ /* GLR 分析器 */
@end example

最终,@acronym{GLR}和Yacc分析器对绝对的纯分析器共享相同的@code{yyerror}调用惯例,
例如,当@code{yylex}@emph{和}@code{%pure-parse}的调用惯例是纯调用,例如:

@example
/* Location tracking.  */ /* 追踪位置 */
%locations
/* Pure yylex.  */ /* 纯yylex */
%pure-parser
%lex-param   @{int *nastiness@}
/* Pure yyparse.  */ /* 纯yyparse */
%parse-param @{int *nastiness@}
%parse-param @{int *randomness@}
@end example

@noindent
导致了如下用于所有种类分析器的原型:

@example
int yylex (YYSTYPE *lvalp, YYLTYPE *llocp, int *nastiness);
int yyparse (int *nastiness, int *randomness);
void yyerror (YYLTYPE *locp,
              int *nastiness, int *randomness,
              char const *msg);
@end example

@noindent
原型只是用来指明Bison产生的代码如何使用@code{yyerror}.
Bison产生的代码通常忽略返回值,
所以@code{yyerror}可以返回任何类型,
包括@code{void}.
并且@code{yyerror}可以是一个变参函数(variadic funcation),
这就是为什么消息总在最后传递的原因.

@code{yyerror}在传统上返回一个经常被忽略的@code{int},
但这仅仅出于纯历史的原因.
@code{void}是更好的选择,
因为它更精确的反应了@code{yyerror}的返回类型.

@vindex yynerrs
变量@code{yynerrs}包含了到目前位置遭遇的语法错误的数量.
这个变量通常是全局的;
但是如果你要求一个纯分析器(@pxref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}),
那么这个变量就是一个只能被动作访问的局部变量.

@node Action Features
@section 在动作中使用的特殊特征-Special Features for Use in Actions
@cindex 总结(summary), 动作特征(action features)
@cindex 动作特征总结(action features summary)

这里是在动作中使用的Bison结构,变量和红的列表.

@deffn {变量} $$
像一个变量一样工作,这个变量包含了由当前规则构成的组的语义值.
@xref{Actions, ,动作-Actions}.
@end deffn

@deffn {变量} $@var{n}
像一个变量一样工作,这个变量包含了当前动作第@var{n}个部件的语义值.
@xref{Actions, ,动作-Actions}.
@end deffn

@deffn {变量} $<@var{typealt}>$
类似@code{$$}但是指明了@code{%union}声明中的@var{typealt}选项.
@xref{Action Types, ,动作中值的数据类型-Data Types of Values in Actions}.
@end deffn

@deffn {变量} $<@var{typealt}>@var{n}
类似@code{$@var{n}}但是指明@code{%union}声明中的@var{typealt}选项.
@xref{Action Types, ,动作中值的数据类型-Data Types of Values in Actions}.
@end deffn

@deffn {宏} YYABORT;
立即从@code{yyparse}返回,表明分析失败.
@xref{Parser Function, ,分析器函数@code{yyparse}-The Parser Function @code{yyparse}}.
@end deffn

@deffn {宏} YYACCEPT;
立即从@code{yyparse}返回,表明分析成功.
@xref{Parser Function, ,分析器函数@code{yyparse}-The Parser Function @code{yyparse}}.
@end deffn

@deffn {宏} YYBACKUP (@var{token}, @var{value});
@findex YYBACKUP
移出一个记号.
这个宏仅仅在一个只归约单一值的规则中使用,并且只在没有超前扫描记号的时候被允许使用.
这个宏也不允许在@acronym{GLR}分析器中使用.
这个宏建立一个超前带有记号类型@var{token}和语义值@var{value}的超前扫描记号;
然后丢弃将要被这个规则归约的值.

如果这个宏在无效的情况下使用,
例如当已经存在超前扫描记号的情况下使用,
那么它会报告一个带有消息@samp{cannot back up}的语法错误
并且执行一个普通的错误恢复程序.

在上述任一种情况下,动作的其余部分不会被执行.
@end deffn

@deffn {宏} YYEMPTY
@vindex YYEMPTY
当没有超前扫描记号的时候,值被存放在@code{yychar}中.
@end deffn

@deffn {宏} YYERROR;
@findex YYERROR
立即导致一个语法错误.
这个语句启动错误恢复就像分析器自己已经侦测到一个错误一样;
然而,它并不调用@code{yyerror}并且不打印任何消息.
如果你要打印一个错误消息,
在@samp{YYERROR;}语句之前显式地调用@code{yyerror}.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {宏} YYRECOVERING
当分析器从语法错误中恢复的时候,这个表达式的值为1.
其余时候值为0.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {变量} yychar
包含当前超前扫描记号的变量.
(在一个纯分析器中,这实际上是一个@code{yyparse}中的局部变量).
当没有超前扫描记号的时候,这个变量存储@code{YYEMPTY}的值.
@xref{Look-Ahead, ,超前扫描记号-Look-Ahead Tokens}.
@end deffn

@deffn {宏} yyclearin;
丢弃当前的超前扫描记号.
它主要用于错误恢复规则.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {宏} yyerrok;
对后来的语法错误立即恢复产生错误消息.
它主要用于错误恢复规则.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {值} @@$
@findex @@$
像一个包含文字位置信息的结构一样工作.
这个位置是由当前规则构成的组的位置.
@xref{Locations, ,追踪位置-Tracking Locations}.

@c Check if those paragraphs are still useful or not.

@c @example
@c struct @{
@c   int first_line, last_line;
@c   int first_column, last_column;
@c @};
@c @end example

@c Thus, to get the starting line number of the third component, you would
@c use @samp{@@3.first_line}.

@c In order for the members of this structure to contain valid information,
@c you must make @code{yylex} supply this information about each token.
@c If you need only certain members, then @code{yylex} need only fill in
@c those members.

@c The use of this feature makes the parser noticeably slower.
@end deffn

@deffn {值} @@@var{n}
@findex @@@var{n}
像一个包含文字位置信息的结构一样工作.
这个位置是由当前规则的第@var{n}个部件的位置.
@xref{Locations, ,追踪位置-Tracking Locations}.
@end deffn


@node Algorithm
@chapter Bison分析器算法-The Bison Parser Algorithm
@cindex Bison分析器算法(Bison parser algorithm)
@cindex 分析器的算法(algorithm of parser)
@cindex 移进(shifting)
@cindex 归约(reduction)
@cindex 分析器栈(parser stack)
@cindex 栈(stack), 分析器(parser)

当Bison读取记号的时候,它将这些记号同它们的语义值一起压入栈中.
这个栈被称为@dfn{分析器栈(parser stack)}.
将一个记号压入栈在传统上被称为@dfn{移进(shifting)}.

例如,假设中缀计算器已经读取@samp{1 + 5 *}并且将要读取@samp{3}.
分析器栈此时有四个元素,每个元素对应一个被移进的符号.

但是这个栈并不是总含有每个被读入记号的元素.
当最后@var{n}个被移进的记号和组匹配语法规则部件时,
可以由那个规则将它们结合起来.
这叫做@dfn{归约(reduction)}.
这些栈中的记号和组被一个单一的组取代.
那个组的符号是这个规则的结果(左手端).
运行规则的动作是处理归约的一部分,
@c bad translation
因为这就是什么在计算结果组的语义值.
@c up

例如,如果中缀计算器的分析器栈包含这个:

@example
1 + 5 * 3
@end example

@noindent
并且下一个输入是一个换行符,
那么最后三个元素可通过这个规则被归约成15:

@example
expr: expr '*' expr;
@end example

@noindent
那么这个栈仅含有三个元素:

@example
1 + 15
@end example

@noindent
在整个时候可以进行另外一个结果为16的归约.
然后,换行符记号才可以被移进.

分析器通过移进和归约尝试将整个输入化为一个符号为语法开始符号的单一组.
(@pxref{Language and Grammar, ,语言与上下文无关文法-Languages and Context-Free Grammars}).

这种类型的分析器被称之为@dfn{自底向上(bottom-up)}的分析器.

@menu
* 超前扫描记号(Look-Ahead):Look-Ahead.        当分析器决定做什么的时候它查看的一个记号.
* 移进/归约(Shift/Reduce):Shift/Reduce.      冲突:移进和归约均有效.
* 优先级(Precedence):Precedence.        用于解决冲突的操作符优先级.
* 上下文优先级(Contextual Precedence):Contextual Precedence.  当一个操作符的优先级依赖上下文.
* 分析器状态(Parser States):Parser States.     分析器是一个带有栈的有限状态机.
* 归约/归约(Reduce/Reduce):Reduce/Reduce.     在同一情况下可以应用两个规则.
* 令人迷惑的冲突(Mystery Conflicts):Mystery Conflicts.  看起来不平等的归约/归约冲突.
* 通用LR分析(Generalized LR Parsing):Generalized LR Parsing.  分析武断的上下文无关文法.
* 栈溢出(Stack Overflow):Stack Overflow.    当栈溢出时发生的事情以及如何避免它.
@end menu

@node Look-Ahead
@section 超前扫描记号-Look-Ahead Tokens
@cindex 超前扫描记号(look-ahead token)

Bison分析器并@emph{不}总是在最后@var{n}个记号和组匹配一个规则时立即进行归约.
这是由于这种策略对于处理大多数语言来说是不够的.
相反,当可以进行一个归约的时候,
分析器有时``超前扫描''下一个记号来决定该怎么做.

当读取一个记号时,
它并不是被马上移进而是首先成为不在栈中的@dfn{超前扫描记号(look-ahead token)}.
现在分析器可以对记号和组进行一个或更多的归约,而超前扫描记号仍在栈外.
这并不意味着所有可能的归约已经执行;
依赖于超前扫描记号的符号类型,
一些规则可以选择推迟它们的应用.

这里有一个需要超前扫描记号的例子.
这三个规则定义了包括二进制加法操作符和一元后缀阶称操作符(@samp{!}),
并且允许括弧分组.

@example
@group
expr:     term '+' expr
        | term
        ;
@end group

@group
term:     '(' expr ')'
        | term '!'
        | NUMBER
        ;
@end group
@end example

假定@w{@samp{1 + 2}}已经被读取和移进;
分析器这时应该做什么?
如果接下来的记号是@samp{)},
那么前三个记号必须被归约成一个@code{expr}.
这是唯一有效的情况,
因为移进@samp{)}会产生一系列的@w{@code{term ')'}},
而没有规则允许这样.

如果接下来的符号是@samp{!},
那么它必须马上被移进以便@w{@samp{2 !}}可以被移进产生一个@code{term}.
如果不这样做,
分析器将会在移进之前进行归约,
@w{@samp{1+2}}会成为一个@code{expr}.
那是移进@samp{!}就是不可能的,
因为这么做会使栈中产生序列@code{expr '!'}.
没有规则允许那样的序列.

@vindex yychar
当前的超前扫描记号被存储在@code{yychar}中.
@xref{Action Features, ,在动作中使用的特殊特征-Special Features for Use in Actions}.


@node Shift/Reduce
@section 移进/归约冲突-Shift/Reduce Conflicts
@cindex 冲突(conflicts)
@cindex 移进/归约冲突(shift/reduce conflicts)
@cindex 悬挂@code{else}问题(dangling @code{else})
@cindex @code{else}, 悬挂(dangling)

假设我们正在分析一个有if-then和if-then-else语句的语言
并且这个语言带有如下一对规则:

@example
@group
if_stmt:
          IF expr THEN stmt
        | IF expr THEN stmt ELSE stmt
        ;
@end group
@end example

@noindent
这里我们假定@code{IF},@code{THEN}和@code{ELSE}是用来指定关键字的终结符.

当@code{ELSE}被读入成为超前扫描记号,
栈中的内容(假定输入是有效的)刚好可以由第一个规则进行归约.
但是移进@code{ELSE}也是合法的,
因为这最终将会导致由第二个规则进行的归约.

这种情况,移进或者归约都是有效的,被称为@dfn{移进/归约冲突(shift/reduce conflict)}.
Bison被设计成选择@strong{移进}来解决这些冲突,
除非有其它的操作符优先级的指导.
为了研究这样做得原因,
我们将它和另一种选择(注:选择归约)做一个对比.

由于分析器选择移进@code{ELSE}.
这样作的结果是将else从句依附到最里面的if语句中,
并使下面两个输入在作用上等价.

@example
if x then if y then win (); else lose;

if x then do; if y then win (); else lose; end;
@end example

但如如果分析器选择在可能的时候归约而不是移进.
这样做的结果是将else从句依附到最外面的if语句中,
并使下面两个输入在作用上等价:

@example
if x then if y then win (); else lose;

if x then do; if y then win (); end; else lose;
@end example

冲突存在的原因是由于语法本身有歧义:
任一种简单的if语句嵌套的分析都是合法的.
已经建立的惯例是通过将else从句依附到最里面的if语句来解决歧义;
这就是Bison为什么选择移进而不是归约的原因.
(在理想的情况下,最好编写一个非歧义的文法,
但是在这种情况下却很难办到.)
这种特殊的歧义在Algol 60的描述中首次出现并被成为``悬挂@code{else}''歧义.

为了避免Bison警告那些可以预见的合法的移进/归约冲突,
我们可以使用@code{%expect @var{n}}声明.
当移进/归约冲突的数目恰好是@var{n}的时候,
Bison不会做出任何警告.
@xref{Expect Decl, ,消除冲突警告-Suppressing Conflict Warnings}.

有人抱怨上面@code{if_stmt}的定义,
但是冲突的确实在没有任何额外规则的时候出现.
这又一个完整的体现这个冲突的Bison输入文件:

@example
@group
%token IF THEN ELSE variable
%%
@end group
@group
stmt:     expr
        | if_stmt
        ;
@end group

@group
if_stmt:
          IF expr THEN stmt
        | IF expr THEN stmt ELSE stmt
        ;
@end group

expr:     variable
        ;
@end example

@node Precedence
@section 操作符优先级-Operator Precedence
@cindex 操作符优先级(operator precedence)
@cindex 操作符的优先级(precedence of operators)

移进/归约冲突也可能出现在算术表达式中.
在这里,移进并不总是优先的选择;
Bison关于操作符优先级的声明允许你指定什么时候移进和什么时候归约.

@menu
* 为什么需要优先级(Why Precedence):Why Precedence.    一个展示为什么需要优先级的例子
* 使用优先级(Using Precedence):Using Precedence.  在Bison的语法中如何指定优先级
* 优先级的例子(Precedence Example):Precedence Examples.  这些特性在前面的例子中是怎样使用的
* 优先级的工作方式(How Precedence):How Precedence.    它们如何工作
@end menu

@node Why Precedence
@subsection 什么时候需要优先级-When Precedence is Needed

考虑下面的歧义文法片段
(产生歧义的原因是输入@w{@samp{1 - 2 * 3}}可以由两种方法进行分析):

@example
@group
expr:     expr '-' expr
        | expr '*' expr
        | expr '<' expr
        | '(' expr ')'
        @dots{}
        ;
@end group
@end example

@noindent
假设分析器已经读入了记号@samp{1},@samp{-}和@samp{2};
那么它是否应该使用减法操作符规则进行归约呢?
这依赖于下一个记号.
当然,如果下一个记号是@samp{)},我们必须归约,
由于没有规则可以归约@w{@samp{- 2 )}}或者以它开始的记号序列,
所以移进是无效的.
但是如果下一个符号是@samp{*}或者@samp{<},
我么有了一个选择:
移进和归约都可以,但是会产生不同的结果.

要决定Bison应该怎么做,我们必须考虑结果.
如果下一个操作符记号@var{op}被移进,
那么它必须首先被归约以便允许进行另外一个归约的机会.
结果为@w{@samp{1 - (2 @var{op} 3)}}.
另一方面,如果在移进@var{op}之前归约减法,
结果为@w{@samp{(1 - 2) @var{op} 3}}.
很明显,选择移进或者归约依靠操作符@samp{-}和@var{op}的相对优先级:
@samp{*}是该首先被移进,而不是@samp{<}.

@cindex 结合性(associativity)
当输入为@w{@samp{1 - 2 - 5}}的时候会怎么样,
这应该是@w{@samp{(1 - 2) - 5}}还是@w{@samp{1 - (2 - 5)}}?
对于大多数操作符来说,我们选择前者.
这被成为@dfn{左结合(left association)}.
后面一种,@dfn{右结合(right association)},
对于赋值操作符是理想的选择.
选择左结合或者有结合是
当栈包含@w{@samp{1 - 2}}并且超前扫描记号是@samp{-}时,分析器选择移进还是归约的问题:
移进代表着右结合.

@node Using Precedence
@subsection 指定操作符的优先级-Specifying Operator Precedence
@findex %left
@findex %right
@findex %nonassoc

Bison允许你使用操作符优先级声明@code{%left}和@code{%right}指定这些选择.
每一个这样的声明包含了一个要声明其优先级和结合性的记号列表.
@code{%left}声明使这些操作符成为左结合的,
@code{%right}声明使这些操作符成为右结合的.
第三种选择是@code{%nonassoc},它声明了
``在一行中''有两个相同的操作符是一个语法错误.

不同操作符的优先级由它们声明的顺序控制.
文件中的第一个@code{%left}或者@code{%right}声明的优先级最低,
下一个类似声明的操作符有稍高的优先级,以此类推.

@node Precedence Examples
@subsection 优先级使用的例子-Precedence Examples

在我们的例子中,我们会发现下面的声明:

@example
%left '<'
%left '-'
%left '*'
@end example

在一个支持其它操作符的更完整的例子中,
我们会成组地声明具有相同优先级的操作符.
例如@code{'+'}和@code{'-'}一起声明.

@example
%left '<' '>' '=' NE LE GE
%left '+' '-'
%left '*' '/'
@end example

@noindent
(在这里@code{NE}代表着``不相等''操作符'',其它以此类推.
我们假定这些记号的长度多于一个字符并且因此由它们的名字代表而不是字符.)

@node How Precedence
@subsection 优先级如何工作-How Precedence Works

优先级声明的第一个作用是赋予声明的终结符以优先级.
第二个作用是赋予特定的规则以优先级:
每个规则从部件中最后一个提及的终结符中获取优先级.
(你也可以明确的指明规则的优先级.  @xref{Contextual
Precedence, ,上下文依赖优先级-Context-Dependent Precedence}.)

最终,解决中冲突的方法是比较正在考虑的规则和超前扫描记号的优先级.
如果超前扫描记号的优先级更高,那么选择移进.
如果规则的优先级更高,那么选择归约.
如果它们有相同的优先级,
那么靠那个优先级的结合性来作出选择.
由选项@samp{-v}(@pxref{Invocation, ,调用Bison-Invoking Bison})制造的冗长的输出文件(The verbose output file)
说明了每个冲突是如何解决的.

并不是所有的规则和记号都有优先级.
如果规则和超前扫描记号都没有优先级,
那么默认的动作是移进.

@node Contextual Precedence
@section 上下文依赖优先级-Context-Dependent Precedence
@cindex 上下文依赖优先级(context-dependent precedence)
@cindex 一元操作符优先级(unary operator precedence)
@cindex 优先级(precedence), 上下文依赖(context-dependent)
@cindex 优先级(precedence), 一元操作符(unary operator)
@findex %prec

一个操作符的优先级通常依赖于上下文.
这最开始听起来很古怪,但它的确很常见.
@c bad translation
例如,典型地,一个负号操作符有比一元操作符更高的优先级
并且比二进制操作符的优先级稍低(低于乘法).
@c up

Bison优先级声明,@code{%left},@code{%right}和@code{%nonassoc}
对于一个给定的操作符只能使用一次;
所以通过这种方法,一个操作符只能有一种优先级.
对于上下文依赖优先级来说,你需要使用一种额外的机制:
规则的@code{%prec}修饰符.

@code{%prec}靠指定用于那个规则终结符的优先级来声明特定规则的优先级.
那个符号不需要以特殊的方式出现在规则中.
修饰符的语法为:

@example
%prec @var{terminal-symbol}
@end example

@noindent
并且它写在规则的部件之后.
它的作用是赋予规则@var{terminal-symbol}的优先级而不考虑从普通方法推导出的优先级.
被改变的规则优先级会影响包含那个规则的冲突的解决方法.
(@pxref{Precedence, ,操作符优先级-Operator Precedence}).

这是@code{%prec}如何解决负号问题的例子.
首先为一个虚构的名为@code{MINUS}的终结符声明优先级.
实际上没有记号是这种类型,
但是这个符号以它自己的优先级来使用.

@example
@dots{}
%left '+' '-'
%left '*'
%left UMINUS
@end example

现在可以在规则中使用@code{MINUS}的优先级.

@example
@group
exp:    @dots{}
        | exp '-' exp
        @dots{}
        | '-' exp %prec UMINUS
@end group
@end example

@ifset defaultprec
如果你忘记对负号规则添加@code{%prec UNMINUS},
Bison默默地认为负号有它通常的优先级.
这种问题很难处理和排除,
因为它们只能靠测试代码来发现.

@code{%no-default-prec;}声明可以更简单地发下这种类型的问题.
它会使缺少@code{%prec}修饰符的规则没有优先级,
即使在它们部件中最后的终结符已经声明优先级.

如果@code{%no-default-prec;}起作用,
你必须对所有参加优先级冲突解决的规则指明@code{%prec}.
那时,除非你靠改变你的语法或者显式地添加一个优先级来告诉Bison如何解决它,
否则你会看到所有的移进/归约冲突.
这可能会要求对语法添加声明,
但它有助于预防不正确的规则优先级.

@code{%no-default-prec;}个作用可以靠默认给定的@code{%default-prec;}保留.
@end ifset

@node Parser States
@section 分析器状态-Parser States
@cindex 有限状态机-finite-state machine
@cindex 分析器状态(parser state)
@cindex 状态(分析器的)(state (of parser))

函数@code{yyparse}是使用有限状态机(finite-state-machine)来实现的.
压入分析器栈中的值不仅仅是符号类型码;
它们代表了整个在栈顶或者靠近栈顶的终结符和非终结符序列.
当前的状态收集了与决定下一步怎么做相关的之前输入的信息.

每次读入一个超前扫描记号,
分析器就在一个表中搜索分析装当前状态和超前扫描记号类型.
这个表项能会说``移进超前扫描记号''
在这种情况下,它在指定了一个新的分析器状态的同时将这个状态压入栈顶.
或者,它(注:指表项)也可能说``使用第@var{n}个规则进行归约.''
这意味着某些个数的记号合组被移出栈,取而代之的是一个组.
用另外一种说法,
那些个数(注:@var{n})的状态被弹出栈,一个新状态被压入栈.

还有另外一种选择:这个表可能会说那个超前扫描记号在当前的状态下是错误的.
这会引发错误处理.(@pxref{Error Recovery, ,错误恢复-Error Recovery}).

@node Reduce/Reduce
@section 归约/归约冲突-Reduce/Reduce Conflicts
@cindex 归约/归约冲突(reduce/reduce conflict)
@cindex 冲突(conflicts), 归约/归约(reduce/reduce)

一个归约/归约冲突发生在有两个或者更多规则可以被用于相同输入序列的情况下.
这通常表明了一个语法中的严重错误.

例如,这里是一个试图定义零个或者更多@code{word}组的错误.

@example
sequence: /* empty */
                @{ printf ("empty sequence\n"); @}
        | maybeword
        | sequence word
                @{ printf ("added word %s\n", $2); @}
        ;

maybeword: /* empty */
                @{ printf ("empty maybeword\n"); @}
        | word
                @{ printf ("single word %s\n", $1); @}
        ;
@end example

@noindent
这个错误是一个歧义:有多种方法可以将单一的@code{word}分析成一个@code{sequence}.
它可以归约为一个@code{maybeword}然后通过第二个规则归约为一个@code{sequence}.
另外,什么都没有可以通过第一个规则归约为一个@code{sequence},
可以使用@code{sequence}的第三个规则将它和@code{word}结合起来.

也有多种方法将什么都没有归约成一个@code{sequence}.
可以直接通过第一个规则归约或者间接通过@code{maybeword}然后通过第二个规则归约.

你可能认为这没有什么区别,
因为不论任意的输入是否有效它没有什么变化.
但是它却影响这该执行哪一条规则.
一种分析顺序运行了第二个规则的动作,
另一个则运行了第一个和第三个规则的动作.
在这个例子中,程序的输出有所变化.

Bison靠选择首先出现在语法中的规则解决归约/归约冲突,
但是依靠这种策略是十分冒险的事情.
必须仔细研究每一个归约/归约冲突并且通常要消灭它们.
这里有一个正确定义@code{sequence}的方法:

@example
sequence: /* empty */
                @{ printf ("empty sequence\n"); @}
        | sequence word
                @{ printf ("added word %s\n", $2); @}
        ;
@end example

这里是另外一个产生归约/归约冲突的普通例子:

@example
sequence: /* empty */
        | sequence words
        | sequence redirects
        ;

words:    /* empty */
        | words word
        ;

redirects:/* empty */
        | redirects redirect
        ;
@end example

@noindent
这里的目的是定一个可以包含@code{word}或@code{redirect}组的序列.
@code{sequence},@code{words}和@code{redirects}的定义都是没有问题的,
但是三个在一起却产生微妙的歧义:
即使一个空输入可以有无限多种分析的方式.

考虑:什么都没有可以是一个@code{words}.
或者它可以是两个在一行的@code{words},或者三个,或者任意个.
它同样也可以是一个@code{words}后跟三个@code{redirects}和另外一个@code{words}.
等等.

这有两种改正这些规则的方法.
第一,使它成为一个单层序列:

@example
sequence: /* empty */
        | sequence word
        | sequence redirect
        ;
@end example

第二,防止@code{words}或者@code{redirects}为空:

@example
sequence: /* empty */
        | sequence words
        | sequence redirects
        ;

words:    word
        | words word
        ;

redirects:redirect
        | redirects redirect
        ;
@end example

@node Mystery Conflicts
@section 神秘的归约/归约冲突-Mysterious Reduce/Reduce Conflicts

[untranslated]
Sometimes reduce/reduce conflicts can occur that don't look warranted.
[/untranslated]
这里有一个例子:

@example
@group
%token ID

%%
def:    param_spec return_spec ','
        ;
param_spec:
             type
        |    name_list ':' type
        ;
@end group
@group
return_spec:
             type
        |    name ':' type
        ;
@end group
@group
type:        ID
        ;
@end group
@group
name:        ID
        ;
name_list:
             name
        |    name ',' name_list
        ;
@end group
@end example

这个文法看起来可以用一个单一的超前扫描记号分析:
当正在读入@code{para_spec}的时候.
如果@code{ID}后面紧跟一个分号,那么它是一个@code{name}.
如果@code{ID}后面跟随另外一个@code{ID},那么它是一个@code{type}.
换句话说,这是一个@acronym{LR}(1)文法.

@cindex @acronym{LR}(1)
@cindex @acronym{LALR}(1)
然而,像大多数分析器产生器一样,Bison实际上并不能处理所有的@acronym{LR}(1)文法.
在这个文法中,两个位于@code{param_spec}的开始,并同样地位于@code{return_spec}开始
,在@code{ID}之后的上下文十分相似,以致于Bison认为它们是相同的.
它们看起来相似因为相同的规则集是活动的---归约到@code{name}的规则和归约到@code{type}的规则.
Bison在那个处理阶段没有能力决定这些规则在两个上下文中需要不同的超前扫描记号,
所以它为两种情况制造了同一个分析器状态.
@c bad translation
结合两个上下文会在稍后引起一个冲突.
@c up
在分析器术语中,
这种情况意味着这个文法不是@acronym{LALR}(1)文法.

通常来讲,最好是修补漏洞而不是将漏洞写入文档.
但是这个特殊的漏洞很难被修复;
处理@acronym{LR}(1)文法的分析器生成器很难编写并且倾向于制造很大的分析器.
在实践中,Bison显得更为实用.

当问题产生时,
你通常可以通过指明两个被混淆的分析器状态
并且添加使它们看起来截然不同的额外的东西
来修补它,
在上面的例子中,
如下地向@code{return_spec}添加一个规则会消除这个问题:

@example
@group
%token BOGUS
@dots{}
%%
@dots{}
return_spec:
             type
        |    name ':' type
        /* This rule is never used.  */ /*  这个规则永远不会被使用 */
        |    ID BOGUS
        ;
@end group
@end example

这样做改正这个问题,
因为在@code{return_spec}开始部分@code{ID}后的上下文中引进了一个可能的额外的活动规则.
这个规则在@code{param_spec}相应的上下文中并不是活动的,
所以两个上下文接受了不同的分析器状态.
只要@code{yylex}永远不产生记号@code{BOGUS},
新增的规则就不能改变分析输入的实际方法.

在这个特殊的例子中,还有另外一种解决问题的方法:
直接使用使用@code{ID}来替代@code{name}来重写@code{return_spec}的规则.
这样做也使两个混淆的上下文有了不同的活动集,
@c bad translation
因为@code{return_spec}的活动集激活了@code{return_spec}的规则而不是@code{name}的.
@c up

@example
param_spec:
             type
        |    name_list ':' type
        ;
return_spec:
             type
        |    ID ':' type
        ;
@end example

@node Generalized LR Parsing
@section 通用@acronym{LR} (@acronym{GLR})分析-Generalized @acronym{LR} (@acronym{GLR}) Parsing
@cindex @acronym{GLR}分析(@acronym{GLR} parsing)
@cindex 通用@acronym{LR} (@acronym{GLR})分析(generalized @acronym{LR} (@acronym{GLR}) parsing)
@cindex 歧义文法(ambiguous grammars)
@cindex 不确定性分析(non-deterministic parsing)

Bison产生@emph{确定性(determinstic)}的分析器.
这种分析器基于先前输入和额外的超前扫描记号的摘要,
唯一性地选择进行归约的时机和如何进行归约.
结果,通常,Bison处理一个上下文无关文法语言族的自己.
由于歧义文法含有可以使用多种可能的归约序列的字符串,
所以在这种情况下不能使用确定的分析器.
这种情况同样适用于需要多于一个超前扫描记号的语言,
因为分析器缺乏做出决定所需要的必要信息,
这时它必须被制作成一个移进-归约分析器.
最终,如同之前提到的(@pxref{Mystery Conflicts, ,神秘的冲突-Mystery Conflicts}),
@c bad translation
有这样一些语言,Bison关于如何总结输入的特殊选择目前看起来缺少必要的信息.
@c up

当你在你的语法文件中使用@samp{%glr-parser}声明的时候,
Bison产生一个使用不同算法的分析器,这种分析器被称为通用@acronym{LR}(或@acronym{GLR})分析器.
一个Bison @acronym{GLR}分析器使用同样基本的算法做为一个普通的Bison分析器进行分析,
但当存在一个不能被优先级规则(@pxref{Precedence, ,优先级-Precedence})解决的移进/归约冲突,
或者一个归约/归约冲突时却有着与普通Bison分析器不同的行为.
当一个@acronym{GLR}分析器遭遇这种情况的时候,
它高效地@emph{分裂(splits)}成多个分析器,
每个对应一种可能的移进或者归约.
这些分析器如常地进行分析,使用锁步(lock-step)消耗记号.
一些栈遭遇了其它的冲突并且进一步分裂,
一个Bison @acronym{GLR}分析栈是一个取代状态序列的高效的分析树.

实际上,每个栈代表一个关于正确分析的猜想.
剩余的输入可能会表明一个猜想是错误的,
在这种情况下,不正确的栈静静地消失.
另外,每个栈中的语义动作被保存而不是立即执行.
但一个栈消失时,它存储的的语义动作永远不会被执行.
当一个归约使两个栈等价的时候,
它们的语义动作集和导致归约的状态都会被保存.
我们说两个栈是等价的
当它们都代表相同的状态序列,
并且每对相应的状态代表一个产生相同输入流片段的语法符号.

每当分析器从有多个分析状态转换为一个分析状态时,
在执行了原来保存的动作后,
这个分析器将转变到通常的@acronym{LALR}(1)分析算法.
在这个转换过程中,一些栈上的状态含有可能的动作集(实际上是多个集)的语义值.
分析器试图从这些动作中挑选一个被@samp{%prec}声明指定的有最高动态优先级的动作.
否则,如果可选择的动作并未被优先级排序,
但对两个规则使用@samp{%merge}声明了相同的合并函数,
Bison评价并解决它们之后调用合并函数求得结果.
否则它会报告一个歧义.

对@acronym{GLR}分析树使用这样一种数据结构是可能的,
这种结构可以以线性的时间(相对输入的大小)处理任意的@acronym{LALR}(1)文法,
在最坏情况下以二次方的时间处理任何非歧义文法(不一定是@acronym{LALR}(1)),
在最坏情况下以三次方的时间处理任何普通(可能是歧义的)上下文无关文法.
然而Bison当前使用一种更简单的数据结构,
这中数据结构需要与输入长度乘以输入的任意前需要缀最大栈数目成比例的时间.
因此,实际上,歧义或者不确定文法可能需要指数的时间和空间来处理.
然而,这种非常糟糕例子通常情况下很难见到.
文法中的不确定性通常是局部的---分析器一次只对很少一些记号``产生疑惑''.
因此,当前的数据结构在大多数情况下足够用了.
特别地,对于文法的@acronym{LALR}(1)部分,它(注:通用@acronym{GLR}分析器)
仅仅比默认的Biosn分析器稍慢.

想获得更详细的@acronym{GLR}分析器的说明,请参阅:
Elizabeth
Scott, Adrian Johnstone and Shamsa Sadaf Hussain, Tomita-Style
Generalised @acronym{LR} Parsers, Royal Holloway, University of
London, Department of Computer Science, TR-00-12,
@uref{http://www.cs.rhul.ac.uk/research/languages/publications/tomita_style_1.ps},
(2000-12-24).

@node Stack Overflow
@section 栈溢出以及如何避免它-Stack Overflow, and How to Avoid It
@cindex 栈溢出(stack overflow)
@cindex 分析器栈溢出(parser stack overflow)
@cindex 分析器栈的溢出(overflow of parser stack)

如果太多的记号被移进而没有被归约,
Bison分析器栈可能会溢出.
在这种情况发生时,
分析器函数@code{yyparse}返回非零值,
暂停执行并调用@code{yyerror}来报告错误.

由于Bison分析器拥有生长的栈,
达到上限通常是由于使用右递归而不是左递归而产生的.
@xref{Recursion, ,递归规则-Recursive Rules}.

@vindex YYMAXDEPTH
靠定义宏@code{YYMAXDEPTH},你可以控制栈溢出之前的最大深度.
我们应该用正数定义这个宏.
这个值是在溢出之前被移进(而没被归约)的记号的最大数目.

允许的栈空间不需要一次分配完毕.
如果你为@code{YYMAXDEPTH}指定了一个很大的数字,
分析器实际上在开始之分配了一个空间很小的栈,
随个阶段性的需求,分析器会扩大栈的容量.
增大空间的分配自动并且沉默地进行.
因此,你不需要为了不需要多少空间的普通输入节省空间
而将@code{YYMAXDEPTH}定义的很小.

然而,我们同样不要把@code{YYMAXDEPTH}定义的很大以至于
在计算栈容量时产生算术溢出.
并且我们也不要将@code{YYMAXDEPTH}定义的比@code{YYINITDEPTH}还小.

@cindex 默认栈容量限制(default stack limit)
如果你没有定义@code{YYMAXDEPTH},那么它的默认值是10000.

@vindex YYINITDEPTH
你可靠定义宏@code{YYINITDEPTH}为一个正值来控制栈初始分配的空间.
除非你使用C99或者其它允许变长数组的语言和编译器,
对于C语言@acronym{LALR}(1)分析器来说,
这个值必须为编译时常量.
@code{YYINITDEPTH}的默认值为200.

不要让@code{YYINITDEPTH}过大以至于当计算栈空间时发生溢出.
同样地,不要让@code{YYINITDEPTH}大于@code{YYMAXDEPTH}.

@c FIXME: C++ output.
由于C和C++语义上的区别,
利用C++编译器编译的用C语言编写的@acronym{LALR}(1)分析器不能生长.
(注:指栈不能生长)
在这种情况下(作为C++来编译C分析器),
我们建议你增加@code{YYINITDEPTH}的大小.
在不久的将来,
我们会提供涉及到这个问题的C++输出.

@node Error Recovery
@chapter 错误恢复-Error Recovery
@cindex 错误恢复(error recovery)
@cindex 从错误中恢复(recovery from errors)

我们通常不能接受让一个程序在遇到语法错误时就终止.
例如,一个编译器应该充分的从错误中恢复
以便分析输入文件的其余部分并且检查其中的错误;
一个计算器应该接受其它的表达式.

在每个输入都是一行的简单互交命令分析器中,
让@code{yyparse}在遇到错误时返回1并且
使调用者忽略剩下的输入行(然后重新调用@code{yyparse}就足够了.
但是这对于编译器来说显然不够,
因为为它忘记了导致错误的全部构造上下文.
在编译器输入中,深入到一个函数内部的语法错误,
并不应该使编译器对待后面的行像对待源文件的开始一样.

@findex error
你可以靠编写一个识别特殊记号@code{error}的规则来定义如何从语法错误中恢复.
它总是一个已经被定义(你不需要声明它)并且保留做错误处理使用的终结符.
每当一个语法错误发生时,Bison分析器就产生一个@code{error}记号;
如果你在当前的上下文中提供了一个识别该记号的规则,
那么分析可以继续进行.

例如:

@example
stmnts:  /* empty string */ /* 空字符串 */
        | stmnts '\n'
        | stmnts exp '\n'
        | stmnts error '\n'
@end example

@c bad translation
这个例子的第四个规则说明了一个错误后紧跟一个换行
对任何@code{stmnts}是有效的添加.
@c up

如果错误发生在@code{exp}中间的话会发生什么情况?
这个错误恢复规则,被精确地解释为应用于一个@code{stmnts},一个@code{error}
和一个换行的精确序列.
如果一个错误发生在一个@code{exp}中间,
那么在栈中最后的@code{stmnts}之后很可能有一些额外的记号或者自表达式,
即有一些记号在下一个换行之前被读入.
所以这个规则并不按通常的方法应用.

但是Bison可以靠丢弃部分语义上下文和部分输入来强制地使这个规则(注:错误恢复规则)适用于这种情况.
首先,它从栈中丢弃状态和对象直到回到一个可以接受@code{error}的状态.
(这意味着分析过的子表达式被丢弃,并且回到最后一个完整的@code{stmnts}.)
这时@code{error}记号可以被移进.
之后,如果旧的超前扫描记号不能接受移进下一个记号,
分析器如读记号并且丢弃它们直到找到一个可以接受的记号.
在这个例子中,Bison读入并丢弃输入直到下一个换行符以便应用第四个规则.
注意到丢弃的符号通常是内存泄露之源,参阅@ref{Destructor Decl, , 
释放丢弃的符号-Freeing Discarded Symbols}以获取更多信息.

在语法中,对于错误恢复规则的选择就是对错误恢复策略的选择.
一个简单而使用的策略是如果检测到一个错误,跳过当前输入行的剩余部分:

@example
stmnt: error ';'  /* On error, skip until ';' is read.  */ /* 当错误出现时,跳过剩余部分直到读入 ';' */
@end example

为一个已经分析的作括号恢复匹配一个右括号也是非常实用的.
否则,右括号很可能不匹配地出现并且引发另外的更严重的错误消息:

@example
primary:  '(' expr ')'
        | '(' error ')'
        @dots{}
        ;
@end example

错误恢复策略是必要的猜测.
当它们猜测的时候,一个语法错误通常会导致另外一个错误.
在上面的例子中,
错误规则猜测:一个错误是由于一个@code{stmnt}中的错误输入引起的.
假设一个伪造的分号被插入到一个有效的@code{stmt}中间.
在错误恢复规则从第一错误恢复之后,分析器会立刻发现另外一个错误,
因为在伪造的分号之后的文字也是一个无效的@code{stmt}.

为了阻止错误的倾泄而出,
分析器在第一个错误之后立即发现另一个错误时,不会输出错误消息;
仅在三个连续的数据记号被成功归约之后,分析器才会恢复输出错误消息.

注意到接受@code{error}记号的规则像其它任何规则一样也可以有动作.

@findex yyerrok
你可以通过使用宏@code{yyerrok}使错误消息立即恢复.
如果你在错误恢复规则的动作中使用它,
没有任何错误消息会被抑制.
这个宏不需要任何参数;
@samp{yyerrok;}是一个有效的C语句.

@findex yyclearin
先前的超前扫描记号在一个错误后会被立即再分析.
如果这是不可接受的,
那么宏@code{yyclearin}可以用于清除这个记号.
将语句@samp{yyclearin;}写入错误恢恢复规则的动作中.

例如,假设在遭遇一个语法错误时,
一个错误处理程序被调用用于将输入流前进到重新开始分析的地方.
词法分析器返回的下一个记号很可能是正确的.
前一个超前扫描记号应该用@samp{yyclearin;}丢弃.

@vindex YYRECOVERING
宏@code{YYRECOVERING}代表一个表达式.
这个表达式在分析器从语法错误中恢复时值为1,在其它的时候值为0.
值为1指明了要抑制新的语法错误产生的错误消息.

@node Context Dependency
@chapter 处理上下文依赖-Handling Context Dependencies

Bison的分析模式是首先分析记号,之后将它们组合成更大的句法单元.
在许多语言中,一个记号的意义受到上下文的影响.
尽管这破坏了Bison范例,
某些技术(被称为@dfn{kludges})可以使你有能力为这种语言编写Bison分析器.

@menu
* 语义记号(Semantic Tokens):Semantic Tokens.   对记号的分析可能依赖于语义上下文.
* 词法关联(Lexcial Tie-ins):Lexical Tie-ins.   对记号的分析可能依赖于语言构造的上下文.
* 关联恢复(Tie-in Recovery):Tie-in Recovery.   词法关联含有如何编写错误恢复规则的暗示.
@end menu

(实际上, ``kludge''意思是既不干净也不健壮地完成某项工作的技术)

@node Semantic Tokens
@section 符号类型中的语义信息-Semantic Info in Token Types

C语言就有上下文依赖:
标识符使用的方法依赖于当当前的意义.
例如,考虑这个:

@example
foo (x);
@end example

这看起来是一个函数调用语句,但如果@code{foo}是一个typedef名称,
那么这实际上是一个@code{x}的声明.
C语言的Bison分析器如何决定怎么分析这个输入呢?

@acronym{GNU} C使用的办法是让它们有不同的记号类型,
@code{INDENTIFIER}和@code{TYPENAME}.
当@code{yylex}发现一个标识符,
它搜索当前的标识符声明以便决定返回什么样的记号类型:
如果标识符由一个typedef声明的,就返回@code{TYPENAME},否则返回@code{IDENTIFIER}.

这时,语法规则就可以通过对要识别的记号类型的选择来表达上下文依赖.
@code{IDENTIFIER}可以作为一个表达式被接受,但是@code{TYPENAME}却不能.
@code{TYPENAME}可以开始一个声明,但是@code{IDENTIFIER}却不可以.
在标识符的意义@emph{不}明显的上下文中,
@c bad translation
例如在可以隐藏一个typedef名称的声明中,
@code{TYPENAME}和@code{IDENTIFIER}都是可接受的---
并没有一个针对没一种记号类型的规则.
@c up

@c bad translation
如果在接近分析标识符的地方决定允许什么种类的标识符,
那么这个技术可以简单的应用.
@c up
但是在C语言中却不总是这样:
C允许重新声明之前声明的带有明确类型的typedef名称.

@example
typedef int foo, bar, lose;
static foo (bar);        /* @r{redeclare @code{bar} as static variable} */ /* @r{重新声明@code{bar}为一个静态变量} */
static int foo (lose);   /* @r{redeclare @code{foo} as function} */ /* @r{重新声明@code{foo}为一个函数} */
@end example

不幸的是,这个名称被一个复杂的句法结构---``声明符''所分隔.

结果,C语言的Bison分析器的某些部分要被复制,并且要改变所有非终结符的名称:
一次是为了分析可以被重定义的typedef声明,
一次是为了分析不能被重定义的声明.
这里是复制的部分.
为了简洁省略了动作.

@example
initdcl:
          declarator maybeasm '='
          init
        | declarator maybeasm
        ;

notype_initdcl:
          notype_declarator maybeasm '='
          init
        | notype_declarator maybeasm
        ;
@end example

@noindent
在这里@code{initdcl}可以重新声明一个typedef名称,
但是@code{notype_initdcl}却不能.
@c bad translation
@code{declarator}和@code{notype_declarator}的区别在于同一类型的不同种类.
@c up

这种技术和词法关联技术(在下一节描述)有一些相似之处.
@c skip
@c [untranslated]
@c in that information (which alters the lexical analysis) is changed during 
@c parsing by other parts of the program.
@c [/untranslated]
@c up
它们的区别是,这里的信息是全局的并且用于程序的其它目的.
一个真正的词法关联含有一个受上下文控制的特殊目的标志.

@node Lexical Tie-ins
@section 词法关联-Lexical Tie-ins
@cindex 词法关联(lexical tie-in)

另外一种处理上下文依赖的方法是@dfn{词法关联(lexical tie-in)}: 
一个由Bison动作设置的标志,
它的目的是改变分析记号的方式.

例如,假设我们有一种类似C的语言,
但是它带有一个特殊的@samp{hex (@var{hex-expr})}结构.
在关键字@code{hex}之后是一个括号之中全部为十六进制整数的表达式.
特别地,在那个上下文中,记号@samp{a1b}必须被看做是一个整数而不是一个标识符.
这里就是你如何处理它:

@example
@group
%@{
  int hexflag;
  int yylex (void);
  void yyerror (char const *);
%@}
%%
@dots{}
@end group
@group
expr:   IDENTIFIER
        | constant
        | HEX '('
                @{ hexflag = 1; @}
          expr ')'
                @{ hexflag = 0;
                   $$ = $4; @}
        | expr '+' expr
                @{ $$ = make_sum ($1, $3); @}
        @dots{}
        ;
@end group

@group
constant:
          INTEGER
        | STRING
        ;
@end group
@end example

@noindent
这里我们假设@code{yylex}观察@code{hexflag}的值;
当它的值非零时,所有的整数被分析成十六进制数,
并且带有字母的标识符也尽可能的被翻译成整数.

@code{hexflag}出现在分析器文件的@var{Prologue}部分以便动作可以访问它
(@pxref{Prologue, ,@var{Prologue}部分-The Prologue}).
你还必须在@code{yylex}中编写代码来获得这个标志.

@node Tie-in Recovery
@section 词法关联和错误恢复-Lexical Tie-ins and Error Recovery

词法关联对你使用的任何错误恢复规则都有严格的要求.
@xref{Error Recovery, ,错误恢复-Error Recovery}.

这样的原因是错误恢复规则的目的是放弃对一个结构的分析并且恢复到某个更大的结构中去.
不例如,在类似C的语言中,
一个典型的错误恢复规则是跳过记号直到下一个分号,
并且开始分析一个新的语句,
像这样:

@example
stmt:   expr ';'
        | IF '(' expr ')' stmt @{ @dots{} @}
        @dots{}
        error ';'
                @{ hexflag = 0; @}
        ;
@end example

如果在@samp{hex (@var{expr})}之中存在一个语法错误,
这个错误恢复规则就会被应用,
完整的@samp{hex (@var{expr})}的动作永远都不会执行.
所以对于其余的输入或者直到下一个关键字@code{hex},
@code{hexflag}仍然被置1.这会导致标识符被错误地解释为整数.

为了避免这个错误,错误恢复规则自己要将@code{hexflag}清零.

也有可能存在一个与表达式一起工作的错误恢复规则.
例如,可能有一个应用于括号匹配的规则,
并且它跳跃到右括号:

@example
@group
expr:   @dots{}
        | '(' expr ')'
                @{ $$ = $2; @}
        | '(' error ')'
        @dots{}
@end group
@end example

如果这个规则在@code{hex}结构中执行,
它不会放弃那个结构(由于它作于在结构内部的括号(注:结构指@code{hex}结构)).
因此,它不应该将标志清零:
@code{hex}结构的其余部分应该在该标志仍然有效的情况下被分析.

如果有一个错误规则依靠当时的状况可能放弃@code{hex}结构也可能不放弃的话,
我们该怎么办?
没有办法编写一个可以决定是否放弃@code{hex}结构的动作.
所以,如果你使用了词法关联,
最好保证你的错误恢复规则不是这种类型.
你必须要确定每个规则总是要清零或总不要清零.


@c ================================================== Debugging Your Parser

@node Debugging
@chapter 调式你的分析器-Debugging Your Parser

开发分析器可能是一种挑战,特别当你不理解它的算法的时候
(@pxref{Algorithm, ,Bison分析器算法-The Bison Parser Algorithm}).
即使是这样,有些时候一个关于自动的详细描述可能会有所帮助
(@pxref{Understanding, ,理解你的分析器- Understanding Your Parser}),
或者跟踪分析器的执行可以给你关于为它什么做出不正确的行为一些灵感.
(@pxref{Tracing, ,跟踪你的分析器- Tracing Your Parser}).

@menu
* 理解(Understanding):Understanding.     理解你的分析器的结构
* 跟踪(Tracing):Tracing.           跟踪你的分析器的执行
@end menu

@node Understanding
@section 理解你的分析器-Understanding Your Parser

如同本文档其它部分描述的
(@pxref{Algorithm, ,Bison分析器算法-The Bison Parser Algorithm}),
Bison分析器是@dfn{移进/归约自动机(shift/reduce automata)}.
在一些情况下(比你希望的要更频繁),
调整或者简单的修正一个分析器需要考虑这个自动机.
Bions提供了它(自动机)的两种表示方法,文本的或者图形的(作为一个@acronym{VCG}文件).

当指定选项@option{--report}或者@option{--verbose}时Bison生成文本文件,
@xref{Invocation, ,调用Bison-Invoking Bison}.
它的名称由移除分析器输出文件名@samp{.tab.c}或者@samp{.c}而添加@samp{.output}取代.
因此,如果输入文件是@file{foo.y},
那么默认的分析器文件为@file{foo.tab.c}.
结果,冗长(verbose)输出文件为@file{foo.output}.

下面的语法文件@file{calc.y}将在稍后使用:

@example
%token NUM STR
%left '+' '-'
%left '*'
%%
exp: exp '+' exp
   | exp '-' exp
   | exp '*' exp
   | exp '/' exp
   | NUM
   ;
useless: STR;
%%
@end example

@command{bison} 报告:

@example
calc.y: warning: 1 useless nonterminal and 1 useless rule
calc.y:11.1-7: warning: useless nonterminal: useless
calc.y:11.10-12: warning: useless rule: useless: STR
calc.y: conflicts: 7 shift/reduce
@end example

当指定@option{--report=state},
除了文件@file{calc.tab.c},
它还创建了包含如下详细信息的文件@file{calc.outut}.
输出和精确表述的顺序可能有所不同,
但是对此的解释是相同的.

第一个部分包括了由前面的与/或结合性解决的冲突的详细信息.

@example
Conflict in state 8 between rule 2 and token '+' resolved as reduce.
Conflict in state 8 between rule 2 and token '-' resolved as reduce.
Conflict in state 8 between rule 2 and token '*' resolved as shift.
@exdent @dots{}
@end example

@noindent
下一个部分列出了仍然有冲突的状态清单.

@example
State 8 conflicts: 1 shift/reduce
State 9 conflicts: 1 shift/reduce
State 10 conflicts: 1 shift/reduce
State 11 conflicts: 4 shift/reduce
@end example

@noindent
@cindex 记号(token), 没用处(useless)
@cindex 没用处的记号(useless token)
@cindex 非终结符(nonterminal), 没用处(useless)
@cindex 没用处的非终结符(useless nonterminal)
@cindex 规则(rule), 没用处(useless)
@cindex 没用处的规则(useless rule)
下一个部分报告了没有用处的记号,非终结符和规则.
没用处的非终结符和规则被移除以便产生一个更小的分析器,
但是没用记号被保留,因为它们可能被扫描器使用,
(应该注意到``没用处的''和``没被使用的''之间的区别).

@example
Useless nonterminals:
   useless

Terminals which are not used:
   STR

Useless rules:
#6     useless: STR;
@end example

@noindent
下一个部分重新制造了Bison使用的精确语法:

@example
Grammar

  Number, Line, Rule
    0   5 $accept -> exp $end
    1   5 exp -> exp '+' exp
    2   6 exp -> exp '-' exp
    3   7 exp -> exp '*' exp
    4   8 exp -> exp '/' exp
    5   9 exp -> NUM
@end example

@noindent
并且报告了使用的符号:

@example
Terminals, with rules where they appear

$end (0) 0
'*' (42) 3
'+' (43) 1
'-' (45) 2
'/' (47) 4
error (256)
NUM (258) 5

Nonterminals, with rules where they appear

$accept (8)
    on left: 0
exp (9)
    on left: 1 2 3 4 5, on right: 0 1 2 3 4
@end example

@noindent
@cindex 项目(item)
@cindex 指明规则(pointed rule)
@cindex 规则(rule), 指明的(pointed)
Bison之后进入到自己的自动机,
并且用@dfn{项目(items)}集,也被成为@dfn{指明规则(pointed rules)},来描述每个状态.
每个都是一个产生式规则,并且带有表示输入光标的点号.

@example
state 0

    $accept  ->  . exp $   (rule 0)

    NUM         shift, and go to state 1

    exp         go to state 2
@end example

这些有如下含义: ``状态0相应地处于分析的开始,
在初始规则中,处于开始符号(这里是@code{exp})的右端.
当分析器归约了一个产生的@code{exp}的规则并返回这个状态之后,
控制流跳转到状态2.
如果没有这样的非终结符转化并且超前扫描记号是@code{NUM},
那么这个记号被移进到分析器栈中,控制流跳转到状态1.
任何其它的超前扫描记号都会引发一个语法错误.''

@cindex 核心(core), 项目集(item set)
@cindex 项目集核心(item set core)
@cindex 核心(kernel), 项目集(item set)
@cindex 项目集核心(item set core)
即使状态0中的唯一活动规则看起来是规则0,
报告将@code{NUM}列举为一个超前扫描记号,
这是因为@code{NUM}可以在任何转向@code{exp}的规则的开头.
默认地,Bison报告项目集的核心(@dfn{core} or @dfn{kernel} of the item set).
但是如果你想查看更详细的信息,你可以使用选项@option{--report=itemset}调用@command{bison}
来列出所有的项目,包括那些可以由此派生的.

@example
state 0

    $accept  ->  . exp $   (rule 0)
    exp  ->  . exp '+' exp   (rule 1)
    exp  ->  . exp '-' exp   (rule 2)
    exp  ->  . exp '*' exp   (rule 3)
    exp  ->  . exp '/' exp   (rule 4)
    exp  ->  . NUM   (rule 5)

    NUM         shift, and go to state 1

    exp         go to state 2
@end example

@noindent
在状态1中...

@example
state 1

    exp  ->  NUM .   (rule 5)

    $default    reduce using rule 5 (exp)
@end example

@noindent
规则5,@samp{exp: NUM;}是完整的.
无论超前扫描记号(@samp{$default})是什么,分析器都会归约它.
如果是从状态0跳转过来,在归约之后会回到到状态0,并且之后会跳转到状态2(@samp{exp: go to state 2}).

@example
state 2

    $accept  ->  exp . $   (rule 0)
    exp  ->  exp . '+' exp   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)

    $           shift, and go to state 3
    '+'         shift, and go to state 4
    '-'         shift, and go to state 5
    '*'         shift, and go to state 6
    '/'         shift, and go to state 7
@end example

@noindent
在状态2中,自动机只能进行归约符号.
例如,根据项目@samp{exp -> exp . '+' exp},如果超前扫描记号为@samp{+},
它会被移进到分析器栈中,并且状态机控制会跳转到状态4,
对应项目@samp{exp -> exp '+' . exp}.
由于没有默认动作,任何非上述列出的记号会引起一个语法错误.

状态3被称为@dfn{终态(finial state)})或者@dfn{接受态(accepting state)}:

@example
state 3

    $accept  ->  exp $ .   (rule 0)

    $default    accept
@end example

@noindent
初始规则已经完成(已经读取开始符号和输入终结),
分析成功退出.

状态4到7解释的很直接,留给读者自己分析:

@example
state 4

    exp  ->  exp '+' . exp   (rule 1)

    NUM         shift, and go to state 1

    exp         go to state 8

state 5

    exp  ->  exp '-' . exp   (rule 2)

    NUM         shift, and go to state 1

    exp         go to state 9

state 6

    exp  ->  exp '*' . exp   (rule 3)

    NUM         shift, and go to state 1

    exp         go to state 10

state 7

    exp  ->  exp '/' . exp   (rule 4)

    NUM         shift, and go to state 1

    exp         go to state 11
@end example

正如报告开始部分声明的,@samp{State 8 conflicts:1 shift/reduce}:

@example
state 8

    exp  ->  exp . '+' exp   (rule 1)
    exp  ->  exp '+' exp .   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)

    '*'         shift, and go to state 6
    '/'         shift, and go to state 7

    '/'         [reduce using rule 1 (exp)]
    $default    reduce using rule 1 (exp)
@end example

的确,有两个与超前扫描记号@samp{/}关联的动作:
或者移进(并且转到状态7),或者归约规则1.
这个冲突意味着或者语法是歧义的或者分析器缺少做出正确决定的信息.
这个语法确实是歧义的,因为我们并未指明@samp{/}的优先级,
句子@samp{NUM + NUM / NUM}可以被分析为对应于移进@samp{/}的@samp{NUM + (NUM / NUM)},
也可以被分析为对应于归约规则1的@samp{(NUM + NUM) / NUM}.

由于在@acronym{LALR}(1)分析中只能做出一个动作,
Bison武断地选择不使用归约,参阅@ref{Shift/Reduce, ,移进/归约冲突-Shift/Reduce Conflicts}.
被丢弃的动作被报告于方括号中.

注意到先前的所有状态只有一个单一可能的动作:
或者移进下一个记号并且转到相应的状态,
或者归约一个规则.
在其它的情况下,
例如,
当移进@emph{和}归约都是可能的或者@emph{多个}归约都是可能的,
这是需要超前扫描记号来选择动作.
状态8就是这样一种状态:如果超前扫描记号是@samp{*}或者@samp{/}
那么多做是移进,否则动作是归约动作1.
换句话说,前两项,对应于规则1,当超前扫描记号是@samp{*}的时候是不符合条件的,
因为我们指明了@samp{*}有比@samp{+}更高的优先级.
更普通地说,
一些项目仅在某些可能的超前扫描记号下是符合条件的.
当使用选项@option{--report=look-ahead},Bison会指明这些超前扫描记号:

@example
state 8

    exp  ->  exp . '+' exp  [$, '+', '-', '/']   (rule 1)
    exp  ->  exp '+' exp .  [$, '+', '-', '/']   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)

    '*'         shift, and go to state 6
    '/'         shift, and go to state 7

    '/'         [reduce using rule 1 (exp)]
    $default    reduce using rule 1 (exp)
@end example

其余的状态与之类似:

@example
state 9

    exp  ->  exp . '+' exp   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp '-' exp .   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)

    '*'         shift, and go to state 6
    '/'         shift, and go to state 7

    '/'         [reduce using rule 2 (exp)]
    $default    reduce using rule 2 (exp)

state 10

    exp  ->  exp . '+' exp   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp '*' exp .   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)

    '/'         shift, and go to state 7

    '/'         [reduce using rule 3 (exp)]
    $default    reduce using rule 3 (exp)

state 11

    exp  ->  exp . '+' exp   (rule 1)
    exp  ->  exp . '-' exp   (rule 2)
    exp  ->  exp . '*' exp   (rule 3)
    exp  ->  exp . '/' exp   (rule 4)
    exp  ->  exp '/' exp .   (rule 4)

    '+'         shift, and go to state 4
    '-'         shift, and go to state 5
    '*'         shift, and go to state 6
    '/'         shift, and go to state 7

    '+'         [reduce using rule 4 (exp)]
    '-'         [reduce using rule 4 (exp)]
    '*'         [reduce using rule 4 (exp)]
    '/'         [reduce using rule 4 (exp)]
    $default    reduce using rule 4 (exp)
@end example

@noindent
注意到状态11包含冲突不仅仅因为缺少@samp{/}相对于@samp{+},@samp{-}和@samp{*}的优先级,
还由于并未指定@samp{/}的结合性.

@node Tracing
@section 跟踪你的分析器-Tracing Your Parser
@findex yydebug
@cindex 调试(debugging)
@cindex 追踪分析器(tracing the parser)

如果Bison语法编译正确但是在运行的时候并未达到你想要的目的,
@code{yydeug}分析器追踪特性可以帮你指明原因.

有多种方法激活追踪机制的编译:

@table @asis
@item 宏 @code{YYDEBUG}
@findex YYDEBUG
当你编译分析器的时候,将宏@code{YYDEBUG}定义成非零指.
这种方式与@acronym{POSIX} Yacc兼容.
你可以使用@samp{-DYYDEBUG=1}作为一个编译器选项或者你可以将@samp{define YYDEBUG 1}
放入语法文件的@var{Prologue}部分.(@pxref{Prologue, ,@var{Prologue}部分- The Prologue}).

@item 选项 @option{-t}, @option{--debug}
当你运行Bison(@pxref{Invocation, ,调用Bison-Invoking Bison})时,
使用@samp{-t}选项.
这也与@acronym{POSIX}兼容.

@item 指令 @samp{%debug}
@findex %debug
加入@code{%debug}指令(@pxref{Decl Summary, ,Bison声明总结-Bison Declaration Summary}).
这是一个Bison扩展,当Bison为不使用预处理器的语言输出分析器的时候很实用.
除非你要考虑@acronym{POSIX}可移植性问题,
否则这是一个很好的解决方案.
@end table

我们建议你应该总是激活调式选项以便随时进行调试.

追踪机制使用@code{YYFPRINTF (stderr, @var{format}, @var{args})}形式的宏调用输出信息.
在这里@var{format}和@var{args}是普通的@code{printf}的格式和参数.
如果你定义@code{YYDEBUG}为一个非零值但是没有定义@code{YYFPRINTF},
@code{<stdio.h>}自动被加入并且@code{YYPRINTF}被定义为@code{fprintf}.

一旦你使用了追踪机制编译程序,
请求一个追踪的方法是在变量@code{yydebug}中存储一个非零值.
你可以考编写C代码(也许在@code{main}中)做到这一点,
你也可以使用C调试器来改变这个值.

当@code{yydebug}为非零的时候,分析器执行的每一步都产生一个写入@code{stderr}一两行的追踪信息.
追踪信息告诉你这些东西:

@itemize @bullet
@item
每次调用@code{yylex}时,读取记号的种类.

@item
每次移进记号的时候,分析器栈的深度和完整的内容.
(@pxref{Parser States, ,分析器状态-Parser States})

@item
每次归约一个规则时,这个规则是哪个规则,和在归约之后状态栈的完整内容.
@end itemize

弄清这些信息的意思有助于查阅由Bison选项@samp{-v}产生的列表文件(listing file).
(@pxref{Invocation, ,调用Bison-Invoking Bison}).
这个文件按照各种规则的位置展示了每个状态的意义,
还展示了每个状态会怎样处理每个输入记号.
当你阅读连续的追踪信息时,
你可以看到分析器按照它在列表文件中的指示工作.
最终你会到达发生不期望事情的地方,
并且你会发现语法的哪一个部分存在问题.

分析器文件是一个C程序,你可以使用C调式器调试它,
但是我们很难解释它在做些什么.
分析器函数是一个有限状态机解释器,
除了动作以外它反复执行相同的代码.
只有变量的值才能表示它正在语法的那个地方工作.

@findex YYPRINT
调试信息通常给出了每个读入记号的符号类型而不是它的语义值.
你可以定一个名为@code{YYPRINT}的宏来打印这个值.
如果你定义@code{YYPRINT},
它应带有三个参数.
分析器将传递标准I/O流,记号类型的数字码和记号指(从@code{yylval}中).

这里有一个适用于多功能计算器的@code{YYPRINT}
(@pxref{Mfcalc Decl, ,@code{mfcalc}的声明部分-Declarations for @code{mfcalc}}):

@smallexample
%@{
  static void print_token_value (FILE *, int, YYSTYPE);
  #define YYPRINT(file, type, value) print_token_value (file, type, value)
%@}

@dots{} %% @dots{} %% @dots{}

static void
print_token_value (FILE *file, int type, YYSTYPE value)
@{
  if (type == VAR)
    fprintf (file, "%s", value.tptr->name);
  else if (type == NUM)
    fprintf (file, "%d", value.val);
@}
@end smallexample

@c ================================================= Invoking Bison

@node Invocation
@chapter 调用Bison-Invoking Bison
@cindex 调用Bison(invoking Bison)
@cindex Bison调用(Bison invocation)
@cindex Bison的调用选项(options for invoking Bison)

调用Bison的通常方法如下:

@example
bison @var{infile}
@end example

这里的@var{file}是通常以@samp{.y}结尾的语法文件名.
分析器文件名由@file{.tab.c}代替@samp{.y}取得.
因此,@samp{bison foo.y}产生@file{foo.tab.c},
@samp{bison hack/foo.y}产生@file{hack/foo.tab.c}.
如果你在你语法文件中使用C++代码而不是C,
把它命名为@file{foo.ypp}或者@file{foo.y++}.
那么,输出文件的扩展名类型给定的输入
(分别为@file{foo.tab.cpp}和@file{foo.tab.c++}.

例如:

@example
bison -d @var{infile.yxx}
@end example
@noindent
将会产生@file{infile.tab.cxx}和@file{infile.tab.hxx},并且

@example
bison -d -o @var{output.c++} @var{infile.y}
@end example
@noindent
会产生@file{output.c++}和@file{outfile.h++}.

为了与@acronym{POSIX}兼容,
标准的Bison发行版也包含一个名为@command{yacc}的脚本,
该脚本使用@option{-y}选项调用Bison.

@menu
* Bison选项(Bison Options):Bison Options.     按简写选项的字母顺序详细描述所有选项
* 选项交叉键(Option Cross Key):Option Cross Key.  按字母顺序列出长选项
* Yacc库(Yacc Library):Yacc Library.      与Yacc兼容的@code{yylex}和@code{main}
@end menu

@node Bison Options
@section Bison选项-Bison Options

Bison既支持传统的单字母选项也支持可记忆长选项名称.
用@samp{--}取代@samp{-}来指明常长项名称.
Bison允许选项名称缩写只要它们是唯一的.
当长选项带有一个参如,如@samp{--file-prefix},
用@samp{=}连接选项名称和参数.

这里有一个Bison可以是用的选项清单,
按照短选项字母顺序排列.
在它之后是一个常选项的交叉键.

@c Please, keep this ordered as in `bison --help'.
@noindent
操作模式:
@table @option
@item -h
@itemx --help
打印一个Bison命令行选项的总结并退出.

@item -V
@itemx --version
打印Bison的版本号并退出.

@need 1750
@item -y
@itemx --yacc
与@samp{-o y.tab.c}等价;
分析器输出文件名为@file{y.tab.c},
并且其它输出称为@file{y.output}和@file{y.tab.h}.
这个选项的目的是模拟Yacc的输出文件命名惯例.
因此,如下的shell脚本可以替代Yacc,
并且Bison发行版包含一个这种为@acronym{POSIX}兼容的脚本.

@example
#! /bin/sh
bison -y "$@@"
@end example
@end table

@noindent
调整分析器:

@table @option
@item -S @var{file}
@itemx --skeleton=@var{file}
指明要使用的骨架(skeleton).
除非你正在开发Bison否你很可能不需要这个选项.

@item -t
@itemx --debug
在分析器文件中,定义宏@code{YYDEBUG}为1,如果还没有定义它,
以便调试机制被编译.
@xref{Tracing, ,追踪你的分析器-Tracing Your Parser}.

@item --locations
@code{%locations}的伪装.@xref{Decl Summary, ,声明总结-Decl Summary}.

@item -p @var{prefix}
@itemx --name-prefix=@var{prefix}
@code{%name-prefix="@var{prefix}"}的伪装.
@xref{Decl Summary, ,声明总结-Decl Summary}.

@item -l
@itemx --no-lines
在分析器文件中不放入任何的@code{#line}预处理器命令.
Bison通常将它们放入分析器文件以便C编译起和调试器将错误关联到你的源文件,
语法文件.
这个选项会关联错误到分析器文件,将它视为一个独立的源文件.

@item -n
@itemx --no-parser
@code{%no-parser}的伪装.
@xref{Decl Summary, ,声明总结-Decl Summary}.

@item -k
@itemx --token-table
@code{%token-table}的伪装.
@xref{Decl Summary, ,声明总结-Decl Summary}.
@end table

@noindent
调整输出:

@table @option
@item -d
@itemx --defines
伪装@code{%defines},例如,向一个额外的文件写入语法中记号类型名称的宏定义和一些其它的声明.
@xref{Decl Summary, ,声明总结-Decl Summary}.

@item --defines=@var{defines-file}
与上述相同,但是保存到文件@var{defines-file}.

@item -b @var{file-prefix}
@itemx --file-prefix=@var{prefix}
@code{%verbose}的伪装,例如,指明所有Bison输出文件的前缀.
@xref{Decl Summary, ,声明总结-Decl Summary}.

@item -r @var{things}
@itemx --report=@var{things}
向一个额外的输出文件写入如下@var{things}的详细描述清,并由逗号分隔:

@table @code
@item state
语法,冲突(解决的和未解决的)以及@acronym{LALR}自动机.

@item look-ahead
包含@code{state}并且增加每个规则的超前扫描记号集自动机的描述.

@item itemset
包含@code{state}并且增加每个状态的全部项目集的自动机而不仅仅是它核心的自动机.
@end table

例如,在下面的语法中

@item -v
@itemx --verbose
@code{%verbose}的伪装,例如,向额外的输出文件写入语法和分析器的详细描述.
@xref{Decl Summary, ,声明总结-Decl Summary}.

@item -o @var{filename}
@itemx --output=@var{filename}
为分析器文件指明@var{filename}.

其它输出文件的名称像@samp{-v}和@samp{-d}选项的描述一样由@var{filename}构成.

@item -g
输出一个由Bison计算的@acronym{LALR}(1)语法自动机的@acronym{VCG}定义.
如果语法文件是@file{foo.y},
@acronym{VCG}输出文件将会是@file{foo.vcg}.

@item --graph=@var{graph-file}
@var{--graph}的行为和@samp{-g}的行为一样.
唯一的区别在于它含有一个指明输出图形文件的可选参数.
@end table

@node Option Cross Key
@section 选项交叉键-Option Cross Key

这里有一个选项列表,按照长选项的字母排序,来帮助你找到相应的所写选项.

@tex
\def\leaderfill{\leaders\hbox to 1em{\hss.\hss}\hfill}

{\tt
\line{ --debug \leaderfill -t}
\line{ --defines \leaderfill -d}
\line{ --file-prefix \leaderfill -b}
\line{ --graph \leaderfill -g}
\line{ --help \leaderfill -h}
\line{ --name-prefix \leaderfill -p}
\line{ --no-lines \leaderfill -l}
\line{ --no-parser \leaderfill -n}
\line{ --output \leaderfill -o}
\line{ --token-table \leaderfill -k}
\line{ --verbose \leaderfill -v}
\line{ --version \leaderfill -V}
\line{ --yacc \leaderfill -y}
}
@end tex

@ifinfo
@example
--debug                               -t
--defines=@var{defines-file}          -d
--file-prefix=@var{prefix}                  -b @var{file-prefix}
--graph=@var{graph-file}              -d
--help                                -h
--name-prefix=@var{prefix}                  -p @var{name-prefix}
--no-lines                            -l
--no-parser                           -n
--output=@var{outfile}                      -o @var{outfile}
--token-table                         -k
--verbose                             -v
--version                             -V
--yacc                                -y
@end example
@end ifinfo

@node Yacc Library
@section Yacc库-Yacc Library

Yacc库包含@code{yyerror}和@code{main}函数的默认实现.
通常情况下,这些默认实现没有什么用处,但是@acronym{POSIX}要求它们.
要使用Yacc库,使用选项@option{-ly}链接你的程序.
注意到Bison实现的Yacc库在@acronym{GNU}通用许可证下发行. 
(@pxref{Copying}).

如果你使用Yacc库的@code{yyerror}函数,
你应该如下地声明@code{yyerror}:

@example
int yyerror (char const *);
@end example

Bison忽略@code{yyerror}返回的@code{int}值.
如果你使用Yacc库的@code{main}函数,
你的@code{yyparse}函数应该有如下原型:

@example
int yyparse (void);
@end example

@c ================================================= Invoking Bison

@node FAQ
@chapter 常见问题-Frequently Asked Questions
@cindex 常见问题(frequently asked questions)
@cindex 问题(questions)

许多关于Bison的问题会偶尔出现.
这里提到一些.

@menu
* 分析器栈溢出(Parser Stack Overflow):Parser Stack Overflow.      突破栈限制
* 我如何复位分析器(How Can I Reset the Parser):How Can I Reset the Parser. @code{yyparse}保持一些状态
* 销毁字符串(Strings are Destroyed):Strings are Destroyed.      @code{yylval}丢掉了字符串的追踪
* C++分析器(C++ Parsers):C++ Parsers.                使用C++编译器编译分析器
* 实现Gotos/Loops(Imlementing Gotos/Loops):Implementing Gotos/Loops.   在计算器中控制流
@end menu

@node Parser Stack Overflow
@section 分析器栈溢出-Parser Stack Overflow

@display
我的分析器返回带有@samp{parser stack overflow}的消息.
我能做些什么?
@end display

这个问题已经在其它地方讨论过了@xref{Recursion, ,Recursive Rules}.

@node How Can I Reset the Parser
@section 我如何复位分析器-How Can I Reset the Parser

下面的现象有许多征兆,导致了下面典型的问题:

@display
我调用了@code{yyparse}多次,
当输入正确时,它正确地工作;
但是当发现一个分析错误的时,所有其它的调用也失败了.
我如何才能重置@code{yyparse}的错误标志?
@end display

@noindent
或者:

@display
我的分析器包含了一个对@samp{#include}类似特性的支持.
当我从@code{yyparse}调用@code{yyparse}时,即使我指明我需要一个@code{%pure-parser},
它仍然会失败.
@end display

这些典型的问题并不产生于Bison自己而是产生于Lex生成的扫描器.
出于速度的目的,这些扫描器使用容量很大的缓冲区,
它们可能不会注意到输入文件的变化.
作为一个例子,考虑下面的源文件,

@file{first-line.l}:

@verbatim
%{
#include <stdio.h>
#include <stdlib.h>
%}
%%
.*\n    ECHO; return 1;
%%
int
yyparse (char const *file)
{
  yyin = fopen (file, "r");
  if (!yyin)
    exit (2);
  /* One token only.  */ /* 只有一个记号 */
  yylex ();
  if (fclose (yyin) != 0)
    exit (3);
  return 0;
}

int
main (void)
{
  yyparse ("input");
  yyparse ("input");
  return 0;
}
@end verbatim

@noindent
如果文件@file{input}包含

@verbatim
input:1: Hello,
input:2: World!
@end verbatim

@noindent
那么你并未两次取得第一行,而是:

@example
$ @kbd{flex -ofirst-line.c first-line.l}
$ @kbd{gcc  -ofirst-line   first-line.c -ll}
$ @kbd{./first-line}
input:1: Hello,
input:2: World!
@end example

因此,无论什么时候改变@code{yyin},
你必须告诉Lex声称的扫描器丢弃当前的缓冲转换到新的缓冲中.
这依赖于你的Lex的实现;可以参阅它的文档获取更多信息.
对于Flex,在每一个@code{yyin}的改变后调用@samp{YY_FLUSH_BUFFER}可以做到这一点.
如果你的Flex生成扫描器需要读取多个输入流来处理类似文件包含的特性,
你可以考虑使用FLex函数如@samp{yy_switch_to_buffer}来操纵多个输入缓冲.

如果你的FLex声称扫描器使用了开始条件(@pxref{Start
conditions, , Start conditions, flex, The Flex Manual}),
你还可能复位扫描器状态,例如,
使用一个@code{BEGIN (0)}调用,退回到开始条件.

@node Strings are Destroyed
@section 被销毁的字符串-Strings are Destroyed

@display
我的分析器好像销毁了旧字符串,或者它可能失去了对它们的追踪.
它报告@samp{"bar", "bar"}或者甚至@samp{"foo\nbar", "bar"}而不是
报告@samp{"foo", "bar"}.
@end display

这个错误可能是发送到Bison``错误报告''列表中最频繁的一个,
但它只是一个对扫描器角色产生的误解.
考虑如下的Lex代码:

@verbatim
%{
#include <stdio.h>
char *yylval = NULL;
%}
%%
.*    yylval = yytext; return 1;
\n    /* IGNORE */ /* 忽略 */
%%
int
main ()
{
  /* Similar to using $1, $2 in a Bison action.  */
  /* 类似在Bison动作中使用的$1,$2 */
  char *fst = (yylex (), yylval);
  char *snd = (yylex (), yylval);
  printf ("\"%s\", \"%s\"\n", fst, snd);
  return 0;
}
@end verbatim

如果你编译并且运行这段代码,你得到:

@example
$ @kbd{flex -osplit-lines.c split-lines.l}
$ @kbd{gcc  -osplit-lines   split-lines.c -ll}
$ @kbd{printf 'one\ntwo\n' | ./split-lines}
"one
two", "two"
@end example

@noindent
这是由于@code{yytext}是一个在动作中用于@emph{读取}的缓冲区,
但是如果你要保留它,你必须复制它(例如,使用@code{strdup}).
应注意到输出可能依赖于你的Lex实现怎么处理@code{yytext}.
例如当指定了Lex兼容性选项@option{-l}(它引发了选项@samp{%array}),
Flex产生了不同的行为:

@example
$ @kbd{flex -l -osplit-lines.c split-lines.l}
$ @kbd{gcc     -osplit-lines   split-lines.c -ll}
$ @kbd{printf 'one\ntwo\n' | ./split-lines}
"two", "two"
@end example


@node C++ Parsers
@section C++分析器-C++ Parsers

@display
我如何产生使用C++代码的分析器?
@end display

我们正致力于Bison的C++输出,
但是不幸的是,由于缺少时间,骨架尚未完成.
它的功能很强大,但是由于多方面的关系,
它@emph{可能}破坏向后兼容性的额外工作.
由于C++骨架尚未编入文档,
@c bad translation
我们并不认为我们自己必须超这个接口努力,
@c up
尽管如此,我们会尽最大努力保证兼容性.

另一种可能是使用正规C分析器并使用C++编译器进行编译.
倘若你能忍受一些简单C++规则,例如不能在联合体中加入``真正的类''(例如,
带有构造函数的结构体),这个就可以正常工作.
因此,在@code{%union}中应该使用指向类的指针.

@node Implementing Gotos/Loops
@section 实现跳转/循环-Implementing Gotos/Loops

@display
我的简单计算器支持变量,赋值和函数,
但是我如何才能实现跳转或循环?
@end display

虽然这个文档中包含的例子很有教学性,
但它模糊了分析器(它的工作是恢复文字的结构并将它转化为程序模块)
和处理这些结构的过程(如执行)之间的区别.
这在被称为直接线性程序中工作良好.
例如直接执行模式:一个接一个的执行简单指令.

@cindex 抽象语法树(abstract syntax tree)
@cindex @acronym{AST}
如果你需要的更丰富的模式,
你可能需要分析器生一种表示它(注:分析器)已经恢复的结构的树;
这种树被通常成为@dfn{抽象语法树(abstract syntax tree)}或者简写为@dfn{@acronym{AST}}.
之后,用多种方法遍历这棵树会激活对它的执行或翻译,
这最终会导致产生一个解释起或者编译器.

这个主题超出了这个手册的讨论范围,
读者可以参阅这方面的专门文献.


@c ================================================= Table of Symbols

@node Table of Symbols
@appendix Bison符号-Bison Symbols
@cindex Bison符号(Bison symbols), 表格(table of)
@cindex Bison中的符号(symbols in Bison), 表格(table of)

@deffn {变量} @@$
在动作中,规则左手端的位置
@xref{Locations, ,位置概述-Locations Overview}.
@end deffn

@deffn {变量} @@@var{n}
在动作中,规则右端第@var{n}个符号的位置.
@xref{Locations, ,位置概述-Locations Overview}.
@end deffn

@deffn {变量} $$
在动作中,规则左端的语义值.
@xref{Actions, ,动作-Actions}.
@end deffn

@deffn {变量} $@var{n}
在动作中,规则右端第@var{n}个符号的语义值.
@xref{Actions, ,动作-Actions}.
@end deffn

@deffn {分隔符} %%
用于分隔语法规则部分和Bison声明部分或者@var{epilogue}部分.
@xref{Grammar Layout, ,Bison语法文件的布局-The Overall Layout of a Bison Grammar}.
@end deffn

@c Don't insert spaces, or check the DVI output.
@deffn {分隔符} %@{@var{code}%@}
在@samp{%@{}和@samp{%@}}之间的代码不做任何解释被直接复制到输出文件.
这些代码组成了输入文件的@var{Prologue}部分.  
@xref{Grammar Outline, ,Bison语法的提纲-Outline of a Bison Grammar}.
@end deffn

@deffn {结构} /*@dots{}*/
注释分隔符,类似C.
@end deffn

@deffn {分隔符} :
分隔动作的结果和它的部件.
@xref{Rules, ,描述语法规则的语法-Syntax of Grammar Rules}.
@end deffn

@deffn {分隔符} ;
结束一个规则.  @xref{Rules, ,描述语法规则的语法-Syntax of Grammar Rules}.
@end deffn

@deffn {分隔符} |
分隔同一个非终结符结果的不同规则.
@xref{Rules, ,描述语法规则的语法-Syntax of Grammar Rules}.
@end deffn

@deffn {符号} $accept
预定义非终结符,
它的唯一规则为@samp{$accept: @var{start}
$end},
这里的@var{start}是开始符号.
@xref{Start Decl, ,开始符号- The Start-Symbol}.
它不能在语法中使用.
@end deffn

@deffn {指令} %debug
激活分析器调试.  
@xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@ifset defaultprec
@deffn {指令} %default-prec
给缺少显式@samp{%prec}修饰符的规则赋予一个优先级.
 @xref{Contextual Precedence, ,上下文依赖优先级-Context-Dependent Precedence}.
@end deffn
@end ifset

@deffn {指令} %defines
为扫描器创建一个头文件的Bison声明.
@xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@deffn {指令} %destructor
指明分析器如何回收被丢弃符号相关的内存.
@xref{Destructor Decl, ,释放丢弃的符号- Freeing Discarded Symbols}.
@end deffn

@deffn {指令} %dprec
在分析的时候赋予规则一个优先级来解决归约/归约冲突的Bison声明.
@xref{GLR Parsers, ,编写@acronym{GLR}分析器-Writing @acronym{GLR} Parsers}.
@end deffn

@deffn {符号} $end
用来标记流结束的预定义记号,不能在语法中使用.
@end deffn

@deffn {符号} error
一个保留的用于错误恢复的记号名称.
这个记号可以用在语法规则中用来允许Bison分析器在不终止处理的前提下
识别一个错误.
实际上,一个包含错误的句子可被认为是有效的.
遇到一个语法错误时,
记号@code{error}成为了当前的超前扫描记号.
与@code{error}相应的动作被执行,并且超前扫描记号被重置为
最初引起错误的记号.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {指令} %error-verbose
请求冗长模式的Bison声明,
指明了当调用@code{yyerror}时的错误消息字符串.
@end deffn

@deffn {指令} %file-prefix="@var{prefix}"
设置输出文件前缀的Bison声明.@xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@deffn {指令} %glr-parser
声称@acronym{GLR}分析器的Bison声明.
@xref{GLR Parsers, ,编写@acronym{GLR}分析器-Writing @acronym{GLR} Parsers}.
@end deffn

@deffn {指令} %initial-action
在分析器前运行代码.@xref{Initial Action Decl, ,在分析前执行动作- Performing Actions before Parsing}.
@end deffn

@deffn {指令} %left
为操作符指定左结合性的Bison声明.
@xref{Precedence Decl, ,操作符优先级-Operator Precedence}.
@end deffn

@deffn {指令} %lex-param @{@var{argument-declaration}@}
指明@code{yylex}的额外参数的Bison声明.
@xref{Pure Calling, ,纯分析器的调用惯例-Calling Conventions for Pure Parsers}.
@end deffn

@deffn {指令} %merge
赋予规则一个合并函数的Bison声明.
@c bad translation
如果有一个归约/归约冲突的规则带有相同的合并函数,
那么这个函数被应用于两个语义值来获得单一的结果.
@c up
@xref{GLR Parsers, ,Writing @acronym{GLR} Parsers-编写@acronym{GLR}分析器}.
@end deffn

@deffn {指令} %name-prefix="@var{prefix}"
重命名外部符号的Bison声明.@xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@ifset defaultprec
@deffn {指令} %no-default-prec
不指定缺少显式@samp{%prec}修饰符声规则的优先级.
@xref{Contextual Precedence, ,上下文依赖优先级-Context-Dependent Precedence}.
@end deffn
@end ifset

@deffn {指令} %no-lines
在分析器文件中避免产生@code{#line}指令的Bison声明. @xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@deffn {指令} %nonassoc
声明一个无结合性记号.
@xref{Precedence Decl, ,操作符优先级-Operator Precedence}.
@end deffn

@deffn {指令} %output="@var{filename}"
设置分析器文件的Bison声明  @xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@deffn {指令} %parse-param @{@var{argument-declaration}@}
指定@code{yyparse}接受的额外参数的Bison声明.
@xref{Parser Function, ,分析器函数@code{yyparse}- The Parser Function @code{yyparse}}.
@end deffn

@deffn {指令} %prec
给特定的规则指定优先级的Bison声明.
@xref{Contextual Precedence, ,上下文依赖优先级-Context-Dependent Precedence}.
@end deffn

@deffn {指令} %pure-parser
请求一个纯(可重入)分析器的Bison声明.
@xref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}.
@end deffn

@deffn {指令} %right
指定记号右结合性的Bison声明.
@xref{Precedence Decl, ,操作符优先级-Operator Precedence}.
@end deffn

@deffn {指令} %start
指定开始符号的Bison声明@xref{Start Decl, ,开始符号-The Start-Symbol}.
@end deffn

@deffn {指令} %token
声明记号但不指定优先级.
@xref{Token Decl, ,记号类型名称-Token Type Names}.
@end deffn

@deffn {指令} %token-table
在分析器文件中加入符号名称表的Bison声明.
@xref{Decl Summary, ,声明总结-Decl Summary}.
@end deffn

@deffn {指令} %type
声明非终结符的Bison声明.
@xref{Type Decl, ,非终结符-Nonterminal Symbols}.
@end deffn

@deffn {符号} $undefined
所有@code{yylex}返回的未定义值被映射到这个预定义符号.
它不能在语法中使用,[untranslated]rather,use @code{error}.
@end deffn

@deffn {指令} %union
指定多种可能语义值数据类型的Bison声明.
@xref{Union Decl, ,值类型集-The Collection of Value Types}.
@end deffn

@deffn {宏} YYABORT
通过使@code{yyparse}立即返回1,来伪装发生一个未恢复的语法错误的宏.
并不调用错误报告函数@code{yyerrpr}.
@xref{Parser Function, ,分析器函数@code{ppyarse}-The Parser Function @code{yyparse}}.
@end deffn

@deffn {宏} YYACCEPT
通过使@code{yyparse}立即返回0,来伪装语言的一个完整的表达已经被读取的宏.
@xref{Parser Function, ,分析器函数@code{ppyarse}-The Parser Function @code{yyparse}}.
@end deffn

@deffn {宏} YYBACKUP
从分析器栈中丢弃一个值并伪造一个超前扫描记号的宏.
@xref{Action Features, ,在动作中使用的特殊特征-Special Features for Use in Actions}.
@end deffn

@deffn {变量} yychar
包含当前超前扫描记号的正数值的外部整数变量.
(在一个纯分析器中,它是一个在@code{yyparse}中的局部变量).
错误恢复规则可能要检查这个变量.
@xref{Action Features, ,在动作中使用的特殊特征-Special Features for Use in Actions}.
@end deffn

@deffn {变量} yyclearin
在错误恢复规则中使用的宏.它清除先前的超前扫描记号.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {宏} YYDEBUG
使分析器带有追踪代码的宏.
@xref{Tracing, ,跟踪你的分析器-Tracing Your Parser}.
@end deffn

@deffn {变量} yydebug
默认被置0的外部整数变量.
如果@code{yydebug}被赋予一个非零值,
分析器会输入关于输入符号和分析器动作的信息.
@xref{Tracing, ,跟踪你的分析器-Tracing Your Parser}.
@end deffn

@deffn {宏} yyerrok
使分析器在一个语法错误之后立即恢复到正常模式的宏.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {宏} YYERROR
一个假装刚刚发现一个语法错误的宏:
调用@code{yyerror}然后执行通常的错误恢复如果可能的话(@pxref{Error Recovery, ,错误恢复-Error Recovery})
或者(如果恢复是不可能的)使@code{yyparse}返回1.
@xref{Error Recovery, ,错误恢复-Error Recovery}.
@end deffn

@deffn {函数} yyerror
用户提供的当发现错误时被@code{yyparse}调用的函数.
@xref{Error Reporting, ,错误报告函数@code{yyerror}-The Error Reporting Function @code{yyerror}}.
@end deffn

@deffn {宏} YYERROR_VERBOSE
一个在@var{Prologue}部分用@code{#define}定义的陈旧的宏,
当调用@code{yyerror}时,它请求详细的错误消息字符串.
你把@code{YYERROR_VERBOSE}定义成什么东西并没有影响,
有影响的只是你是否定义了它.
使用@code{%error-verbose}是更好的选择.
@end deffn

@deffn {宏} YYINITDEPTH
指定分析器栈初始大小的宏.
@xref{Stack Overflow, ,栈溢出-Stack Overflow}.
@end deffn

@deffn {函数} yylex
用户提供的词法分析器函数,
不带有任何参数来获得下一个记号.
@xref{Lexical, ,词法分析器函数@code{yylex}-The Lexical Analyzer Function @code{yylex}}.
@end deffn

@deffn {宏} YYLEX_PARAM
一个用于指明@code{yyparse}传递到@code{yylex}的额外参数(或者额外参数列表)的陈旧的宏.
不赞成继续使用这个宏,它只被Yacc类似分析器支持.
@xref{Pure Calling, ,纯分析器的调用惯例-Calling Conventions for Pure Parsers}.
@end deffn

@deffn {变量} yylloc
@code{yylex}应该将一个记号的行列号放入这个外部变量.  
(在纯分析器中,它是一个@code{yyparse}的局部变量,
它的地址被传递到@code{yylex}).
如果你在语法动作中不使用@samp{@@}特性,你可以忽略这个变量.
@xref{Token Locations, ,记号的文字位置-Textual Locations of Tokens}.
@end deffn

@deffn {Type} YYLTYPE
@code{yyloc}的数据类型;默认地为一个带有四个成员的结构体.
@xref{Location Type, ,位置的数据类型-Data Types of Locations}.
@end deffn

@deffn {变量} yylval
@code{yylex}应该将记号相关的语义值放入这个变量.
(在纯分析器中,它是一个@code{yyparse}中的局部变量,并且它的地址被传递到@code{yylex}.)
@xref{Token Values, ,记号的语义值-Semantic Values of Tokens}.
@end deffn

@deffn {宏} YYMAXDEPTH
指明分析器栈最大容的宏.
@xref{Stack Overflow, ,栈溢出-Stack Overflow}.
@end deffn

@deffn {变量} yynerrs
一个全局变量,每次出现语法错误时自增1.
(在纯分析器中,它是一个@code{yyparse}中的局部变量.)
@xref{Error Reporting, ,错误报告函数@code{yyerror}-The Error Reporting Function @code{yyerror}}.
@end deffn

@deffn {函数} yyparse
Bison产生的分析器函数;调用这个函数开始分析.
@xref{Parser Function, ,分析器函数@code{yyparse}-The Parser Function @code{yyparse}}.
@end deffn

@deffn {宏} YYPARSE_PARAM
指明@code{yyparse}应该接受的参数名成的陈旧宏.
不赞成继续使用这个宏,它只被Yacc类似分析器支持.
@xref{Pure Calling, ,纯分析器的调用惯例-Calling Conventions for Pure Parsers}.
@end deffn

@deffn {宏} YYRECOVERING
一个宏,它的值指明了分析器是否正在从错误中恢复.
@xref{Action Features, ,动作中使用的特殊特征-Special Features for Use in Actions}.
@end deffn

@deffn {宏} YYSTACK_USE_ALLOCA
用于控制当C语言@acronym{LALR}(1)分析器需要扩展它的栈时,@code{alloca}的使用.
如果定义为0,分析器会使用@code{malloc}来扩展它的栈.
如果定义为1,分析器则会使用@code{alloca}.
除了0和1以外的值保留用于Bison以后的扩展.
如果没有被定义,
@code{YYSTACK_USA_ALLOCA}默认为0.

如果你定义@code{YYSTACK_USE_ALLOCA}为1,
你有责任使@code{alloca}是可见的,
例如,使用@acronym{GCC},或者包含@code{<stdlib.h>}.
此外,在更普通的情况下,
如果你的代码可能运行在一个有限栈容量和不可信任栈溢出检查的主机上的时候,
你应将@code{YYMAXDEPTH}设为当调用@code{alloca}时,
在一个在任何目标主机不会产生栈溢出的值.
你可以检查Bison生成的代码来决定适当的数值.
这需要在底层详细实现的专业技术.

@end deffn

@deffn {类型} YYSTYPE
语义值的数据类型;默认为@code{int}.
@xref{Value Type, ,语义值的数据类型-Data Types of Semantic Values}.
@end deffn

@node Glossary
@appendix 词汇表-Glossary
@cindex 词汇表(glossary)

@table @asis
@item Backus-Naur Form (@acronym{BNF}; also called ``Backus Normal Form'')
@item Backus-Naur 范式 (@acronym{BNF}; 也被称为 ``Backus正规范式'')
由John Backus倡导的,最初用于描述上下文无关文法的正式方法.
在Peter Naur他的称为Algol60报告的1960-01-02委员会文档中得到少许改进.
@xref{Language and Grammar, ,语言与上下文无关文法-Languages and Context-Free Grammars}.

@item Context-free grammars
@item 上下文无关文法
描述可以使用而不考虑上下文的规则的文法.
因此,如果有一个规则说一个整数可做为一个表达式使用,
那么,整数@emph{在任何地方}都是一个允许的表达式.
@xref{Language and Grammar, ,语言与上下文无关文法-Languages and Context-Free Grammars}.

@item Dynamic allocation
@item 动态分配
在执行期间分配内存,而不是在编译期间或者进入一个函数时.

@item Empty string
@item 空字符串
模拟集合论中的空集,
空字符串是长度为0的字符串.

@item Finite-state stack machine
@item 有限状态栈机
一种含有多种离散状态的机器,每个时刻只有一种状态.
当处理输入的时候,机器按照机器逻辑的指定从一个状态转换到另一个状态.
对于分析器来说,输入就是要分析的语言,
状态对应于语法规则中的各个阶段.
@xref{Algorithm, ,Bison分析器算法-The Bison Parser Algorithm}.

@item Generalized @acronym{LR} (@acronym{GLR})
@item 通用@acronym{LR} (@acronym{GLR})
一种可以处理包括那些不是@acronym{LALR}(1)的上下文无关文法的分析算法.
它用来解决Bison通常的@acronym{LALR}(1)算法不能解决的冲突.
它高效地分裂成多个分析器,尝试所有可能的分析,丢弃那些在额外上下文提示下失败的分析器.
@xref{Generalized LR Parsing, ,通用@acronym{LR}分析-Generalized @acronym{LR} Parsing}.

@item Grouping
@item 分组
一个(通常)在语法上可再分的语言结构;
例如C中的`expression'或者'declaration'.
@xref{Language and Grammar, ,语言和上下文无关文法-Languages and Context-Free Grammars}.

@item Infix operator
@item 中缀操作符
放置在操作数中间指定某些操作的算术操作符.


@item Input stream
@item 输入流
在设备或程序间的连续数据流.

@item Language construct
@item 语言结构
一种语言的典型应用模式.
例如,一种C语言的结构是@code{if}语句.
@xref{Language and Grammar, ,语言和上下文无关文法-Languages and Context-Free Grammars}.

@item Left associativity
@item 左结合性
拥有左结合性的操作符被从左至右地分析:
@samp{a+b+C}首先计算@samp{a+b}然后和@samp{c}一起计算.
@xref{Precedence, ,操作符优先级-Operator Precedence}.

@item Left recursion
@item 左递归
一个结果符号同样是第一个部件符号的规则;
例如:
@samp{expseq1 : expseq1 ',' exp;}.  
@xref{Recursion, ,递归规则-Recursive Rules}.

@item Left-to-right parsing
@item 自左至右分析
同过自左至右分析一个个地分析记号来分析一个句子.
@xref{Algorithm, ,Bison 分析器算法-The Bison Parser Algorithm}.

@item Lexical analyzer (scanner)
@item 词法分析器 (扫描器)
一个读取输入流并逐个返回记号的函数.
@xref{Lexical, ,词法分析器函数@code{yylex}-The Lexical Analyzer Function @code{yylex}}.


@item Lexical tie-in
@item 词法关联
一个由语法规则动作设置的标志用来改变分析记号的方法.
@xref{Lexical Tie-ins, ,词法关联-Lexical tie-in}.

@item Literal string token
@item 字符串文字记号
一个由两个或者更多字符组成的记号.@xref{Symbols, ,符号-Symbols}.

@item Look-ahead token
@item 超前扫描记号
一个已经读取但未移进的记号.
@xref{Look-Ahead, ,超前扫描记号-Look-Ahead Tokens}.

@item @acronym{LALR}(1)
一种Bison(像大多数其它分析器一样)可以处理的上下文无关文法.
@acronym{LR}(1)的子集.
@xref{Mystery Conflicts, ,令人迷惑的归约/归约冲突-Mysterious Reduce/Reduce Conflicts}.

@item @acronym{LR}(1)
一种上下文无关文法,
它在大多数时侯需要一个超前扫描记号来消除任何输入片段的歧义.

@item Nonterminal symbol
@item 非终结符
一个代表可以通过规则表达为更小结构的语法结构;
换句话说,一个不是记号的结构.
@xref{Symbols, ,符号-Symbols}.

@item Parser
@item 分析器
一个靠分析从词法分析器传递过来的记号的语法结构来识别有效句子的函数.

@item Postfix operator
@item 后缀操作符
放置在操作数后执行某些操作的算术操作符.

@item Reduction
@item 归约
依照一个语法规则将非终结符和/或终结符的序列替换为非终结符.
@xref{Algorithm, ,Bison分析器算法-The Bison Parser Algorithm}.

@item Reentrant
@item 可重入
一个可重入的子程序是一个可以被任意次地并行调用并且调用间相互不干扰的子程序.
@xref{Pure Decl, ,一个纯(可重入)分析器-A Pure (Reentrant) Parser}.

@item Reverse polish notation
@item 逆波兰记号
一种所有操作符都是后缀操作符的语言.

@item Right recursion
@item 右递归
一个结果记号也是它最后部件记号的规则;
例如 @samp{expseq1: exp ',' expseq1;}.
@xref{Recursion, ,递归规则-Recursive rules}.

@item Semantics-语义
在计算机语言中,语义由每个语言实例的动作指明,
例如,每个语句的意义.
@xref{Semantics, ,定义语言的语义-Defining Language Semantics}.

@item Shift-移进
我们说一个分析器移进当它决定进从流中进一步分析输入而不是
立即归约一些已经识别的规则.
@xref{Algorithm, ,Bison分析器算法-The Bison Parser Algorithm}.

@item Single-character literal
@item 单字符文字
一个被识别和解释为它自己的单一字符.
@xref{Grammar in Bison, ,从正规文法转换到Bison输入-From Formal Rules to Bison Input}.

@item Start symbol
@item 开始符号
代表要分析语言的一个完整有效的表述的非终结符.
在语言描述中,开始符号通常被列为第一个非终结符.
@xref{Start Decl, ,开始符号-The Start-Symbol}.

@item Symbol table
@item 符号表
用来识别和使用已经存在的符号信息的数据结构.
在进行分析器符号表存储符号名称和相关数据.
@xref{Multi-function Calc, ,多功能计算器-Multi-function Calc}.

@item Syntax error
@item 语法错误
一个发生在分析无效语法的输入流时的错误.  
@xref{Error Recovery, ,错误恢复-Error Recovery}.

@item Token
@item 记号
一个基本的不可再分的语言单元.
在语法中,描述一个记号的符号是终结符.
Bison分析器的输入是来自词法分析器的记号流.
@xref{Symbols, ,Symbols-符号}.

@item Terminal symbol
@item 终结符
在语法中不包含含规则因此语法上不可再分的语法符号.
它表示的输入片段是一个记号.
@xref{Language and Grammar, ,语言与上下文无关文法-Languages and Context-Free Grammars}.
@end table

@node Copying This Manual
@appendix 复制这个手册-Copying This Manual

@menu
* GNU Free Documentation License::  复制这个手册的许可.
@end menu

@include fdl.texi

@node Index
@unnumbered 索引-Index

@printindex cp

@bye

@c LocalWords: texinfo setfilename settitle setchapternewpage finalout
@c LocalWords: ifinfo smallbook shorttitlepage titlepage GPL FIXME iftex
@c LocalWords: akim fn cp syncodeindex vr tp synindex dircategory direntry
@c LocalWords: ifset vskip pt filll insertcopying sp ISBN Etienne Suvasa
@c LocalWords: ifnottex yyparse detailmenu GLR RPN Calc var Decls Rpcalc
@c LocalWords: rpcalc Lexer Gen Comp Expr ltcalc mfcalc Decl Symtab yylex
@c LocalWords: yyerror pxref LR yylval cindex dfn LALR samp gpl BNF xref
@c LocalWords: const int paren ifnotinfo AC noindent emph expr stmt findex
@c LocalWords: glr YYSTYPE TYPENAME prog dprec printf decl init stmtMerge
@c LocalWords: pre STDC GNUC endif yy YY alloca lf stddef stdlib YYDEBUG
@c LocalWords: NUM exp subsubsection kbd Ctrl ctype EOF getchar isdigit
@c LocalWords: ungetc stdin scanf sc calc ulator ls lm cc NEG prec yyerrok
@c LocalWords: longjmp fprintf stderr preg yylloc YYLTYPE cos ln
@c LocalWords: smallexample symrec val tptr FNCT fnctptr func struct sym
@c LocalWords: fnct putsym getsym fname arith fncts atan ptr malloc sizeof
@c LocalWords: strlen strcpy fctn strcmp isalpha symbuf realloc isalnum
@c LocalWords: ptypes itype YYPRINT trigraphs yytname expseq vindex dtype
@c LocalWords: Rhs YYRHSLOC LE nonassoc op deffn typeless typefull yynerrs
@c LocalWords: yychar yydebug msg YYNTOKENS YYNNTS YYNRULES YYNSTATES
@c LocalWords: cparse clex deftypefun NE defmac YYACCEPT YYABORT param
@c LocalWords: strncmp intval tindex lvalp locp llocp typealt YYBACKUP
@c LocalWords: YYEMPTY YYRECOVERING yyclearin GE def UMINUS maybeword
@c LocalWords: Johnstone Shamsa Sadaf Hussain Tomita TR uref YYMAXDEPTH
@c LocalWords: YYINITDEPTH stmnts ref stmnt initdcl maybeasm VCG notype
@c LocalWords: hexflag STR exdent itemset asis DYYDEBUG YYFPRINTF args
@c LocalWords: YYPRINTF infile ypp yxx outfile itemx vcg tex leaderfill
@c LocalWords: hbox hss hfill tt ly yyin fopen fclose ofirst gcc ll
@c LocalWords: yyrestart nbar yytext fst snd osplit ntwo strdup AST
@c LocalWords: YYSTACK DVI fdl printindex
