第三章 语法基础

2016-05-11  本文已影响0人  _火魂_

正如大多数现代Smalltalk方言,Squeak使用一种非常接近Smalltalk-80的语法。这个语法设计的非常符合英语阅读习惯

(Smalltalk includes:Class) ifTrue:[Transcript show: Class superclass]

Squeak语法核心非常简洁,仅仅包含方法声明与消息发送语法。表达式基于少量的原语元素构建。仅仅保护6个关键词,没有任何控制结构语法和声明新类语法。取而代之的是,逻辑控制ifelse等语言结构使用发送消息ifTrue到布尔对象,创建新的子类发送superclass消息给父类。

3.1 语法要素

表达式保护以下的内建块结构:

  1. 六个保留关键词 pseudo-variables:self super nil true false thisContext
  2. 字面量常量表达式 数字 字符 字符串 符号表和数组
  3. 变量声明
  4. 变量赋值
  5. 块结构
  6. 消息发送

我可以看看表3.1中的语法要素使用例子

syntax

本地变量 Local 小写

startPoint是一个变量名称或标识符。通常,标识符使用驼峰大小写camelCase。实例变量的首字符是小写的,表明这个变量是私有作用域

共享变量 Shared 大写

Transcript是一个全局变量,类TranscriptStream的实例。通常以大写字母开头。

消息接受者 Receiver

self是一个关键词代表当前方法所在的对象。我们将这个称为消息接受者,因为通常用来接收消息来执行方法。self称为预定义变量不可以手动赋值

数字类型 Integers

squeak除了十进制42,还提供了其他类型的数字,2r101等

浮点数
squeak提供了十进制的浮点数 2.4e7

字符
字符使用$进行声明 $a表示a。可以发生消息给字符Character类 来处理 如 space tab等

字符串
单引号用来定义字符串字面量,可以在单引号中使用双号'G"day'

符号
与字符串类似,符号包含一系列的字符,然而不同于字符串,符号字面量是全局唯一的。#hello是一个符号对象 'Hello'是一个字符串

编译时数组

使用#()定义编译时数组,包含空格分隔的字面量。其中的参数必须是编译时常量 #(27 #(true false) abc)是一个三个元素的编译时字面量数组

运行时数组

使用{}定义运行时数组,其中的元素使用句号分隔 {1. 2. 1+2} 顶一个包含三个元素的运行时数组,

注释

使用双引号包围字符串作为注释"hello"。在smalltalk中双引号不是字符串只是注释说明

本地定义变量

使用||包含方法中的一个或多个本地变量定义

赋值

使用:=赋值对象变量。有时可以使用<-代替。因此x := 1 等价于x <- 1x _ 1

块语法

使用[]表明块语法。作为块语法或谓词语法。通常代表一个一个方法对象。块语法可以接受形参和本地变量

原语
使用<primitive:...>引入一个虚拟机原语,其中的原语代码将会在原语执行失败时运行。

一元消息
仅仅一个方法名称的消息
二元消息
包含一个参数的方法的消息
关键字消息
包含多个参数的方法的小

方法返回
可以使用^ 返回方法的运算结果
语句结束
可以使用.语法作为语法分隔符。
级联消息
可以发送多个消息到一个对象,使用(;)实现级联消息发送

3.2 预定义变量

在Smalltalk中,包含6个保留关键词或者说是预定义变量
nil,true,false,self,super,thisContext

这些预定义变量的值是运行环境决定的,无法进行手动赋值,
其中true,false,nil是常量值。
self,super,thisContext是动态变量值

truefalse是Boolean类的子类True和False的唯一实例对象。 在第八章详细讲解

nil代表未定义对象。是UndefinedObject类的唯一实例对象。实例变量通常初始化为nil

self 通常表示当前方法所在的对象,也就是方法消息的接受者对象。

super当前方法所在对象的父级,发送消息给super,方法查找将会从当前对象类的父类开始进行查找。这个在第5章详解

thisContext代表运行栈的顶帧。也就是说,代表当前运行方法上下文,块上下文。在大多数开发过程中不需要关心当前上下文,但是在实现调试器等开发工具中非常有用,也会用来实现异常处理和继续运行。

3.3 消息发送

在Squeak中包含三类消息

  1. 一元消息 (Unary message)
    通常不需要参数,如1 factorial发送消息factorial给数字对象 1

2 .二元消息(Binary message)
通常需要一个参数,如1 + 2发送消息+携带一个参数2给数字对象 1

  1. 关键字消息(Keyword message)
    通常可以携带任意数量的参数, 如2 raisedTo: 6 modulo: 10 发送消息包含多个消息选择器raisedTo: modulo:分别包含参数6 10到数字对象 2

一元消息选择器通常使用字符数字字符,通常使用小写字母开头

二元消息选择器包含以下的一个或多个字符
+-/\*~<>=@%|&?,

关键词消息选择包含一系列的关键词,每个关键词都使用小写字母开头并以分号结束

一元消息选择器通常有最高的优先级,然后是二元消息选择器,最后是关键词消息选择器
2 raisedTo: 1 + 3 factorial

我们首先发送 factorial到数字对象3,然后发送6到数字对象1,最后我们发送raisedTo: 7到数字对象2.
我们可以使用表达式—>表示这个表达式的计算结果

如果不考虑优先级,我们通常直接计算如下
1 + 2 * 3 —> 9
因为没有优先级,所以先计算得到3然后相乘得到9
然而我们包含的优先级法则如下
1 + (2 * 3) —> 7

消息发送可以使用句号和分号进行组合。

一个句号分隔符可以将一系列表达式的依次一个接一个的运行。

Transcript cr.
Transcript show: 'hello world'.
Transcript cr

上面的语句将会发送cr,show:'hello world'到Transcript。最后再次发送cr。

当多个消息被发送到相同的接受者,这些消息将会按照级联消息运行。接受者只需要被指明一次,一系列的消息按照级联的方式发送给消息接受者

Transcript cr;
  show: 'hello world';
  cr

上面两个表达式的执行结果将会相同

3.4 消息语法

在Squeak中,一个表达式可以在workspace,debugger,browser等任意的地方运行。

方法的定义可以在系统查看器或者调试器中

编程者通常在给的类的上下文中开发一个方法,一个类的定义通过发送消息给已存在的类,要求创建一个子类,因此不需要使用特定的语法来定义新的类

下面是字符串类String中定义lineCount的方法。

lineCount

通常一个方法的定义包含以下结构

  1. 方法参数,包含方法名称和任意多个参数(lineCount)
  2. 方法注释,方法体中双引号中的字符串形描述
  3. 本地变量,使用||声明的本地变量
    4.方法体表达式,使用句号分隔的方法体表达式。

可以使用^终止方法运行,返回运行结果。
一个方法没有包含显式返回值的将默认返回self,
这个默认实现链式运算

参数和本地变量通常以小写字母开头,大写字母开头的变量假设为全局变量。例如Character类,仅仅是简单的全局变量代表字符类对象。

3.5 块语法

块语法提供一种机制来定义表达式的运算。一个块语法通常是匿名函数。可以给块语法发送value求值一个块语法。块语法可以使用^显示表明返回值,默认的通常返回一系列表达式最后一个表达式的值,

[1 + 2] value —> 3

块语法作为匿名函数,还可以接受形参,通常使用分号开头作为形参声明。使用垂直线分割形参与块语法

[:x | 1 + x] value: 2 —> 3
[:x :y | x + y] value: 1 value: 2 —> 3

如果块语法包含多余四个参数的,必须使用valueWithArguments:,并且以数组形式传递参数。
包含多个参数的块语法通常拾遗设计错误的标志

块语法中可以声明本地变量,使用双竖线声明本地变量,通常本地变量在参数之后声明

[:x :y | |z| z := x+y. z] value: 1 value: 2 —> 3

块语法事实上词法作用域,因为我们可以将当前块的包围环境作为变量的作用域。下面的块语法限制了x变量作用范围

|x|
x := 1
[:y | x + y] value:2 —> 3

块语法通常是类BlockContext类的实例对象。也就是说它们仅仅是对象,可以被赋值给变量或者作为参数传递给任何其他对象

3.6 条件与循环控制

smalltal并没有提供任何特殊的语法用来实现控制结构。相反,这些仅仅典型的通过发送以语法块作为参数发送消息给布尔值,数字或者集合,

条件语句通常发送ifTure:,ifFalse: ifTrue:ifFalse:给布尔值变量。

(17*13>220)
  ifTrue:['bigger']
  ifFalse:['smaller']  —>'bigger'

循环语句通常典型的发送消息给块,数字或者集合。因为循环语句的跳出条件或许会被反复运行,因此条件语句组织为一个可执行的块语法而不是一个布尔变量值,下面是一个典型的循环

n := 1.
[n < 1000] whileTrue: [ n := n*2].
n —> 1024

whileFlase: 用来退出循环

n := 1.
[n > 1000] whileFalse: [n := n*2].
n —> 1024

timeRepest: 实现固定次数循环

n := 1.
10 timesRepeat: [n := n*2].
n —> 1024

还可以使用to:do发送给一个数字,这个数字作为循环计数的初始化值。第二个参数作为上限,语法块接受当前计数值,运行求值结果

n := 0 .
1 to: 10 do: [:counter | n:= n + counter ] .
n —> 55

高级迭代器。集合中包含大量的不同的类,这些类通常支持相同的协议。可以用于集合迭代的消息类有do:,collect:,select:,reject:,detect: inject:into:。这些消息定义了较为高级的迭代器。使用这些迭代器可以简化代码

Interval可以作为一系列数字集合,通常包含开始与结束位置信息。1 to: 10代表从1到10的数字集合。因为这个是数字集合,我们可以发送do:给数字集合,携带一个块语法参数可以用来遍历执行

n := 0.
(1 to: 10) do: [:element|n := n +element ].
n —>  55

可以使用 collect:构建一个集合,转换元素集合

(1 to: 10) collect:[:each|each * each]
—> #(1 4 9 16 25 36 49 64 81 100)

可以使用select:reject:构建新的集合,每个包含(不)符合一定条件的选择内容。detect:返回固定集合的第一个元素

'hello there' select: [:char | char isVowel] —>  'eeee'
'hello there' reject:  [:char | char isVowel] —> 'hll thr'
'hello there' detect: [:char | char isVowel] —> $e

最终,有些集合在inject:into:方法中支持fold运算符。
通常我们使用一个种子值然后使用一个表达式累积起来得到结果,通常使用求和和求积

(1 to: 10) inject: 0 into:[:sum :each |sum + each]
—> 55

等价于使用
0+1+2+3+4+5+6+7+8+9+10

更多的有关集合与流的操作可以在第九章与第十章中找到

3.7 原语与程序组织

在smalltalk中,每个变量都是对象,每个变量都可以发送消息。然而,特定的时刻我们可能接触到岩底。特定的对象仅仅通过调用虚拟机的原语才可以得到执行

比如,下面的操作通常实现为原语结构:内存操作(new new:),二进制运算(bitAnd:,bitOr:,bitShift),指针操作和数字运算(+ - < > * / = ==)还有数组访问(at:,at:put:)

原子操作可以调用原子语法实现<primirive:aNumber>。调用原语操作的过程可能包含Smalltalk代码,通常是在原语执行失败的时候运行。

可以查看SmallInteger>>+代码。如果原语执行失败,表达式 + aNumber将会得到执行并返回

+ aNumber
  <privmitive: 1>
^ super + aNumber

Squeak3.9后,尖括号语法也会用在方法作为编译指示的注释使用。

上一篇下一篇

猜你喜欢

热点阅读