4. 词汇语法(Lexical syntax)和数据语法(datum syntax)
Scheme的语法被组织进三个层次:
- 词汇语法描述一个程序文本怎样被分成语义(lexemes)的序列。
- 数据语法,按词汇语法制定,将句法(syntactic)数据的序列组织为语义的序列,其中一个句法数据递归地组织一个实体。
- 程序语法按数据语法制定,实施进一步的句法数据含义的组织和分配。(本句已根据勘误表修改。)
句法数据(也叫做外部表示(external representations))兼作为对象的一个符号,且Scheme的(rnrs io ports (6))库(见库的8.2小节)提供了get-datum和put-datum过程用作句法数据的读写,在它们的文本表示和对应的对象之间进行转换。每一个句法数据表示一个对应的数据值。一个句法数据可以使用quote在一个程序中获得对应的数据值(见第11.4.1节)。
Scheme源程序包含句法数据和(不重要的)注释。Scheme源程序中的句法数据叫做形式。(嵌套在另一个形式中的形式叫做子形式。)因此,Scheme的语法有如下性质,字符的任何序列是一个形式也是一个代表一些对象的句法数据。这可能导致混淆,因为在脱离上下文的情况下,一个给定的字符序列是用于对象的表示还是一个程序的文本可能不是很明显。它可能也是使Scheme强大的原因,因为它使得编写解释器或编译器这类把程序当作对象(或相反)的程序变得简单。
一个数据值可能有多个不同的外部表示。比如“#e28.000”和“#x1c”都是表示精确整数对象28的句法数据,句法数据“(8 13)”, “( 08 13 )”和“(8 . (13 . ()))”都表示包含精确整数对象8和13的表。表示相同对象(从equal?的意义来说;见11.5小节)的句法数据作为程序的形式来说总是等价的。
因为句法数据和数据值之间密切的对应关系,所以当根据上下文精确含义显而易见时,本报告有时使用术语数据(datum)来表示句法数据或数据值。
一个实现不允许以任何方式扩展词汇或数据语法,仅有一个例外:对任何不是r6rs内建的<identifier>(见3.8.4小节),实现不需要把#!<identifier>当作一个语法错误,且实现可以使用特定的#!前缀的标识符作为指示接下来的输入包含标准词汇和数据语法的扩展的标记。语法#!r6rs可被用作预示接下来的输入是用本报告中描述的词汇语法和数据语法写成的。或者,#!r6rs被当作注释对待,见4.2.3小节。
4.1. 符号(Notation)
Scheme的形式语法(formal syntax)被用扩展的BNF写成。非终结符(Non-terminals)使用尖角括号进行书写。非终结符名字的大小写是无关紧要的。
语法中的空格都是为了便于阅读而存在的。<Empty>代表空字符串。
对BNF的以下扩展可以使描述更加简洁:<thing>*代表零个或更多的<thing>;<thing>+代表至少一个<thing>。
一些非终结符的名字表示相同名字的Unicode标量值:<character tabulation> (U+0009), <linefeed> (U+000A), <line tabulation> (U+000B), <form feed> (U+000C), <carriage return> (U+000D), <space> (U+0020), <next line> (U+0085), <line separator> (U+2028)和<paragraph separator> (U+2029)。(本句已根据勘误表修改。)
4.2. 词汇语法
词汇语法决定了怎样将字符的序列分隔成语义(lexemes)的序列,省略不重要的部分如注释和空白。字符序列被假定是Unicode标准16的文本。词汇语法的一些语义,比如标识符,数字对象的表示,字符串等等,是数据语法中的句法数据,且因此代表对象。除了语法的形式解释(formal account),本节还描述了这些句法数据表示什么数据值。
注释中描述的词汇语法包含<datum>的一个向前引用,这是数据语法的一部分。然而,作为注释,这些<datum>在语法中并不起什么重要的作用。
除了布尔,数字对象以及用16进制表示的Unicode标量是不区分大小写的,其它情况下大小写是敏感的。比如,#x1A和#X1a是一样的。然而,标识符Foo和标识符FOO是有区别的。
4.2.1. 形式解释
<Interlexeme space>可以出现在任意词位(lexeme)的两侧,但不允许出现在一个词位的中间。
<Identifier>, ., <number>, <character>和<boolean>必须被一个<delimiter>或输入的结尾终结。
下面的两个字符保留用作未来的语言扩展:{}
(以下规则已根据勘误表修改。)
<lexeme> → <identifier> ∣ <boolean> ∣ <number>
∣ <character> ∣ <string>
∣ ( ∣ ) ∣ [ ∣ ] ∣ #( ∣ #vu8( | ' ∣ ` ∣ , ∣ ,@ ∣ .
∣ #' ∣ #` ∣ #, ∣ #,@
<delimiter> → ( ∣ ) ∣ [ ∣ ] ∣ " ∣ ; ∣ #
∣ <whitespace>
<whitespace> → <character tabulation>
∣ <linefeed> ∣ <line tabulation> ∣ <form feed>
∣ <carriage return> ∣ <next line>
∣ <any character whose category is Zs, Zl, or Zp>
<line ending> → <linefeed> ∣ <carriage return>
∣ <carriage return> <linefeed> ∣ <next line>
∣ <carriage return> <next line> ∣ <line separator>
<comment> → ; <all subsequent characters up to a
<line ending> or <paragraph separator>>
∣ <nested comment>
∣ #; <interlexeme space> <datum>
∣ #!r6rs
<nested comment> → #| <comment text>
<comment cont>* |#
<comment text> → <character sequence not containing
#| or |#>
<comment cont> → <nested comment> <comment text>
<atmosphere> → <whitespace> ∣ <comment>
<interlexeme space> → <atmosphere>*
<identifier> → <initial> <subsequent>*
∣ <peculiar identifier>
<initial> → <constituent> ∣ <special initial>
∣ <inline hex escape>
<letter> → a ∣ b ∣ c ∣ ... ∣ z
∣ A ∣ B ∣ C ∣ ... ∣ Z
<constituent> → <letter>
∣ <any character whose Unicode scalar value is greater than
127, and whose category is Lu, Ll, Lt, Lm, Lo, Mn,
Nl, No, Pd, Pc, Po, Sc, Sm, Sk, So, or Co>
<special initial> → ! ∣ $ ∣ % ∣ & ∣ * ∣ / ∣ : ∣ < ∣ =
∣ > ∣ ? ∣ ^ ∣ _ ∣ ~
<subsequent> → <initial> ∣ <digit>
∣ <any character whose category is Nd, Mc, or Me>
∣ <special subsequent>
<digit> → 0 ∣ 1 ∣ 2 ∣ 3 ∣ 4 ∣ 5 ∣ 6 ∣ 7 ∣ 8 ∣ 9
<hex digit> → <digit>
∣ a ∣ A ∣ b ∣ B ∣ c ∣ C ∣ d ∣ D ∣ e ∣ E ∣ f ∣ F
<special subsequent> → + ∣ - ∣ . ∣ @
<inline hex escape> → \x<hex scalar value>;
<hex scalar value> → <hex digit>+
<peculiar identifier> → + ∣ - ∣ ... ∣ -> <subsequent>*
<boolean> → #t ∣ #T ∣ #f ∣ #F
<character> → #\<any character>
∣ #\<character name>
∣ #\x<hex scalar value>
<character name> → nul ∣ alarm ∣ backspace ∣ tab
∣ linefeed ∣ newline ∣ vtab ∣ page ∣ return
∣ esc ∣ space ∣ delete
<string> → " <string element>* "
<string element> → <any character other than " or \>
∣ \a ∣ \b ∣ \t ∣ \n ∣ \v ∣ \f ∣ \r
∣ \" ∣ \\
∣ \<intraline whitespace>*<line ending>
<intraline whitespace>*
∣ <inline hex escape>
<intraline whitespace> → <character tabulation>
∣ <any character whose category is Zs>
<hex scalar value>表示0到#x10FFFF的Unicode标量值,这个范围要排除[#xD800, #xDFFF]。
让R = 2, 8, 10 和 16,然后重复下面的规则<num R>, <complex R>, <real R>, <ureal R>, <uinteger R>和<prefix R>。没有<decimal 2>, <decimal 8>和<decimal 16>的规则,这意味着包含小数点和指数的数字表示必须使用十进制基数。
下面的规则中,大小写是不重要的。(本句根据勘误表添加。)
(以下规则已根据勘误表修改。)
<number> → <num 2> ∣ <num 8>
∣ <num 10> ∣ <num 16>
<num R> → <prefix R> <complex R>
<complex R> → <real R> ∣ <real R> @ <real R>
∣ <real R> + <ureal R> i ∣ <real R> - <ureal R> i
∣ <real R> + <naninf> i ∣ <real R> - <naninf> i
∣ <real R> + i ∣ <real R> - i
∣ + <ureal R> i ∣ - <ureal R> i
∣ + <naninf> i ∣ - <naninf> i
∣ + i ∣ - i
<real R> → <sign> <ureal R>
∣ + <naninf> ∣ - <naninf>
<naninf> → nan.0 ∣ inf.0
<ureal R> → <uinteger R>
∣ <uinteger R> / <uinteger R>
∣ <decimal R> <mantissa width>
<decimal 10> → <uinteger 10> <suffix>
∣ . <digit 10>+ <suffix>
∣ <digit 10>+ . <digit 10>* <suffix>
<uinteger R> → <digit R>+
<prefix R> → <radix R> <exactness>
∣ <exactness> <radix R>
<suffix> → <empty>
∣ <exponent marker> <sign> <digit 10>+
<exponent marker> → e ∣ E ∣ s ∣ S ∣ f ∣ F
∣ d ∣ D ∣ l ∣ L
<mantissa width> → <empty>
∣ | <digit 10>+
<sign> → <empty> ∣ + ∣ -
<exactness> → <empty>
∣ #i∣ #I ∣ #e∣ #E
<radix 2> → #b∣ #B
<radix 8> → #o∣ #O
<radix 10> → <empty> ∣ #d ∣ #D
<radix 16> → #x∣ #X
<digit 2> → 0 ∣ 1
<digit 8> → 0 ∣ 1 ∣ 2 ∣ 3 ∣ 4 ∣ 5 ∣ 6 ∣ 7
<digit 10> → <digit>
<digit 16> → <hex digit>
4.2.2. 换行符
在Scheme单行注释(见4.2.3小节)和字符串字面量中,换行符是重要的。在Scheme源代码中,在<line ending>中的任意换行符都指示一个行的结束。此外,两字符的换行符<carriage return> <linefeed>和<carriage return> <next line>都仅表示一个单独的换行。
在一个字符串字面量中,一个之前没有\的<line ending>表示一个换行字符(linefeed character),这个字符是Scheme中的标准换行符。
4.2.3. 空白和注释
空白字符是空格,换行,回车,字符制表符,换页符,行制表符和其它种类是Zs,Zl或Zp的任意其它字符。空白字符用于提高可读性和必要地相互分隔词位。空白可以出现在两个词位中间,但不允许出现在一个词位的中间。空白字符也可以出现在一个字符串中,但此时空白是有意义的。
词汇语法包括一些注释形式。在所有的情况下,注释对Scheme是不可见的,除非它们作为分隔符,所以,比如,一个注释不能出现在标识符或一个数字对象的表示的中间。
一个分号(;)指示一个行注释的开始。注释一直延续到分号出现的那一行的结尾。
另一个指示注释的方法是在<datum>(参见4.3.1节)前加一个前缀#;,可选的在<datum>前加上<interlexeme space>。注释包括注释前缀#;和<datum>。这个符号用作“注释掉”代码段。
块注释可用正确嵌套的#|和|#指示。
#|
FACT过程计算一个非负数的阶乘。
|#
(define fact
(lambda (n)
;; 基础条件
(if (= n 0)
#;(= n 1)
1 ; *的单位元(identity)
(* n (fact (- n 1))))))
词位#!r6rs,表示接下来的输入是用本报告中描述的词汇语法和数据语法写成的,另外,它也被当作注释对待。
4.2.4. 标识符
其他程序设计语言认可的大多数标识符也能被Scheme接受。通常,第一个字符不是任何数值的字母、数字和“扩展字符”序列就是一个标识符。此外,+、-和...都是标识符,以两个字符序列->开始的字母、数字和“扩展字符”序列也是。这里有一些标识符的例子:
lambda q soup
list->vector + V17a
<= a34kTMNs ->-
the-word-recursion-has-many-meanings
扩展字符可以像字母那样用于标识符内。以下是扩展字符:
! $ % & * + - . / : < = > ? @ ^ _ ~
此外,所有Unicode标量值大于127且类型属于Lu, Ll, Lt, Lm, Lo, Mn, Mc, Me, Nd, Nl, No, Pd, Pc, Po, Sc, Sm, Sk, So或Co的字符都可以被用在标识符中。当通过一个<inline hex escape>表示的时候,任何字符都可以用在标识符中。比如,标识符H\x65;llo和标识符Hello是一样的,标识符\x3BB;和标识符λ是一样的。
在Scheme程序中,任意标识符可作为一个变量或一个语法关键词(见5.2和9.2节)。任何标识符也可以作为一个句法数据,在这种情况下,它表示一个符号(见11.10小节)。
4.2.5. 布尔
标准布尔对象真和假有外部表示#t和#f。
4.2.6.字符
字符可以用符号#\<character>或#\<character name>或#\x<hex scalar value>表示。
比如:
#\a |
小写字母a |
#\A |
大写字母A |
#\( |
做小括号 |
#\ |
空格 |
#\nul |
U+0000 |
#\alarm |
U+0007 |
#\backspace |
U+0008 |
#\tab |
U+0009 |
#\linefeed |
U+000A |
#\newline |
U+000A |
#\vtab |
U+000B |
#\page |
U+000C |
#\return |
U+000D |
#\esc |
U+001B |
#\space |
U+0020 |
| 表示一个空格时优先使用这种方法 | |
#\delete |
U+007F |
#\xFF |
U+00FF |
#\x03BB |
U+03BB |
#\x00006587 |
U+6587 |
#\λ |
U+03BB |
#\x0001z |
词法异常(&lexical exception) |
#\λx |
词法异常 |
#\alarmx |
词法异常 |
#\alarm x |
U+0007 |
跟着x |
|
#\Alarm |
词法异常 |
#\alert |
词法异常 |
#\xA |
U+000A |
#\xFF |
U+00FF |
#\xff |
U+00FF |
#\x ff |
U+0078 |
跟着另外一个数据,ff |
|
#\x(ff) |
U+0078 |
| 跟着另外一个数据, | |
一个被括号括着的ff |
|
#\(x) |
词法异常 |
#\(x |
词法异常 |
#\((x) |
U+0028 |
| 跟着另外一个数据, | |
一个被括号括着的x |
|
#\x00110000 |
词法异常 |
| 超出范围 | |
#\x000000001 |
U+0001 |
#\xD800 |
词法异常 |
| 在被排除的范围内 |
(词法异常记号表示有问题的行违反了词汇语法。)
在#\<character>和#\<character name>中,大小写是敏感的,但#\x<hex scalar value>中<hex scalar value>的大小写是不敏感的。(上句已根据勘误表进行修改。)一个<character>必须跟着一个<delimiter>或输入的结束。此规则解决了关于命名字符的各种歧义,比如,要求字符序列#\space被解释为一个空白字符而不是字符#\s和跟着的标识符pace。
#\newline。它的使用是不赞成的;应使用#\linefeed代替。
4.2.7. 字符串
字符串使用被双引号(")括起来的字符序列表示。在字符串字面量中,各种转义序列(escape sequences)被用来表示字符,而不是字符自己。转义序列总是以一个反斜杠(\)开始:
\a: 响铃(alarm), U+0007\b: 退格(backspace), U+0008\t: 制表(character tabulation), U+0009\n: 换行(linefeed), U+000A\v: 行制表(line tabulation), U+000B\f: 换页(formfeed), U+000C\r: 回车(return), U+000D\": 双引号(doublequote), U+0022\\: 反斜杠(backslash), U+005C\<intraline whitespace><line ending><intraline whitespace>: 无\x<hex scalar value>;: 指定字符(注意结尾的分号)。
这些转义序列是大小写敏感的,除了<hex scalar value>中的字母数字(alphabetic digits)可以是大写也可以是小写。
字符串中反斜杠后的任意其它字符都是违反语法的。除了行结尾外,任意在转义序列外且不是一个双引号的字符在字符串字面量中代表它自己。比如,但字符字符串字面量"λ"(双引号,一个小写的lambda,双引号)和"\x03bb;"表示一样的字符串。一个前面不是反斜杠的行结尾表示一个换行字符。
比如:
| “abc” | U+0061, U+0062, U+0063 |
| “\x41;bc” | “Abc” ; U+0041, U+0062, U+0063 |
| “\x41; bc” | “A bc” |
| U+0041, U+0020, U+0062, U+0063 | |
| “\x41bc;” | U+41BC |
| “\x41” | 词法异常 |
| “\x;” | 词法异常 |
| “\x41bx;” | 词法异常 |
| “\x00000041;” | “A” ; U+0041 |
| “\x0010FFFF;” | U+10FFFF |
| “\x00110000;” | 词法异常 |
| 超出范围 | |
| “\x000000001;” | U+0001 |
| “\xD800;” | 词法异常 |
| 在被排除的范围内 | |
| “A | |
| bc” | U+0041, U+000A, U+0062, U+0063 |
| 如果A后面没有空格 |
4.2.8. 数字
数字对象外部表示的语法在形式语法的<number>规则中被正式描述。在数字对象的外部表示中,大小写是不重要的。
数字对象的表示可以通过特定的基数前缀被写作二进制,八进制,十进制和十六进制。基数前缀是#b(二进制),#o(八进制),#d(十进制)和#x(十六进制)。在没有基数前缀时,一个数字对象的表示被假设是十进制的。
一个数字对象的表示可通过前缀被指定为精确的货非精确的。前缀#e表示精确的,前缀#i表示非精确的。如果使用基数前缀的话,精确性前缀可使用在其之前或之后。如果一个数字对象的表示没有精确性前缀,则在下列情况是非精确的,包含一个小数点,指数,或一个非空的尾数宽度(mantissa width),否则它是精确的。
在一个非精确数可以有不同精度的系统中,指定一个常数的精度可能是有用的。如果这样的话,数字对象的表示可以用一个指示非精确数预期精度的指数标记写成。字母s, f, d和l分别表示使用short,single,double和long精度。(当内部非精确表示少于四种时,这四个精度定义被映射到当前可用的定义。例如,只有两种内部表示的实现可以将short和single映射为一种精度,将long和double映射为一种)。另外,指数标记e指明了Scheme实现的缺省精度。缺省精度应达到或超过double的精度,但Scheme实现也许会希望用户可设置此缺省精度。
3.1415926535898F0
舍入到single, 大概是3.141593
0.6L0
扩展到long, 大概是.600000000000000
一个有非空尾数宽度数字对象的表示,x|p,代表有p位有效数字的x的最好的二进制浮点数近似。比如,1.1|53是使用IEEE double精度的1.1最好的近似的表示。如果x是一个不包含竖线的非精确实数对象的外部表示,那么它的数值应该被认为有53或更多的尾数宽度。
如果实际可行的话,实数对象使用二进制浮点表示的实现应该用p位精度表示x|p,或者如果实际不行的话,应该使用大于p位精度的实现,或者如果以上两个都不行的话,应该使用实现中最大的精度。
(define (precision)
(do ((n 0 (+ n 1))
(x 1.0 (/ x 2.0)))
((= 1.0 (+ 1.0 x)) n)))
注意:当优先使用的浮点表示是IEEE double精度时,|p后缀不应该总是被省略:非规范化浮点数在精度上有所松懈,所以它们的外部表示应该加上一个使用有效数字真实宽度的|p后缀。
字面量+inf.0和-inf.0分别表示正无穷大和负无穷大。字面量+nan.0表示非数,它是(/ 0.0 0.0)的结果,且同样也可以表示其它非数。字面量-nan.0也表示一个非数。(上句根据勘误表添加。)
如果x是一个非精确实数对象的外部表示且不包括竖线且不包括除了e之外的指数标记,这个非精确实数对象表示一个浮点数(见库的第11.3小节)。其它非精确实数对象外部表示的一些或所有也可以表示浮点数,但这不是本报告要求的。
4.3. 数据语法
数据语法按照词汇语法中定义的<lexeme>的序列描述句法数据(syntactic data,句法数据)的语法。
句法数据包括本报告前面章节描述的语义数据以及以下用于组织复合数据的结构:
- 点对和表,被
( )或[ ](见4.3.2节)括起来 - 向量(见4.3.3节)
- 字节向量(bytevectors)(见4.3.4节)
4.3.1. 形式解释
下面的语法按照定义在4.2节的语法的各种语义描述句法数据的语法:
<datum> → <lexeme datum>
∣ <compound datum>
<lexeme datum> → <boolean> ∣ <number>
∣ <character> ∣ <string> ∣ <symbol>
<symbol> → <identifier>
<compound datum> → <list> ∣ <vector> ∣ <bytevector>
<list> → (<datum>*) ∣ [<datum>*]
∣ (<datum>+ . <datum>) ∣ [<datum>+ . <datum>]
∣ <abbreviation>
<abbreviation> → <abbrev prefix> <datum>
<abbrev prefix> → ' ∣ ` ∣ , ∣ ,@
∣ #' | #` | #, | #,@
<vector> → #(<datum>*)
<bytevector> → #vu8(<u8>*)
<u8> → <any <number> representing an exact
integer in {0, ..., 255}>
4.3.2. 点对和表
表示值对和值的列表(见11.9节)的列表和点对数据使用小括号和中括号表示。规则<list>中匹配的中括号对等价于匹配的小括号对。
Scheme点对的句法数据最常用的符号是“点”符号(<datum1> . <datum2>),其中<datum1>是car区域的值的表示,<datum2>是cdr区域的值的表示。比如(4 . 5)是一个car是4,cdr是5的点对。
表可以使用一个改进的记号:表中的元素被简单地括进小括号中并用空格分隔。空表用()表示。比如:
(a b c d e)
和
(a . (b . (c . (d . (e . ())))))
作为符号的表是等价的表示。
一个通用的规则是,如果一个点跟着一个左小括号,则在外部表示中可以省略这个点,左小括号和对应的右小括号。
符号序列“(4 . 5)”是一个点对的外部表示,而不是一个计算结果为点对的表达式。类似的,符号序列“(+ 2 6)”不是整数8的外部表示,虽然它是一个计算结果是整数8的表达式(在(rnrs base (6))库的语言中);当然,它是一个句法数据,这个句法数据表示一个三个元素的表,这个表的元素是符号+,整数2和6。
4.3.3. 向量
表示向量对象(见11.13小节)的向量数据使用#(<datum> ...)表示。比如:一个长度是3,且0号元素位置是数字对象零,1号元素位置是表(2 2 2 2),2号元素位置是字符串"Anna",的向量可以如下表示:
#(0 (2 2 2 2) "Anna")
这是一个向量的外部表示,而不是一个计算结果为向量的表达式。
4.3.4. 字节向量
表示字节向量(见库的第2章)的向量数据使用符号#vu8(<u8> ...)表示,其中<u8>表示字节向量的八位字节(octets)。比如:一个长度是3且包含八位字节2,24和123的字节向量可以如下表示:
#vu8(2 24 123)
这是一个字节向量的外部表示,也是一个计算结果为字节向量的表达式。
4.3.5 缩写(Abbreviations)
'<datum>
`<datum>
,<datum>
,@<datum>
#'<datum>
#`<datum>
#,<datum>
#,@<datum
上面的每一个都是一个缩写:
'<datum>是(quote <datum>)的缩写,
`<datum>是(quasiquote <datum>)的缩写,
,<datum>是(unquote <datum>)的缩写,
,@<datum>是(unquote-splicing <datum>)的缩写,
#'<datum>是(syntax <datum>)的缩写,
#`<datum>是(quasisyntax <datum>)的缩写,
#,<datum>是(unsyntax <datum>)的缩写,且
#,@<datum>是(unsyntax-splicing <datum>)的缩写。