終於實現了一門屬於自己的編程語言 前言 特性 例子 語法 標準庫 總結

[圖片上傳失敗...(image-802cf0-1662517021420)]

前言

都說程序員的三大浪漫是:操作系統、編譯原理、圖形學;最後的圖形學確實是特定的專業領域,我們幾乎接觸不到,所以對我來說換成網絡更合適一些,最後再加上一個數據庫。

這四項技術如果都能掌握的話那豈不是在 IT 行業橫着走了,加上這幾年互聯網行業越來越不景氣,越底層的技術就越不可能被替代;所以爲了給自己的 30+ 危機留點出路,從今年上半年開始我就逐漸開始從頭學習編譯原理。

功夫不負有心人,經過近一個月的挑燈夜戰,每晚都在老婆的催促下才休息,克服了中途好幾次想放棄的衝動,終於現在完成了 GScript 一個預覽版。

預覽版的意思是語法結構與整體設計基本完成,後續更新也不太會改動這部分內容、但還缺少一些易用功能。

特性

首先來看看保留環節, GScript 是如何編寫 hello world 的。

hello_world.gs:

println("hello world");
❯ gscript hello_world.gs
hello world

廢話說完了接下來重點聊聊 GScript 所支持的特性了。

[圖片上傳失敗...(image-a13b19-1662517021420)]
後文會重點說明每一個特性。

例子

除了剛纔提到的 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<len(a);i++){
    println(a[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
[<nil> <nil> 1]
[100 <nil> 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

目前實現的標準庫並不多,這完全是一個體力活;基於現有的語法和基礎數據類型,幾乎可以實現大部分的數據結構了,所以感興趣的朋友也歡迎來貢獻標準庫代碼;比如 StackSet 之類的數據結構。

MapString

以這個 MapString 爲例:鍵值對都爲 stringHashMap

int count =100;
MapString m1 = MapString();
for (int i=0;i<count;i++){
    string key = i+"";
    string value = key;
    m1.put(key,value);
}
println(m1.getSize());
assertEqual(m1.getSize(),count);

for (int i=0;i<count;i++){
    string key = i+"";
    string value = m1.get(key);
    println("key="+key+ ":"+ value);
    assertEqual(key,value);
}

使用起來和 JavaHashMap 類似,當然他的實現源碼也是參考的 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 還是沒有問題的。

[圖片上傳失敗...(image-ba5121-1662517021420)]

從這 65 個 todo 就能看出還有很長的路要走,我對它的終極目標就是可以編寫一個網站那就算是一個成熟的語言了。

目前還有一個問題是沒有集成開發環境,現在的開發體驗和白板上寫代碼相差無異,所以後續有時間的話嘗試寫一個 VS Code 的插件,至少能有語法高亮與提示。

最後對 GScript 或者是編譯原理感興趣的小夥伴可以加我微信一起交流。

項目源碼:https://github.com/crossoverJie/gscript

下載地址:https://github.com/crossoverJie/gscript/releases/tag/v0.0.6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章