前言
都說(shuō)程序員的三大浪漫是:操作系統(tǒng)、編譯原理、圖形學(xué);最后的圖形學(xué)確實(shí)是特定的專(zhuān)業(yè)領(lǐng)域,我們幾乎接觸不到,所以對(duì)我來(lái)說(shuō)換成網(wǎng)絡(luò)更合適一些,最后再加上一個(gè)數(shù)據(jù)庫(kù)。
這四項(xiàng)技術(shù)如果都能掌握的話那豈不是在 IT 行業(yè)橫著走了,加上這幾年互聯(lián)網(wǎng)行業(yè)越來(lái)越不景氣,越底層的技術(shù)就越不可能被替代;所以為了給自己的 30+ 危機(jī)留點(diǎn)出路,從今年上半年開(kāi)始我就逐漸開(kāi)始從頭學(xué)習(xí)編譯原理。
功夫不負(fù)有心人,經(jīng)過(guò)近一個(gè)月的挑燈夜戰(zhàn),每晚都在老婆的催促下才休息,克服了中途好幾次想放棄的沖動(dòng),終于現(xiàn)在完成了 GScript 一個(gè)預(yù)覽版。
預(yù)覽版的意思是語(yǔ)法結(jié)構(gòu)與整體設(shè)計(jì)基本完成,后續(xù)更新也不太會(huì)改動(dòng)這部分內(nèi)容、但還缺少一些易用功能。
特性
首先來(lái)看看保留環(huán)節(jié), GScript 是如何編寫(xiě) hello world
的。
hello_world.gs:
println("hello?world");
??gscript?hello_world.gs
hello?world
廢話說(shuō)完了接下來(lái)重點(diǎn)聊聊 GScript
所支持的特性了。
后文會(huì)重點(diǎn)說(shuō)明每一個(gè)特性。
例子
除了剛才提到的 hello world,再來(lái)看一個(gè)也是示例代碼經(jīng)常演示的打印斐波那契數(shù)列
。
void?fib(){
????int?a?=?0;
????int?b?=?1;
????int?fibonacci(){
????????int?c?=?a;
????????a?=?b;
????????b?=?a+c;
????????return?c;
????}
????return?fibonacci;
}
func?int()?f?=?fib();
for?(int?i?=?0;?i?5;?i++){
????println(f());
}
輸出結(jié)果如下:
0
1
1
2
3
整體寫(xiě)法與 Go 官方推薦的類(lèi)似:https://go.dev/play/p/NeGuDahW2yP
//?fib?returns?a?function?that?returns
//?successive?Fibonacci?numbers.
func?fib()?func()?int?{
?a,?b?:=?0,?1
?return?func()?int?{
??a,?b?=?b,?a+b
??return?a
?}
}
func?main()?{
?f?:=?fib()
?//?Function?calls?are?evaluated?left-to-right.
?fmt.Println(f(),?f(),?f(),?f(),?f())
}
都是通過(guò)閉包變量實(shí)現(xiàn)的,同時(shí)也展示了 GScript
對(duì)閉包、函數(shù)的使用,后文詳細(xì)介紹閉包的用法。
語(yǔ)法
GScript
的語(yǔ)法與常見(jiàn)的 Java/Go
類(lèi)似,所以上手非常簡(jiǎn)單。
基本類(lèi)型
先來(lái)看看基本類(lèi)型,目前支持 int/string/float/bool
四種基本類(lèi)型以及 nil
特殊類(lèi)型。
變量聲明語(yǔ)法和 Java
類(lèi)似:
int?a=10;
string?b,c;
float?e?=?10.1;
bool?f?=?false;
個(gè)人覺(jué)得將類(lèi)型放在前面,代碼閱讀起來(lái)會(huì)更清晰一些,當(dāng)然這也是個(gè)人喜好。
數(shù)組
//?聲明并初始化
int[]?a={1,2,3};
println(a);
//?聲明一個(gè)空數(shù)組并指定大小
int[]?table?=?[4]{};
println();
//?向數(shù)組?append?數(shù)據(jù)
a?=?append(a,4);
println(a);
for(int?i=0;i//?通過(guò)下標(biāo)獲取數(shù)組數(shù)據(jù)
int?b=a[2];
println(b);
其實(shí)嚴(yán)格來(lái)講這并不算是數(shù)組,因?yàn)樗牡讓邮怯?Go
切片實(shí)現(xiàn)的,所以可以動(dòng)態(tài)擴(kuò)容。
以這段代碼為例:
int[]?a=[2]{};
println("數(shù)組大小:"+len(a));
a?=?append(a,1);
println("數(shù)組大小:"+len(a));
println(a);
a[0]=100;
println(a);
輸出:
數(shù)組大小:2
數(shù)組大小:3
[??1]
[100??1]
Class
類(lèi)的支持非常重要,是實(shí)現(xiàn)面向?qū)ο蟮幕A(chǔ),目前還未完全實(shí)現(xiàn)面向?qū)ο螅粚?shí)現(xiàn)了數(shù)據(jù)與函數(shù)的封裝。
class?ListNode{
????int?value;
????ListNode?next;
????ListNode(int?v,?ListNode?n){
????????value?=v;
????????next?=?n;
????}
}
//?調(diào)用構(gòu)造函數(shù)時(shí)不需要使用 new 關(guān)鍵字。
ListNode?l1?=?ListNode(1,?nil);
//?使用 . 調(diào)用對(duì)象屬性或函數(shù)。
println(l1.value);
缺省情況下 class
具有無(wú)參構(gòu)造函數(shù):
class?Person{
?int?age=10;
?string?name="abc";
?int?getAge(){
??return?100+age;
?}
}
//?無(wú)參構(gòu)造函數(shù)
Person?xx=?Person();
println(xx.age);
assertEqual(xx.age,?10);
println(xx.getAge());
assertEqual(xx.getAge(),?110);
得益于 class
的實(shí)現(xiàn),結(jié)合剛才的數(shù)組也可以定義出自定義類(lèi)型的數(shù)組:
//?大小為?16?的?Person?數(shù)組
Person[]?personList?=?[16]{};
函數(shù)
函數(shù)其實(shí)分為兩類(lèi):
- 普通的全局函數(shù)。
- 類(lèi)的函數(shù)。
本質(zhì)上沒(méi)有任何區(qū)別,只是所屬范圍不同而已。
//?判斷鏈表是否有環(huán)
bool?hasCycle(ListNode?head){
????if?(head?==?nil){
????????return?false;
????}
????if?(head.next?==?nil){
????????return?false;
????}
????ListNode?fast?=?head.next;
????ListNode?slow?=?head;
????bool?ret?=?false;
????for?(fast.next?!=?nil){
????????if?(fast.next?==?nil){
????????????return?false;
????????}
????????if?(fast.next.next?==?nil){
????????????return?false;
????????}
????????if?(slow.next?==?nil){
????????????return?false;
????????}
????????if?(fast?==?slow){
????????????ret?=?true;
????????????return?true;
????????}
????????fast?=?fast.next.next;
????????slow?=?slow.next;
????}
????return?ret;
}
ListNode?l1?=?ListNode(1,?nil);
bool?b1?=hasCycle(l1);
println(b1);
assertEqual(b1,?false);
ListNode?l4?=?ListNode(4,?nil);
ListNode?l3?=?ListNode(3,?l4);
ListNode?l2?=?ListNode(2,?l3);
bool?b2?=?hasCycle(l2);
println(b2);
assertEqual(b2,?false);
l4.next?=?l2;
bool?b3?=?hasCycle(l2);
println(b3);
assertEqual(b3,?true);
這里演示了鏈表是否有環(huán)的一個(gè)函數(shù),只要有其他語(yǔ)言的使用基礎(chǔ),相信閱讀起來(lái)沒(méi)有任何問(wèn)題。
add(int?a){}
當(dāng)函數(shù)沒(méi)有返回值時(shí),可以聲明為 void 或直接忽略返回類(lèi)型。
閉包
閉包我認(rèn)為是非常有意思的一個(gè)特性,可以實(shí)現(xiàn)很靈活的設(shè)計(jì),也是函數(shù)式編程的基礎(chǔ)。
所以在 GScript
中函數(shù)是作為一等公民存在;因此 GScript
也支持函數(shù)類(lèi)型的變量。
函數(shù)變量聲明語(yǔ)法如下:func typeTypeOrVoid '(' typeList? ')'
//?外部變量,全局共享。
int?varExternal?=10;
func?int(int)?f1(){
?//?閉包變量對(duì)每個(gè)閉包單獨(dú)可見(jiàn)
?int?varInner?=?20;
?int?innerFun(int?a){
??println(a);
??int?c=100;
??varExternal++;
??varInner++;
??return?varInner;
?}
?//?返回函數(shù)
?return?innerFun;
}
// f2 作為一個(gè)函數(shù)類(lèi)型,接收的是一個(gè)返回值和參數(shù)都是 int 的函數(shù)。
func?int(int)?f2?=?f1();
for(int?i=0;i<2;i++){
?println("varInner="?+?f2(i)?+?",?varExternal="?+?varExternal);
}
println("=======");
func?int(int)?f3?=?f1();
for(int?i=0;i<2;i++){
?println("varInner="?+?f3(i)?+?",?varExternal="?+?varExternal);
}
最終輸出如下:
0
varInner=21,?varExternal=11
1
varInner=22,?varExternal=12
=======
0
varInner=21,?varExternal=13
1
varInner=22,?varExternal=14
func?int(int)?f2?=?f1();
以這段代碼為例:f2 是一個(gè)返回值,入?yún)⒍紴?int 的函數(shù)類(lèi)型;所以后續(xù)可以直接當(dāng)做函數(shù)調(diào)用 f2(i)
.
例子中將閉包分別賦值給 f2 和 f3 變量,這兩個(gè)變量中的閉包數(shù)據(jù)也是互相隔離、互不影響的,所有基于這個(gè)特性甚至還是實(shí)現(xiàn)面向?qū)ο蟆?/p>
關(guān)于閉包的實(shí)現(xiàn),后續(xù)會(huì)單獨(dú)更新一篇。
更多樣例請(qǐng)參考:https://github.com/crossoverJie/gscript/tree/main/example
標(biāo)準(zhǔn)庫(kù)
標(biāo)準(zhǔn)庫(kù)源碼:https://github.com/crossoverJie/gscript/tree/main/internal
目前實(shí)現(xiàn)的標(biāo)準(zhǔn)庫(kù)并不多,這完全是一個(gè)體力活;基于現(xiàn)有的語(yǔ)法和基礎(chǔ)數(shù)據(jù)類(lèi)型,幾乎可以實(shí)現(xiàn)大部分的數(shù)據(jù)結(jié)構(gòu)了,所以感興趣的朋友也歡迎來(lái)貢獻(xiàn)標(biāo)準(zhǔn)庫(kù)代碼;比如 Stack
、Set
之類(lèi)的數(shù)據(jù)結(jié)構(gòu)。
MapString
以這個(gè) MapString
為例:鍵值對(duì)都為 string
的 HashMap
。
int?count?=100;
MapString?m1?=?MapString();
for?(int?i=0;i"";
?string?value?=?key;
?m1.put(key,value);
}
println(m1.getSize());
assertEqual(m1.getSize(),count);
for?(int?i=0;i"";
?string?value?=?m1.get(key);
?println("key="+key+?":"+?value);
?assertEqual(key,value);
}
使用起來(lái)和 Java
的 HashMap
類(lèi)似,當(dāng)然他的實(shí)現(xiàn)源碼也是參考的 jdk1.7 的 HashMap
。
由于目前并有一個(gè)類(lèi)似于 Java 的
object
或者是 go 中的interface{}
, 所以如果需要存放 int,那還得實(shí)現(xiàn)一個(gè) MapInt,不過(guò)這個(gè)通用類(lèi)型很快會(huì)實(shí)現(xiàn)。
內(nèi)置函數(shù)
int[]?a={1,2,3};
//?len?返回?cái)?shù)組大小
println(len(a));
//?向數(shù)組追加數(shù)據(jù)
a?=?append(a,4);
println(a);
//?output:?[1,2,3,4]
//?斷言函數(shù),不相等時(shí)會(huì)拋出運(yùn)行時(shí)異常,并中斷程序。
assertEqual(len(a),4);
//?返回?hashcode
int?hashcode?=?hash(key);
也內(nèi)置了一些基本函數(shù),當(dāng)然也這不是由 GScript
源碼實(shí)現(xiàn)的,而是編譯器實(shí)現(xiàn)的,所以新增起來(lái)要稍微麻煩一些;后續(xù)會(huì)逐步完善,比如和 IO 相關(guān)的內(nèi)置函數(shù)。
總結(jié)
現(xiàn)階段的 GScript
還有許多功能沒(méi)有完善,比如 JSON、網(wǎng)絡(luò)庫(kù)、更完善的語(yǔ)法檢查、編譯報(bào)錯(cuò)信息等;現(xiàn)在拿來(lái)刷刷 LeetCode
還是沒(méi)有問(wèn)題的。
![a0a87048-3363-11ed-ba43-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/96/72/wKgZomTnH4mAAjb1AAD__P0c3As091.jpg)
從這 65 個(gè) todo 就能看出還有很長(zhǎng)的路要走,我對(duì)它的終極目標(biāo)就是可以編寫(xiě)一個(gè)網(wǎng)站那就算是一個(gè)成熟的語(yǔ)言了。
目前還有一個(gè)問(wèn)題是沒(méi)有集成開(kāi)發(fā)環(huán)境,現(xiàn)在的開(kāi)發(fā)體驗(yàn)和白板上寫(xiě)代碼相差無(wú)異,所以后續(xù)有時(shí)間的話嘗試寫(xiě)一個(gè) VS Code 的插件,至少能有語(yǔ)法高亮與提示。
最后對(duì) GScript
或者是編譯原理感興趣的小伙伴可以加我微信一起交流。
項(xiàng)目源碼:https://github.com/crossoverJie/gscript
下載地址:https://github.com/crossoverJie/gscript/releases/tag/v0.0.6
審核編輯:湯梓紅
評(píng)論