LoveString基礎(chǔ)
注意:我不打算實(shí)際構(gòu)建這門語言!
設(shè)計(jì)目標(biāo)
完全基于Dictionary、僅有String類型的語言,無敵炫酷的玩具。
先來個(gè)Hello World吧!
func init:
????print: "Hello World!"
從了解符號開始
兩種引號,
[]
圍住的部分為Key
,""
圍住的部分為Value;=
作為賦值符號使用。#
與LF
圍住的部分將被視為注釋。%
為“Value化”符號,用于轉(zhuǎn)義和格式字符串。print: "%"%"" ?#""
print: "My name is %s, welcome to learn %s%s." % "John""Love""String"
# My name is John, welcome to learn LoveString.
()
圍住的部分稱為優(yōu)先行,用于提高運(yùn)算優(yōu)先級,以及跨行書寫同一語句;優(yōu)先行會嘗試捕獲一個(gè)Value。Space
用于分隔關(guān)鍵字。因此,各個(gè)關(guān)鍵字之間必須存在空格(除非換行)。LF
用于分隔語句(即:每行代表一個(gè)語句),Tab
用于表明代碼的局部級別。要求以
:
作方法關(guān)鍵字的后綴,唯一例外是func:
(定義一個(gè)匿名函數(shù))
除=
為從右向左結(jié)合,其余關(guān)鍵字全部為從左向右結(jié)合。
LoveString的字典
入手字典,先從Key
和Value
開始??梢院唵蔚匕?code>Key理解為變量名,Value
理解為變量值,但實(shí)際上更為簡單。
一般的字典攜帶一系列的鍵值對
或鍵鍵對
,現(xiàn)在我們來定義一個(gè)一般的字典:
root[My First Dictionary] = {
? ??[1] = "Love"
????[2] = "String"
}
root
關(guān)鍵字代表對根字典進(jìn)行操作,后面我們會對根字典作出詳細(xì)的說明。
由于空格分詞,{
不要換行;由于字典結(jié)束是一個(gè)單獨(dú)的語句,}
需要換行。
添加鍵,刪除鍵
不存在的Key對應(yīng)的Value是""
,即空字符串。因此:
添加一個(gè)鍵,只需訪問一個(gè)不存在的鍵,然后賦值;
刪除一個(gè)鍵,只需將一個(gè)存在的鍵賦為空值。
示例如下:
func init:
????root[my_new_key_1] = "Love"
????root[my_new_key_2] = "String"
????root[to_be_erased] = "Foobar"
????root[to_be_erased] = ""
????print: [undefined_key] ?#輸出一個(gè)空行
方法
方法是代碼段的封裝,可以用于支持外部語言、實(shí)現(xiàn)代碼復(fù)用、標(biāo)記程序入口等?,F(xiàn)在我們來定義一個(gè)一般的方法:
func my_method: [var1]
????[var2] = "String"
????print: ([var1] + [var2])
然后執(zhí)行它:
func init:
????my_method: "Love"
# LoveString
從定義過程中可以看出,方法包含一個(gè)自定義關(guān)鍵字、一個(gè)內(nèi)部字典和一系列的語句。以func
開頭的行,將檢測第一個(gè)空格后的關(guān)鍵字名,并對第二個(gè)空格后的鍵進(jìn)行“導(dǎo)出”。
執(zhí)行方法時(shí),內(nèi)部字典從空字典開始,先將傳入的鍵/值寫入內(nèi)部字典,然后執(zhí)行其后的一系列語句。最后,把內(nèi)部字典全部置空。
使用return關(guān)鍵字能終結(jié)方法的執(zhí)行,并“導(dǎo)出”一個(gè)局部變量,這點(diǎn)與其它語言是一致的。
特別地,使用return_self
可以保證方法“逐級蠶食”后面的方法,理解這點(diǎn)需要一些匿名函數(shù)的知識。
鍵名是靜態(tài)的
遇到這樣一個(gè)代碼段怎么辦?
func my_add_root: [sth]
????root[sth] = [sth]
答:將傳入的[sth]鍵/值寫入根字典的名為sth
的鍵。
我們強(qiáng)烈不建議用動態(tài)鍵名擴(kuò)充字典,但真的想這么做的話,可以使用add
家族的方法:
func my_dynamic_add: [name][value]
????add_root: [name][value]
????add_to: root[dynamicadds][name][value]
func init:
????my_dynamic_add: "my_new_key_1""Love"
????my_dynamic_add: "my_new_key_2""String"
????print: (root[my_new_key_1] + root[dynamicadds][my_new_key_2])
# LoveString
add_root方法取第一個(gè)參數(shù)的最終值作為鍵名,將第二個(gè)參數(shù)以引用形式存儲到根字典中。
add_to方法相較第一個(gè)方法在一開始多一個(gè)“目的地”。
默認(rèn)值參數(shù)
考慮LoveString“未定義即空”的特性,我們允許執(zhí)行方法時(shí)傳入任意項(xiàng)參數(shù),但需要注意賦值的目標(biāo)問題:
func 123: [1][2][3]
????root[1] = [1]
????root[2] = [2]
????root[3] = [3]
func init:
????123: "Love""String"
????print: (root[1] + root[2]) #LoveString
????print: (root[1] + root[3]) #Love
????123: "Love""""String"
????print: (root[1] + root[2]) #Love
????print: (root[1] + root[3]) #LoveString
????123: "Love""""""String" #在這里,"String"被忽略了
????print: (root[1] + root[2]) #Love
????print: (root[1] + root[3]) #Love
為此,我們提供一個(gè)小小的語法糖,方便定義函數(shù)的默認(rèn)值:
func 123: [1][2][3]
????default[1] = "Love "
????default[2] = "String "
????default[3] = "Forever"
????return ([1] + [2] + [3])
效果如下:
func init:
????print: (123:) ?#Love String Forever
????print: (123: "Love""String") ?#LoveString Forever
????print: (123: "Love""String""") ?#LoveString
傳入一個(gè)方法,還是一個(gè)值?
上一節(jié)中,您可能已經(jīng)注意到我們對于嵌套方法的處理方式了:總是用優(yōu)先行圍住先執(zhí)行并傳值的方法。
如果不這樣做會怎么樣?下面給出一個(gè)有趣的例子:
func sth_lambda: [1]
????print: "I am a Lambda!"
????print: [1]
func init:
????print: sth_lambda: "Not Ignored"
# ----------------?
# func sth_lambda: [1]?
# ????print: "I am a Lambda!"?
# ??? print: [1]?
# ----------------?
# Not Ignored
在此例子中:
我們向print:
方法傳入了sth_lambda
方法,于是print
方法打印出了sth_lambda
方法的定義,并返回了一個(gè)print:
?匿名函數(shù)。
之后,新的print:
匿名函數(shù)與字面量"Not Ignored"結(jié)合,于是打印出了一行"Not Ignored"語句。
怎么調(diào)用一個(gè)傳入的方法呢?這里也只需要使用優(yōu)先行:
func method_to_be_called:
????print: "Called"
func method_passer: [method]
????if ([method] isfunc) -> func:
????????([method]) #這樣書寫,傳入的方法就會被執(zhí)行
????????root[method] = [method]
????else -> return
func init:
????method_passer: method_to_be_called:
????(root[method])?
# Called
# Called
之后再展開講解程序的選擇與循環(huán)結(jié)構(gòu)。
傳入兩個(gè)方法,還是遵照傳遞性進(jìn)行運(yùn)算?
始終記住,分詞器依賴空格工作,方法的各個(gè)參數(shù)之間不用空格分割。
因此:
myfunc: func1:func2:
?是向myfunc:傳入兩個(gè)方法;
myfunc: func1: func2:
是向myfunc:傳入方法func1:
,如果返回了方法,則向返回方法傳入func2:
;否則func2:
?將被無視。
print:
在設(shè)計(jì)時(shí)同時(shí)支持return_self
式的運(yùn)算符重載以及任意數(shù)量的可變參數(shù),因此print:
后各個(gè)參數(shù)間寫空格與不寫空格等效。
值傳遞與引用傳遞(了解即可)
閱讀到這里就可以展開講解參數(shù)傳遞方式了。
LoveString對于“需要生成新值”的行為采取值傳遞,即傳遞字典的值;
對于其他行為,如賦值、函數(shù)傳參,則使用引用傳遞。
如何實(shí)現(xiàn)引用傳遞呢?這里將介紹我們的內(nèi)存模型。
程序初始狀態(tài)下存在一個(gè)root_map,存儲了Key的hash和字面量/字典的引用。賦值過程的流程如下:
判斷賦值動作是
Key-Key
型,Key-Value
型,還是Key-Dict
型Key-Key
型:對右側(cè)Key進(jìn)行hash處理,遍歷root_map來獲得該key對應(yīng)的引用;然后對左側(cè)key進(jìn)行hash處理,在root_map新增左側(cè)Key-hash
的記錄;Key-Value
型:開辟內(nèi)存寫入Value,然后在root_map新增一條記錄;Key-Dict
型(將鍵與字典字面量綁定):為該字典字面量開辟submap,進(jìn)行Value寫入;root_map內(nèi)新增內(nèi)容為Key-Hash、submap引用
的記錄。
每個(gè)Value和Submap均綁定一個(gè)引用計(jì)數(shù)。每個(gè)賦值動作均會導(dǎo)致對舊Value/Submap和新Value/Submap的引用計(jì)數(shù)變化與檢測。引用計(jì)數(shù)歸零時(shí),該Value/Submap會被釋放;Submap的釋放過程中,其內(nèi)各Value的引用計(jì)數(shù)均會減一。
循環(huán)引用問題(了解即可)
考慮如下的代碼段:
func init:
? ? root[A] = {
? ???? [sthB] = ""
????}
????root[B] = {
? ?????[sthA] = root[A]
????}
?
??root[A][sthB] = root[B]
這個(gè)代碼段將按以下順序工作:
root_map中新建
A
,然后構(gòu)建Submap(A),內(nèi)含的sthB
指向空字符串。root_map中新建
B
,然后構(gòu)建Submap(B),內(nèi)含的sthA
對應(yīng)了Submap(A)的引用。修改Submap(A)中的
sthB
使包含對Submap(A)的引用。
這時(shí)兩個(gè)Submap彼此引用,導(dǎo)致引用計(jì)數(shù)無法歸零。為了解決這樣的循環(huán)引用問題,每個(gè)Map(root_map或Submap)會預(yù)先包含一個(gè)SubmapList供賦值時(shí)檢查。
循環(huán)引用的賦值時(shí)檢查僅在Debug模式工作。
檢查到循環(huán)引用時(shí),程序會形成一個(gè)斷點(diǎn)。請手動修復(fù)這樣的循環(huán)引用問題。
根字典
在從了解符號開始
一節(jié)中,提到了用func:
?定義匿名函數(shù)的方法。現(xiàn)在我們可以對字典的定義作一點(diǎn)簡單的擴(kuò)充:
字典是一個(gè)記錄了一系列Key - Value引用
的Map(root_map或Submap),Key是一個(gè)hash化的名稱,Value包含了對String字面量的引用、對Submap的引用、匿名函數(shù),三者之一。
到此,可以給出根字典的定義:
根字典是字典的超集。除了Key - Value
關(guān)系表之外,根字典允許您使用func
定義具名方法。因此,具名方法不能嵌套定義。此外,根字典自帶一個(gè)叫init:
的虛方法,其在全局變量全部加載完成后自動執(zhí)行,作為程序入口而存在。
init:
虛方法可以攜帶參數(shù)。
LoveString將每個(gè)程序文件視為一個(gè)根字典,沒有通過腳本新建根字典的方法。
運(yùn)算符
作為高級語言,總不能僅僅賦一堆值就完事了吧?
于是我們嘗試引入一些運(yùn)算符
。在此提醒兩點(diǎn):
運(yùn)算符是特殊的關(guān)鍵字,空格不能少!
關(guān)鍵字出現(xiàn)結(jié)合失敗的情形時(shí),其后的關(guān)鍵字和元素都會被忽略。
上文介紹了%
的兩種用法,在這里作出簡單的補(bǔ)充:
%% :形成一個(gè)百分號
%" : 形成一個(gè)引號
%n :換行
%t :Tab縮進(jìn)
%s :字符串替換符
對%s作格式替換的例子:
[sth] % "Value1"[Value2][Dict3](func4:)func5:func6:
依次為:String字面量"Value1",[Value2]對應(yīng)的值,[Dict3]對應(yīng)Submap的文本輸出,方法
func4:
的值,func5:``func6:
的定義。
接下來引入幾種布爾運(yùn)算符(優(yōu)先級上高下低左高右低):
==
?!=
?:等值檢測and
?or
?not
:邏輯與/或/非in
:若前者為Key,后者對應(yīng)字典,且后者對應(yīng)的字典包含了該Key,則返回true
。使用
self
訪問方法的內(nèi)部字典。isstr``isdict
?isfunc
:類型判斷,分別對應(yīng)String/字典/函數(shù)isroot
:檢查根字典是否有對該字面量的引用isroot_deep
:檢查根字典及其子字典是否有對該字面量的引用(很慢)
對于布爾運(yùn)算符的返回值,作出如下約定:
空字符串代表“非”,含內(nèi)容的字符串代表“是”(一般記錄為"true")
關(guān)鍵字
false
代表“非“,true
代表“是”
高級優(yōu)先行
高級優(yōu)先行
是特殊的優(yōu)先行,是LoveString擴(kuò)展模塊的有力語法支撐。官方計(jì)劃維護(hù)以下幾種高級優(yōu)先行:
group()
是字典結(jié)構(gòu)的語法糖:
root[current_color] = group("1.0""1.0""1.0""1.0")
# 相當(dāng)于
root[current_color] = {
? ??[1] = "1.0"
? ??[2] = "1.0"
????[3] = "1.0"
????[4] = "1.0"
}
此外,group()
為方法提供了可變參數(shù)支持:
func grouped_function: group()
????for [i] in self -> func:
????????print: [i]
????return "That's all, thanks."
func init:
????print: (grouped_function: "LoveString""Mutable Amount of Arguments")
# LoveString
# Mutable Amount of Arguments
# That's all, thanks.
math()
?會將傳入的Value全部轉(zhuǎn)成數(shù)字(失敗時(shí)則轉(zhuǎn)成0),并提供了一系列符合數(shù)學(xué)書寫習(xí)慣的表達(dá)式:
root[math_instance] = math( 3^2 + 6%5 + root(4)by(2) + exp(ln2) )
func init:
????print: root[math_instance]
# 14
注意:該高級優(yōu)先行使用了不同的分詞器,以盡可能符合正常數(shù)學(xué)表達(dá)式的書寫習(xí)慣。
duo()
允許傳入兩個(gè)Value和一個(gè)高級運(yùn)算符(“先計(jì)算再賦值”運(yùn)算符,以及大小比較類運(yùn)算符),Value會被轉(zhuǎn)換為數(shù)字:
root[my_set] = duo( root[my_advanced_pass] = "5.0" )
root[my_set] = duo( root[my_set] += "1")
root[compare] = duo( root[my_set] > "5" )
func init:
????print: root[my_set]root[compare]
# 6.0
# true
在循環(huán)結(jié)構(gòu)中還會再介紹幾種高級優(yōu)先行。
匿名函數(shù)與“導(dǎo)向“算符
現(xiàn)在,我們掌握了了基于自定義關(guān)鍵字的方法
。教程的一開始我們也透露了用func:
關(guān)鍵字定義的匿名函數(shù)
,怎么把這兩種元素統(tǒng)一起來呢?
現(xiàn)在我們可以使用“導(dǎo)向”算符->
?幫助我們完成此操作。“導(dǎo)向”算符用于向匿名函數(shù)傳參,并將具名方法等連接到匿名函數(shù)或優(yōu)先行:
root[myfunc] = func: [a][b][c]
????if ([d] in self) -> (print: [d])
????print: [a][b][c]
????if (not [c] in self) -> (print: "%n--String Lover)
func my_func: [d] -> root[myfunc]
func init:
????print: (root[myfunc] -> "Love""String""Forever")
????print:
????print: (my_func: "I""love""string""forever.")
# Love?
# String?
# Forever?
# --Srting Lover?
#?
# I?
# love?
# string?
# forever.
通過“導(dǎo)向”連接具名方法和匿名函數(shù)時(shí),具名方法的參數(shù)、匿名函數(shù)的參數(shù)都是內(nèi)部字典的Key。這允許你將匿名函數(shù)導(dǎo)向具名方法時(shí),要求具名方法傳入更多的參數(shù)。
從示例中還可以挖掘到我們設(shè)計(jì)的語法糖:具名方法“多出來”的參數(shù)可以流入導(dǎo)向的匿名函數(shù)內(nèi)。
注意:
沒有匿名函數(shù)“導(dǎo)向”匿名函數(shù)的語法,“導(dǎo)向”的左側(cè)若是匿名函數(shù),則向匿名函數(shù)傳參。
如果不導(dǎo)向具名方法的話,不裝入優(yōu)先行的匿名函數(shù)不會執(zhí)行。
LoveString禁止了具名方法執(zhí)行時(shí)導(dǎo)向參數(shù),以保證具名方法的統(tǒng)一性和分詞器設(shè)計(jì)的簡潔性。
結(jié)構(gòu)式
本節(jié)中,介紹程序設(shè)計(jì)中非常關(guān)鍵的選擇結(jié)構(gòu)及循環(huán)結(jié)構(gòu)。
先從結(jié)構(gòu)式開始:結(jié)構(gòu)式是形如condition -> callable
的語句,callable
允許無參關(guān)鍵字、具名方法、匿名函數(shù)及優(yōu)先行;為了降低語法復(fù)雜度,結(jié)構(gòu)式自身不能向callable
傳入自定義參數(shù)。但是,我們可以給出一個(gè)“結(jié)構(gòu)式傳參”的示例:
root[arg_to_structureline] = func: [a][b][c]
????foo: [a][b]
????bar: [c]
func init:
????if (true) -> (root[arg_to_structureline] -> root[a]root[b]root[c])
選擇結(jié)構(gòu)的結(jié)構(gòu)式是if () -> callable
。
強(qiáng)制要求用優(yōu)先行(包含高級優(yōu)先行)表示條件,以確保能捕獲到返回值。
我們深知匿名函數(shù)是一種相當(dāng)晦澀的設(shè)計(jì),因此大多數(shù)情況只需使用如下的寫法:
# 單行
func init:
????root[input] = receive_and_pause:
????if (root[input] == "hp+1") -> duo(root[hp] += 1)
????else -> return
????return_self
# 多行
func init: [sth]
????if ([sth] == "") -> func:
????????[sth] = "String"
????????[lover] = "Lover"
????????print: ([sth] + " " + [lover])
# 該多行示例的輸出結(jié)果(執(zhí)行時(shí)不傳參):String Lover
注意:結(jié)構(gòu)式不自帶字典,但匿名函數(shù)自帶字典!
來到while
循環(huán),我們提供了while_true () -> callable
和before () -> callable
兩種結(jié)構(gòu)式,見名知義。
for
家族則更加常用一些,我們提供了以下幾種遍歷模式:
# 字典遍歷模式
for [i] in root -> func1:
for "sth" in root -> func2:
deepfor [i] in root -> func3:
deepfor "sth" in root -> func4:
# 高級優(yōu)先行模式
for "Values" in range("0""100") -> print:
for "Values" in ftb("0""100""10") -> print:
可以看出for
家族的通式:for A in B -> callable
。
其中,A
如果是Key的話,會自動向“導(dǎo)向”的callable
傳入一個(gè)Key;如果是Value的話,則傳入一個(gè)鍵名字面量。
字典遍歷模式中,B
一般是指向字典的元素,以root
和self
居多,也可以是自定義字典。
????如果B
部分是空字典的話,該循環(huán)被跳過。
????如果B
部分是一個(gè)Value的話,for
循環(huán)將會執(zhí)行一次,此時(shí)傳入的都是該Value。
對于高級優(yōu)先行模式,range()
優(yōu)先行要求內(nèi)含兩個(gè)數(shù)字,左閉右開,效果如該例:
func init:
????for "Values" in range(0,2) -> print:?
# 0
# 1
ftb()
是"from-to-by"的縮寫,ftb([A][B][C])
表示從[A]開始(左閉),到[B]結(jié)束(右開),每次步進(jìn)[C]個(gè)單位,效果如該例:
func init:
????for "Values" in ftb("0""50""10) -> print:
# 0
# 10?
# 20?
# 30?
# 40?
deepfor
比較少用,但可以把傳入的字典所包含的子字典一并遍歷。高級表達(dá)式模式中,for
與deepfor
完全等價(jià)。
在LoveString中運(yùn)用OOP思想
通過多層次的字典設(shè)計(jì),可以實(shí)現(xiàn)面向?qū)ο蟮姆庋b性。
通過編寫“模板”,可以形成實(shí)例,也可以完成繼承。例如:
root[Templates][Character] = {
? ??[HP] = "100"
? ? [MP] = "75"
}
root[Templates][Wizard] = (
? ? root[Templates][Character] + {
? ? ? ??[MP] = "200"
? ? ? ??[Skill] = "Magic"
? ? }
)
對于字典,
+
是合并運(yùn)算符,深拷貝右側(cè)字典,然后對左側(cè)字典順次賦值改鍵。“成員變量私有化“
子字典中加入
[lock] = "true"
,[lock] = "before_addup"
這樣的鍵值對,則只能通過向上一級的匿名函數(shù)傳參來訪問該子字典。僅在Debug模式起作用。