The Go Blog

The first Go program

Andrew Gerrand
18 July 2013

Brad Fitzpatrick and I (Andrew Gerrand) recently started restructuring godoc, and it occurred to me that it is one of the oldest Go programs. Robert Griesemer started writing it back in early 2009, and we’re still using it today.

When I tweeted about this, Dave Cheney replied with an interesting question: what is the oldest Go program? Rob Pike dug into his mail and found it in an old message to Robert and Ken Thompson.

What follows is the first Go program. It was written by Rob in February 2008, when the team was just Rob, Robert, and Ken. They had a solid feature list (mentioned in this blog post) and a rough language specfication. Ken had just finished the first working version of a Go compiler (it didn’t produce native code, but rather transliterated Go code to C for fast prototyping) and it was time to try writing a program with it.

Rob sent mail to the “Go team”:

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.

(The icounter line in the program output is the number of executed statements, printed for debugging.)

// +build OMIT

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();
}

The program parses and prints an S-expression. It takes no user input and has no imports, relying only on the built-in print facility for output. It was written literally the first day there was a working but rudimentary compiler. Much of the language wasn’t implemented and some of it wasn’t even specified.

Still, the basic flavor of the language today is recognizable in this program. Type and variable declarations, control flow, and package statements haven’t changed much.

But there are many differences and absences. Most significant are the lack of concurrency and interfaces—both considered essential since day 1 but not yet designed.

A func was a function, and its signature specified return values before arguments, separating them with <-, which we now use as the channel send/receive operator. For example, the WhiteSpace function takes the integer c and returns a boolean.

function WhiteSpace(bool <- c int)

This arrow was a stop-gap measure until a better syntax arose for declaring multiple return values.

Methods were distinct from functions and had their own keyword.

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

And methods were pre-declared in the struct definition, although that changed soon.

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

There were no strings, although they were in the spec. To work around this, Rob had to build the input string as an uint8 array with a clumsy construction. (Arrays were rudimentary and slices hadn’t been designed yet, let alone implemented, although there was the unimplemented concept of an “open array”.)

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;
...

Both panic and print were built-in keywords, not pre-declared functions.

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

And there are many other little differences; see if you can identify some others.

Less than two years after this program was written, Go was released as an open source project. Looking back, it is striking how much the language has grown and matured. (The last thing to change between this proto-Go and the Go we know today was the elimination of semicolons.)

But even more striking is how much we have learned about writing Go code. For instance, Rob called his method receivers this, but now we use shorter context-specific names. There are hundreds of more significant examples and to this day we’re still discovering better ways to write Go code. (Check out the glog package’s clever trick for handling verbosity levels.)

I wonder what we’ll learn tomorrow.

Next article: Arrays, slices (and strings): The mechanics of 'append'
Previous article: Introducing the Go Race Detector
Blog Index