swordfish
v0.1.25
Published
SWordFish is a Programming Language
Downloads
552
Maintainers
Readme
SWordFish Programming Language
### SWordFish 是一種全新的編程語言. (SWordFish is a Programming Language.) 工欲善其事,必先利其器. 編程語言作爲一種人機接口的軟件應用規範,語義必須邏輯清晰,簡捷明快.同時不失強大的表述力. 同時語言形式上的優雅與美感將視其爲一個重要的特徵. 我在對編程語言本身的實際應用與學習過程中,借鑑了 javascript , lisp , perl 等多種編程語言的優點. , 同時又摒棄一些不易用的方面,在詞法構成上還做了一些個調整 ( 特別是對於字符串 String 的表述 ), 還有在語法構成上則是用直寫語法樹的方式,來重新設計規劃這個全新的編程語言,暫稱其爲 SWordFish . 正因爲語法非常簡單,也可以很容易的實現對不同編程代碼之間的源碼對源碼的轉譯.
安裝方法 ( Installation )
考慮到目前基於 Web 的前後端應用已經相當的廣泛,所以它目前的開發版本是一個基於 javascript 虛擬機之上的一種實現.
$ sudo npm install swordfish -g
概括的使用說明 ( Overview & Example )
運行程式 ( Execute a script ):
swordfish /path/to/script.sw
書寫程式 ( Writting Program )
SWordFish 的作用域的概念與 javascript 保持了一致. 都是以一個函數定義單元來劃分一個作用域的.
對於變量有兩種概念: '定義' 與 '賦值'. 他們都是給一個有名子的東西賦值.(或是說給一個東西在某個作用域內起一個名子.)
但不同之處是:定義是要嚴格限定住變量所在的作用域的.而賦值只是改變那個有已有名子的東西的具體指向.(借用 C 與 java 說法是 "指針" 和 "引用")
在當前作用域內定義名稱變量 ( Definition ):
number:42
opposite:true
變量賦值 ( Assignment ):
number=42
opposite=true
你能注意到這個定義與其他語言有很大差別,只有一個 ':' 冒號,javascript 中的語法已經很簡單了,但至少還是要一個 'var ' 關鍵字, 你會觀察到:它其實與 JSON 中的 key 一樣,是用來明確限定一個命名範圍的,和那種用法是一會事兒. 所以 SWordFish 直接就用 ":" 了. 簡化語法的同時,讓一種語義只用一種語法.也大大減少了代碼的長度.使代碼一眼看上去更直觀,從而減少 bug . 從傳輸上來說,字節量的減少也會更有效率.也更"低炭".
函數 ( Functions ):
square:(lambda (x)
(* x x)
)
數組 ( Arrays ):
array:[1,2,3,4,5]
對象 ( Objects ):
obj:{
field:"name"
method:(lambda() field)
}
正則表達式 ( Regular Expression ):
regExp:`/^swordfish$/ig
JSON 語法兼容的 SWordFish 語言.
你可以觀察到 SWordFish 語法的一個顯著特點就是與 JSON 語法相兼容. 定義與賦值的標號( Key )僅僅是用來說明其後的一個表達式( Value )在其作用域內的名稱. 這個名稱一般也會被稱其爲通常意義上的變量名,但是 SWordFish 標號可寫成字符串 (String) 字面量形式.
"array":[1 2 3 4 5 6]
這樣與它其編程語言不同之處,就是可以不用限定變量名必須爲字母下劃線開始的字母與數字的組合,同時又符合流行的 JSON 數劇格式.
一個表達式不管有沒有標號,其後是否被用的,都會立即被求值,(這裏摒棄了很多函數式編程語言都有的一種惰值的概念.惰值雖然有好處, 但更易讓程序員感到混亂.而惰值這種特性又很容易被寫代碼的程序員在應用層面上解決.所以在 SWordFish 中它被從語言本身的設計上摒棄掉.)
其實標號不會對程序的執行須序有任何影響,只不過就是把一個表達式的求值結果保存在特定的作用域裏. 標識符,其實就是函數作用域內的字符串索引,理论上没有長度與範圍的限制. 其實這才是變量的本質 - 是對程序運行時的某一時刻的某種狀態,所進行的命名存儲.
一: SWordFish 語言的詞法構成.
到現在我們就不難看出:SWordFish 在詞法構成上是符合 javascript 的,也符合大多數動態編程語言對詞法的基本要求.
有字符串 ( String ), 數字 ( Number ), 數組 ( Array ), 對象 ( Object ), 還有正規表達式 ( Regular Expression ). ,這五種基本的詞法單元. 另外還有一種用來輔助組織語法的單元,例如:空白字符 ( BLANK 包括回車換行符 CRLF ) , 逗號 ( Comma -- , ) , 括號 ( Brackets -- (,),[,],{,} ) 與註釋符 (Comment -- ; # // ). 它們在詞與詞之間起鏈接作用.用來確定具體的詞與詞之間的關係,從而一起組成有邏輯含義的語法樹.
表達式與表達式之間的分割就是用空白符的 " " , 但你也可以用 "," 是一樣的. 所以數組也可以寫成這樣:
array:[1 2 3 4 5]
其實所有空白字符在 SWordFish 中都被當成了 "," ( 逗號 ), SWordFish 程式本身就是一個以 "," 分割的表達式列,表達式列的返回值就是最後一個表達式的值. 有點象類於 C 語言中的逗號表達式的概念.
#####代碼註釋: SWordFish 支持行註釋,也支持代碼塊註釋.它們共有四種不同的註釋方法;
#!/usr/bin/env swordfish
# 這些是以 " # " 開始的註釋行,符合作爲 bash 文件的一般用法
; 這是以 " ; " 開始的註釋行, 一些lisp語言用這種方法來註釋行.
// 這是以兩個 " / " 斜槓開始的行註釋,javascript 和類 C 語法的語言都有這種註釋.
/*
還有這種 C 語言風格的代碼塊註釋.
*/
有這麼多的註釋方法,就是爲了迎合現有的不同編程習慣與應用場景,也是很有必要的.
二: SWordFish 語言的語法.
SWordFish 在語法組織上是符合了象 lisp 這類語言的直寫語法樹形式的. 我們在使用和學習一個流行的編程語言時的一個最大的障礙,就是對某種程式語言的掌握在很大成度上,完全就是在學習與領會這門語言的語法規則. 很多時間都浪費在熟悉這種語言的語法規則的經驗積累上了.像什麼操作符,有什麼優先級等等這些小事情上.但是真正的技術不在程序語言本身,而是背後你用它來表述的思想.所以我們不能捨本逐末. 但是對於像 lisp 這樣的直寫語法樹方式,就以其極其簡單的語法,避免了這樣的問題.同時又更清晰準確的表述出程序員的意圖,不會因爲混淆複雜的語法概念而引發問題 ( BUG ) . perl 是我很喜歡的編程語言中的一種,據說是由某個人類語言學家所設計的.所以它被設計成了一種可以用很象人類的表述方式來寫代碼的程序語言,而且一種事情能以多種方式來表達.就像這個 SWordFish 介紹一樣. 但是高度類似於人類的語法一定就好嗎? 不一定 ( No ). 因爲忽略了它要類似於哪一種人類.有不同的地域,種族,文化,語言,方言等等... 用法上的差異非常多. 中國學生學個英語語法也不是人人都能及格的,他最多只考慮了一部份的拉丁語系的語言組織習慣.對於像我們這種用漢語的用戶,不得不都要先瞭解一下這兩種語言之後,才能比較熟練的用它來寫程序,當你看另一個人寫的代碼時,又暈了,因爲那是個日本人寫的. 它這種語法設計走向了一個極端.什麼特性它都想有,最後就是沒有特性. 個人認爲目前大多數學校裏與職場上所推崇的流行的編程語言也都有類似的毛病.人爲的把它複雜化了,還標新立異,試圖壟斷語言的設計標準,捆綁具體的平臺,技術.甚至把兜售的一套編程思想也摻和到語言裏. 而 SWordFish 可以說是另一種與它完全相返的極端,技術與語言本身應該分離,語言不管是人類用的,還是人與機器用的,它就是約定俗成的,大家都能認可與理解的規範. 對於自然語言,人的大腦本身就有語言加工區,像對應於機器上編譯器.(所以編程語言不應該設計成:在人腦與機器中,都要同時經過兩套複雜的編譯過程.). 語言即是工具又是在具體應用之前必須要跨越的認知障礙,所以我認爲它越是簡單越好.特別是人與機器對話的這種語言(編程語言). 有人說語法樹不符合人類閱讀習慣,其實你看 C , java 它們就是"半語法樹",混着來,平行結構又參進樹結構裏,學着更別扭.還不好掌握. 所以很多學校都把它當成一門科目來學.這很沒有必要,這是設計上的問題. 有人說平行的類似於數學上的這種語法更節省代碼量,但是 lisp 編程者會更有發言權,從實際效果上來說,減少了代碼量的同時,還更清晰明確. 其實編程語言的本質不是寫給人看的,是寫給機器的,只是借用一點類人的語素來方便輸入輸出而已.而且它能直觀的對應在機器運行序中,也方便於用機器自已來動態生產這種執行序,說遠了,這是人工智能的應用範疇了. 總之:以上就是爲什麼 SWordFish 要採用直寫語法樹形式的原因.
SWordFish 的運算子 與 函數 ( Operation & Function )
(operation param1 param2)
SWordFish 語法結構与 lisp 一至,整個程式是一組"原子"表達式的列表. 程序會須序求值,返回最後一個表達式的值. 由圓括號包圍的一組子原子表達式列表一般情況下叫作一個操作 (或函數調用).這個子列表的第一個原子就是這個操作的函數或運算子, 隨後的那些原子都是這個操作的所用的參數. 函數與運算子主要區別是,函數在調用其間會生成新的作用域.(一般類 C 語言稱爲函數棧). 而運算子就不一定. 另外說明一下 SWordFish 的作用域有函數棧不具備的能力,就是動態編程語言所具有的閉包特性. 而比 javascript 更強的就是這個"作用域"的本身是可以被直接引用的.不像 javascript 外部必須持有在作用域中定義的函數才能訪問作用域.
函數的定義:
SWordFish 作爲編程語言本身,語法上沒有函數的定義這個概念.函數僅僅是一種運行時會有作用域的比較特別的運算子而已. 這個運算子就是一個很普通的值,可以當成操作參數或返回值進行傳遞 (λ演算).所以我們只是內置了一個能返回函數的運算子 - lambda,對它的調用就會返了一個函數.
(lambda (x,y)
x:(+ x 1)
y:(+ y 1)
(list x y)
)
這個 lambda 的操作會返回了一個函數,有 x , y 兩個參數, lambda 的第一個參數是以圓括號起來參數列表,剩餘參數部份爲函數體的原子表達式列表,調用時返回最後一個求值表達式的值. 這與主程式的行爲是一至的.其實主程式也是一個函數,只不過是沒有明確的聲明而已.寫法上僅僅只存在函數體的原子表達式列表而已. 用一些其它的編程語言中的概念來說,它就是主函數 ( main ).
回過頭來,我以上邊的示例來說,函數返 (list x y) 就是參數 x , y 被分別加一後的結果. list 操作返一個特別的值,就是多值.多值的概念在 perl 語言裏有,函數可以返回多個值. 當多值在運行時作爲另一個函數的參數時將會被動態擴展. 請看以下示例:
fun1:(lambda (x,y)
x:(+ x 1)
y:(+ y 1)
(list x y)
)
fun2:(lambda (a,b)
(+ a b)
)
(fun2 (fun1 1 2))
就上面的示例來說:fun2 的調用看起來只有一個參數,實際上 fun1 調用後因爲把多值傳給了 fun2 ,最後 fun2 參數 a , b 收到了分別加 1 後的 x 和 y 的值 ( 2 , 3 ) .
如果一個會返回多值的表達式前加了一個或多個標號,那麼多值中的每個值會分別給多個標號(變量)賦值.
a:b:c:(list 1 2 3)
與
a:1
b:2
c:3
是一樣的. 但是如果表達式結果是非多值情況:
a:
b:
c:123
其效果就相當於 javascript 的前置聲明變量一樣:
a:undefined
b:undefined
c:123
只有最後一個標號才是表達式值的變量名
還有如果想要把一個多值保存在一個數組裏,可以在標號前加 @ 標記:
d:@ret:c:(list 1 2 3)
((. console "log") ret d c)
最後 ret 孌量就保存成一個 [2,3] 數組了. 輸出:
[ 2, 3 ] 1 3
有點像給標號再加標號,只是目前只有這一種 @ 的情況.
從理論上來說,函數本身就是應該有多個輸入與多種輸出的,如果只能返回一個值是非常不自然的. 例如在 C 語言中,你得用傳指針作爲參數讓函數內部來改其外部的值. 或者是把多個返回值再一步包裝成一個值,外邊用的時候還要解包. 用這種額外的手段來解決這類本來函數本身應該具備的功能.是非常不合理的. 所以 SWordFish 的函數設計成可以支持多返回值.
在函數運行時的作用域上,有個叫 "callee" 的變量,引用正在運行的函數的本身.
函數的動態參數
javascript 要想用動態參數你得用 arguments 變量的方法. 可是要想使用傳遞給 SWordFish 的函數中的參數,必須在函數定義時有明確聲明. 不管是不是動態參數. 以下面的例子來說:
(lambda (a,b,c,@others)
)
當參數名以 @ 开头作标记的,表示它是最後一個可以保存包它本身在內的其餘所有後續參數的數組. 如果你把名子寫成這樣:
(lambda (@arguments)
)
這樣就與 javascript 中的 arguments 等價了.
這個設計目地是既要有明確的參數聲明,又要保留動態參數的能力,同時還要考慮只有部份參數是非動態的這樣一種混合的用法而提供的一個方便的用法.
但也要非常注意"參數必須聲"明這一點: 象我寫代碼時習慣用 @arguments 的名子,因爲它與 javascript 用法一樣. 但是 javascript 的 arguments 是永遠都有的,但是在 SWordFish 中如果你沒有聲明它, 你在用 arguments 變量時,它可不是你所認爲的此函數的所參數了,它可能是上一層作用域中,在它聲明處的那個函數的參數了,或者是其他的什麼東西. 所以要注意這一點.
還有被以 @ 開頭作標記的標識符,標識符就是那個變量名,它本身是沒有這個 @ 符號的.
SWordFish 中的字符串 (String).
爲了學習容易與詞法兼容,象一般的 javascript 這種字符串書寫樣式, 在 SWordFish 中是可以被正確解析出來的. 一樣的可以當成 SWordFish 的字符串對待. 但是出於更方便的書寫,作爲與協議的傳輸,存儲等實際使用場景的需要, 還是要有很多不同之處,來增強功能,簡化操作.
SWordFish 中的字符串字面量定義形式,是能以單引號或雙引號 ( Quote or Double Quote ) 括起來字符集.
支持反斜線字符轉譯:
\b \t \v \f \" \' \r \n \\
轉譯規則與 javascript 保持一至. 例如 \a , 當 \ 後跟其它字符時, \a 沒有轉譯, \ 不會在字符串中,a會被保留.
"\b \t \v \f \" \' \r \n \\ \a "
輸出:
"\b " '
\ a "
在單引號括起來的字符中如果存在沒有轉移的雙引號,則將視其爲普通字符, 如要包含單引號本身則必須要轉譯. 同樣,以雙引號括起方法定義的字符串中出現單引號,單引號就可以不必轉譯.
"'" #單引號
'"' #雙引號
還有一種是 javascript 所支持的 \x \u 後加十六進制數字, \ 後加八時制數字的轉義,與 javascript 行爲一至. 當出現這種寫法時,SWordFish 實際上是把 被轉義的數字所代表的字符轉成了 UTF-8 所代表的字節保在那個位置上.因爲 SWordFish 源碼就也是以 UTF-8 保存的. 這是一種你可以以數字形式來指定固定的特定的字符的方式,數字所代表的就是那個字符的 unicode 整數編碼.
((. console "log")
"--|\u5fc3|\067|\x37|\u0037|--
==============")
輸出:
--|心|7|7|7|--
==============
SWordFish 刪除了在定義字符串時以 \ 做爲行尾最後一個字符時,使連續輸入的下一行的字符當成同一行的字符串這個用法.刪除了這種在代碼中用多行輸入單行字符串的行爲.
相反,SWordFish 在定義字符串時,如果字符內部代碼上存在沒有轉移的 回車 ( CR ) 換行 ( LF ) 符時,CR LF 將被原封不動的保留. 不會像 javascript 一樣有詞法錯誤.
" This is first line .
This is second line .
"
這樣就出現了一個非常易用的特點,有點類似 perl 中 HereDoc.充分發揮 lisp 中的 " 數據即程式,程式即數據 " 的理念. 這樣任何人都會很容易的用 SWordFish 寫一種有大段大段文字的文檔. 如果單行必須很長,現代的代碼編輯器都是可以折行顯示的 ( Word Wrap),所以沒有必要保留這個用法,它同時又會把字符內容搞的很混亂.
SWordFish 還有一種以重音符號開始的字符串定義樣式.
重音符號 ` ( Stress Mark ) 後跟單引號 ' 或雙引號 " 或數字 .也可以定義字符串. 這是一種沒有字符轉譯的 HereDoc 樣式定義字串的方式.
`"
This is HereDoc.
This is second line .
\t\r\n ,all are not been translated.
"
加標記,是不是有點像perl.
`"StrEND
This is HereDoc.
This is second line .
\t\r\n ,all are not been translated.
"StrEND
重音符號 ` 加數字:
`10
abcdefghij
數字表示下一行開始多少個原始字節是這樣字符串中的字符.
所以 SWordFish 的字符串定義方法涵蓋了單行定義,多行 DereDoc 定義,有無轉譯選擇,二進制原始數據嵌入的多種方法. 全方的來位便利書寫,便利協議傳輸的各種苛刻使用要求. 這也是爲爲解決 JSON 在這方面的不足之處." 數據即程式,程式即數據 " 畢竟JSON作爲數據傳輸協議,要儘可能的易用,承載數據本身是很重要的.
OldJSON:{
"key":"Hello\nWorld"
}
NewJSON:{
"key":"Hello
World"
}
同時解決 JSON 原始二進制數有多次編碼導致效率底下,存儲數據與數冗餘等問題. 這樣我們就能非常方便的把 SWordFish 當作數據傳輸與存儲時的內容協議來使用.
字符串是以單引號或雙引號引起的有什麼不同之處?
perl 語言能定義作爲引號本身的替代字符,還有單引號與雙引號在字符轉譯上行爲是不同的. 就這一點,上邊已說過,以重音符號 ` 加引號來定義字符串的方法是直寫文本沒有轉譯的. 所以在語法上來說,是沒有任意差別的.對用戶來說就是在代碼中引用了一塊原始的數據--字節數組 ( byte array ). 但是在運行時,運行環境是清楚在定義時它是以什麼符號聲明的,以決定它將以什麼行爲生成真實的內存實體.
舉個例子來說: C 語言中單引號引的會生成 char 八位數, 雙引號的生成 char* 指向八位數的指針,就是字節數組. 我認爲這是運行時由虛擬機實現者決定的行爲,而不是語言本身要規定的東西.也許虛擬機實現者不把它們當成字符處現,按着某種特徵,表現出一個複雜對象呢,也是可以的. 甚至最終的用戶都可以參與這個過程,自定義產生行爲.
目前我在 SWordFish 的用 javascript 實現的這一個版本的虛擬機中,會以 UTF-8 來解碼存在代碼中的數據,再以 Unicode 原始的 javascript String 對像來表示出在內存中的數據形式.
但是,如果你的 javascript 的運行環境爲 nodejs 而不是瀏覽器, 我會用 nodejs 字節數組對像 Buffer 來表示單引號中聲明的東西. 所以你儘量用雙引號來表示字符串,不管你在用哪一種編程語言,這都是一個非常很好的習慣.
SWordFish 中的正則表達式 ( Regular Expression ).
類似 perl 這種語言一樣,在詞法上內置正則表達式的語言一般都是非常受歡迎的. 現在大多數程式都會用來處理文本的,這樣做會給在程式書寫上帶來更加方便的使用體驗.
SWordFish 中內嵌的正則表達式表定義規則與 javascript 語言一模一樣. 只是斜杆前必須要以一個重音符號 ` 開始.
`/^swordfish$/ig
因爲 lisp 這種可以以空白做分割的特點," / " 即要表示出一個運算符( 運算子的名稱引用 ) 又要表示一個正規表達式開始,編譯器是很難區分的,所以 SWordFish 的正則表達式是以 " `/ " 開始的,其他的都與 javascript 一樣,包括附加標記 --" i "," g "," m ". (一定要注意這一點正則表達式以重音符號開始,很容易寫錯.)
SWordFish 中的數字 (Number).
這個就不多說了,與 javascript 沒什麼不同, 請看以下示例:
1234 #十進制整數
-0xF6 #十六進制負整數
072 #八進制整數
3.14 #浮點數
-3.04e+2 #科學計數法
SWordFish 語言的控制結構.
SWordFish 語言的語法本身上沒有控制結構, 流程控制是運行時的行爲,象函數的聲明一樣,都是在運算子中完成的流程控制操作. 虚拟机启动后内置了一些这样的运算子,在全局作用域中以標號命名了它們的存在,写在代码里就是普通的变量.对於用來作流程控制的這種情況的操作符(就是個變量)我們也可以稱其爲"運算符". 被稱爲運算符的這些變量名,還包括了用來做算術運算的這種操作的那些變量名. 如:
+ - * %
它們其實都是普通變量,只是引用的是一個做算術運算的操作或函數而已. 下面具說明:
條件表達式 if
(if true
"This is true"
"This is false"
)
第二個原子,就是一個參數,爲條件表達式,if 操作要用它的值來決定來求取的是第三個原子(爲真時的條件匹配表達式),還是第四個原子(爲假時的條件匹配表達式)的值來做爲整體反回結果.
當然 if 操作也可以沒有 false 條件的匹配,這時它會返回條件本身 false . 但"有 if 沒 else " 這種編碼習慣在哪兒都不太好,經常會引發 bug ,所以不建議大家這樣用.
另外說一下 "?"" 與 "if" 引用的是同一個操作:因爲很多語言的 if 只是個語句,沒有返回值. 爲了在表達式中有選擇條件就必須用三目運算符" ? : ". 所以這個 ? 作爲 if 的簡寫,看起就與 " ? : " 做的事在形式上很象了.
//javascript://a=true?1:2;
A:(? true 1 2)
循環表達式 while
i:8
(while (> i 0)
(+ "while i:" i)
i:(- i 1)
)
while 循環內的 break 操作
break 是一個特別的操作,
當遭遇到 (break) 或 (break onvalue) (break value1 value2) 時,會產生一個運行時中斷異常. 如果正好它處在 while 循環體內的一次循環過程當中,那麼: while 操作將不會再次檢測條件表達式的值,直接用 break 出來的值作爲 while 表達式的計算結果返回. 這相當於是一個隱含的 try catch 邏輯.
i:8
exp:(while (> i 0)
((. console "log") "while i:" i)
i:(- i 1)
(if (< i 5)
(break i)))
((. console "log") "while expression 's value is" exp)
運行結果:
while i: 8
while i: 7
while i: 6
while i: 5
while expression 's value is 4
還有個類似情況就是函數內的 return 操作,它也是產生一個運行時中斷異常. 中斷之後,餘下的操作就不能再執行了,就一直向上拋.直到被定議的函數外殼收到,它把 return 操作的參數值當成此時函數的返回值返回. 與 break 同樣,當參數沒有時爲 undefined ,也可以有多值. 還有 return 與 break 異常同樣也不會被 try 操作所截獲.
fun:(lambda ()
i:8
(while (> i 0)
i:(- i 1)
(if (< i 5)
(return i))
((. console "log") "i:" i)))
((. console "log") "fun value is" (fun))
while 循環內的 continue 操作
continue 與 break 都是作用在 while 操作內用於中斷當前while內的逗號表達式繼續執行. 唯一的區別就是 break 不再檢測條件完全中斷整個 while 操作.而 continue 的中斷, 只是中斷本次 while 的一次循環,它還要再檢測循環條件來決定要不要退出整個 while 操作.
另一個條件表達式 cond
這個cond操作符能起到與 javascript 的 switch 結構相似的用來簡化多路 if else 的分枝用法.
(cond
{false,"true 1"}
{(> 1 2),"true 2"}
{false,"true 3"}
{default}
)
這個多路選擇的用法借鑑了 racket 編程語言的 cond 操作.只是他是用的方括號,我這裏用的是花括號. 就 default 來說,與 true false 一樣是個預設的變量,其值爲 true,就是用來方便 cond 操作沒匹配時走最後的求值過程.
在 cond 中有一個專門爲它存在的操作 next ,它會中斷已匹配的當前條件,讓 cond 繼續一下條件的匹配. 例如:
(cond
{true
((. console "log") "Hello")
(next)
}{default,
((. console "log") "Defaut")
}
)
輸出的是 : Hello 還有 Default.
這些用於流程控制的操作符與普通的運算子一樣都是有返回值的,就是運行時最後一個表達式求值結果.
其實 try catch 異常捕獲用法也是一類特別的流程控制.
(try
1234
(throw "exception 1")
4567
(lambda(e)
((. console "log") "===== Throw Exception ====\nthrow value:" e )
))
try 操作給所有表達式依次求值,除最後一個函數表達式,如果求值過程中有異常,則把異常作爲參數求取最後一個函數表達式的調用返回值,否則返回最後一個求值結果. 其實只要有 while 和 try 就能完成所有流程控制需求了.如果用戶認爲不夠用,其實是可以自己增加新操作符或修改的已有的流程控制操作.
SWordFish 在 javascript 環境下內置 API
就是內置一些操作符或全局變量.
eval: 行爲與 javascript 的 eval 用法一至,在函數作用域中把一個字符串當成代碼來求值.
(eval "{a:1,b:2}")
lambda: 用來定義函數.
類似於關鍵字的那些全局變量:
true
false
null
undefined
global
global:引用被用來代表最頂層的函數作用域.
流程控制,條件運算子 ( Conditionals ) 的變量:
throw 手動產生異常. if 條件表達式. while 循環條件表達式.
for 爲對象屬性迭代操作. in 爲判斷是否存在某個名子的屬性. in 的第三個參數表示爲自有屬性,就是不是由原型繼承來的,對應 javascript 的 Object 中的 hasOwnProperty 方法. 舉例如下:
obj1={
"key1":"value1"
}
obj2={
"key2":"value2"
"__proto__":obj1
}
(for obj2 (lambda (key value)
((. console "log") key value)
))
((. console "log") (in "key1" obj2))
((. console "log") (in "key1" obj2 true))
輸出爲:
key2 value2
key1 value1
true
false
each 爲數組元素迭代操作. do 合並一組表達式當成一個表達式
c:true
(if c
(do
1
2
3
)
(do
4
5
6
)
)
c 變量爲 true 時返回 3,否則返回 6 .
try 與 do 是一樣的,只是它的最後一個表達式將爲視爲異常捕獲函數,沒異常時它不會被求值.
cond :多條件匹配選擇器
list :把參數變成一個多值列表,多用於函數多返回值.
catlist :將多個數組,列表,簡單值串連在一個列表中.
(catlist 1 [2,3] 4)
返回 [1,2,3,4]
list 與 catlist 很容易混淆,list 只處理參數並合並成一個多值,而 catlist 是讓數組變成多值,如果參數上給了多個數組和其它值,它們都合並到同一個多值中.
getscope :獲得當前運行時所在的作用域引用.這個能力是 javascript 做不到的.
正則表達式操作有: match 和 replace
(match "abc" `/abc/)
返回true
(replace "abc" `/\S/g "A1")
返回"A1A1A1"
(replace "abc" `/\S/g (lambda($0)
(+ $0 "=")
))
返回"a=b=c="
算數運算符: "+" :加的參數除了可以用於數值以外,可以用於字符串,是表示鏈接多個字符串的行爲,與 javascript 一樣. 例如:
(+ 1 2 3) # 6
(+ "1" 2 3) # "123"
"-"
"*"
"/"
"%"
比較運算
"<"
">"
">="
"!="
"=="
"==="
"<="
邏輯操作符
"&&"
"||"
"!"
位操作
"&"
"|"
"~"
"^"
"<<"
">>"
">>>"
console 控制臺,含有用於簡單的輸出文本的函數. 它在 nodejs 環境裏就直接是 nodejs 的 console 對像.
((.console "log") "Hello" " " "World" "\n") #> Hello World
調用外圍 javascript 宿主機的 api
"JSCall"
"JSCallObject"
"JSEval"
"JSNew"
"JSInstanceof"
"JSTypeof"
"JSCallback"
print:(bind (.console "log") console)
value:(JSEval "({\"p1\":1})")
(print (. value "p1") "\n")
(print (JSCall (. value "toString") value) "\n")
(print (JSCallObject value "toString") "\n")
value=(JSNew "Array" 5)
(print (. value "length") "\n")
(print (JSInstanceof value (JSEval "String")) "\n")
(print (JSTypeof value) "\n")
以上代碼輸出:
1
[object Object]
[object Object]
5
false
object
用這樣的方法,在 SWordFish 內部就可以把外部的 javascript 宿主機的能力很容易的擴充進來.
JSCallback
當我們要利用 javascript 宿主環境的一些函數時.有些函數要求把回調函數當作參數給它.如果你想用 SWordFish 的函數來處理回調就得把它轉成純 javascript 的函數才行.這個操作就是這個目地.看例子:
array:[1,2,3,4]
((. array "forEach") (JSCallback (lambda (item)
((. console "log") "Hi:" item)
)))
輸出:
Hi: 1
Hi: 2
Hi: 3
Hi: 4
SWordFish 的面向對象編程
我雖然不提倡面向對象的編程方法,但是對於像 javascript 這樣的面向對象編程的這樣的用法已成爲很多人的編碼習慣,所以 SWordFish 再精簡一下用法,也支持簡單的面向對象編程. SWordFish 的面向對象編程遵循幾個簡單的規則. 1 對像就是用花括號圍起來定義的這種 JSON 對象. 2 對像有原型鏈 "proto" 用來設定原型對象. 3 "." 是對象的屬性操作符 讀一個屬性的值:
(. obj "key") # 相當於 javascript 的: obj.key
設一個屬性的值:
(. obj "key" value) # 相當於 javascript 的: obj.key=value
調用設屬性操作後會返回被設屬性的值.
3.1 對象的方法調用,就是取對像的方法作爲運算子的函數調用,這種情況函數運行時的作用域內會有 "this" 變量引用對象本身.
((. obj "setValue") value) # 相當於 javascript 的: obj.setValue(value)
4 delete 是屬性刪除操作
(delete obj "key") //相當於 javascript 的 delete obj.key 或 delete obj["key"]
SWordFish 的 bind
大家知道面向對象方法調用的本質,就是隱含了一個叫 this 的參數, 大多數編程語言的用法都是先有那個 this 的對象,然後按名子再拿 this 中的那個方法,然後把 this 作爲隱含的參數與其他參數一齊傳給那個方法.
這樣所有對象方法的調用都要多做一件事就是取方法的屬性.如果只有一次執行還好,多次調用就會在寫法上與實際運行時都要面臨效能不佳的問題. 如 javascript code :
utf8.push((c>>12)|0xE0);
utf8.push(((c>>6)&0x3F)|0x80);
utf8.push((c&0x3F)|0x80);
拿push方法就連續走了好幾次,實際每次的結果都是一樣的.所以就需要 bind 操作把 this 和一些固定參數只取一次,bind在一起.
SWordFish 的 bind 也是爲了這樣的目的,因爲有時不得不用 javascript 裏的面相對象的 API . 也要有個優化的方案才行.
但是 SWordFish 的 bind 操作有兩個重要的不同: 一個是,參數位置可調. 另一個是,可有主動惰性參數. 下邊就詳細說明一下:
fun1:(lambda (a,b,c,d)
((. console "log") this a b c d)
)
//函數 fun1 ,綁定 this 到一個對象 obj1.
obj1:{"name":"obj1"}
newFun1:(bind fun1 obj1);
//然後調用它:
(newFun1)
//結果爲: { name: 'obj1' } undefined undefined undefined undefined
//再加其他參數調用:
(newFun1 1 2 3 4)
//結果爲: { name: 'obj1' } 1 2 3 4
; 這就是 bind 操作的一般用法.
; 對於參數位置可調,有兩種站位符,它們是 bind 的屬性 "Anchor" 與 "Anchors", 分別表示站一個參數位與站後續所有參數位.
; 我們先在代碼裏手動命名兩個易用的變量.
A:(. bind "Anchor")
S:(. bind "Anchors")
fun2:(lambda (@argv)
((. console "log") argv)
)
;我先把 bind 操作參數稱爲 "參數的模板(argvPattern)"
; bind 之後實際運行時的參數爲 "運行時參數(runtimeArgv)"
;那真正作用的在被 bind 的函數參數是實際參數 (argv).
newFun2:(bind fun2 obj1 "a" A "b" A "ohters" S "END")
(newFun2 1 2 3 4)
;結果爲 : [ 'a', 1, 'b', 2, 'ohters', 3, 4, 'END' ]
關於主動惰性參數:
前面已經提及了 SWordFish 沒有一般函數式編程語言的惰值概念,但並不妨礙你主動要求有這種類似的功能,實際上在任何程式語言下我們都可以人爲的做這樣的事情. SWordFish 內置的惰值有點象 java bean 對象的一個引用.你想用時就 get 它,你想改變它就 set . 只是你只針對這一個值,不是 java bean 的多個屬性.
name:(lazy)
聲明惰值用都用 lazy 操作,後接兩個參數,第一個爲初始值,第二爲true 時表示 這它這個值是一個函數( lambda ) ,你在主動求值時就會用這個函數調用來返回它的值.你可以什麼都不給它就是 undefined. 當然你可以後設,後設它的就是把它當成操作數,也同樣接這兩個參數.沒有任何參數就表示你要求值.
name:(lazy)
(name "hello")
((. console "log") (name) )
結果爲: hello
如果惰值作爲 bind 操作的一個參數,那在實際函數執行時它會被求取值後的那個值做爲實際的參數. 比如:
i:0
y: (lazy (lambda ()
i=(+ i 1)
) true)
fun3:(lambda (x,y)
((. console "log") x y ))
newFun3:(bind fun3 null "hello" y)
(newFun3)
(newFun3)
(newFun3)
(newFun3)
結果爲:
hello 1
hello 2
hello 3
hello 4
再說一下 eval
SWordFish 的 eval 操作較其它動態編程語言的 eval 更加強大. 在 javascript 裏 eval:
window.eval("var a=2;");
javascript 用 window.eval 是在全局作用域內求值. ,而 eval 則是在當前作用域內求值.
運行期間不能對除以上兩種作用域進行修改( 通過 eval 注入代碼來改變作用域內的環境 ),也不能改變函數本身,只能完全替換掉.
SWordFish 的 eval 可以指定作用域,因爲 SWordFish 能把運行時的經過的作用域保存下來.通過 getscope 操作. 應用面向對象的方法來指定作用域.
savedScope:
getCountFun:(lambda()
savedScope=(getscope)
count:1
(lambda()
count;
)
)
getCount:(getCountFun)
((. console "log") (getCount)) # 1
(eval savedScope "count:2")
((. console "log") (getCount)) # 2
你可以相象這樣會帶來什麼.
getscope 操作
getscope 的第一個參數可以指定回溯的作用域級數,也就是說它可以拿到定義它的上層作用域. 不給,缺省值就是 0,就是當前的作用域.
(getscope 1)
就是 parent scope (外一層的作用域).
就如有人所說:
在 javascript 裏,每個函數同時又可以作爲構造函數( constructor ),
所以每個函數裏面都隱含了一個 this 變量,
你嵌套多層對象和函數的時候就發現沒法訪問外層的 this ,非得 bind 一下.
在 SWordFish 中外层的作用域,外外層的作用域都能直接訪問.當然作用域中 this 也能訪問,因爲我們的變量只不過是作用域中的屬性而已.
另外:getscope 第二个参数为是否得到调用栈的上一层调用者的作用域. 这个功能类似于 javascript 里的 Function 上的那个 caller 属性. 但是不推荐使用,因为如果一个函数还要看是谁调用的我,而有不同的行为.那这个副作用有点大. 我们建议都走参数,才符合函数编程的思想.但是有时需要 caller 做参数的这种情况也是很有用的. 所以我把它的用法先隐藏在 getscope 的第二个参数上. 拿上一层 caller:
(getscope 1 true)
SWordFish 模塊管理
模塊管理不是編程語言本身要考慮的問題,把怎麼加載模塊這樣事情交給做架構的軟件工程師更好. java 就是把模塊管理與語言本身捆綁在一起,雖然形成了一個對程序員易用的 JDK .(一種程式必須用的生態圈子) 但是對最終用戶來說,安裝一個小程式,就不得不把一大砣的運行時庫都安裝在一起.非常不靈活方便. javascript 就好很多, 在瀏覽器裏,在 nodejs 裏都各有各的加載程式的方法.同時讓軟件工程師實現自己的模塊加載規則成爲可能. CommonJS,AMD 風格的加載方法都很好,但這並不妨礙你自己設計對你來說更合用的加載方法. 不管對什麼語言來說,我認爲模塊管理的設計要有兩個必要特點. 1 異步加載應該是缺省提供的 API. 2 要易於外部程式分析的同時,寫法上要夠簡化. 對於 javascript 我有足夠的實踐經驗,SWordFish 我想還是先交給有想法的人去做吧,也許有人會做成了象 npmjs.org , cpan.org 這樣的大的 SWordFish 模塊社區呢,也不好說. 動態語言同樣可以做超有大規模的代碼的項目與產品.SWordFish 的設計的一個目標就是通過簡化語法讓語言更易於交流爲目標的. 如果配合優秀的模塊管理架構就能讓你的工作事半功倍.合在一起成爲優秀的軟件開發套件.
瀏覽器中的 SWordFish
//todo:...
SWordFish 的歷史
多年以前,我爲了在 C 語言中嵌入一個腳本,用來方便用文本配置軟件行爲,同時還能支持些邏輯判斷的能力. 就寫了個 oscript 腳本語言.現在發佈在 http://sourceforge.net/projects/oscript 上.也有一個 java 的實現版本. 這個腳本語言幫了我很大的忙,我再也不用 INI 這種難用的東西寫軟件配置了. 但是如果作爲一個能面面具到編程工具來說,還很不夠用.我在工作中用過很多編程工具,都沒有我認爲很合手的. 在有了一點做編譯器的經驗之後,就設計了這個 SWordFish 編程語言. 但是我在工作中現在用的最多的還是 javascript .
javascript 的缺點
我認爲的 javascript 非常優秀,但還是有這幾個主要的我認爲的缺點. 1 ) 字串定义不支持多行. 2 ) 閉包的作用域( Runtime Scope )不能修改. 3 ) 操作符不能重载 , 4 ) 必須显示的返回函數值( return value )而且不能是多返回值. 我在 SWordFish 中把這些問題用不同辦法全部都解決了,上文中已有具體說明.
SWordFish 的已存在的問題
目前最大的可能被質疑的問題是性能方面.因爲目前它的一個可用版本是被實現在 javascript 宿主機之上的虛擬機. 但性能問題一直以來都與語言本身的關系不大.它涉及到硬件性能,IO效率,系統平臺,虛擬機的實現優化.等等方面. 我對 SWordFish 的使用大多數是在 GUI 開發方面.用來響應與用戶交互的各種具體事件邏輯上面.對於交互式 GUI 開發,事件處理的清晰正確是最重要的. 如果一個 UI 事要是引發了一個大計算量或大 IO 吞吐量的任務,大家都會要再建一個異步的 task , 這並不會妨礙 GUI 的快速響應. 所以在 javascript 之上的 SWordFish 對於 GUI 交互類程式在性能方面是夠用的. 當然如果可以,SWordFish 也能實現在 C 語言級別的硬件平臺上,我認爲現在這樣是夠用的,現在 CPU 很快,就算真不夠用,用戶可以導入 javascript 的函數進來,還不行就用 C 語言寫的本地平臺的函數導入進來.有針對性的實現優化,先要確定瓶頸在哪裏才是最有效果的解決問題,而不是什麼都要怪語言本身的問題. 還有另外一種方式 SWordFish 的直寫語法樹的結構很容易變換成任意一種語言的源代碼.在一定程度上能夠解決這類問題.
後記
作爲一個程式編碼人員,很多情況下,是沒有辦法選擇與決定技術架構的,那麼就要去敵適應,但是對於技術來說,在時效上,應用產品或項目上,都要面臨被淘汰或轉形的問題,舉例來說,今天你在一項目中,必須要用 java 來工作,但是過幾天你又開始了另一個工作,人家規定必須得在自家的某個 javasscript 框架上來工作,雖然多年的工作經驗讓你鍛鍊了編程思維,但在具體可操作的技術上,你不一定能每次都處於領先地位,而且框架形式上的跟進總是很累人的,都得深入了解其它人的制定的規則,適應性非常有侷限.這樣就沒有太多能留下來的真正的技能積累. 就算你能把寫過的一個 perl 算法對應的再寫一個 java 版本,也是要花很多代價的. 但是如果你一開始就用一個適應度很強的中間語言來寫這個算法,比如這個 SWordFish 那才是一個真正的技術積累,在不同的工作中,你只要把目標設定在了解不同的技術的核心之後, 得到你所需要的 做出一個轉譯工具.那就是一勞永逸的事情.
基於 nodejs 之上的 SWordFish 的實現 swordfish .
SWordFish 與 nodejs 都是一種編程語言運行環境,(我說是一種人機接口應應用程式,依賴操作系統,可訪問系統資源,文件,網絡...) 它們是一個平級的關係. 我現在的這個實現版本稱爲 swordfish 是一個程式命令名,不是 SWordFish ,這是編程語言名稱.就象 nodejs 與 javascript 的關系一樣.
以下是其它進一步更具體的更詳細的不斷補充的內容與問題:
swordfish 內置操作符和全局變量.
load
swordfish 的 load 操作
(load "include_script_file.sw")
這個操作會讀取一個文件,作爲 SWordFish 的源代碼,在當前的作用域下 eval 這段代碼. 如果所讀取的文件爲相對路徑,那麼就相對於所處腳本的源碼路徑,而不是程式運行期的工作目錄.
GetScriptURI
GetScriptURI 用來取到當前程式的路徑.
(GetScriptURI)
當然也可以取到非當前程式的路徑. "當前"是指: 使用(GetScriptURI)操作的代碼所在的函數代碼塊是由哪個具體路徑或URI加載起來的. "非當前"是指,這個操作可以指定一個作用域作爲參數,這樣就可以得到形成那個作用域的函數代碼塊的代碼資源加載URI. 與圖片,CSS 風格表 一樣,代碼也是一種資源,一般來說一個程式是由很多片代碼組成的,而且是熱加載,按需加載. 這樣每個作用域都能找到創建它的函數,第個函數都能找到它的加載URI.
(GetScriptURI global)
這個就是得到了根代碼的路徑.
================================
typeof
typeof 操作返回一個值的類型:
操作數若爲 SWordFish 的函數則返回 "lambda", 爲數組時就返回 "array", 其他情況暫時與 javascript 返回的是一至的.
swordfish 模塊管理的一个实现 package.sw
模塊设计: 類似於 AMD 風格 package.sw 是一個異步類型的模塊定義方式.
形式爲:
(package "hello" (lambda (NS)
(. NS "World" (lambda ()
((.console "log") "Hello" "World")))
))
說明一下: .sw 文件名後綴是用來指示此文件應該是 SWordFish 語言的代碼,當然你也可以用別的後綴名或不用.
函數 package 用來定義一個模塊,也叫一個包. 第一個參數爲字符串爲此包的名稱.最後一個參數爲一個函數,表示實際上的定義過程. 定義函數的第一個參數就是包的對象.你可以在這個函數裏做一些全局初始化動作,並且賦予包新的對外屬性和方法. 當然定義包的過程可以用多個方法走多次,我們是非常不建議這樣去用的. package 操作內部會看命名空間中是否已經存在包對象了,如果沒有就生成一個新包在命稱空間位置上,然後再把包對像作爲第一個參數送給定義過程.
package 是變參數的,首個參數與最後一個參數形式是不能改變的.在第一個包名後邊,還可以加多個包名,是什麼意思呢? 就是這個定義過程之前所必須依賴的其他包.package 在執行最後一個定義函數之前必須按變參所聲明的包名先加載所有依蘭的包後,再把它們按聲明順序當作第一個要被定義的包像對像的後續參數一起傳遞給定義函數.
(package "hello" "A" "B" "C" (lambda (NS A B C)
(. NS "World" (lambda ()
((.console "log") "Hello" "World")))
))
第一參數名爲什麼是 NS ? 它是 NameSpace 的縮寫,當然你也可以寫成 $ , Hello , exports 等等,能讓讀代碼的人方便知道它就是這個要被賦予新特性的包對像就行了. 這就是 package.sw 模塊管理器的基本用法.
一個明確的包最後與依賴的包之間形成一個樹狀關係.這樣便於程序代分析來裁減到優化最後發佈出來產品,以至不必於象大多數語言運行環行一樣帶太多沒有用的東西. 基於這種設計,需要非常注意的就是:包與包之間的依賴關系必須是單向的,不能是雙向的互相依賴,也不能是多包之間形成環狀依賴. 這就要求寫代碼的人更加嚴謹,更清晰的劃分模塊與模塊之間的關係.要把公用部份提取成獨立的模塊.
在模塊代碼存放結構是什麼樣的? 不管你的代碼系統是在網絡資源上還是在本地的文件系統中,一個模塊就存放一於一個資源文件單元中,並且只被一個函數定義. 文件名就是 包的最後的名子. 後綴爲 sw . 例如: 上邊 hello 模塊的文件名就是 hello.sw . 帶名稱空間的形式像這樣:
(package "org.swordfish.hello" (lambda ($)))
那麼代碼儲存在資源樹上的樣式爲: org/shordfish/hello.sw 名稱空間以點分割,如果對應文件系就是目錄樹.
模塊用另外模塊的形式就是上面說的集成在 package 上的變參,但是一般在主程式代碼段裏都在用現成的模塊,這種臨時的使用已有模塊的方法是: use 操作.
(use "app.info" (lambda (AppInfo)
))
樣子很像 package ,就是保留了依賴包形式,沒有了第一個必須一定是定義包名的參數要求.最後處理過程的函數還是一樣的.
以上就是 package.sw 模塊管理的一個形式要求,也就是它的使用規範. 具體的 package.sw 模塊管理器是與一個平臺相關的實現,它在 nodejs 下的 swordfish 中與在瀏覽器中的 javascript 環境中的具體實現與要求肯定是不一樣的.
比如說在網頁中最初加載它是怎麼樣的?支不支持即使的異步加載?因爲是網絡代碼加載環境,它是不是還要有緩衝機制與版本控制來優化用戶體驗?而在 swordfish 中就不需要考慮這種特定平臺的問題.
package.sw 包路徑 (Package Include Path):
在 nodejs 下的 swordfish 中有一內置的變量 $PATH_SWORDFISH . 它指示出當 swordfish 解析器所安裝的路徑 . 比如在
$ sudo npm install swordfish -g
之後: 安裝程式一般會創建解析器到 /usr/local/bin/swordfish 這裏,實際它是 /usr/local/lib/node_modules/swordfish/swordfish.js 的符號鏈接. 那麼 $PATH_SWORDFISH 的值就是 /usr/local/lib/node_modules/swordfish . 這有什麼好處呢? 我不想像其他語言一樣一上來就把包管理器內置在 swordfish 中,所以我提供了一些庫文件在 $PATH_SWORDFISH 中,包括包管理器 package.sw 本身. 這樣用戶就有機會選擇用或不用它. 我們看一個用的例子:
#!/usr/bin/env swordfish
/**
test2.sw
*/
require:(JSEval "require")
path: (require "path")
D: (. path "sep")
nodejs: (JSCallObject [$PATH_SWORDFISH "nodejs"] "join" D)
prime: (JSCallObject [".." "prime"] "join" D)
(load (+ nodejs D "package.sw"))
((. use "PushINC") prime)
(use "simply.log" (lambda (L)
(L "==hello==" $PATH_SWORDFISH)
(L "use INC:" (.use "INC"))
))
當 load package.sw 時 , package.sw 所在路徑就是第一個 Include Path. 然後再添加時用 use 裏的 PushINC 方法 它有一般有兩個參數 第一個要添加的路徑, 第二個指明 此路徑相對位置. 如果相對於當前所在的源代碼文件 可以這樣寫:
((. use "PushINC") XXXpath (GetScriptURI))
如果參數等於null,或沒有就相對於上邊已加載的 package.sw 所在的位置. 如:
((. use "PushINC") prime)
如果想不指定相對於誰的基準位置,你就是想設一個相對於工作目錄的相對路徑或系經絕對路徑,那麼第二個參數要爲 true , 表式不做翻譯,這樣一般較少見.
((. use "PushINC") "/opt/mylibpath" true)
但是你可提高加入的路徑的查找順序,可以放第三個參數,true 指明把它排在最前.
((. use "PushINC") "/opt/mylibpath" true, true)
所有 Include Path 都在 use 的 "INC" 屬性裏, 它是個數組 :你也可以用 (.use "INC") 操作來直接訪問它.
額外說明一下: $PATH_SWORDFISH 變量的初始值可以通過系統環境變量 PATH_SWORDFISH 手動修改.
我們一般推薦的代碼文件的存放結構是這樣的:
在 swordfish 安裝目錄中附加的庫工具也是這樣佈局的.
bin
explorer
nodejs
prime
projects
worker
bin 用來放由 swordfish 寫成可執行文件. explorer 是存放在瀏覽器中特有的模塊代碼所在地. nodejs 就是放 swordfish 的特有平臺中能用的模塊. prime 這是一個放與平臺無關的代碼模塊. projects 可以存放你在開發的一些項目. worker 這是 WebWorker 環境中的代碼存放地.
以上邊的例子來說:一般主程式文件寫代碼時都是這樣的:
(load (+ nodejs D "package.sw"))
((. use "PushINC") prime)
而在運行期間對於一個模塊的定位就是先 nodejs 目錄再 prime 目錄.
代碼編輯器的一些問題.
因爲 swordfish 有一些符合 lisp 語法, 我修改了一下 Sublime 3 的 lisp 插件讓它能在 Sublime 3 下比較漂亮顯示出來. 下載地址爲: http://iwebapp.github.io/swordfish/tools/Lisp.sublime-package 把它複製到你機器上 Sublime 3 的安裝目錄中的 Packages 文件夾下就行. 比如像這樣的目錄: /opt/sublime_text/Packages.
運行以上 Hello World 程式:
$./hello.sw John
結果如下:
each 數組元素迭代操作.
each 操作第一個參數必須是數組,第二個參數爲迭代器函數, 如果有第三個參數,就是每次迭代後的結果要不要合並成一個新的數組作爲此操作的最後返回值(缺省值爲 false). 如果還有其他參數,就作爲迭代器函數附加參數, 迭代器參數順序規格是(item, index, array, @ohters) 迭代器內部可以返回(continue)和 (break) continue 異常會使此次迭代結果被忽略. break 異常會使此次迭代結果被忽略,並中斷之後的所有的迭代.如果你不是要一個新的數組,each將返回break出來的值.
array:[1,2,3,4]
((. array "forEach") (JSCallback (lambda (item)
((. console "log") "Hi:" item)
)))
這是一利用javascript能力的數組迭代示例,它要用 JSCallback 有點繁瑣,而且功能有限. 用each 來寫它應該是這樣的:
(each array (lambda (item)
((. console "log") "Hi:" item)
))
它們的結果都是一樣的:
Hi: 1
Hi: 2
Hi: 3
Hi: 4
以下示例簡單演示 break continue 在 each 中的用法:
array:[1,2,3,4,5,6]
newArray:(each array (lambda (item,idx,array,@ohters)
(cond {(== idx 1)
(continue)
}{(== idx 4)
(break)
})
((. console "log") "Hi==:" item ohters)
item=(- item)
),true,"this" "is" "others")
((. console "log") "<<<<<==newArray:" newArray)
結果爲:
Hi==: 1 [ 'this', 'is', 'others' ]
Hi==: 3 [ 'this', 'is', 'others' ]
Hi==: 4 [ 'this', 'is', 'others' ]
<<<<<==newArray: [ -1, -3, -4 ]
所以 each 操作爲可以做 javascript Array 的 map , some , forEach , every ,等等需要迭代才能做的能力,還更有靈活性. 第三個參數(isReturnNewArray),加上擴充參數.還是內部的 contiune , break,都明確直觀的讓你控制迭代的細節.
####編程語言的生態圈 SWordFish 的編譯器與運行器,可以用 C 語言來寫,從而使它運行在更接近硬件的層面,從而象其它的有虛擬機的語言運行環境一樣有較好的性能. 現在的swordfish的實現不是這樣的,有一個原因是:我作爲這個語言的設計者,只是提出了這樣的一個設計,對於個人而言沒有太多精力面面具到的把它實現出來.當然如果有人能夠做這樣的事是再最好不過了! 另一個原因是:作爲輔助自己工作與生活的這樣一種工具,它能整合最大的可利用的現有資源也是非常重要的.這有點象 perl 一樣,當時被人家稱之爲"膠水語言",也許是當時它非常流行的一個重要的原因.但是現在是網絡時代,富客戶端,基於 HTML5 的瀏覽器應用方面的開發站很大的比重.爲了在這樣的環境下,整合這樣的開發資源就顯的很重要,而 perl 不能被瀏覽器所支持,也是它用的人減少的一個原因. 所以,我自認爲 SWordFish 很優秀,但作爲個人而言沒有更多資源去開發一個支持它的瀏覽器,並讓它流行起來.所以就利用現有的資源好了.所以它現在是建立在 javascript 之上,就能無縫地利用已有的基於 javascript 的工作經驗和代碼庫,等等資源來優化你自己的工作. 至於性能的問題,之前已說過了,我們有不同的理解,性能的問題不是語言本身的問題,它是一種實現方法在多種約束條件下的效果,所以有多種解決辦法. 用原生的機器代碼只是一種可以提高性能的方法之一. 之前開發 web 應用,開發者在前端與後端的代碼都要用兩個以上的開發語言,有了 nodejs 就有了另一種更好的選擇.從編程語言層面上讓開發者保持一至的開發體驗,前端與後端就能共享好多同類的代碼. SWordFish 的目標也是一樣.你可以把它當成 Web 時代的新膠水語言好了. 能深度对接於各種平臺,環境也是語言實現者要考慮的重要事情,畢竟我們不是僅僅出於興趣,爲了學術研究.我們要用它來提高工作效率,積累可再生的技術經驗. 編程語言的設計本身也是一種產品,一般來說它面向程式開發者,系統管理員.所以要更加注重用戶的體驗. SWordFish 就是爲了這樣一種目標而設計的編程語言.
####應用示例