diff options
author | Andrew Gerrand <adg@golang.org> | 2013-07-18 15:32:09 +1000 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2013-07-18 15:32:09 +1000 |
commit | 70aea4431c21f510ac86a3b7a5dc1f5e61392190 (patch) | |
tree | cbac484e8b888516793cd7627f1c31fadaae5ead | |
parent | 74485dbaa4509d369b543f23d5dd659f6c77246c (diff) |
go.blog: add "The first Go program"
R=r
CC=rsc
https://golang.org/cl/11499043
-rw-r--r-- | content/first-go-program.article | 123 | ||||
-rw-r--r-- | content/first-go-program/slist.go | 322 |
2 files changed, 445 insertions, 0 deletions
diff --git a/content/first-go-program.article b/content/first-go-program.article new file mode 100644 index 0000000..e972ed1 --- /dev/null +++ b/content/first-go-program.article @@ -0,0 +1,123 @@ +The first Go program +18 Jul 2013 +Tags: history + +Andrew Gerrand + +* Introduction + +Brad Fitzpatrick and I (Andrew Gerrand) recently started restructuring +[[http://golang.org/cmd/godoc/][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 [[https://twitter.com/enneff/status/357403054632484865][tweeted]] about +this, Dave Cheney replied with an [[https://twitter.com/davecheney/status/357406479415914497][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 [[http://commandcenter.blogspot.com.au/2012/06/less-is-exponentially-more.html][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.) + +.code first-go-program/slist.go + +The program parses and prints an +[[https://en.wikipedia.org/wiki/S-expression][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 +[[http://golang.org/change/8b8615138da3][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 [[https://github.com/golang/glog][glog package]]'s clever trick for +[[https://github.com/golang/glog/blob/master/glog.go#L854][handling verbosity levels]].) + +I wonder what we'll learn tomorrow. + diff --git a/content/first-go-program/slist.go b/content/first-go-program/slist.go new file mode 100644 index 0000000..bde7e2b --- /dev/null +++ b/content/first-go-program/slist.go @@ -0,0 +1,322 @@ +// +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(); +} |