Icon語言

Icon是一門領域特定的高階程式語言，有著“目的導向執行”特徵，和操縱字串和文字模式的很多設施。它衍生自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還在很多方面分享了多數指令碼語言的特徵：變數不需要宣告，型別是自動轉換的，就說數字和字串可以自動來回轉換。另一個常見於很多而非全部的指令碼語言的特徵是，缺少行終止字元；在Icon中，不結束於分號的行，若其確有意義則由暗含的分號來終結。過程是Icon程式的基本建造塊。儘管它們使用Pascal名稱，但工效上更像C函式並可以返回值；在Icon中沒有codice_5關鍵字。procedure doSomething(aString)write(aString)end目的導向執行.Icon的關鍵概念之一就是其控制結構基於表示式的“成功”或“失敗”，而非大多數其他程式語言中的布林邏輯。這個特徵直接派生自SNOBOL，在其中表達式求值、模式匹配和模式匹配連帶替換，都可以跟隨著成功或失敗子句，用來指定在這個條件下要分支到一個語句標籤。例如，下列程式碼列印“Hello, World!”五次：I = 1LOOP OUTPUT = "Hello, World!"I = I + 1LE(I, 5) : S(LOOP)END要進行迴圈，在索引變數codice_6之上呼叫內建的函式codice_7，意味著codice_6小於等於codice_10，它分支到命名標籤codice_11並繼續下去。Icon保留了基於成功或失敗的控制流程的基本概念，但進一步發展了語言。一個變更是將加標籤的codice_12式的分支，替代為面向塊的結構，符合在1960年代後期席捲計算機工業的結構化程式設計風格。另一個變更是允許失敗沿著呼叫鏈向上傳遞，使得整個塊作為一個整體的成功或失敗。這是Icon語言的關鍵概念。而在傳統語言中，必須包括基於布林邏輯的測試成功或失敗的程式碼，並接著基於產出結果進行分支，這種測試和分支是固有於Icon程式碼的，而不需要明確的寫出。考慮如下複製標準輸入到標準輸出的簡單程式碼：while a := read() then write(a)它的含義是：“只要讀取不返回失敗，呼叫寫出，否則停止”。在Icon中，codice_13函式返回一行文字或codice_14。codice_14不是簡單的Java中的特殊返回值codice_16的類似者，因為它被語言依據上下文明確理解為意味著“停止處理”或“按失敗狀況處理”。這裡即使codice_13導致一個錯誤它都會工作，比如說如果檔案不存在。在這種情況下，語句codice_18會失敗，而寫操作簡單的不呼叫。成功和失敗將沿著呼叫鏈向上傳遞，意味著可以將函式嵌入其他函式內，在失敗時，它們整體停止。例如，上面的程式碼可以精簡為：while write(read())在codice_13命令失敗的時候，比如在檔案結束之處，失敗將沿著呼叫鏈上傳，而codice_20也會失敗。codice_21作為一個控制結構，在失敗時停止。Icon稱謂這個概念為“目的導向執行”，指稱這種只要某個目的達到執行就繼續的方式。在上面的例子中目的是讀整個檔案；讀命令在有資訊讀到的時候成功，而在沒有的時候失敗。目的因此直接編碼於語言中，不用再去檢查返回碼或類似的構造。Icon使用目的導向機制用於進行傳統的布林測試，儘管有著微妙的差異。一個簡單的比較如，這裡的codice_22子句，不像在多數語言中那樣意味著：“如果右側運算求值為真”；轉而它的意味更像是：“如果右側運算成功”。在這種情況下，如果這個比較為真，codice_23運算元成功。如果codice_22子句的這個表示式成功，則呼叫codice_25子句，如果它失敗了，則呼叫codice_26子句或下一行。結果同於在其他語言中見到的傳統codice_1，如果codice_28小於codice_29，codice_22進行codice_25子句。微妙之處是相同的比較表示式可以放置在任何地方，例如：write(a  y)在內部，交替子不是簡單的一個codice_76，它還可以用來構造值的任意列表。這可以用來在任意的一組值上迭代，比如：every i := (1|3|4|5|10|11|23) do write(i)codice_101類似於codice_21，迴圈經過一個生成器的返回的所有專案，在失敗時退出。因為整數列表在很多程式設計場景都是很常見的，Icon還包括了codice_103關鍵字來構造“事實上的”整數生成器：every k := i to j do write(k)在這種情況下，從codice_42到codice_68的值，將注入到codice_65並寫出多行輸出。它可以簡寫為：every write(i to j)Icon不是強型別的，所以交替子列表可以包含不同型別的專案：every i := (1 | "hello" | x &lt; 5) do write(i)這將依賴於codice_51的值，寫出codice_60、codice_109或可能的codice_10。同樣的“合取算符”codice_111，以類似於布林算符codice_112的方式來使用：every x := ItoJ(0,10) &amp; x % 2 == 0 do write(x)這個程式碼呼叫codice_113並返回一個初始值codice_114，它被賦值給codice_51。接著進行合取的右手端，並且因為codice_116不等於codice_114，它寫出這個值。接著再次呼叫codice_113生成器，它賦值codice_60到codice_51，這使得右手端失敗而不列印任何東西。最終結果是從codice_114到codice_43的所有偶數的一個列表。生成器的概念對於字串操作是很強大的。在Icon中，codice_44函式是個生成器。下面的例子程式碼，在一個字串中找出codice_124的所有出現位置：s := "All the world's a stage. And all the men and women merely players"every write(find("the", s))codice_44在每次被codice_101恢復的時候，將返回codice_124的下一個例項的索引，最終達到字串結束處並失敗。當然人們有時會想要找到在輸入中某點之後的一個字串，例如，掃描包含多列資料的一個文字檔案。目的導向執行也能起效：write(5 &lt; find("the", s))只返回codice_124出現在位置codice_10之後的那些位置；否則比較會失敗。成功的比較返回右手側的結果，所以把codice_44放置到這個比較的右手側是重要的。蒐集.Icon包括了一些蒐集型別，包括列表，它還可以用作堆疊和佇列，表格，和等。Icon稱它們為“結構”。蒐集是固有的生成器，並可以使用“歎號語法”來輕易呼叫。例如：lines := [] # 建立一個空列表while line := read() do { # 迴圈從標準輸入讀取行push(lines, line) # 使用類堆疊語法來將行壓入列表while line := pop(lines) do { # 迴圈於行可以從列表彈出之時write(line) # 將行寫出使用如前面例子中見到的失敗傳播，可以組合測試和迴圈：lines := [] # 建立空列表while push(lines, read()) # 壓入直到為空while write(pop(lines)) # 寫直到為空由於列表蒐集是個生成器，可以使使用歎號語法進一步簡化：lines := []every push(lines, !&amp;input)every write(!lines)在這種情況下，在codice_65內的歎號，導致Icon從陣列一個接一個的返回一行文字，並且在結束處失敗。codice_132是基於生成器的codice_37的類似者，它從標準輸入讀取一行，所以codice_134繼續讀取行直到檔案結束。因為Icon是無型別的，列表可以包含任何不同型別的值：aCat := ["muffins", "tabby", 2002, 8]在列表內的專案可以包括其他結構。為了建造更大的列表，Icon包括了codice_135生成器；codice_136生成包含codice_137的codice_43個複本的一個列表。就像其他語言中的陣列，Icon允許專案按位置來查詢，比如codice_139。就像那樣，索引是在元素之間的，可以透過指定範圍來獲得列表的分片，比如codice_140產生列表codice_141。表格本質上是具有任意索引鍵而非僅為整數的列表：symbols := table(0)symbols["there"] := 1symbols["here"] := 2這個程式碼建立使用的codice_114作為任何未知鍵的預設值的一個codice_143。接著向它增加了兩個專案，具有鍵codice_144和codice_145，和分別的值codice_60和codice_147。集合也類似於列表，但是隻包含任何給定值的一個單一成員。Icon包括了codice_148來產生兩個集合的並集，codice_149用於交集，和codice_150用於差集。Icon包括一些預定義的codice_151，即包含各種字元的集合。在Icon中有四個標準codice_151：codice_153、codice_154、codice_155和codice_156。可以透過用單引號包圍字串來建造codice_151，例如codice_158.字串.在Icon中，字串是字元的列表。作為一個列表，它們是生成器，並可以使用“歎號語法”來迭代：write(!"Hello, world!")這將在獨立行上打印出字串的每個字元。子字串可以使用在方括號內的一個範圍規定從字串中提取出來。範圍規定可以返回到一個單一字元的一個點，或字串的一個分片。字串可以從左或從右索引。在一個字串內的位置被定義為在字元之間：codice_159，也可以從右規定：codice_160。例如："Wikipedia"[1] == "W""Wikipedia"[3] == "k""Wikipedia"[0] == "a""Wikipedia"[1:3] == "Wi""Wikipedia"[-2:0] == "ia""Wikipedia"[2+:3] == "iki"這裡最後例子採用了表示式，產生codice_161在codice_162和codice_163之間的子字串。子字串規定可以用作字串內的左值。這可以被用來把字串插入到另一個字串，或刪除字串的某部份。例如：s := "abc"s[2] := "123"s := "abcdefg"s[3:5] := "ABCD"s := "abcdefg"s[3:5] := ""Icon的下標索引是在元素之間的。給定字串codice_164，索引是codice_165。分片codice_166是在索引codice_167和codice_10之間的字串，它是字串codice_169。字串掃描.對處理字串的進一步簡化是“掃描”系統，透過codice_170來發起，它在一個字串上呼叫函式：s ? write(find("the"))Icon稱呼codice_170的左手端為“主語”，並將它傳遞到字串函式中。所呼叫的codice_44接受兩個引數，查詢的文字作為引數一，而要在其中查詢的字串是引數二。使用codice_170，第二個引數是隱含的，而不由程式設計者來指定。在多個函式被依次呼叫在一個單一字串上的常見情況下，這種風格可以顯著的所見結果程式碼的長度並增加清晰性。codice_170不是簡單的一種語法糖，它還為任何隨後的字串操作，建立一個“字串掃描環境”。這基於了兩個內部變數，codice_175和codice_176，這裡的codice_175是要掃描的字串，而codice_176是在這個主語字串內的“遊標”或當前位置。例如：s := "this is a string"s ? write("subject=[",&amp;subject,"] pos=[",&amp;pos,"]")將產生：subject=[this is a string] pos=[1]內建和使用者定義的函式，可以被用於在要掃描的字串上移動。所有內建函式預設採用codice_175和codice_176，來允許用上掃描語法。比如函式，它設定掃描位置：產生，並將codice_42賦值到codice_176。下列例子程式碼，寫出在一個字串內，所有空白界定出的codice_183：s := "this is a string"s ? { # 建立字串掃描環境while not pos(0) do { # 測試字串結束tab(many(' ')) # 躍過任何空白word := tab(upto(' ') | 0) # 下一個word是直到下一個空白或行結束write(word) # 寫這個word這個例子介入了一些新函式。codice_184返回codice_176的當前值。為何需要這個函式，而不簡單的直接使用codice_176的值，不是顯而易見的；原因是codice_176是一個變數，而不能呈現值codice_14，而過程codice_184能。因此codice_184提供對codice_176的輕量級包裝，它允許輕易使用Icon的目的導向控制流，而不用針對codice_176提供手寫的布林測試。在這種情況下，測試是“codice_176是零”，在Icon的字串位置的特異編碼中，零是行結束。如果它不是零，codice_184返回codice_14，它透過codice_196反轉而使得迴圈繼續。codice_197從當前codice_176開始，找到提供的codice_151引數的一個或多個例子。在這種情況下，它查詢空格字元，所以這個函式的結果是在codice_176之後的第一個非空格字元的位置。codice_201移動codice_176到那個位置，這種情況下再次具有潛在的codice_14，例如codice_197在字元結束處脫離。codice_205本質上是codice_197的反函式；它返回緊前於提供的codice_151的例子的位置，接著由另一個codice_201來設定codice_176。這裡的交替用來在行結束處也停止。這個例子透過使用更合適的“字分隔”codice_151，可以包括句號、逗號和其他標點，還有其他空白字元如tab和不換行空格，能夠變得更加健壯。這個codice_151可以接著用於codice_197和codice_205。一個更復雜的例子演示了在這個語言內生成器和字串掃描的整合：procedure main()s := "Mon Dec 8"s ? write(Mdate() | "not a valid date")endprocedure Mdate()static datesstatic daysinitial {days := ["Mon","Tue","Wed","Thr","Fri","Sat","Sun"]dates := ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]every suspend (retval &lt;- tab(match(!days)) || # 匹配一個day=" " || # 跟隨著一個空白tab(match(!dates)) || # 跟隨著一個month=" " || # 跟隨著一個空白matchdigits(2)) &amp; # 跟隨著最多2位數字(=" " | pos(0)) &amp; # 要麼是空白要麼是字串結束retval # 最終返回這個字串endprocedure matchdigits(n)suspend (v := tab(many(&amp;digits)) &amp; *v &lt;= n) &amp; vend表示式計算codice_51的大小。這裡介入了內建函式，它匹配初始字串：如果，產生，否則失敗；它設定有預設值：codice_215為codice_175，如果codice_215缺省了，codice_162為codice_176，否則為codice_60，codice_221為codice_114。