Icon语言

Icon是一门领域特定的高级编程语言，有着“目标（goal）导向执行”特征，和操纵字符串和文本模式的很多设施。它衍生自SNOBOL和SL5字符串处理语言。Icon不是面向对象的，但在1996年开发了叫做Idol的面向对象扩展，它最终变成了Unicon。
历史.
在1971年8月，SNOBOL的设计者之一离开了贝尔实验室，成为了亚利桑那大学的教授。他那时将SNOBOL4介入为研究工具。
作为最初在1960年代早期开发的语言，SNOBOL的语法带有其他早期编程语言的印记，比如FORTRAN和COBOL。特别是，语言是依赖列的，像很多要录入到打孔卡的语言一样，有着列布局是很自然的。此外，控制结构几乎完全基于了分支，而非使用块，而块在ALGOL 60中介入之后，已经成为了必备的特征。在他迁移到亚利桑那的时候，SNOBOL4的语法已然过时了。
Griswold开始致力于用传统的流程控制结构如codice_1，来实现SNOBOL底层的成功和失败概念。这成为了SL5，即“SNOBOL Language 5”的简写，但是结果不令人满意。在1977年，他考虑设计语言的新版本。他放弃了在SL5中介入的非常强力的函数系统，介入更简单的暂停和恢复概念，并为SNOBOL4自然后继者开发了新概念，具有如下的原则：
新语言最初叫做SNOBOL5，但因为除了底层概念外，全都与SNOBOL有着显著的差异，最终想要一个新名字。在这个时候Xerox PARC发表了他们关于图形用户界面的工作，术语“icon”从而进入了计算机词汇中。起初确定为“icon”而最终选择了“Icon”。
基本语法.
Icon语言衍生自ALGOL类的结构化编程语言，因而有着类似C或Pascal的语法。Icon最类似于Pascal的地方，是使用了codice_2语法的赋值，codice_3关键字和类似的语法。在另一方面，Icon使用C风格的花括号来结构化执行分组，并且程序开始于运行叫做codice_4的过程。
Icon还在很多方面分享了多数脚本语言（还有SNOBOL及SL5）的特征：变量不需要声明，类型是自动转换的，就说数字和字符串可以自动来回转换。另一个常见于很多而非全部的脚本语言的特征，是脚本中缺少行终止字符；在Icon中，对于不结束于分号的行，由Icon编译器自动的插入分号来终结，而这种插入的前提是它不会导致跨行的表达式遭到错误的分隔。
过程是Icon程序的基本建造块。尽管它们使用Pascal名称，但工效上更像C函数并可以返回值；在Icon中没有codice_5关键字。
procedure main() 
 write("Hello, world!")
end
Icon允许任何过程，执行或语句，来分别返回一个单一值或依次返回多个值中的一个值。过程已经执行到它的处，或者执行了语句，它会返回。例如：
procedure fn(x)
 if x &gt; 0 then {
 return 1
 fail
end
这里的在不是必需的，因为它紧前于，增加它是为了清晰性。调用codice_6将返回codice_7，而调用codice_8将返回。这将导致不明显的行为，比如codice_9由于codice_8失败而导致codice_11也跟着中止了；还有codice_12对codice_13赋值也不会发生。
目标导向执行.
Icon的关键概念之一就是其控制结构基于表达式的“成功”或“失败”，而非大多数其他编程语言中的布尔逻辑。这个特征直接派生自SNOBOL，在其中表达式求值、模式匹配和模式匹配连带替换，都可以跟随着成功或失败子句，用来指定在这个条件下要分支到一个语句标签。例如，下列代码打印“Hello, World!”五次：
 I = 1
LOOP OUTPUT = "Hello, World!"
 I = I + 1
 LE(I, 5) : S(LOOP)
END
要进行循环，在索引变量codice_14之上调用内建的函数codice_15（小于等于），并且codice_16测试它是否成功，即在codice_14小于等于codice_18之时，分支到命名标签codice_19而继续下去。
Icon保留了基于成功或失败的控制流程的基本概念，但进一步发展了语言。一个变更是将加标签的codice_20式的分支，替代为面向块的结构，符合在1960年代后期席卷计算机工业的结构化编程风格。另一个变更是允许失败沿着调用链向上传递，使得整个块作为一个整体的成功或失败。这是Icon语言的关键概念。而在传统语言中，必须包括基于布尔逻辑的测试成功或失败的代码，并接着基于产出结果进行分支，这种测试和分支是固有于Icon代码的，而不需要明确的写出。考虑如下复制标准输入到标准输出的简单代码：
while a := read() then write(a)
它的含义是：“只要读取不返回失败，调用写出，否则停止”。在Icon中，codice_21函数返回一行文本或。不同于C语言中的特殊返回值codice_22（文件结束），它被语言依据上下文明确理解为意味着“停止处理”或“按失败状况处理”。这里即使codice_21导致一个错误它都会工作，比如说如果文件不存在。在这种情况下，语句codice_24会失败，而codice_11简单的不被调用。
沿调用链传递.
成功和失败将沿着调用链向上传递，意味着可以将函数调用嵌入其他函数调用内，在嵌套的函数调用失败时，它们整体停止。例如，上面的代码可以精简为：
while write(read())
在codice_21命令失败的时候，比如在文件结束之处，失败将沿着调用链上传，而codice_11也会失败。codice_28作为一个控制结构，在失败时停止。Icon称谓这个概念为“目标导向执行”，指称这种只要某个目标达到执行就继续的方式。在上面的例子中目标是读整个文件；读命令在有信息读到的时候成功，而在没有的时候失败。目标因此直接编码于语言中，不用再去检查返回码或类似的构造。
成败测试.
Icon使用目标导向机制用于进行传统的布尔测试，尽管有着微妙的差异。一个简单的比较如，这里的codice_29子句，不像在多数语言中那样意味着：“如果测试运算求值为真”；转而它的意味更像是：“如果测试运算成功”。在这个例子情况下，如果这个比较为真，则codice_30运算成功。如果codice_29子句测试这个表达式成功，则调用codice_32子句，如果它失败了，则调用codice_33子句或下一行。结果同于在其他语言中见到的传统codice_1，它表示如果codice_35小于codice_36为真，则执行codice_32关联的语句。微妙之处是相同的比较表达式可以放置在任何地方，例如：
write(a &lt; b)
另一个不同是codice_30算子如果成功，返回它的第二个运算元，在这个例子中，如果codice_36大于codice_35，则导致codice_36的值被写出，否则什么都不写。因为并非测试本身，而是一个算子返回一个值，它们可以串联在一起，允许如下这样的链式测试：
a &lt; b &lt; c
在多数语言中的平常类型的比较之下，同样的语义必须写为两个不等式合取，比如在C语言中表达为。在Icon中，按数值序比较和按词典序比较，采用了不同的中缀算子，比如采用codice_42进行数值比较，采用codice_43进行字符串比较，以此类推。
需要注意如果测试一个变量比如codice_44，意图确定它是否已初始化，它在未初始化时返回一个值，所以对它的测试总是成功的，故而需要测试或。
回溯.
目标导向执行的一个关键方面，是程序可能必须在一个过程失败时倒转到以前的状态，这种任务叫做回溯。例如，考虑设置一个变量为一个开始位置，并接着进行可以改变这个值的操作，这是在字符串扫描中常见情况，这里前进游标通过它所扫描的字符串。如果这个过程失败了，任何对这个变量的后续读取都返回最初的状态，而非被内部操纵后的状态是很重要的。对于这种任务，Icon有一个可逆（reversible）赋值算子codice_45，和可逆交换算子codice_46。
例如下列表达式：
i := 10 &amp; j := i &lt; find(pattern, inString)
其中的在指定字符串中查找符合特定模式的子字符串并依次返回其起始位置。这个表达式由其优先级比赋值codice_2更低的codice_48分隔为两部份，在第一部份中设置了一个阈值codice_49为codice_50。在第二个部份中，如果能成功返回了一个值并且这个值大于阈值，则成功，接着将这个值赋值到codice_51；否则失败，对codice_51的赋值和整个表达式都会一起失败。作为在失败情况下不想要的副作用，这个表达式的第一部份会导致codice_49的值遗留为codice_50，故而应将codice_55替代为codice_56：
i &lt;- 10 &amp; j := i &lt; find(pattern, inString)
在这个表达式失败之时，codice_49会被重置为它以前的值。这提供了在执行中的类似于的机制。
例外.
将成功和失败的概念与例外的概念相对比是很重要的：异常是不寻常的状况，不是预期的结果；而例外是预期的结果，比如读取文件时到达文件的结束处，是预期的例外状况而不是异常。Icon没有传统意义上的例外处理，尽管失败经常被用于类似例外的状况下。例如，如果要读取的文件的不存在，codice_21失败而不指示出特殊状况。在传统语言中，没有指示这些“其他状况”的自然方式，典型的例外处理是codice_59“抛出”一个值。例如Java使用codice_60/codice_61语法来处理例外：
try {
 // 读取文件等等。
} catch (EOFException e) {
 // 遇到文件结束例外
} catch (IOException e) {
 // 发生IO异常
生成器.
在Icon中表达式经常返回一个单一的值，例如codice_62，将求值并且如果codice_13的值小于codice_18则成功并返回codice_13，否则失败。但是，Icon还包括了过程不立即返回成功或失败，转而每次调用它们之时返回一个新值的概念。这些过程叫做生成器，并且是Icon语言的关键部份。在Icon的用语中，一个表达式或函数的求值产生一个“结果序列”。结果序列包含这个表达式或函数生成的所有可能的值。在结果序列被耗尽的时候，这个表达式或函数失败。
因为整数列表在很多编程场景都是很常见的，Icon包括了codice_66中缀表达式来构造整数生成器：
every k := i to j do write(k)
遍历生成器所生成的所有结果，可以使用表达式，这里的codice_67子句是可选的，它针对codice_68生成的每个结果求值codice_69，在codice_68不再产生结果时失败。
中缀表达式codice_66的优先级高于赋值算子。在这种情况下，从codice_49到codice_51的值，将注入到codice_11并写出多行输出。它可以简写为：
every write(i to j)
在Icon中，经常将合取算子codice_48用于控制流程，它的使用方式类似于C语言和Bourne Shell中短路求值的逻辑与算子codice_76，需要注意不同于C语言的情况，codice_48的优先级低于赋值算子codice_2：
every x := 0 to 10 &amp; x % 2 == 0 do write(x)
这段代码调用整数生成器并得到其初始值codice_79，它被赋值给codice_13。接着进行合取的右手端，因为codice_81确实等于codice_79而成功，进而合取成功，随即执行codice_11写出codice_13的值。接着再次调用这个生成器，它赋值codice_7到codice_13，这使得合取的右手端失败进而合取失败，故而不写出任何东西。最终结果是写出从codice_79到codice_50的一个列表中的所有偶数。
生成器的概念对于字符串操作是很强大的。在Icon中，函数是个生成器。下面的例子代码，在一个字符串中找出codice_89的所有出现位置：
s := "All the world's a stage. And all the men and women merely players"
every write(find("the", s))
在每次被codice_90恢复的时候，将返回codice_89的下一个实例的索引，最终达到字符串结束处并失败。
在想要在一个字符串的某点之后的进行查找之时，目标导向执行也能起效：
every write(5 &lt; find("the", s))
如果找出的codice_89出现在位置codice_18之后，则比较成功并返回右手侧的结果，否则比较失败进行下一次查找。这里把放置到这个比较的右手侧是重要的。
交替表达式.
最常见类型的生成器建造器是codice_94即（alternation）表达式，求值交替者的严格语法描述为：，首先生成codice_68的结果，然后生成codice_69的结果，最终依次产生多个值codice_97。它可以用来构造值的任意列表。例如可以在任意的一组值上进行迭代：
every i := 1|3|4|5|10|11|23 do write(i)
Icon不是强类型的，所以交替表达式形成的列表可以包含不同类型的项目：
every i := 1 | "hello" | x &lt; 5 do write(i)
这将写出codice_7、codice_99和在codice_100的情况下出现的codice_18。
交替表达式的优先级低于比较算子，高于codice_66表达式和赋值算子codice_2。它在条件语句中担当C语言和Bourne Shell中短路求值的逻辑或算子codice_104功能，例如：
if y &lt; (x | 5) then write("y=", y)
这段代码中的codice_94符号起到了逻辑或的作用：如果codice_106小于codice_13或者codice_18，那么写出codice_106的值。实际上codice_110是生成器的一种简写形式，它依次返回这个列表的值直到脱离于这个列表结束处，而这个列表的值被注入到这里的codice_30运算之中。首先测试codice_112，如果codice_13实际上大于codice_106，则这次测试成功并且codice_29子句成功；否则这次测试失败而交替运算继续，从而再次测试codice_116。如果直到交替运算完成而所有测试都未通过，则codice_29子句失败。在codice_29子句成功之后，才会执行codice_32子句codice_11写出codice_106的值。
函数只有在求值它们的参数成功之后才会被调用，所以这个例子可以简写为：
write("y=", (x | 5) &gt; y)
暂停表达式.
要将一个过程转换成一个生成器，可以使用表达式，这里的codice_67子句是可选的；它暂停当前过程，将codice_68生成的每个结果作为这个过程所产生结果返回。在再次调用这个过程之时于这个暂停之处恢复执行，此时先求值codice_69再恢复codice_68；如果codice_68是生成器并产生了新结果，则再次暂停并返回它的结果；如果codice_68不是生成器或者是生成器但不再产生新结果，则继续执行后面的语句。
例如：自己定义一个codice_128生成器：
procedure ItoJ(i, j)
 while i &lt;= j do {
 suspend i
 i +:= 1
end
它建立一个生成器，它返回一系列的数，开始于codice_49并结束于codice_51，接着在它们之后返回。停止执行，并返回codice_49的值，而不重置这个函数的任何状态。当对相同函数做出另一次调用的时候，在这一点上于以前的状态下恢复执行。它接着进行codice_132，然后循环回到codice_28的开始处，接着返回下一个值并再次暂停。这个循环将持续直到codice_134失败，这时调用退出。这种机制允许轻易的构造迭代器。
将例子稍作修改：
procedure ItoJ(i, j)
 while i &lt;= j do {
 suspend i | i*i
 i +:= 1
end
procedure main ()
 every write(ItoJ(1,2))
end
将产生：
1
1
2
4
数据结构.
Icon将值的搜集称为“结构”，包括：记录、列表、和表格。
记录是固定大小的并且它们的值通过字段名来提及。记录像过程那样声明并且对整个程序而言是全局的，一个记录的实例可以通过记录构造子来创建。例如：
record employee(name, age, ssn, salary)
clerk := employee("John Doe", 36, "123–45–6789", 35000.0)
记录的字段通过codice_135形式的表达式来提及，例如。
列表在Icon有两个角色。它是可用下标定位的一维数组（向量），也是可用专属访问函数操纵从而增长或缩短的堆栈和队列。因为Icon是无类型的，列表可以包含任何不同类型的值：
clerk := ["John Doe", 36, "123–45–6789", 35000.0]
就像其他语言中的数组，Icon允许项目按编号从codice_7开始的位置来查找，例如。在Icon中，可以通过指定范围来获得列表的分节（section），就像那样，索引设立在元素之间，比如产生列表。在列表内的项目可以包括其他结构。Icon包括了列表创建函数，用来建造更大的列表；例如，生成codice_50个codice_138的一个列表。
类似于列表，但是只包含任何给定值的一个单一成员。Icon包括了codice_139来产生两个集合的并集，codice_140用于交集，和codice_141用于差集。可以通过用单引号包围字符串来建造codice_142，例如：
vowel := 'aeiou'
Icon包括一些预定义的codice_142，即包含各种字符的集合，它有四个标准codice_142：、、和。
表格是有序对的搜集，有序对被称为元素，它由键和对应的值组成。表格在其他语言中叫做关联数组、映射或字典。表格类似于列表，但是它的键或称为“下标”不必须为整数而可以是任何类型的值：
symbols := table(0)
symbols["here"] := 1
symbols["there"] := 2
表格创建函数建立了使用的codice_79作为任何未知键的缺省值的一个表格。接着向它增加了两个项目，具有键codice_146和codice_147，及其各自的值codice_7和codice_149。
搜集遍历.
搜集可以通过固有的生成器来遍历。例如针对文件的codice_21、针对队列的codice_151和针对堆栈的codice_152：
lines := [] # 建立一个空列表
while line := read() do { # 循环从标准输入读取行
 put(lines, line) # 将行添加到队列
while line := get(lines) do { # 循环从队列取出行
 write(line) # 将行写到标准输出
使用如前面例子中见到的失败传播，可以组合测试和循环：
lines := [] # 建立空列表
while put(lines, read()) # 添加直到所读文件为空
while write(get(lines)) # 写出直到所取队列为空
前缀算子，其严格语法描述为，从一个运算元codice_153生成多个值codice_154：
遍历搜集可以使使用叹号语法进一步简化：
lines := []
every put(lines, !&amp;input)
every write(!lines)
在这种情况下，在codice_11内的codice_169，导致从列表codice_170一个接一个的返回一行文本，并且在结束处失败。而，类似于从标准输入文件读取一行的codice_21，一个接一个读取并返回诸行直到文件结束。
在Icon中，字符串是字符的列表，可以使用“叹号语法”来迭代：
every write(!"Hello, world!")
这将一行一个的打印出字符串的每个字符。
子字符串提取.
字符串是字符的列表，可以使用在方括号内的一个范围规定从字符串中提取出子字符串。子字符串范围规定，可以返回到一个单一字符的一个点，或字符串的一个分节（section）或（slice）。
字符串可以从左至右索引或者从右至左索引，并且在一个字符串内的位置被定义为在字符之间：codice_172或者codice_173。例如：
"Wikipedia"[1] == "W"
"Wikipedia"[3] == "k"
"Wikipedia"[-1] == "a"
"Wikipedia"[1:3] == "Wi"
"Wikipedia"[-2:0] == "ia"
"Wikipedia"[2+:3] == "iki"
会导致失败。这里最后例子采用了表达式，产生codice_153在codice_175和codice_176之间的子字符串。
子字符串规定可以用作字符串内的左值。这可以被用来把字符串插入到另一个字符串，或删除字符串的某部份。例如：
s := "abc"
s[2] := "123" # s现在的值是"a123c"
s := "abcdefg" 
s[3:5] := "ABCD" # s现在的值是"abABCDefg"
s := "abcdefg"
s[3:5] := "" # s现在的值是"abefg"
字符串扫描.
对处理字符串的进一步简化是“扫描”系统，通过codice_177来发起，它在一个字符串上调用函数：
s ? write(find("the"))
Icon称呼codice_177的左手端为“主语”，并将它传递到字符串函数中。所调用的生成器函数接受两个参数，查找的文本和要在其中进行查找的字符串。使用codice_177之时，第二个参数是隐含的，而不再由编程者来指定。多个函数被依次调用于一个单一字符串上，是一种常见情况，这种风格可以显著的缩减代码长度并增加清晰性。
codice_177不是简单的一种语法糖，它还为任何随后的字符串操作，建立一个“字符串扫描环境”。这基于了两个内部变量，和，这里的是要扫描的字符串，而是在这个主语字符串内的当前位置或“游标”。
表达式，保存当前的主语和位置，并接着分别设置它们为codice_13和codice_7，接着求值codice_183；它的产出就是codice_183的产出，在从codice_183退出时它将主语和位置复原为保存的值。例如：
procedure main()
 local s
 s := "this is a string"
 s ? write("subject=[",&amp;subject,"] pos=[",&amp;pos,"]")
end
将产生：
subject=[this is a string] pos=[1]
内建和用户定义的函数，可以被用于在要扫描的字符串上移动。所有内建函数缺省采用和，来允许用上扫描语法。比如设置扫描位置的函数：产生子字符串，并且将codice_49赋值到。下列例子代码，写出在一个字符串内，所有空白界定出的codice_187：
procedure main()
 local s, t, words
 s := " this is a string"
 words := []
 s ? { # 建立字符串扫描环境
 while not pos(0) do { # 测试字串结束
 tab(many(' ')) # 跃过任何空白
 put(words, tab(upto(' ') | 0)) # 添加直到下一个空白或结束之前的诸字符
 t := get(words) 
 every t ||:= " " || !words
 write(t)
end
将产生：
this is a string
算子codice_104串接两个字符串，codice_189是一种增广赋值，codice_190等价于codice_191。这个例子介入了一些新函数，用于测试扫描位置，如果返回，否则失败。这里的循环以通过codice_192反转的作为条件，codice_79在Icon的字符串位置编码中指示行结束，如果当前扫描位置不是codice_79，返回。Icon不简单的直接使用，因为是一个变量，不能提供值，需要提供函数对做轻量级包装，从而便于使用Icon的目标导向控制流程。
函数从当前开始，找到在所提供的codice_142参数中字符的最长序列之后的位置。在这例子中，它查找空格字符，所以这个函数的结果是在之后的第一个非空格字符的位置；接着用移动到那个位置。函数返回在所提供的codice_142中字符之前的位置，接着由另一个来设置。
这个例子通过可以使用更合适的“字分隔”codice_142，包括上句号、逗号和其他标点，还有其他空白字符如tab和不换行空格，从而变得更加健壮，还可以将其用于和函数。
下面给出结合了生成器与字符串扫描的一个更复杂的例子：
procedure main()
 local s
 s := "Mon Dec 8" 
 s ? write(Mdate() | "not a valid date")
end
procedure Mdate()
 local retval
 static months, days
 initial { # 定义一些初始值
 days := [
 "Mon","Tue","Wed","Thr","Fri","Sat","Sun"]
 months := [
 "Jan","Feb","Mar","Apr","May","Jun",
 "Jul","Aug","Sep","Oct","Nov","Dec"]
 suspend retval &lt;- 
 tab(match(!days)) || # 保存匹配星期的一个字符串
 =" " || # 追加随后匹配的一个空白
 tab(match(!months)) || # 追加随后匹配月份的一个字符串
 =" " || # 追加随后匹配的一个空白
 matchdigits(2) &amp; # 追加随后匹配的最多2位数字
 =" " | pos(0) &amp; # 随后匹配一个空白或者结束
 retval # 返回保存的字符串
end
procedure matchdigits(n)
 local v
 suspend v := tab(many(&amp;digits)) &amp; *v &lt;= n &amp; v 
end
将产生：
Mon Dec 8
初始化子句形式为，其中的codice_183是于所在函数首次被调用时求值的表达式。表达式等价于。表达式计算codice_13的大小。这里介入了内建函数，它匹配初始字符串：如果，产生，否则失败；它设定有缺省值：codice_200为；codice_175在codice_200缺省时为，否则为codice_7；codice_204为codice_79。
八皇后问题例子.
采用回溯法求解八皇后问题的例子代码：
global n
procedure main()
 local i, r, s, u, v, x
 n := 8
 s := "abcdefghijklmnopqrstuvwxyz"
 every x := pipeline(1) do {
 r := []
 every i := 1 to n do put(r, s[x[i]] || i)
 u := sort(r)
 v := get(u)
 every v ||:= "," || !u
 write(v)
end
procedure pipeline(i)
 if i &lt; n then
 suspend [q(i)] ||| pipeline(i+1)
 else 
 suspend [q(i)]
end
procedure q(r)
 suspend place(r, 1 to n)
end
procedure place(r, c)
 static col, up, down
 initial {
 col := list(n, 0) 
 down := list(n*2-1, 0)
 up := list(n*2-1, 0) 
 if col[c] = 0 = down[r+c-1] = up[r-c+n] then
 suspend col[c] &lt;- down[r+c-1] &lt;- up[r-c+n] &lt;- c
end
算子codice_206串接两个列表。可逆（reversible）赋值算子codice_45，在被恢复的时候，上次暂停时它所设置的值被逆转回原来的值。不使用的codice_45话，可以将这里的替代为。
将这段代码保存入codice_209文件中，下面演示其执行结果并提取其codice_210个解中的前两个解：
$ icon ./queens.icn | wc -l
92
$ icon ./queens.icn | sed -n '1,2p'
a1,b7,c5,d8,e2,f4,g6,h3
a1,b7,c4,d6,e8,f2,g5,h3