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