Go 部落格

第一個 Go 程式

安德魯·傑蘭德 (Andrew Gerrand)
2013 年 7 月 18 日

布拉德·費茲派翠克和 (安德魯·傑蘭德) 我最近開始重組 godoc,我突然想到它是其中一個最古老的 Go 程式。羅伯特·格里斯墨早在 2009 年初就開始撰寫它,而我們至今仍在使用它。

當我 發推特 提及此事時,戴夫·錢尼用一個 有趣的問題 回覆:最古老的 Go 程式是什麼?勞伯·派克搜尋自己的郵件,並在寄給羅伯特和肯·湯普森的舊訊息中找到了它。

以下是第一個 Go 程式。它是由勞伯在 2008 年 2 月編寫的,當時團隊只有勞伯、羅伯特和肯。他們有一份穩健的功能清單(這篇網誌文章 中有提到),以及一份粗略的語言規範。肯剛完成 Go 編譯器的第一個可用版本(它沒有產生原生程式碼,而是將 Go 程式碼轉換為 C,以便快速建立原型),而且是時候試著用它撰寫一個程式了。

Rob 向「前往團隊」寄送郵件

From: Rob 'Commander' Pike
Date: Wed, Feb 6, 2008 at 3:42 PM
To: Ken Thompson, Robert Griesemer
Subject: slist

it works now.

roro=% a.out
(defn foo (add 12 34))
return: icounter = 4440
roro=%

here's the code.
some ugly hackery to get around the lack of strings.

(程式輸出中 icounter 列為執行敘述的數目,印出以利除錯。)

package main

// fake stuff
type char uint8;

// const char TESTSTRING[] = "(defn foo (add 'a 'b))\n";

type Atom struct {
        string  *[100]char;
        integer int;
        next    *Slist;  /* in hash bucket */
}

type List struct {
        car     *Slist;
        cdr     *Slist;
}

type Slist struct {
        isatom          bool;
        isstring        bool;
        //union {
        atom    Atom;
        list    List;
        //} u;

        Free method();
        Print method();
        PrintOne method(doparen bool);
        String method(*char <-);
        Integer method(int <-);
        Car method(*Slist <-);
        Cdr method(*Slist <-);
}

method (this *Slist) Car(*Slist <-) {
        return this.list.car;
}

method (this *Slist) Cdr(*Slist <-) {
        return this.list.cdr;
}

method (this *Slist) String(*[100]char <-) {
        return this.atom.string;
}

method (this *Slist) Integer(int <-) {
        return this.atom.integer;
}

function OpenFile();
function Parse(*Slist <-);

//Slist* atom(char *s, int i);

var token int;
var peekc int = -1;
var lineno int32 = 1;

var input [100*1000]char;
var inputindex int = 0;
var tokenbuf [100]char;

var EOF int = -1;  // BUG should be const

function main(int32 <-) {
        var list *Slist;

        OpenFile();
        for ;; {
                list = Parse();
                if list == nil {
                        break;
                }
                list.Print();
                list.Free();
                break;
        }

        return 0;
}

method (slist *Slist) Free(<-) {
        if slist == nil {
                return;
        }
        if slist.isatom {
//              free(slist.String());
        } else {
                slist.Car().Free();
                slist.Cdr().Free();
        }
//      free(slist);
}

method (slist *Slist) PrintOne(<- doparen bool) {
        if slist == nil {
                return;
        }
        if slist.isatom {
                if slist.isstring {
                        print(slist.String());
                } else {
                        print(slist.Integer());
                }
        } else {
                if doparen {
                        print("(");
                }
                slist.Car().PrintOne(true);
                if slist.Cdr() != nil {
                        print(" ");
                        slist.Cdr().PrintOne(false);
                }
                if doparen {
                        print(")");
                }
        }
}

method (slist *Slist) Print() {
        slist.PrintOne(true);
        print "\n";
}

function Get(int <-) {
        var c int;

        if peekc >= 0 {
                c = peekc;
                peekc = -1;
        } else {
                c = convert(int, input[inputindex]);
                inputindex = inputindex + 1; // BUG should be incr one expr
                if c == '\n' {
                        lineno = lineno + 1;
                }
                if c == '\0' {
                        inputindex = inputindex - 1;
                        c = EOF;
                }
        }
        return c;
}

function WhiteSpace(bool <- c int) {
        return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

function NextToken() {
        var i, c int;
        var backslash bool;

        tokenbuf[0] = '\0';     // clear previous token
        c = Get();
        while WhiteSpace(c)  {
                c = Get();
        }
        switch c {
                case EOF:
                        token = EOF;
                case '(':
                case ')':
                        token = c;
                        break;
                case:
                        for i = 0; i < 100 - 1; {  // sizeof tokenbuf - 1
                                tokenbuf[i] = convert(char, c);
                                i = i + 1;
                                c = Get();
                                if c == EOF {
                                        break;
                                }
                                if WhiteSpace(c) || c == ')' {
                                        peekc = c;
                                        break;
                                }
                        }
                        if i >= 100 - 1 {  // sizeof tokenbuf - 1
                                panic "atom too long\n";
                        }
                        tokenbuf[i] = '\0';
                        if '0' <= tokenbuf[0] && tokenbuf[0] <= '9' {
                                token = '0';
                        } else {
                                token = 'A';
                        }
        }
}

function Expect(<- c int) {
        if token != c {
                print "parse error: expected ", c, "\n";
                panic "parse";
        }
        NextToken();
}

// Parse a non-parenthesized list up to a closing paren or EOF
function ParseList(*Slist <-) {
        var slist, retval *Slist;

        slist = new(Slist);
        slist.list.car = nil;
        slist.list.cdr = nil;
        slist.isatom = false;
        slist.isstring = false;

        retval = slist;
        for ;; {
                slist.list.car = Parse();
                if token == ')' {       // empty cdr
                        break;
                }
                if token == EOF {       // empty cdr  BUG SHOULD USE ||
                        break;
                }
                slist.list.cdr = new(Slist);
                slist = slist.list.cdr;
        }
        return retval;
}

function atom(*Slist <- i int) {  // BUG: uses tokenbuf; should take argument
        var h, length int;
        var slist, tail *Slist;

        slist = new(Slist);
        if token == '0' {
                slist.atom.integer = i;
                slist.isstring = false;
        } else {
                slist.atom.string = new([100]char);
                var i int;
                for i = 0; ; i = i + 1 {
                        (*slist.atom.string)[i] = tokenbuf[i];
                        if tokenbuf[i] == '\0' {
                                break;
                        }
                }
                //slist.atom.string = "hello"; // BUG! s; //= strdup(s);
                slist.isstring = true;
        }
        slist.isatom = true;
        return slist;
}

function atoi(int <-) {  // BUG: uses tokenbuf; should take argument
        var v int = 0;
        for i := 0; '0' <= tokenbuf[i] && tokenbuf[i] <= '9'; i = i + 1 {
                v = 10 * v + convert(int, tokenbuf[i] - '0');
        }
        return v;
}

function Parse(*Slist <-) {
        var slist *Slist;

        if token == EOF || token == ')' {
                return nil;
        }
        if token == '(' {
                NextToken();
                slist = ParseList();
                Expect(')');
                return slist;
        } else {
                // Atom
                switch token {
                        case EOF:
                                return nil;
                        case '0':
                                slist = atom(atoi());
                        case '"':
                        case 'A':
                                slist = atom(0);
                        case:
                                slist = nil;
                                print "unknown token"; //, token, tokenbuf;
                }
                NextToken();
                return slist;
        }
        return nil;
}

function OpenFile() {
        //strcpy(input, TESTSTRING);
        //inputindex = 0;
        // (defn foo (add 12 34))\n
        inputindex = 0;
        peekc = -1;  // BUG
        EOF = -1;  // BUG
        i := 0;
        input[i] = '('; i = i + 1;
        input[i] = 'd'; i = i + 1;
        input[i] = 'e'; i = i + 1;
        input[i] = 'f'; i = i + 1;
        input[i] = 'n'; i = i + 1;
        input[i] = ' '; i = i + 1;
        input[i] = 'f'; i = i + 1;
        input[i] = 'o'; i = i + 1;
        input[i] = 'o'; i = i + 1;
        input[i] = ' '; i = i + 1;
        input[i] = '('; i = i + 1;
        input[i] = 'a'; i = i + 1;
        input[i] = 'd'; i = i + 1;
        input[i] = 'd'; i = i + 1;
        input[i] = ' '; i = i + 1;
        input[i] = '1'; i = i + 1;
        input[i] = '2'; i = i + 1;
        input[i] = ' '; i = i + 1;
        input[i] = '3'; i = i + 1;
        input[i] = '4'; i = i + 1;
        input[i] = ')'; i = i + 1;
        input[i] = ')'; i = i + 1;
        input[i] = '\n'; i = i + 1;
        NextToken();
}

程式解析並印出 S 式。它不接受使用者輸入,也没有導入,只依賴內建 print 機制進行輸出。它是 一個可運作但基礎的編譯器存在的首日 親自撰寫的。語言的大部分內容尚未實作,甚至有些部分尚未明定規格。

儘管如此,程式中仍可辨識出今日這門語言的基本精髓。型別和變數宣告、控制流程和套件陳述至今變化不大。

不過,有許多相異和缺失之處。最重要的是缺乏並行性和介面,這兩部分從一開始就視為必要,但尚未設計。

funcfunction,而其簽章指出回傳值 參數之前,並以 <- 分隔,這是我們現在用來當作通道傳送和接收運算子的符號。舉例來說,WhiteSpace 函式接收整數 c 並回傳布林值。

function WhiteSpace(bool <- c int)

這個箭頭作為權宜措施,直到出現更理想的語法來宣告多個回傳值。

方法與函式不同,並有其專屬的關鍵字。

method (this *Slist) Car(*Slist <-) {
    return this.list.car;
}

方法會預先在結構體定義中宣告,儘管這部分很快地就改變了。

type Slist struct {
    ...
    Car method(*Slist <-);
}

雖然說明規格中包含字串,但在這裡並不存在。為了因應,Rob 必須建構輸入字串為一個 uint8 陣列,這是一個笨拙的架構。(陣列仍處於基礎階段,切片尚未設計出來,更別說實作了,即便有「開放式陣列」這一個未實作的概念。)

input[i] = '('; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'e'; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'n'; i = i + 1;
input[i] = ' '; i = i + 1;
...

panicprint 兩者皆為內建關鍵字,而非預先宣告的函式。

print "parse error: expected ", c, "\n";
panic "parse";

還有許多其他微小的差異,看看你是否能找出其他差異處。

在程式撰寫不到兩年後,Go 作為一個開放原始碼專案釋出。回顧過去,這門語言的成長和成熟令人驚嘆。(原型 Go 與我們今日認知的 Go 中最後一個更改的,是刪除了分號。)

然而更令人驚嘆的是,我們對於 撰寫 Go 程式已學到多少內容。舉例來說,Rob 稱其方法接收器為 this,但現在我們使用較短且符合特定脈絡的名稱。有數百個更重要的範例,而直到今天我們仍持續發現撰寫 Go 程式的最佳方式。(看看 glog 套件 的小技巧,用於 處理詳細程度。)

我很好奇我們明天能學到什麼。

下一篇:陣列、切片(及字串):『append』機制
上一篇:介紹 Go 競爭條件偵測器
部落格索引