Repository: adonovan/gopl.io Branch: master Commit: 1ae3ec64947b Files: 171 Total size: 201.3 KB Directory structure: gitextract_engomvr4/ ├── CNAME ├── README.md ├── ch1/ │ ├── dup1/ │ │ └── main.go │ ├── dup2/ │ │ └── main.go │ ├── dup3/ │ │ └── main.go │ ├── echo1/ │ │ └── main.go │ ├── echo2/ │ │ └── main.go │ ├── echo3/ │ │ └── main.go │ ├── fetch/ │ │ └── main.go │ ├── fetchall/ │ │ └── main.go │ ├── helloworld/ │ │ └── main.go │ ├── lissajous/ │ │ └── main.go │ ├── server1/ │ │ └── main.go │ ├── server2/ │ │ └── main.go │ └── server3/ │ └── main.go ├── ch10/ │ ├── cross/ │ │ └── main.go │ └── jpeg/ │ └── main.go ├── ch11/ │ ├── echo/ │ │ ├── echo.go │ │ └── echo_test.go │ ├── storage1/ │ │ └── storage.go │ ├── storage2/ │ │ ├── quota_test.go │ │ └── storage.go │ ├── word1/ │ │ ├── word.go │ │ └── word_test.go │ └── word2/ │ ├── word.go │ └── word_test.go ├── ch12/ │ ├── display/ │ │ ├── display.go │ │ └── display_test.go │ ├── format/ │ │ ├── format.go │ │ └── format_test.go │ ├── methods/ │ │ ├── methods.go │ │ └── methods_test.go │ ├── params/ │ │ └── params.go │ ├── search/ │ │ └── main.go │ └── sexpr/ │ ├── decode.go │ ├── encode.go │ ├── pretty.go │ └── sexpr_test.go ├── ch13/ │ ├── bzip/ │ │ ├── bzip2.c │ │ ├── bzip2.go │ │ └── bzip2_test.go │ ├── bzip-print/ │ │ ├── bzip2.c │ │ ├── bzip2.go │ │ └── bzip2_test.go │ ├── bzipper/ │ │ └── main.go │ ├── equal/ │ │ ├── equal.go │ │ └── equal_test.go │ └── unsafeptr/ │ └── main.go ├── ch2/ │ ├── boiling/ │ │ └── main.go │ ├── cf/ │ │ └── main.go │ ├── echo4/ │ │ └── main.go │ ├── ftoc/ │ │ └── main.go │ ├── popcount/ │ │ ├── main.go │ │ └── popcount_test.go │ ├── tempconv/ │ │ ├── conv.go │ │ └── tempconv.go │ └── tempconv0/ │ ├── celsius.go │ └── tempconv_test.go ├── ch3/ │ ├── basename1/ │ │ └── main.go │ ├── basename2/ │ │ └── main.go │ ├── comma/ │ │ └── main.go │ ├── mandelbrot/ │ │ └── main.go │ ├── netflag/ │ │ └── netflag.go │ ├── printints/ │ │ └── main.go │ └── surface/ │ └── main.go ├── ch4/ │ ├── append/ │ │ └── main.go │ ├── autoescape/ │ │ └── main.go │ ├── charcount/ │ │ └── main.go │ ├── dedup/ │ │ └── main.go │ ├── embed/ │ │ └── main.go │ ├── github/ │ │ ├── github.go │ │ └── search.go │ ├── graph/ │ │ └── main.go │ ├── issues/ │ │ └── main.go │ ├── issueshtml/ │ │ └── main.go │ ├── issuesreport/ │ │ └── main.go │ ├── movie/ │ │ └── main.go │ ├── nonempty/ │ │ └── main.go │ ├── rev/ │ │ └── main.go │ ├── sha256/ │ │ └── main.go │ └── treesort/ │ ├── sort.go │ └── sort_test.go ├── ch5/ │ ├── defer1/ │ │ └── defer.go │ ├── defer2/ │ │ └── defer.go │ ├── fetch/ │ │ └── main.go │ ├── findlinks1/ │ │ └── main.go │ ├── findlinks2/ │ │ └── main.go │ ├── findlinks3/ │ │ └── findlinks.go │ ├── links/ │ │ └── links.go │ ├── outline/ │ │ └── main.go │ ├── outline2/ │ │ └── outline.go │ ├── squares/ │ │ └── main.go │ ├── sum/ │ │ └── main.go │ ├── title1/ │ │ └── title.go │ ├── title2/ │ │ └── title.go │ ├── title3/ │ │ └── title.go │ ├── toposort/ │ │ └── main.go │ ├── trace/ │ │ └── main.go │ └── wait/ │ └── wait.go ├── ch6/ │ ├── coloredpoint/ │ │ └── main.go │ ├── geometry/ │ │ └── geometry.go │ ├── intset/ │ │ ├── intset.go │ │ └── intset_test.go │ └── urlvalues/ │ └── main.go ├── ch7/ │ ├── bytecounter/ │ │ └── main.go │ ├── eval/ │ │ ├── ast.go │ │ ├── check.go │ │ ├── coverage_test.go │ │ ├── eval.go │ │ ├── eval_test.go │ │ ├── parse.go │ │ └── print.go │ ├── http1/ │ │ └── main.go │ ├── http2/ │ │ └── main.go │ ├── http3/ │ │ └── main.go │ ├── http3a/ │ │ └── main.go │ ├── http4/ │ │ └── main.go │ ├── sleep/ │ │ └── sleep.go │ ├── sorting/ │ │ └── main.go │ ├── surface/ │ │ └── surface.go │ ├── tempconv/ │ │ ├── tempconv.go │ │ └── tempconv.go.~master~ │ ├── tempflag/ │ │ └── tempflag.go │ └── xmlselect/ │ └── main.go ├── ch8/ │ ├── cake/ │ │ ├── cake.go │ │ └── cake_test.go │ ├── chat/ │ │ ├── chat.go │ │ └── chat.go.~master~ │ ├── clock1/ │ │ └── clock.go │ ├── clock2/ │ │ └── clock.go │ ├── countdown1/ │ │ └── countdown.go │ ├── countdown2/ │ │ └── countdown.go │ ├── countdown3/ │ │ └── countdown.go │ ├── crawl1/ │ │ └── findlinks.go │ ├── crawl2/ │ │ └── findlinks.go │ ├── crawl3/ │ │ └── findlinks.go │ ├── du1/ │ │ └── main.go │ ├── du2/ │ │ └── main.go │ ├── du3/ │ │ └── main.go │ ├── du4/ │ │ └── main.go │ ├── netcat1/ │ │ └── netcat.go │ ├── netcat2/ │ │ └── netcat.go │ ├── netcat3/ │ │ └── netcat.go │ ├── pipeline1/ │ │ └── main.go │ ├── pipeline2/ │ │ └── main.go │ ├── pipeline3/ │ │ └── main.go │ ├── reverb1/ │ │ └── reverb.go │ ├── reverb2/ │ │ └── reverb.go │ ├── spinner/ │ │ └── main.go │ └── thumbnail/ │ ├── main.go │ ├── thumbnail.go │ └── thumbnail_test.go ├── ch9/ │ ├── bank1/ │ │ ├── bank.go │ │ └── bank_test.go │ ├── bank2/ │ │ ├── bank.go │ │ └── bank_test.go │ ├── bank3/ │ │ ├── bank.go │ │ └── bank_test.go │ ├── memo1/ │ │ ├── memo.go │ │ └── memo_test.go │ ├── memo2/ │ │ ├── memo.go │ │ └── memo_test.go │ ├── memo3/ │ │ ├── memo.go │ │ └── memo_test.go │ ├── memo4/ │ │ ├── memo.go │ │ └── memo_test.go │ ├── memo5/ │ │ ├── memo.go │ │ └── memo_test.go │ └── memotest/ │ └── memotest.go ├── go.mod └── go.sum ================================================ FILE CONTENTS ================================================ ================================================ FILE: CNAME ================================================ gopl.io ================================================ FILE: README.md ================================================ # The Go Programming Language This repository provides the downloadable example programs for the book, "The Go Programming Language"; see http://www.gopl.io. These example programs are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Creative Commons License You can download, build, and run the programs with the following commands: $ export GOPATH=$HOME/gobook # choose workspace directory $ go get gopl.io/ch1/helloworld # fetch, build, install $ $GOPATH/bin/helloworld # run Hello, 世界 Many of the programs contain comments of the form `//!+` and `//!-`. These comments bracket the parts of the programs that are excerpted in the book; you can safely ignore them. In a few cases, programs have been reformatted in an unnatural way so that they can be presented in stages in the book. ================================================ FILE: ch1/dup1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 8. //!+ // Dup1 prints the text of each line that appears more than // once in the standard input, preceded by its count. package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) input := bufio.NewScanner(os.Stdin) for input.Scan() { counts[input.Text()]++ } // NOTE: ignoring potential errors from input.Err() for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } } //!- ================================================ FILE: ch1/dup2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 10. //!+ // Dup2 prints the count and text of lines that appear more than once // in the input. It reads from stdin or from a list of named files. package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) files := os.Args[1:] if len(files) == 0 { countLines(os.Stdin, counts) } else { for _, arg := range files { f, err := os.Open(arg) if err != nil { fmt.Fprintf(os.Stderr, "dup2: %v\n", err) continue } countLines(f, counts) f.Close() } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } } func countLines(f *os.File, counts map[string]int) { input := bufio.NewScanner(f) for input.Scan() { counts[input.Text()]++ } // NOTE: ignoring potential errors from input.Err() } //!- ================================================ FILE: ch1/dup3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 12. //!+ // Dup3 prints the count and text of lines that // appear more than once in the named input files. package main import ( "fmt" "io/ioutil" "os" "strings" ) func main() { counts := make(map[string]int) for _, filename := range os.Args[1:] { data, err := ioutil.ReadFile(filename) if err != nil { fmt.Fprintf(os.Stderr, "dup3: %v\n", err) continue } for _, line := range strings.Split(string(data), "\n") { counts[line]++ } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } } //!- ================================================ FILE: ch1/echo1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 4. //!+ // Echo1 prints its command-line arguments. package main import ( "fmt" "os" ) func main() { var s, sep string for i := 1; i < len(os.Args); i++ { s += sep + os.Args[i] sep = " " } fmt.Println(s) } //!- ================================================ FILE: ch1/echo2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 6. //!+ // Echo2 prints its command-line arguments. package main import ( "fmt" "os" ) func main() { s, sep := "", "" for _, arg := range os.Args[1:] { s += sep + arg sep = " " } fmt.Println(s) } //!- ================================================ FILE: ch1/echo3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 8. // Echo3 prints its command-line arguments. package main import ( "fmt" "os" "strings" ) //!+ func main() { fmt.Println(strings.Join(os.Args[1:], " ")) } //!- ================================================ FILE: ch1/fetch/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 16. //!+ // Fetch prints the content found at each specified URL. package main import ( "fmt" "io/ioutil" "net/http" "os" ) func main() { for _, url := range os.Args[1:] { resp, err := http.Get(url) if err != nil { fmt.Fprintf(os.Stderr, "fetch: %v\n", err) os.Exit(1) } b, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err) os.Exit(1) } fmt.Printf("%s", b) } } //!- ================================================ FILE: ch1/fetchall/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 17. //!+ // Fetchall fetches URLs in parallel and reports their times and sizes. package main import ( "fmt" "io" "io/ioutil" "net/http" "os" "time" ) func main() { start := time.Now() ch := make(chan string) for _, url := range os.Args[1:] { go fetch(url, ch) // start a goroutine } for range os.Args[1:] { fmt.Println(<-ch) // receive from channel ch } fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds()) } func fetch(url string, ch chan<- string) { start := time.Now() resp, err := http.Get(url) if err != nil { ch <- fmt.Sprint(err) // send to channel ch return } nbytes, err := io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() // don't leak resources if err != nil { ch <- fmt.Sprintf("while reading %s: %v", url, err) return } secs := time.Since(start).Seconds() ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url) } //!- ================================================ FILE: ch1/helloworld/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 1. // Helloworld is our first Go program. //!+ package main import "fmt" func main() { fmt.Println("Hello, 世界") } //!- ================================================ FILE: ch1/lissajous/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // Run with "web" command-line argument for web server. // See page 13. //!+main // Lissajous generates GIF animations of random Lissajous figures. package main import ( "image" "image/color" "image/gif" "io" "math" "math/rand" "os" ) //!-main // Packages not needed by version in book. import ( "log" "net/http" "time" ) //!+main var palette = []color.Color{color.White, color.Black} const ( whiteIndex = 0 // first color in palette blackIndex = 1 // next color in palette ) func main() { //!-main // The sequence of images is deterministic unless we seed // the pseudo-random number generator using the current time. // Thanks to Randall McPherson for pointing out the omission. rand.Seed(time.Now().UTC().UnixNano()) if len(os.Args) > 1 && os.Args[1] == "web" { //!+http handler := func(w http.ResponseWriter, r *http.Request) { lissajous(w) } http.HandleFunc("/", handler) //!-http log.Fatal(http.ListenAndServe("localhost:8000", nil)) return } //!+main lissajous(os.Stdout) } func lissajous(out io.Writer) { const ( cycles = 5 // number of complete x oscillator revolutions res = 0.001 // angular resolution size = 100 // image canvas covers [-size..+size] nframes = 64 // number of animation frames delay = 8 // delay between frames in 10ms units ) freq := rand.Float64() * 3.0 // relative frequency of y oscillator anim := gif.GIF{LoopCount: nframes} phase := 0.0 // phase difference for i := 0; i < nframes; i++ { rect := image.Rect(0, 0, 2*size+1, 2*size+1) img := image.NewPaletted(rect, palette) for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) } phase += 0.1 anim.Delay = append(anim.Delay, delay) anim.Image = append(anim.Image, img) } gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors } //!-main ================================================ FILE: ch1/server1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 19. //!+ // Server1 is a minimal "echo" server. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } //!- ================================================ FILE: ch1/server2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 20. //!+ // Server2 is a minimal "echo" and counter server. package main import ( "fmt" "log" "net/http" "sync" ) var mu sync.Mutex var count int func main() { http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } // counter echoes the number of calls so far. func counter(w http.ResponseWriter, r *http.Request) { mu.Lock() fmt.Fprintf(w, "Count %d\n", count) mu.Unlock() } //!- ================================================ FILE: ch1/server3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 21. // Server3 is an "echo" server that displays request parameters. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } //!+handler // handler echoes the HTTP request. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto) for k, v := range r.Header { fmt.Fprintf(w, "Header[%q] = %q\n", k, v) } fmt.Fprintf(w, "Host = %q\n", r.Host) fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr) if err := r.ParseForm(); err != nil { log.Print(err) } for k, v := range r.Form { fmt.Fprintf(w, "Form[%q] = %q\n", k, v) } } //!-handler ================================================ FILE: ch10/cross/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 295. // The cross command prints the values of GOOS and GOARCH for this target. package main import ( "fmt" "runtime" ) //!+ func main() { fmt.Println(runtime.GOOS, runtime.GOARCH) } //!- ================================================ FILE: ch10/jpeg/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 287. //!+main // The jpeg command reads a PNG image from the standard input // and writes it as a JPEG image to the standard output. package main import ( "fmt" "image" "image/jpeg" _ "image/png" // register PNG decoder "io" "os" ) func main() { if err := toJPEG(os.Stdin, os.Stdout); err != nil { fmt.Fprintf(os.Stderr, "jpeg: %v\n", err) os.Exit(1) } } func toJPEG(in io.Reader, out io.Writer) error { img, kind, err := image.Decode(in) if err != nil { return err } fmt.Fprintln(os.Stderr, "Input format =", kind) return jpeg.Encode(out, img, &jpeg.Options{Quality: 95}) } //!-main /* //!+with $ go build gopl.io/ch3/mandelbrot $ go build gopl.io/ch10/jpeg $ ./mandelbrot | ./jpeg >mandelbrot.jpg Input format = png //!-with //!+without $ go build gopl.io/ch10/jpeg $ ./mandelbrot | ./jpeg >mandelbrot.jpg jpeg: image: unknown format //!-without */ ================================================ FILE: ch11/echo/echo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 308. //!+ // Echo prints its command-line arguments. package main import ( "flag" "fmt" "io" "os" "strings" ) var ( n = flag.Bool("n", false, "omit trailing newline") s = flag.String("s", " ", "separator") ) var out io.Writer = os.Stdout // modified during testing func main() { flag.Parse() if err := echo(!*n, *s, flag.Args()); err != nil { fmt.Fprintf(os.Stderr, "echo: %v\n", err) os.Exit(1) } } func echo(newline bool, sep string, args []string) error { fmt.Fprint(out, strings.Join(args, sep)) if newline { fmt.Fprintln(out) } return nil } //!- ================================================ FILE: ch11/echo/echo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // Test of echo command. Run with: go test gopl.io/ch11/echo //!+ package main import ( "bytes" "fmt" "testing" ) func TestEcho(t *testing.T) { var tests = []struct { newline bool sep string args []string want string }{ {true, "", []string{}, "\n"}, {false, "", []string{}, ""}, {true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"}, {true, ",", []string{"a", "b", "c"}, "a,b,c\n"}, {false, ":", []string{"1", "2", "3"}, "1:2:3"}, } for _, test := range tests { descr := fmt.Sprintf("echo(%v, %q, %q)", test.newline, test.sep, test.args) out = new(bytes.Buffer) // captured output if err := echo(test.newline, test.sep, test.args); err != nil { t.Errorf("%s failed: %v", descr, err) continue } got := out.(*bytes.Buffer).String() if got != test.want { t.Errorf("%s = %q, want %q", descr, got, test.want) } } } //!- ================================================ FILE: ch11/storage1/storage.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 311. // Package storage is part of a hypothetical cloud storage server. //!+main package storage import ( "fmt" "log" "net/smtp" ) var usage = make(map[string]int64) func bytesInUse(username string) int64 { return usage[username] } // Email sender configuration. // NOTE: never put passwords in source code! const sender = "notifications@example.com" const password = "correcthorsebatterystaple" const hostname = "smtp.example.com" const template = `Warning: you are using %d bytes of storage, %d%% of your quota.` func CheckQuota(username string) { used := bytesInUse(username) const quota = 1000000000 // 1GB percent := 100 * used / quota if percent < 90 { return // OK } msg := fmt.Sprintf(template, used, percent) auth := smtp.PlainAuth("", sender, password, hostname) err := smtp.SendMail(hostname+":587", auth, sender, []string{username}, []byte(msg)) if err != nil { log.Printf("smtp.SendMail(%s) failed: %s", username, err) } } //!-main ================================================ FILE: ch11/storage2/quota_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ //!+test package storage import ( "strings" "testing" ) func TestCheckQuotaNotifiesUser(t *testing.T) { var notifiedUser, notifiedMsg string notifyUser = func(user, msg string) { notifiedUser, notifiedMsg = user, msg } const user = "joe@example.org" usage[user] = 980000000 // simulate a 980MB-used condition CheckQuota(user) if notifiedUser == "" && notifiedMsg == "" { t.Fatalf("notifyUser not called") } if notifiedUser != user { t.Errorf("wrong user (%s) notified, want %s", notifiedUser, user) } const wantSubstring = "98% of your quota" if !strings.Contains(notifiedMsg, wantSubstring) { t.Errorf("unexpected notification message <<%s>>, "+ "want substring %q", notifiedMsg, wantSubstring) } } //!-test /* //!+defer func TestCheckQuotaNotifiesUser(t *testing.T) { // Save and restore original notifyUser. saved := notifyUser defer func() { notifyUser = saved }() // Install the test's fake notifyUser. var notifiedUser, notifiedMsg string notifyUser = func(user, msg string) { notifiedUser, notifiedMsg = user, msg } // ...rest of test... } //!-defer */ ================================================ FILE: ch11/storage2/storage.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 312. // Package storage is part of a hypothetical cloud storage server. package storage import ( "fmt" "log" "net/smtp" ) var usage = make(map[string]int64) func bytesInUse(username string) int64 { return usage[username] } // E-mail sender configuration. // NOTE: never put passwords in source code! const sender = "notifications@example.com" const password = "correcthorsebatterystaple" const hostname = "smtp.example.com" const template = `Warning: you are using %d bytes of storage, %d%% of your quota.` //!+factored var notifyUser = func(username, msg string) { auth := smtp.PlainAuth("", sender, password, hostname) err := smtp.SendMail(hostname+":587", auth, sender, []string{username}, []byte(msg)) if err != nil { log.Printf("smtp.SendMail(%s) failed: %s", username, err) } } func CheckQuota(username string) { used := bytesInUse(username) const quota = 1000000000 // 1GB percent := 100 * used / quota if percent < 90 { return // OK } msg := fmt.Sprintf(template, used, percent) notifyUser(username, msg) } //!-factored ================================================ FILE: ch11/word1/word.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 303. //!+ // Package word provides utilities for word games. package word // IsPalindrome reports whether s reads the same forward and backward. // (Our first attempt.) func IsPalindrome(s string) bool { for i := range s { if s[i] != s[len(s)-1-i] { return false } } return true } //!- ================================================ FILE: ch11/word1/word_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ //!+test package word import "testing" func TestPalindrome(t *testing.T) { if !IsPalindrome("detartrated") { t.Error(`IsPalindrome("detartrated") = false`) } if !IsPalindrome("kayak") { t.Error(`IsPalindrome("kayak") = false`) } } func TestNonPalindrome(t *testing.T) { if IsPalindrome("palindrome") { t.Error(`IsPalindrome("palindrome") = true`) } } //!-test // The tests below are expected to fail. // See package gopl.io/ch11/word2 for the fix. //!+more func TestFrenchPalindrome(t *testing.T) { if !IsPalindrome("été") { t.Error(`IsPalindrome("été") = false`) } } func TestCanalPalindrome(t *testing.T) { input := "A man, a plan, a canal: Panama" if !IsPalindrome(input) { t.Errorf(`IsPalindrome(%q) = false`, input) } } //!-more ================================================ FILE: ch11/word2/word.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 305. //!+ // Package word provides utilities for word games. package word import "unicode" // IsPalindrome reports whether s reads the same forward and backward. // Letter case is ignored, as are non-letters. func IsPalindrome(s string) bool { var letters []rune for _, r := range s { if unicode.IsLetter(r) { letters = append(letters, unicode.ToLower(r)) } } for i := range letters { if letters[i] != letters[len(letters)-1-i] { return false } } return true } //!- ================================================ FILE: ch11/word2/word_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package word import ( "fmt" "math/rand" "time" ) //!+bench import "testing" //!-bench //!+test func TestIsPalindrome(t *testing.T) { var tests = []struct { input string want bool }{ {"", true}, {"a", true}, {"aa", true}, {"ab", false}, {"kayak", true}, {"detartrated", true}, {"A man, a plan, a canal: Panama", true}, {"Evil I did dwell; lewd did I live.", true}, {"Able was I ere I saw Elba", true}, {"été", true}, {"Et se resservir, ivresse reste.", true}, {"palindrome", false}, // non-palindrome {"desserts", false}, // semi-palindrome } for _, test := range tests { if got := IsPalindrome(test.input); got != test.want { t.Errorf("IsPalindrome(%q) = %v", test.input, got) } } } //!-test //!+bench func BenchmarkIsPalindrome(b *testing.B) { for i := 0; i < b.N; i++ { IsPalindrome("A man, a plan, a canal: Panama") } } //!-bench //!+example func ExampleIsPalindrome() { fmt.Println(IsPalindrome("A man, a plan, a canal: Panama")) fmt.Println(IsPalindrome("palindrome")) // Output: // true // false } //!-example /* //!+random import "math/rand" //!-random */ //!+random // randomPalindrome returns a palindrome whose length and contents // are derived from the pseudo-random number generator rng. func randomPalindrome(rng *rand.Rand) string { n := rng.Intn(25) // random length up to 24 runes := make([]rune, n) for i := 0; i < (n+1)/2; i++ { r := rune(rng.Intn(0x1000)) // random rune up to '\u0999' runes[i] = r runes[n-1-i] = r } return string(runes) } func TestRandomPalindromes(t *testing.T) { // Initialize a pseudo-random number generator. seed := time.Now().UTC().UnixNano() t.Logf("Random seed: %d", seed) rng := rand.New(rand.NewSource(seed)) for i := 0; i < 1000; i++ { p := randomPalindrome(rng) if !IsPalindrome(p) { t.Errorf("IsPalindrome(%q) = false", p) } } } //!-random /* // Answer for Exercicse 11.1: Modify randomPalindrome to exercise // IsPalindrome's handling of punctuation and spaces. // WARNING: the conversion r -> upper -> lower doesn't preserve // the value of r in some cases, e.g., µ Μ, ſ S, ı I // randomPalindrome returns a palindrome whose length and contents // are derived from the pseudo-random number generator rng. func randomNoisyPalindrome(rng *rand.Rand) string { n := rng.Intn(25) // random length up to 24 runes := make([]rune, n) for i := 0; i < (n+1)/2; i++ { r := rune(rng.Intn(0x200)) // random rune up to \u99 runes[i] = r r1 := r if unicode.IsLetter(r) && unicode.IsLower(r) { r = unicode.ToUpper(r) if unicode.ToLower(r) != r1 { fmt.Printf("cap? %c %c\n", r1, r) } } runes[n-1-i] = r } return "?" + string(runes) + "!" } func TestRandomNoisyPalindromes(t *testing.T) { // Initialize a pseudo-random number generator. seed := time.Now().UTC().UnixNano() t.Logf("Random seed: %d", seed) rng := rand.New(rand.NewSource(seed)) n := 0 for i := 0; i < 1000; i++ { p := randomNoisyPalindrome(rng) if !IsPalindrome(p) { t.Errorf("IsNoisyPalindrome(%q) = false", p) n++ } } fmt.Fprintf(os.Stderr, "fail = %d\n", n) } */ ================================================ FILE: ch12/display/display.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 333. // Package display provides a means to display structured data. package display import ( "fmt" "reflect" "strconv" ) //!+Display func Display(name string, x interface{}) { fmt.Printf("Display %s (%T):\n", name, x) display(name, reflect.ValueOf(x)) } //!-Display // formatAtom formats a value without inspecting its internal structure. // It is a copy of the the function in gopl.io/ch11/format. func formatAtom(v reflect.Value) string { switch v.Kind() { case reflect.Invalid: return "invalid" case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(v.Uint(), 10) // ...floating-point and complex cases omitted for brevity... case reflect.Bool: if v.Bool() { return "true" } return "false" case reflect.String: return strconv.Quote(v.String()) case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16) default: // reflect.Array, reflect.Struct, reflect.Interface return v.Type().String() + " value" } } //!+display func display(path string, v reflect.Value) { switch v.Kind() { case reflect.Invalid: fmt.Printf("%s = invalid\n", path) case reflect.Slice, reflect.Array: for i := 0; i < v.Len(); i++ { display(fmt.Sprintf("%s[%d]", path, i), v.Index(i)) } case reflect.Struct: for i := 0; i < v.NumField(); i++ { fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name) display(fieldPath, v.Field(i)) } case reflect.Map: for _, key := range v.MapKeys() { display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key)) } case reflect.Ptr: if v.IsNil() { fmt.Printf("%s = nil\n", path) } else { display(fmt.Sprintf("(*%s)", path), v.Elem()) } case reflect.Interface: if v.IsNil() { fmt.Printf("%s = nil\n", path) } else { fmt.Printf("%s.type = %s\n", path, v.Elem().Type()) display(path+".value", v.Elem()) } default: // basic types, channels, funcs fmt.Printf("%s = %s\n", path, formatAtom(v)) } } //!-display ================================================ FILE: ch12/display/display_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package display import ( "io" "net" "os" "reflect" "sync" "testing" "gopl.io/ch7/eval" ) // NOTE: we can't use !+..!- comments to excerpt these tests // into the book because it defeats the Example mechanism, // which requires the // Output comment to be at the end // of the function. func Example_expr() { e, _ := eval.Parse("sqrt(A / pi)") Display("e", e) // Output: // Display e (eval.call): // e.fn = "sqrt" // e.args[0].type = eval.binary // e.args[0].value.op = 47 // e.args[0].value.x.type = eval.Var // e.args[0].value.x.value = "A" // e.args[0].value.y.type = eval.Var // e.args[0].value.y.value = "pi" } func Example_slice() { Display("slice", []*int{new(int), nil}) // Output: // Display slice ([]*int): // (*slice[0]) = 0 // slice[1] = nil } func Example_nilInterface() { var w io.Writer Display("w", w) // Output: // Display w (): // w = invalid } func Example_ptrToInterface() { var w io.Writer Display("&w", &w) // Output: // Display &w (*io.Writer): // (*&w) = nil } func Example_struct() { Display("x", struct{ x interface{} }{3}) // Output: // Display x (struct { x interface {} }): // x.x.type = int // x.x.value = 3 } func Example_interface() { var i interface{} = 3 Display("i", i) // Output: // Display i (int): // i = 3 } func Example_ptrToInterface2() { var i interface{} = 3 Display("&i", &i) // Output: // Display &i (*interface {}): // (*&i).type = int // (*&i).value = 3 } func Example_array() { Display("x", [1]interface{}{3}) // Output: // Display x ([1]interface {}): // x[0].type = int // x[0].value = 3 } func Example_movie() { //!+movie type Movie struct { Title, Subtitle string Year int Color bool Actor map[string]string Oscars []string Sequel *string } //!-movie //!+strangelove strangelove := Movie{ Title: "Dr. Strangelove", Subtitle: "How I Learned to Stop Worrying and Love the Bomb", Year: 1964, Color: false, Actor: map[string]string{ "Dr. Strangelove": "Peter Sellers", "Grp. Capt. Lionel Mandrake": "Peter Sellers", "Pres. Merkin Muffley": "Peter Sellers", "Gen. Buck Turgidson": "George C. Scott", "Brig. Gen. Jack D. Ripper": "Sterling Hayden", `Maj. T.J. "King" Kong`: "Slim Pickens", }, Oscars: []string{ "Best Actor (Nomin.)", "Best Adapted Screenplay (Nomin.)", "Best Director (Nomin.)", "Best Picture (Nomin.)", }, } //!-strangelove Display("strangelove", strangelove) // We don't use an Output: comment since displaying // a map is nondeterministic. /* //!+output Display strangelove (display.Movie): strangelove.Title = "Dr. Strangelove" strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb" strangelove.Year = 1964 strangelove.Color = false strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott" strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden" strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens" strangelove.Actor["Dr. Strangelove"] = "Peter Sellers" strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers" strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers" strangelove.Oscars[0] = "Best Actor (Nomin.)" strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)" strangelove.Oscars[2] = "Best Director (Nomin.)" strangelove.Oscars[3] = "Best Picture (Nomin.)" strangelove.Sequel = nil //!-output */ } // This test ensures that the program terminates without crashing. func Test(t *testing.T) { // Some other values (YMMV) Display("os.Stderr", os.Stderr) // Output: // Display os.Stderr (*os.File): // (*(*os.Stderr).file).fd = 2 // (*(*os.Stderr).file).name = "/dev/stderr" // (*(*os.Stderr).file).nepipe = 0 var w io.Writer = os.Stderr Display("&w", &w) // Output: // Display &w (*io.Writer): // (*&w).type = *os.File // (*(*(*&w).value).file).fd = 2 // (*(*(*&w).value).file).name = "/dev/stderr" // (*(*(*&w).value).file).nepipe = 0 var locker sync.Locker = new(sync.Mutex) Display("(&locker)", &locker) // Output: // Display (&locker) (*sync.Locker): // (*(&locker)).type = *sync.Mutex // (*(*(&locker)).value).state = 0 // (*(*(&locker)).value).sema = 0 Display("locker", locker) // Output: // Display locker (*sync.Mutex): // (*locker).state = 0 // (*locker).sema = 0 // (*(&locker)) = nil locker = nil Display("(&locker)", &locker) // Output: // Display (&locker) (*sync.Locker): // (*(&locker)) = nil ips, _ := net.LookupHost("golang.org") Display("ips", ips) // Output: // Display ips ([]string): // ips[0] = "173.194.68.141" // ips[1] = "2607:f8b0:400d:c06::8d" // Even metarecursion! (YMMV) Display("rV", reflect.ValueOf(os.Stderr)) // Output: // Display rV (reflect.Value): // (*rV.typ).size = 8 // (*rV.typ).ptrdata = 8 // (*rV.typ).hash = 871609668 // (*rV.typ)._ = 0 // ... // a pointer that points to itself type P *P var p P p = &p if false { Display("p", p) // Output: // Display p (display.P): // ...stuck, no output... } // a map that contains itself type M map[string]M m := make(M) m[""] = m if false { Display("m", m) // Output: // Display m (display.M): // ...stuck, no output... } // a slice that contains itself type S []S s := make(S, 1) s[0] = s if false { Display("s", s) // Output: // Display s (display.S): // ...stuck, no output... } // a linked list that eats its own tail type Cycle struct { Value int Tail *Cycle } var c Cycle c = Cycle{42, &c} if false { Display("c", c) // Output: // Display c (display.Cycle): // c.Value = 42 // (*c.Tail).Value = 42 // (*(*c.Tail).Tail).Value = 42 // ...ad infinitum... } } ================================================ FILE: ch12/format/format.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 332. // Package format provides an Any function that can format any value. //!+ package format import ( "reflect" "strconv" ) // Any formats any value as a string. func Any(value interface{}) string { return formatAtom(reflect.ValueOf(value)) } // formatAtom formats a value without inspecting its internal structure. func formatAtom(v reflect.Value) string { switch v.Kind() { case reflect.Invalid: return "invalid" case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(v.Uint(), 10) // ...floating-point and complex cases omitted for brevity... case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: return strconv.Quote(v.String()) case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16) default: // reflect.Array, reflect.Struct, reflect.Interface return v.Type().String() + " value" } } //!- ================================================ FILE: ch12/format/format_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package format_test import ( "fmt" "testing" "time" "gopl.io/ch12/format" ) func Test(t *testing.T) { // The pointer values are just examples, and may vary from run to run. //!+time var x int64 = 1 var d time.Duration = 1 * time.Nanosecond fmt.Println(format.Any(x)) // "1" fmt.Println(format.Any(d)) // "1" fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0" fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0" //!-time } ================================================ FILE: ch12/methods/methods.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 351. // Package methods provides a function to print the methods of any value. package methods import ( "fmt" "reflect" "strings" ) //!+print // Print prints the method set of the value x. func Print(x interface{}) { v := reflect.ValueOf(x) t := v.Type() fmt.Printf("type %s\n", t) for i := 0; i < v.NumMethod(); i++ { methType := v.Method(i).Type() fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name, strings.TrimPrefix(methType.String(), "func")) } } //!-print ================================================ FILE: ch12/methods/methods_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package methods_test import ( "strings" "time" "gopl.io/ch12/methods" ) func ExamplePrintDuration() { methods.Print(time.Hour) // Output: // type time.Duration // func (time.Duration) Hours() float64 // func (time.Duration) Minutes() float64 // func (time.Duration) Nanoseconds() int64 // func (time.Duration) Seconds() float64 // func (time.Duration) String() string } func ExamplePrintReplacer() { methods.Print(new(strings.Replacer)) // Output: // type *strings.Replacer // func (*strings.Replacer) Replace(string) string // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) } /* //!+output methods.Print(time.Hour) // Output: // type time.Duration // func (time.Duration) Hours() float64 // func (time.Duration) Minutes() float64 // func (time.Duration) Nanoseconds() int64 // func (time.Duration) Seconds() float64 // func (time.Duration) String() string methods.Print(new(strings.Replacer)) // Output: // type *strings.Replacer // func (*strings.Replacer) Replace(string) string // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) //!-output */ ================================================ FILE: ch12/params/params.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 349. // Package params provides a reflection-based parser for URL parameters. package params import ( "fmt" "net/http" "reflect" "strconv" "strings" ) //!+Unpack // Unpack populates the fields of the struct pointed to by ptr // from the HTTP request parameters in req. func Unpack(req *http.Request, ptr interface{}) error { if err := req.ParseForm(); err != nil { return err } // Build map of fields keyed by effective name. fields := make(map[string]reflect.Value) v := reflect.ValueOf(ptr).Elem() // the struct variable for i := 0; i < v.NumField(); i++ { fieldInfo := v.Type().Field(i) // a reflect.StructField tag := fieldInfo.Tag // a reflect.StructTag name := tag.Get("http") if name == "" { name = strings.ToLower(fieldInfo.Name) } fields[name] = v.Field(i) } // Update struct field for each parameter in the request. for name, values := range req.Form { f := fields[name] if !f.IsValid() { continue // ignore unrecognized HTTP parameters } for _, value := range values { if f.Kind() == reflect.Slice { elem := reflect.New(f.Type().Elem()).Elem() if err := populate(elem, value); err != nil { return fmt.Errorf("%s: %v", name, err) } f.Set(reflect.Append(f, elem)) } else { if err := populate(f, value); err != nil { return fmt.Errorf("%s: %v", name, err) } } } } return nil } //!-Unpack //!+populate func populate(v reflect.Value, value string) error { switch v.Kind() { case reflect.String: v.SetString(value) case reflect.Int: i, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } v.SetInt(i) case reflect.Bool: b, err := strconv.ParseBool(value) if err != nil { return err } v.SetBool(b) default: return fmt.Errorf("unsupported kind %s", v.Type()) } return nil } //!-populate ================================================ FILE: ch12/search/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 348. // Search is a demo of the params.Unpack function. package main import ( "fmt" "log" "net/http" ) //!+ import "gopl.io/ch12/params" // search implements the /search URL endpoint. func search(resp http.ResponseWriter, req *http.Request) { var data struct { Labels []string `http:"l"` MaxResults int `http:"max"` Exact bool `http:"x"` } data.MaxResults = 10 // set default if err := params.Unpack(req, &data); err != nil { http.Error(resp, err.Error(), http.StatusBadRequest) // 400 return } // ...rest of handler... fmt.Fprintf(resp, "Search: %+v\n", data) } //!- func main() { http.HandleFunc("/search", search) log.Fatal(http.ListenAndServe(":12345", nil)) } /* //!+output $ go build gopl.io/ch12/search $ ./search & $ ./fetch 'http://localhost:12345/search' Search: {Labels:[] MaxResults:10 Exact:false} $ ./fetch 'http://localhost:12345/search?l=golang&l=programming' Search: {Labels:[golang programming] MaxResults:10 Exact:false} $ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100' Search: {Labels:[golang programming] MaxResults:100 Exact:false} $ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming' Search: {Labels:[golang programming] MaxResults:10 Exact:true} $ ./fetch 'http://localhost:12345/search?q=hello&x=123' x: strconv.ParseBool: parsing "123": invalid syntax $ ./fetch 'http://localhost:12345/search?q=hello&max=lots' max: strconv.ParseInt: parsing "lots": invalid syntax //!-output */ ================================================ FILE: ch12/sexpr/decode.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 344. // Package sexpr provides a means for converting Go objects to and // from S-expressions. package sexpr import ( "bytes" "fmt" "reflect" "strconv" "text/scanner" ) //!+Unmarshal // Unmarshal parses S-expression data and populates the variable // whose address is in the non-nil pointer out. func Unmarshal(data []byte, out interface{}) (err error) { lex := &lexer{scan: scanner.Scanner{Mode: scanner.GoTokens}} lex.scan.Init(bytes.NewReader(data)) lex.next() // get the first token defer func() { // NOTE: this is not an example of ideal error handling. if x := recover(); x != nil { err = fmt.Errorf("error at %s: %v", lex.scan.Position, x) } }() read(lex, reflect.ValueOf(out).Elem()) return nil } //!-Unmarshal //!+lexer type lexer struct { scan scanner.Scanner token rune // the current token } func (lex *lexer) next() { lex.token = lex.scan.Scan() } func (lex *lexer) text() string { return lex.scan.TokenText() } func (lex *lexer) consume(want rune) { if lex.token != want { // NOTE: Not an example of good error handling. panic(fmt.Sprintf("got %q, want %q", lex.text(), want)) } lex.next() } //!-lexer // The read function is a decoder for a small subset of well-formed // S-expressions. For brevity of our example, it takes many dubious // shortcuts. // // The parser assumes // - that the S-expression input is well-formed; it does no error checking. // - that the S-expression input corresponds to the type of the variable. // - that all numbers in the input are non-negative decimal integers. // - that all keys in ((key value) ...) struct syntax are unquoted symbols. // - that the input does not contain dotted lists such as (1 2 . 3). // - that the input does not contain Lisp reader macros such 'x and #'x. // // The reflection logic assumes // - that v is always a variable of the appropriate type for the // S-expression value. For example, v must not be a boolean, // interface, channel, or function, and if v is an array, the input // must have the correct number of elements. // - that v in the top-level call to read has the zero value of its // type and doesn't need clearing. // - that if v is a numeric variable, it is a signed integer. //!+read func read(lex *lexer, v reflect.Value) { switch lex.token { case scanner.Ident: // The only valid identifiers are // "nil" and struct field names. if lex.text() == "nil" { v.Set(reflect.Zero(v.Type())) lex.next() return } case scanner.String: s, _ := strconv.Unquote(lex.text()) // NOTE: ignoring errors v.SetString(s) lex.next() return case scanner.Int: i, _ := strconv.Atoi(lex.text()) // NOTE: ignoring errors v.SetInt(int64(i)) lex.next() return case '(': lex.next() readList(lex, v) lex.next() // consume ')' return } panic(fmt.Sprintf("unexpected token %q", lex.text())) } //!-read //!+readlist func readList(lex *lexer, v reflect.Value) { switch v.Kind() { case reflect.Array: // (item ...) for i := 0; !endList(lex); i++ { read(lex, v.Index(i)) } case reflect.Slice: // (item ...) for !endList(lex) { item := reflect.New(v.Type().Elem()).Elem() read(lex, item) v.Set(reflect.Append(v, item)) } case reflect.Struct: // ((name value) ...) for !endList(lex) { lex.consume('(') if lex.token != scanner.Ident { panic(fmt.Sprintf("got token %q, want field name", lex.text())) } name := lex.text() lex.next() read(lex, v.FieldByName(name)) lex.consume(')') } case reflect.Map: // ((key value) ...) v.Set(reflect.MakeMap(v.Type())) for !endList(lex) { lex.consume('(') key := reflect.New(v.Type().Key()).Elem() read(lex, key) value := reflect.New(v.Type().Elem()).Elem() read(lex, value) v.SetMapIndex(key, value) lex.consume(')') } default: panic(fmt.Sprintf("cannot decode list into %v", v.Type())) } } func endList(lex *lexer) bool { switch lex.token { case scanner.EOF: panic("end of file") case ')': return true } return false } //!-readlist ================================================ FILE: ch12/sexpr/encode.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 339. package sexpr import ( "bytes" "fmt" "reflect" ) //!+Marshal // Marshal encodes a Go value in S-expression form. func Marshal(v interface{}) ([]byte, error) { var buf bytes.Buffer if err := encode(&buf, reflect.ValueOf(v)); err != nil { return nil, err } return buf.Bytes(), nil } //!-Marshal // encode writes to buf an S-expression representation of v. //!+encode func encode(buf *bytes.Buffer, v reflect.Value) error { switch v.Kind() { case reflect.Invalid: buf.WriteString("nil") case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: fmt.Fprintf(buf, "%d", v.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: fmt.Fprintf(buf, "%d", v.Uint()) case reflect.String: fmt.Fprintf(buf, "%q", v.String()) case reflect.Ptr: return encode(buf, v.Elem()) case reflect.Array, reflect.Slice: // (value ...) buf.WriteByte('(') for i := 0; i < v.Len(); i++ { if i > 0 { buf.WriteByte(' ') } if err := encode(buf, v.Index(i)); err != nil { return err } } buf.WriteByte(')') case reflect.Struct: // ((name value) ...) buf.WriteByte('(') for i := 0; i < v.NumField(); i++ { if i > 0 { buf.WriteByte(' ') } fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name) if err := encode(buf, v.Field(i)); err != nil { return err } buf.WriteByte(')') } buf.WriteByte(')') case reflect.Map: // ((key value) ...) buf.WriteByte('(') for i, key := range v.MapKeys() { if i > 0 { buf.WriteByte(' ') } buf.WriteByte('(') if err := encode(buf, key); err != nil { return err } buf.WriteByte(' ') if err := encode(buf, v.MapIndex(key)); err != nil { return err } buf.WriteByte(')') } buf.WriteByte(')') default: // float, complex, bool, chan, func, interface return fmt.Errorf("unsupported type: %s", v.Type()) } return nil } //!-encode ================================================ FILE: ch12/sexpr/pretty.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package sexpr // This file implements the algorithm described in Derek C. Oppen's // 1979 Stanford technical report, "Pretty Printing". import ( "bytes" "fmt" "reflect" ) func MarshalIndent(v interface{}) ([]byte, error) { p := printer{width: margin} if err := pretty(&p, reflect.ValueOf(v)); err != nil { return nil, err } return p.Bytes(), nil } const margin = 80 type token struct { kind rune // one of "s ()" (string, blank, start, end) str string size int } type printer struct { tokens []*token // FIFO buffer stack []*token // stack of open ' ' and '(' tokens rtotal int // total number of spaces needed to print stream bytes.Buffer indents []int width int // remaining space } func (p *printer) string(str string) { tok := &token{kind: 's', str: str, size: len(str)} if len(p.stack) == 0 { p.print(tok) } else { p.tokens = append(p.tokens, tok) p.rtotal += len(str) } } func (p *printer) pop() (top *token) { last := len(p.stack) - 1 top, p.stack = p.stack[last], p.stack[:last] return } func (p *printer) begin() { if len(p.stack) == 0 { p.rtotal = 1 } t := &token{kind: '(', size: -p.rtotal} p.tokens = append(p.tokens, t) p.stack = append(p.stack, t) // push p.string("(") } func (p *printer) end() { p.string(")") p.tokens = append(p.tokens, &token{kind: ')'}) x := p.pop() x.size += p.rtotal if x.kind == ' ' { p.pop().size += p.rtotal } if len(p.stack) == 0 { for _, tok := range p.tokens { p.print(tok) } p.tokens = nil } } func (p *printer) space() { last := len(p.stack) - 1 x := p.stack[last] if x.kind == ' ' { x.size += p.rtotal p.stack = p.stack[:last] // pop } t := &token{kind: ' ', size: -p.rtotal} p.tokens = append(p.tokens, t) p.stack = append(p.stack, t) p.rtotal++ } func (p *printer) print(t *token) { switch t.kind { case 's': p.WriteString(t.str) p.width -= len(t.str) case '(': p.indents = append(p.indents, p.width) case ')': p.indents = p.indents[:len(p.indents)-1] // pop case ' ': if t.size > p.width { p.width = p.indents[len(p.indents)-1] - 1 fmt.Fprintf(&p.Buffer, "\n%*s", margin-p.width, "") } else { p.WriteByte(' ') p.width-- } } } func (p *printer) stringf(format string, args ...interface{}) { p.string(fmt.Sprintf(format, args...)) } func pretty(p *printer, v reflect.Value) error { switch v.Kind() { case reflect.Invalid: p.string("nil") case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p.stringf("%d", v.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p.stringf("%d", v.Uint()) case reflect.String: p.stringf("%q", v.String()) case reflect.Array, reflect.Slice: // (value ...) p.begin() for i := 0; i < v.Len(); i++ { if i > 0 { p.space() } if err := pretty(p, v.Index(i)); err != nil { return err } } p.end() case reflect.Struct: // ((name value ...) p.begin() for i := 0; i < v.NumField(); i++ { if i > 0 { p.space() } p.begin() p.string(v.Type().Field(i).Name) p.space() if err := pretty(p, v.Field(i)); err != nil { return err } p.end() } p.end() case reflect.Map: // ((key value ...) p.begin() for i, key := range v.MapKeys() { if i > 0 { p.space() } p.begin() if err := pretty(p, key); err != nil { return err } p.space() if err := pretty(p, v.MapIndex(key)); err != nil { return err } p.end() } p.end() case reflect.Ptr: return pretty(p, v.Elem()) default: // float, complex, bool, chan, func, interface return fmt.Errorf("unsupported type: %s", v.Type()) } return nil } ================================================ FILE: ch12/sexpr/sexpr_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package sexpr import ( "reflect" "testing" ) // Test verifies that encoding and decoding a complex data value // produces an equal result. // // The test does not make direct assertions about the encoded output // because the output depends on map iteration order, which is // nondeterministic. The output of the t.Log statements can be // inspected by running the test with the -v flag: // // $ go test -v gopl.io/ch12/sexpr // func Test(t *testing.T) { type Movie struct { Title, Subtitle string Year int Actor map[string]string Oscars []string Sequel *string } strangelove := Movie{ Title: "Dr. Strangelove", Subtitle: "How I Learned to Stop Worrying and Love the Bomb", Year: 1964, Actor: map[string]string{ "Dr. Strangelove": "Peter Sellers", "Grp. Capt. Lionel Mandrake": "Peter Sellers", "Pres. Merkin Muffley": "Peter Sellers", "Gen. Buck Turgidson": "George C. Scott", "Brig. Gen. Jack D. Ripper": "Sterling Hayden", `Maj. T.J. "King" Kong`: "Slim Pickens", }, Oscars: []string{ "Best Actor (Nomin.)", "Best Adapted Screenplay (Nomin.)", "Best Director (Nomin.)", "Best Picture (Nomin.)", }, } // Encode it data, err := Marshal(strangelove) if err != nil { t.Fatalf("Marshal failed: %v", err) } t.Logf("Marshal() = %s\n", data) // Decode it var movie Movie if err := Unmarshal(data, &movie); err != nil { t.Fatalf("Unmarshal failed: %v", err) } t.Logf("Unmarshal() = %+v\n", movie) // Check equality. if !reflect.DeepEqual(movie, strangelove) { t.Fatal("not equal") } // Pretty-print it: data, err = MarshalIndent(strangelove) if err != nil { t.Fatal(err) } t.Logf("MarshalIdent() = %s\n", data) } ================================================ FILE: ch13/bzip/bzip2.c ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 362. // // The version of this program that appeared in the first and second // printings did not comply with the proposed rules for passing // pointers between Go and C, described here: // https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md // // The version below, which appears in the third printing, // has been corrected. See bzip2.go for explanation. //!+ /* This file is gopl.io/ch13/bzip/bzip2.c, */ /* a simple wrapper for libbzip2 suitable for cgo. */ #include int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen) { s->next_in = in; s->avail_in = *inlen; s->next_out = out; s->avail_out = *outlen; int r = BZ2_bzCompress(s, action); *inlen -= s->avail_in; *outlen -= s->avail_out; s->next_in = s->next_out = NULL; return r; } //!- ================================================ FILE: ch13/bzip/bzip2.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 362. // // The version of this program that appeared in the first and second // printings did not comply with the proposed rules for passing // pointers between Go and C, described here: // https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md // // The rules forbid a C function like bz2compress from storing 'in' // and 'out' (pointers to variables allocated by Go) into the Go // variable 's', even temporarily. // // The version below, which appears in the third printing, has been // corrected. To comply with the rules, the bz_stream variable must // be allocated by C code. We have introduced two C functions, // bz2alloc and bz2free, to allocate and free instances of the // bz_stream type. Also, we have changed bz2compress so that before // it returns, it clears the fields of the bz_stream that contain // pointers to Go variables. //!+ // Package bzip provides a writer that uses bzip2 compression (bzip.org). package bzip /* #cgo CFLAGS: -I/usr/include #cgo LDFLAGS: -L/usr/lib -lbz2 #include #include bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); } int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen); void bz2free(bz_stream* s) { free(s); } */ import "C" import ( "io" "unsafe" ) type writer struct { w io.Writer // underlying output stream stream *C.bz_stream outbuf [64 * 1024]byte } // NewWriter returns a writer for bzip2-compressed streams. func NewWriter(out io.Writer) io.WriteCloser { const blockSize = 9 const verbosity = 0 const workFactor = 30 w := &writer{w: out, stream: C.bz2alloc()} C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor) return w } //!- //!+write func (w *writer) Write(data []byte) (int, error) { if w.stream == nil { panic("closed") } var total int // uncompressed bytes written for len(data) > 0 { inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf)) C.bz2compress(w.stream, C.BZ_RUN, (*C.char)(unsafe.Pointer(&data[0])), &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) total += int(inlen) data = data[inlen:] if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { return total, err } } return total, nil } //!-write //!+close // Close flushes the compressed data and closes the stream. // It does not close the underlying io.Writer. func (w *writer) Close() error { if w.stream == nil { panic("closed") } defer func() { C.BZ2_bzCompressEnd(w.stream) C.bz2free(w.stream) w.stream = nil }() for { inlen, outlen := C.uint(0), C.uint(cap(w.outbuf)) r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { return err } if r == C.BZ_STREAM_END { return nil } } } //!-close ================================================ FILE: ch13/bzip/bzip2_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package bzip_test import ( "bytes" "compress/bzip2" // reader "io" "testing" "gopl.io/ch13/bzip" // writer ) func TestBzip2(t *testing.T) { var compressed, uncompressed bytes.Buffer w := bzip.NewWriter(&compressed) // Write a repetitive message in a million pieces, // compressing one copy but not the other. tee := io.MultiWriter(w, &uncompressed) for i := 0; i < 1000000; i++ { io.WriteString(tee, "hello") } if err := w.Close(); err != nil { t.Fatal(err) } // Check the size of the compressed stream. if got, want := compressed.Len(), 255; got != want { t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want) } // Decompress and compare with original. var decompressed bytes.Buffer io.Copy(&decompressed, bzip2.NewReader(&compressed)) if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) { t.Error("decompression yielded a different message") } } ================================================ FILE: ch13/bzip-print/bzip2.c ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 362. // This is the version that appears in print, // but it does not comply with the proposed // rules for passing pointers between Go and C. // (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) // See gopl.io/ch13/bzip for an updated version. //!+ /* This file is gopl.io/ch13/bzip/bzip2.c, */ /* a simple wrapper for libbzip2 suitable for cgo. */ #include int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen) { s->next_in = in; s->avail_in = *inlen; s->next_out = out; s->avail_out = *outlen; int r = BZ2_bzCompress(s, action); *inlen -= s->avail_in; *outlen -= s->avail_out; return r; } //!- ================================================ FILE: ch13/bzip-print/bzip2.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 362. // This is the version that appears in print, // but it does not comply with the proposed // rules for passing pointers between Go and C. // (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) // See gopl.io/ch13/bzip for an updated version. //!+ // Package bzip provides a writer that uses bzip2 compression (bzip.org). package bzip /* #cgo CFLAGS: -I/usr/include #cgo LDFLAGS: -L/usr/lib -lbz2 #include int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen); */ import "C" import ( "io" "unsafe" ) type writer struct { w io.Writer // underlying output stream stream *C.bz_stream outbuf [64 * 1024]byte } // NewWriter returns a writer for bzip2-compressed streams. func NewWriter(out io.Writer) io.WriteCloser { const ( blockSize = 9 verbosity = 0 workFactor = 30 ) w := &writer{w: out, stream: new(C.bz_stream)} C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor) return w } //!- //!+write func (w *writer) Write(data []byte) (int, error) { if w.stream == nil { panic("closed") } var total int // uncompressed bytes written for len(data) > 0 { inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf)) C.bz2compress(w.stream, C.BZ_RUN, (*C.char)(unsafe.Pointer(&data[0])), &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) total += int(inlen) data = data[inlen:] if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { return total, err } } return total, nil } //!-write //!+close // Close flushes the compressed data and closes the stream. // It does not close the underlying io.Writer. func (w *writer) Close() error { if w.stream == nil { panic("closed") } defer func() { C.BZ2_bzCompressEnd(w.stream) w.stream = nil }() for { inlen, outlen := C.uint(0), C.uint(cap(w.outbuf)) r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { return err } if r == C.BZ_STREAM_END { return nil } } } //!-close ================================================ FILE: ch13/bzip-print/bzip2_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package bzip_test import ( "bytes" "compress/bzip2" // reader "io" "testing" "gopl.io/ch13/bzip" // writer ) func TestBzip2(t *testing.T) { var compressed, uncompressed bytes.Buffer w := bzip.NewWriter(&compressed) // Write a repetitive message in a million pieces, // compressing one copy but not the other. tee := io.MultiWriter(w, &uncompressed) for i := 0; i < 1000000; i++ { io.WriteString(tee, "hello") } if err := w.Close(); err != nil { t.Fatal(err) } // Check the size of the compressed stream. if got, want := compressed.Len(), 255; got != want { t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want) } // Decompress and compare with original. var decompressed bytes.Buffer io.Copy(&decompressed, bzip2.NewReader(&compressed)) if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) { t.Error("decompression yielded a different message") } } ================================================ FILE: ch13/bzipper/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 365. //!+ // Bzipper reads input, bzip2-compresses it, and writes it out. package main import ( "io" "log" "os" "gopl.io/ch13/bzip" ) func main() { w := bzip.NewWriter(os.Stdout) if _, err := io.Copy(w, os.Stdin); err != nil { log.Fatalf("bzipper: %v\n", err) } if err := w.Close(); err != nil { log.Fatalf("bzipper: close: %v\n", err) } } //!- ================================================ FILE: ch13/equal/equal.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 359. // Package equal provides a deep equivalence relation for arbitrary values. package equal import ( "reflect" "unsafe" ) //!+ func equal(x, y reflect.Value, seen map[comparison]bool) bool { if !x.IsValid() || !y.IsValid() { return x.IsValid() == y.IsValid() } if x.Type() != y.Type() { return false } // ...cycle check omitted (shown later)... //!- //!+cyclecheck // cycle check if x.CanAddr() && y.CanAddr() { xptr := unsafe.Pointer(x.UnsafeAddr()) yptr := unsafe.Pointer(y.UnsafeAddr()) if xptr == yptr { return true // identical references } c := comparison{xptr, yptr, x.Type()} if seen[c] { return true // already seen } seen[c] = true } //!-cyclecheck //!+ switch x.Kind() { case reflect.Bool: return x.Bool() == y.Bool() case reflect.String: return x.String() == y.String() // ...numeric cases omitted for brevity... //!- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return x.Int() == y.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return x.Uint() == y.Uint() case reflect.Float32, reflect.Float64: return x.Float() == y.Float() case reflect.Complex64, reflect.Complex128: return x.Complex() == y.Complex() //!+ case reflect.Chan, reflect.UnsafePointer, reflect.Func: return x.Pointer() == y.Pointer() case reflect.Ptr, reflect.Interface: return equal(x.Elem(), y.Elem(), seen) case reflect.Array, reflect.Slice: if x.Len() != y.Len() { return false } for i := 0; i < x.Len(); i++ { if !equal(x.Index(i), y.Index(i), seen) { return false } } return true // ...struct and map cases omitted for brevity... //!- case reflect.Struct: for i, n := 0, x.NumField(); i < n; i++ { if !equal(x.Field(i), y.Field(i), seen) { return false } } return true case reflect.Map: if x.Len() != y.Len() { return false } for _, k := range x.MapKeys() { if !equal(x.MapIndex(k), y.MapIndex(k), seen) { return false } } return true //!+ } panic("unreachable") } //!- //!+comparison // Equal reports whether x and y are deeply equal. //!-comparison // // Map keys are always compared with ==, not deeply. // (This matters for keys containing pointers or interfaces.) //!+comparison func Equal(x, y interface{}) bool { seen := make(map[comparison]bool) return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen) } type comparison struct { x, y unsafe.Pointer t reflect.Type } //!-comparison ================================================ FILE: ch13/equal/equal_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package equal import ( "bytes" "fmt" "testing" ) func TestEqual(t *testing.T) { one, oneAgain, two := 1, 1, 2 type CyclePtr *CyclePtr var cyclePtr1, cyclePtr2 CyclePtr cyclePtr1 = &cyclePtr1 cyclePtr2 = &cyclePtr2 type CycleSlice []CycleSlice var cycleSlice = make(CycleSlice, 1) cycleSlice[0] = cycleSlice ch1, ch2 := make(chan int), make(chan int) var ch1ro <-chan int = ch1 type mystring string var iface1, iface1Again, iface2 interface{} = &one, &oneAgain, &two for _, test := range []struct { x, y interface{} want bool }{ // basic types {1, 1, true}, {1, 2, false}, // different values {1, 1.0, false}, // different types {"foo", "foo", true}, {"foo", "bar", false}, {mystring("foo"), "foo", false}, // different types // slices {[]string{"foo"}, []string{"foo"}, true}, {[]string{"foo"}, []string{"bar"}, false}, {[]string{}, []string(nil), true}, // slice cycles {cycleSlice, cycleSlice, true}, // maps { map[string][]int{"foo": {1, 2, 3}}, map[string][]int{"foo": {1, 2, 3}}, true, }, { map[string][]int{"foo": {1, 2, 3}}, map[string][]int{"foo": {1, 2, 3, 4}}, false, }, { map[string][]int{}, map[string][]int(nil), true, }, // pointers {&one, &one, true}, {&one, &two, false}, {&one, &oneAgain, true}, {new(bytes.Buffer), new(bytes.Buffer), true}, // pointer cycles {cyclePtr1, cyclePtr1, true}, {cyclePtr2, cyclePtr2, true}, {cyclePtr1, cyclePtr2, true}, // they're deeply equal // functions {(func())(nil), (func())(nil), true}, {(func())(nil), func() {}, false}, {func() {}, func() {}, false}, // arrays {[...]int{1, 2, 3}, [...]int{1, 2, 3}, true}, {[...]int{1, 2, 3}, [...]int{1, 2, 4}, false}, // channels {ch1, ch1, true}, {ch1, ch2, false}, {ch1ro, ch1, false}, // NOTE: not equal // interfaces {&iface1, &iface1, true}, {&iface1, &iface2, false}, {&iface1Again, &iface1, true}, } { if Equal(test.x, test.y) != test.want { t.Errorf("Equal(%v, %v) = %t", test.x, test.y, !test.want) } } } func Example_equal() { //!+ fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true" fmt.Println(Equal([]string{"foo"}, []string{"bar"})) // "false" fmt.Println(Equal([]string(nil), []string{})) // "true" fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true" //!- // Output: // true // false // true // true } func Example_equalCycle() { //!+cycle // Circular linked lists a -> b -> a and c -> c. type link struct { value string tail *link } a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"} a.tail, b.tail, c.tail = b, a, c fmt.Println(Equal(a, a)) // "true" fmt.Println(Equal(b, b)) // "true" fmt.Println(Equal(c, c)) // "true" fmt.Println(Equal(a, b)) // "false" fmt.Println(Equal(a, c)) // "false" //!-cycle // Output: // true // true // true // false // false } ================================================ FILE: ch13/unsafeptr/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 357. // Package unsafeptr demonstrates basic use of unsafe.Pointer. package main import ( "fmt" "unsafe" ) func main() { //!+main var x struct { a bool b int16 c []int } // equivalent to pb := &x.b pb := (*int16)(unsafe.Pointer( uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))) *pb = 42 fmt.Println(x.b) // "42" //!-main } /* //!+wrong // NOTE: subtly incorrect! tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b) pb := (*int16)(unsafe.Pointer(tmp)) *pb = 42 //!-wrong */ ================================================ FILE: ch2/boiling/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 29. //!+ // Boiling prints the boiling point of water. package main import "fmt" const boilingF = 212.0 func main() { var f = boilingF var c = (f - 32) * 5 / 9 fmt.Printf("boiling point = %g°F or %g°C\n", f, c) // Output: // boiling point = 212°F or 100°C } //!- ================================================ FILE: ch2/cf/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 43. //!+ // Cf converts its numeric argument to Celsius and Fahrenheit. package main import ( "fmt" "os" "strconv" "gopl.io/ch2/tempconv" ) func main() { for _, arg := range os.Args[1:] { t, err := strconv.ParseFloat(arg, 64) if err != nil { fmt.Fprintf(os.Stderr, "cf: %v\n", err) os.Exit(1) } f := tempconv.Fahrenheit(t) c := tempconv.Celsius(t) fmt.Printf("%s = %s, %s = %s\n", f, tempconv.FToC(f), c, tempconv.CToF(c)) } } //!- ================================================ FILE: ch2/echo4/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 33. //!+ // Echo4 prints its command-line arguments. package main import ( "flag" "fmt" "strings" ) var n = flag.Bool("n", false, "omit trailing newline") var sep = flag.String("s", " ", "separator") func main() { flag.Parse() fmt.Print(strings.Join(flag.Args(), *sep)) if !*n { fmt.Println() } } //!- ================================================ FILE: ch2/ftoc/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 29. //!+ // Ftoc prints two Fahrenheit-to-Celsius conversions. package main import "fmt" func main() { const freezingF, boilingF = 32.0, 212.0 fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C" fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF)) // "212°F = 100°C" } func fToC(f float64) float64 { return (f - 32) * 5 / 9 } //!- ================================================ FILE: ch2/popcount/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 45. // (Package doc comment intentionally malformed to demonstrate golint.) //!+ package popcount // pc[i] is the population count of i. var pc [256]byte func init() { for i := range pc { pc[i] = pc[i/2] + byte(i&1) } } // PopCount returns the population count (number of set bits) of x. func PopCount(x uint64) int { return int(pc[byte(x>>(0*8))] + pc[byte(x>>(1*8))] + pc[byte(x>>(2*8))] + pc[byte(x>>(3*8))] + pc[byte(x>>(4*8))] + pc[byte(x>>(5*8))] + pc[byte(x>>(6*8))] + pc[byte(x>>(7*8))]) } //!- ================================================ FILE: ch2/popcount/popcount_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package popcount_test import ( "testing" "gopl.io/ch2/popcount" ) // -- Alternative implementations -- func BitCount(x uint64) int { // Hacker's Delight, Figure 5-2. x = x - ((x >> 1) & 0x5555555555555555) x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333) x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f x = x + (x >> 8) x = x + (x >> 16) x = x + (x >> 32) return int(x & 0x7f) } func PopCountByClearing(x uint64) int { n := 0 for x != 0 { x = x & (x - 1) // clear rightmost non-zero bit n++ } return n } func PopCountByShifting(x uint64) int { n := 0 for i := uint(0); i < 64; i++ { if x&(1< a, a.go => a, a/b/c.go => c, a/b.c.go => b.c func basename(s string) string { // Discard last '/' and everything before. for i := len(s) - 1; i >= 0; i-- { if s[i] == '/' { s = s[i+1:] break } } // Preserve everything before last '.'. for i := len(s) - 1; i >= 0; i-- { if s[i] == '.' { s = s[:i] break } } return s } //!- ================================================ FILE: ch3/basename2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 72. // Basename2 reads file names from stdin and prints the base name of each one. package main import ( "bufio" "fmt" "os" "strings" ) func main() { input := bufio.NewScanner(os.Stdin) for input.Scan() { fmt.Println(basename(input.Text())) } // NOTE: ignoring potential errors from input.Err() } // basename removes directory components and a trailing .suffix. // e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c //!+ func basename(s string) string { slash := strings.LastIndex(s, "/") // -1 if "/" not found s = s[slash+1:] if dot := strings.LastIndex(s, "."); dot >= 0 { s = s[:dot] } return s } //!- ================================================ FILE: ch3/comma/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 73. // Comma prints its argument numbers with a comma at each power of 1000. // // Example: // $ go build gopl.io/ch3/comma // $ ./comma 1 12 123 1234 1234567890 // 1 // 12 // 123 // 1,234 // 1,234,567,890 // package main import ( "fmt" "os" ) func main() { for i := 1; i < len(os.Args); i++ { fmt.Printf(" %s\n", comma(os.Args[i])) } } //!+ // comma inserts commas in a non-negative decimal integer string. func comma(s string) string { n := len(s) if n <= 3 { return s } return comma(s[:n-3]) + "," + s[n-3:] } //!- ================================================ FILE: ch3/mandelbrot/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 61. //!+ // Mandelbrot emits a PNG image of the Mandelbrot fractal. package main import ( "image" "image/color" "image/png" "math/cmplx" "os" ) func main() { const ( xmin, ymin, xmax, ymax = -2, -2, +2, +2 width, height = 1024, 1024 ) img := image.NewRGBA(image.Rect(0, 0, width, height)) for py := 0; py < height; py++ { y := float64(py)/height*(ymax-ymin) + ymin for px := 0; px < width; px++ { x := float64(px)/width*(xmax-xmin) + xmin z := complex(x, y) // Image point (px, py) represents complex value z. img.Set(px, py, mandelbrot(z)) } } png.Encode(os.Stdout, img) // NOTE: ignoring errors } func mandelbrot(z complex128) color.Color { const iterations = 200 const contrast = 15 var v complex128 for n := uint8(0); n < iterations; n++ { v = v*v + z if cmplx.Abs(v) > 2 { return color.Gray{255 - contrast*n} } } return color.Black } //!- // Some other interesting functions: func acos(z complex128) color.Color { v := cmplx.Acos(z) blue := uint8(real(v)*128) + 127 red := uint8(imag(v)*128) + 127 return color.YCbCr{192, blue, red} } func sqrt(z complex128) color.Color { v := cmplx.Sqrt(z) blue := uint8(real(v)*128) + 127 red := uint8(imag(v)*128) + 127 return color.YCbCr{128, blue, red} } // f(x) = x^4 - 1 // // z' = z - f(z)/f'(z) // = z - (z^4 - 1) / (4 * z^3) // = z - (z - 1/z^3) / 4 func newton(z complex128) color.Color { const iterations = 37 const contrast = 7 for i := uint8(0); i < iterations; i++ { z -= (z - 1/(z*z*z)) / 4 if cmplx.Abs(z*z*z*z-1) < 1e-6 { return color.Gray{255 - contrast*i} } } return color.Black } ================================================ FILE: ch3/netflag/netflag.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 77. // Netflag demonstrates an integer type used as a bit field. package main import ( "fmt" . "net" ) //!+ func IsUp(v Flags) bool { return v&FlagUp == FlagUp } func TurnDown(v *Flags) { *v &^= FlagUp } func SetBroadcast(v *Flags) { *v |= FlagBroadcast } func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 } func main() { var v Flags = FlagMulticast | FlagUp fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true" TurnDown(&v) fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false" SetBroadcast(&v) fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false" fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true" } //!- ================================================ FILE: ch3/printints/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 74. // Printints demonstrates the use of bytes.Buffer to format a string. package main import ( "bytes" "fmt" ) //!+ // intsToString is like fmt.Sprint(values) but adds commas. func intsToString(values []int) string { var buf bytes.Buffer buf.WriteByte('[') for i, v := range values { if i > 0 { buf.WriteString(", ") } fmt.Fprintf(&buf, "%d", v) } buf.WriteByte(']') return buf.String() } func main() { fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]" } //!- ================================================ FILE: ch3/surface/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 58. //!+ // Surface computes an SVG rendering of a 3-D surface function. package main import ( "fmt" "math" ) const ( width, height = 600, 320 // canvas size in pixels cells = 100 // number of grid cells xyrange = 30.0 // axis ranges (-xyrange..+xyrange) xyscale = width / 2 / xyrange // pixels per x or y unit zscale = height * 0.4 // pixels per z unit angle = math.Pi / 6 // angle of x, y axes (=30°) ) var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°) func main() { fmt.Printf("", width, height) for i := 0; i < cells; i++ { for j := 0; j < cells; j++ { ax, ay := corner(i+1, j) bx, by := corner(i, j) cx, cy := corner(i, j+1) dx, dy := corner(i+1, j+1) fmt.Printf("\n", ax, ay, bx, by, cx, cy, dx, dy) } } fmt.Println("") } func corner(i, j int) (float64, float64) { // Find point (x,y) at corner of cell (i,j). x := xyrange * (float64(i)/cells - 0.5) y := xyrange * (float64(j)/cells - 0.5) // Compute surface height z. z := f(x, y) // Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy). sx := width/2 + (x-y)*cos30*xyscale sy := height/2 + (x+y)*sin30*xyscale - z*zscale return sx, sy } func f(x, y float64) float64 { r := math.Hypot(x, y) // distance from (0,0) return math.Sin(r) / r } //!- ================================================ FILE: ch4/append/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 88. // Append illustrates the behavior of the built-in append function. package main import "fmt" func appendslice(x []int, y ...int) []int { var z []int zlen := len(x) + len(y) if zlen <= cap(x) { // There is room to expand the slice. z = x[:zlen] } else { // There is insufficient space. // Grow by doubling, for amortized linear complexity. zcap := zlen if zcap < 2*len(x) { zcap = 2 * len(x) } z = make([]int, zlen, zcap) copy(z, x) } copy(z[len(x):], y) return z } //!+append func appendInt(x []int, y int) []int { var z []int zlen := len(x) + 1 if zlen <= cap(x) { // There is room to grow. Extend the slice. z = x[:zlen] } else { // There is insufficient space. Allocate a new array. // Grow by doubling, for amortized linear complexity. zcap := zlen if zcap < 2*len(x) { zcap = 2 * len(x) } z = make([]int, zlen, zcap) copy(z, x) // a built-in function; see text } z[len(x)] = y return z } //!-append //!+growth func main() { var x, y []int for i := 0; i < 10; i++ { y = appendInt(x, i) fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y) x = y } } //!-growth /* //!+output 0 cap=1 [0] 1 cap=2 [0 1] 2 cap=4 [0 1 2] 3 cap=4 [0 1 2 3] 4 cap=8 [0 1 2 3 4] 5 cap=8 [0 1 2 3 4 5] 6 cap=8 [0 1 2 3 4 5 6] 7 cap=8 [0 1 2 3 4 5 6 7] 8 cap=16 [0 1 2 3 4 5 6 7 8] 9 cap=16 [0 1 2 3 4 5 6 7 8 9] //!-output */ ================================================ FILE: ch4/autoescape/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 117. // Autoescape demonstrates automatic HTML escaping in html/template. package main import ( "html/template" "log" "os" ) //!+ func main() { const templ = `

A: {{.A}}

B: {{.B}}

` t := template.Must(template.New("escape").Parse(templ)) var data struct { A string // untrusted plain text B template.HTML // trusted HTML } data.A = "Hello!" data.B = "Hello!" if err := t.Execute(os.Stdout, data); err != nil { log.Fatal(err) } } //!- ================================================ FILE: ch4/charcount/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 97. //!+ // Charcount computes counts of Unicode characters. package main import ( "bufio" "fmt" "io" "os" "unicode" "unicode/utf8" ) func main() { counts := make(map[rune]int) // counts of Unicode characters var utflen [utf8.UTFMax + 1]int // count of lengths of UTF-8 encodings invalid := 0 // count of invalid UTF-8 characters in := bufio.NewReader(os.Stdin) for { r, n, err := in.ReadRune() // returns rune, nbytes, error if err == io.EOF { break } if err != nil { fmt.Fprintf(os.Stderr, "charcount: %v\n", err) os.Exit(1) } if r == unicode.ReplacementChar && n == 1 { invalid++ continue } counts[r]++ utflen[n]++ } fmt.Printf("rune\tcount\n") for c, n := range counts { fmt.Printf("%q\t%d\n", c, n) } fmt.Print("\nlen\tcount\n") for i, n := range utflen { if i > 0 { fmt.Printf("%d\t%d\n", i, n) } } if invalid > 0 { fmt.Printf("\n%d invalid UTF-8 characters\n", invalid) } } //!- ================================================ FILE: ch4/dedup/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 96. // Dedup prints only one instance of each line; duplicates are removed. package main import ( "bufio" "fmt" "os" ) //!+ func main() { seen := make(map[string]bool) // a set of strings input := bufio.NewScanner(os.Stdin) for input.Scan() { line := input.Text() if !seen[line] { seen[line] = true fmt.Println(line) } } if err := input.Err(); err != nil { fmt.Fprintf(os.Stderr, "dedup: %v\n", err) os.Exit(1) } } //!- ================================================ FILE: ch4/embed/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 106. // Embed demonstrates basic struct embedding. package main import "fmt" type Point struct{ X, Y int } type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } func main() { var w Wheel //!+ w = Wheel{Circle{Point{8, 8}, 5}, 20} w = Wheel{ Circle: Circle{ Point: Point{X: 8, Y: 8}, Radius: 5, }, Spokes: 20, // NOTE: trailing comma necessary here (and at Radius) } fmt.Printf("%#v\n", w) // Output: // Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20} w.X = 42 fmt.Printf("%#v\n", w) // Output: // Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20} //!- } ================================================ FILE: ch4/github/github.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 110. //!+ // Package github provides a Go API for the GitHub issue tracker. // See https://developer.github.com/v3/search/#search-issues. package github import "time" const IssuesURL = "https://api.github.com/search/issues" type IssuesSearchResult struct { TotalCount int `json:"total_count"` Items []*Issue } type Issue struct { Number int HTMLURL string `json:"html_url"` Title string State string User *User CreatedAt time.Time `json:"created_at"` Body string // in Markdown format } type User struct { Login string HTMLURL string `json:"html_url"` } //!- ================================================ FILE: ch4/github/search.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ //!+ package github import ( "encoding/json" "fmt" "net/http" "net/url" "strings" ) // SearchIssues queries the GitHub issue tracker. func SearchIssues(terms []string) (*IssuesSearchResult, error) { q := url.QueryEscape(strings.Join(terms, " ")) resp, err := http.Get(IssuesURL + "?q=" + q) if err != nil { return nil, err } //!- // For long-term stability, instead of http.Get, use the // variant below which adds an HTTP request header indicating // that only version 3 of the GitHub API is acceptable. // // req, err := http.NewRequest("GET", IssuesURL+"?q="+q, nil) // if err != nil { // return nil, err // } // req.Header.Set( // "Accept", "application/vnd.github.v3.text-match+json") // resp, err := http.DefaultClient.Do(req) //!+ // We must close resp.Body on all execution paths. // (Chapter 5 presents 'defer', which makes this simpler.) if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("search query failed: %s", resp.Status) } var result IssuesSearchResult if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { resp.Body.Close() return nil, err } resp.Body.Close() return &result, nil } //!- ================================================ FILE: ch4/graph/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 99. // Graph shows how to use a map of maps to represent a directed graph. package main import "fmt" //!+ var graph = make(map[string]map[string]bool) func addEdge(from, to string) { edges := graph[from] if edges == nil { edges = make(map[string]bool) graph[from] = edges } edges[to] = true } func hasEdge(from, to string) bool { return graph[from][to] } //!- func main() { addEdge("a", "b") addEdge("c", "d") addEdge("a", "d") addEdge("d", "a") fmt.Println(hasEdge("a", "b")) fmt.Println(hasEdge("c", "d")) fmt.Println(hasEdge("a", "d")) fmt.Println(hasEdge("d", "a")) fmt.Println(hasEdge("x", "b")) fmt.Println(hasEdge("c", "d")) fmt.Println(hasEdge("x", "d")) fmt.Println(hasEdge("d", "x")) } ================================================ FILE: ch4/issues/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 112. //!+ // Issues prints a table of GitHub issues matching the search terms. package main import ( "fmt" "log" "os" "gopl.io/ch4/github" ) //!+ func main() { result, err := github.SearchIssues(os.Args[1:]) if err != nil { log.Fatal(err) } fmt.Printf("%d issues:\n", result.TotalCount) for _, item := range result.Items { fmt.Printf("#%-5d %9.9s %.55s\n", item.Number, item.User.Login, item.Title) } } //!- /* //!+textoutput $ go build gopl.io/ch4/issues $ ./issues repo:golang/go is:open json decoder 13 issues: #5680 eaigner encoding/json: set key converter on en/decoder #6050 gopherbot encoding/json: provide tokenizer #8658 gopherbot encoding/json: use bufio #8462 kortschak encoding/json: UnmarshalText confuses json.Unmarshal #5901 rsc encoding/json: allow override type marshaling #9812 klauspost encoding/json: string tag not symmetric #7872 extempora encoding/json: Encoder internally buffers full output #9650 cespare encoding/json: Decoding gives errPhase when unmarshalin #6716 gopherbot encoding/json: include field name in unmarshal error me #6901 lukescott encoding/json, encoding/xml: option to treat unknown fi #6384 joeshaw encoding/json: encode precise floating point integers u #6647 btracey x/tools/cmd/godoc: display type kind of each named type #4237 gjemiller encoding/base64: URLEncoding padding is optional //!-textoutput */ ================================================ FILE: ch4/issueshtml/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 115. // Issueshtml prints an HTML table of issues matching the search terms. package main import ( "log" "os" "gopl.io/ch4/github" ) //!+template import "html/template" var issueList = template.Must(template.New("issuelist").Parse(`

{{.TotalCount}} issues

{{range .Items}} {{end}}
# State User Title
{{.Number}} {{.State}} {{.User.Login}} {{.Title}}
`)) //!-template //!+ func main() { result, err := github.SearchIssues(os.Args[1:]) if err != nil { log.Fatal(err) } if err := issueList.Execute(os.Stdout, result); err != nil { log.Fatal(err) } } //!- ================================================ FILE: ch4/issuesreport/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 113. // Issuesreport prints a report of issues matching the search terms. package main import ( "log" "os" "text/template" "time" "gopl.io/ch4/github" ) //!+template const templ = `{{.TotalCount}} issues: {{range .Items}}---------------------------------------- Number: {{.Number}} User: {{.User.Login}} Title: {{.Title | printf "%.64s"}} Age: {{.CreatedAt | daysAgo}} days {{end}}` //!-template //!+daysAgo func daysAgo(t time.Time) int { return int(time.Since(t).Hours() / 24) } //!-daysAgo //!+exec var report = template.Must(template.New("issuelist"). Funcs(template.FuncMap{"daysAgo": daysAgo}). Parse(templ)) func main() { result, err := github.SearchIssues(os.Args[1:]) if err != nil { log.Fatal(err) } if err := report.Execute(os.Stdout, result); err != nil { log.Fatal(err) } } //!-exec func noMust() { //!+parse report, err := template.New("report"). Funcs(template.FuncMap{"daysAgo": daysAgo}). Parse(templ) if err != nil { log.Fatal(err) } //!-parse result, err := github.SearchIssues(os.Args[1:]) if err != nil { log.Fatal(err) } if err := report.Execute(os.Stdout, result); err != nil { log.Fatal(err) } } /* //!+output $ go build gopl.io/ch4/issuesreport $ ./issuesreport repo:golang/go is:open json decoder 13 issues: ---------------------------------------- Number: 5680 User: eaigner Title: encoding/json: set key converter on en/decoder Age: 750 days ---------------------------------------- Number: 6050 User: gopherbot Title: encoding/json: provide tokenizer Age: 695 days ---------------------------------------- ... //!-output */ ================================================ FILE: ch4/movie/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 108. // Movie prints Movies as JSON. package main import ( "encoding/json" "fmt" "log" ) //!+ type Movie struct { Title string Year int `json:"released"` Color bool `json:"color,omitempty"` Actors []string } var movies = []Movie{ {Title: "Casablanca", Year: 1942, Color: false, Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}}, {Title: "Cool Hand Luke", Year: 1967, Color: true, Actors: []string{"Paul Newman"}}, {Title: "Bullitt", Year: 1968, Color: true, Actors: []string{"Steve McQueen", "Jacqueline Bisset"}}, // ... } //!- func main() { { //!+Marshal data, err := json.Marshal(movies) if err != nil { log.Fatalf("JSON marshaling failed: %s", err) } fmt.Printf("%s\n", data) //!-Marshal } { //!+MarshalIndent data, err := json.MarshalIndent(movies, "", " ") if err != nil { log.Fatalf("JSON marshaling failed: %s", err) } fmt.Printf("%s\n", data) //!-MarshalIndent //!+Unmarshal var titles []struct{ Title string } if err := json.Unmarshal(data, &titles); err != nil { log.Fatalf("JSON unmarshaling failed: %s", err) } fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]" //!-Unmarshal } } /* //!+output [{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true," Actors":["Steve McQueen","Jacqueline Bisset"]}] //!-output */ /* //!+indented [ { "Title": "Casablanca", "released": 1942, "Actors": [ "Humphrey Bogart", "Ingrid Bergman" ] }, { "Title": "Cool Hand Luke", "released": 1967, "color": true, "Actors": [ "Paul Newman" ] }, { "Title": "Bullitt", "released": 1968, "color": true, "Actors": [ "Steve McQueen", "Jacqueline Bisset" ] } ] //!-indented */ ================================================ FILE: ch4/nonempty/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 91. //!+nonempty // Nonempty is an example of an in-place slice algorithm. package main import "fmt" // nonempty returns a slice holding only the non-empty strings. // The underlying array is modified during the call. func nonempty(strings []string) []string { i := 0 for _, s := range strings { if s != "" { strings[i] = s i++ } } return strings[:i] } //!-nonempty func main() { //!+main data := []string{"one", "", "three"} fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]` fmt.Printf("%q\n", data) // `["one" "three" "three"]` //!-main } //!+alt func nonempty2(strings []string) []string { out := strings[:0] // zero-length slice of original for _, s := range strings { if s != "" { out = append(out, s) } } return out } //!-alt ================================================ FILE: ch4/rev/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 86. // Rev reverses a slice. package main import ( "bufio" "fmt" "os" "strconv" "strings" ) func main() { //!+array a := [...]int{0, 1, 2, 3, 4, 5} reverse(a[:]) fmt.Println(a) // "[5 4 3 2 1 0]" //!-array //!+slice s := []int{0, 1, 2, 3, 4, 5} // Rotate s left by two positions. reverse(s[:2]) reverse(s[2:]) reverse(s) fmt.Println(s) // "[2 3 4 5 0 1]" //!-slice // Interactive test of reverse. input := bufio.NewScanner(os.Stdin) outer: for input.Scan() { var ints []int for _, s := range strings.Fields(input.Text()) { x, err := strconv.ParseInt(s, 10, 64) if err != nil { fmt.Fprintln(os.Stderr, err) continue outer } ints = append(ints, int(x)) } reverse(ints) fmt.Printf("%v\n", ints) } // NOTE: ignoring potential errors from input.Err() } //!+rev // reverse reverses a slice of ints in place. func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } } //!-rev ================================================ FILE: ch4/sha256/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 83. // The sha256 command computes the SHA256 hash (an array) of a string. package main import "fmt" //!+ import "crypto/sha256" func main() { c1 := sha256.Sum256([]byte("x")) c2 := sha256.Sum256([]byte("X")) fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1) // Output: // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881 // 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015 // false // [32]uint8 } //!- ================================================ FILE: ch4/treesort/sort.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 101. // Package treesort provides insertion sort using an unbalanced binary tree. package treesort //!+ type tree struct { value int left, right *tree } // Sort sorts values in place. func Sort(values []int) { var root *tree for _, v := range values { root = add(root, v) } appendValues(values[:0], root) } // appendValues appends the elements of t to values in order // and returns the resulting slice. func appendValues(values []int, t *tree) []int { if t != nil { values = appendValues(values, t.left) values = append(values, t.value) values = appendValues(values, t.right) } return values } func add(t *tree, value int) *tree { if t == nil { // Equivalent to return &tree{value: value}. t = new(tree) t.value = value return t } if value < t.value { t.left = add(t.left, value) } else { t.right = add(t.right, value) } return t } //!- ================================================ FILE: ch4/treesort/sort_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package treesort_test import ( "math/rand" "sort" "testing" "gopl.io/ch4/treesort" ) func TestSort(t *testing.T) { data := make([]int, 50) for i := range data { data[i] = rand.Int() % 50 } treesort.Sort(data) if !sort.IntsAreSorted(data) { t.Errorf("not sorted: %v", data) } } ================================================ FILE: ch5/defer1/defer.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 150. // Defer1 demonstrates a deferred call being invoked during a panic. package main import "fmt" //!+f func main() { f(3) } func f(x int) { fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0 defer fmt.Printf("defer %d\n", x) f(x - 1) } //!-f /* //!+stdout f(3) f(2) f(1) defer 1 defer 2 defer 3 //!-stdout //!+stderr panic: runtime error: integer divide by zero main.f(0) src/gopl.io/ch5/defer1/defer.go:14 main.f(1) src/gopl.io/ch5/defer1/defer.go:16 main.f(2) src/gopl.io/ch5/defer1/defer.go:16 main.f(3) src/gopl.io/ch5/defer1/defer.go:16 main.main() src/gopl.io/ch5/defer1/defer.go:10 //!-stderr */ ================================================ FILE: ch5/defer2/defer.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 151. // Defer2 demonstrates a deferred call to runtime.Stack during a panic. package main import ( "fmt" "os" "runtime" ) //!+ func main() { defer printStack() f(3) } func printStack() { var buf [4096]byte n := runtime.Stack(buf[:], false) os.Stdout.Write(buf[:n]) } //!- func f(x int) { fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0 defer fmt.Printf("defer %d\n", x) f(x - 1) } /* //!+printstack goroutine 1 [running]: main.printStack() src/gopl.io/ch5/defer2/defer.go:20 main.f(0) src/gopl.io/ch5/defer2/defer.go:27 main.f(1) src/gopl.io/ch5/defer2/defer.go:29 main.f(2) src/gopl.io/ch5/defer2/defer.go:29 main.f(3) src/gopl.io/ch5/defer2/defer.go:29 main.main() src/gopl.io/ch5/defer2/defer.go:15 //!-printstack */ ================================================ FILE: ch5/fetch/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 148. // Fetch saves the contents of a URL into a local file. package main import ( "fmt" "io" "net/http" "os" "path" ) //!+ // Fetch downloads the URL and returns the // name and length of the local file. func fetch(url string) (filename string, n int64, err error) { resp, err := http.Get(url) if err != nil { return "", 0, err } defer resp.Body.Close() local := path.Base(resp.Request.URL.Path) if local == "/" { local = "index.html" } f, err := os.Create(local) if err != nil { return "", 0, err } n, err = io.Copy(f, resp.Body) // Close file, but prefer error from Copy, if any. if closeErr := f.Close(); err == nil { err = closeErr } return local, n, err } //!- func main() { for _, url := range os.Args[1:] { local, n, err := fetch(url) if err != nil { fmt.Fprintf(os.Stderr, "fetch %s: %v\n", url, err) continue } fmt.Fprintf(os.Stderr, "%s => %s (%d bytes).\n", url, local, n) } } ================================================ FILE: ch5/findlinks1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 122. //!+main // Findlinks1 prints the links in an HTML document read from standard input. package main import ( "fmt" "os" "golang.org/x/net/html" ) func main() { doc, err := html.Parse(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) os.Exit(1) } for _, link := range visit(nil, doc) { fmt.Println(link) } } //!-main //!+visit // visit appends to links each link found in n and returns the result. func visit(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key == "href" { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = visit(links, c) } return links } //!-visit /* //!+html package html type Node struct { Type NodeType Data string Attr []Attribute FirstChild, NextSibling *Node } type NodeType int32 const ( ErrorNode NodeType = iota TextNode DocumentNode ElementNode CommentNode DoctypeNode ) type Attribute struct { Key, Val string } func Parse(r io.Reader) (*Node, error) //!-html */ ================================================ FILE: ch5/findlinks2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 125. // Findlinks2 does an HTTP GET on each URL, parses the // result as HTML, and prints the links within it. // // Usage: // findlinks url ... package main import ( "fmt" "net/http" "os" "golang.org/x/net/html" ) // visit appends to links each link found in n, and returns the result. func visit(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key == "href" { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = visit(links, c) } return links } //!+ func main() { for _, url := range os.Args[1:] { links, err := findLinks(url) if err != nil { fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err) continue } for _, link := range links { fmt.Println(link) } } } // findLinks performs an HTTP GET request for url, parses the // response as HTML, and extracts and returns the links. func findLinks(url string) ([]string, error) { resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("getting %s: %s", url, resp.Status) } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url, err) } return visit(nil, doc), nil } //!- ================================================ FILE: ch5/findlinks3/findlinks.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 139. // Findlinks3 crawls the web, starting with the URLs on the command line. package main import ( "fmt" "log" "os" "gopl.io/ch5/links" ) //!+breadthFirst // breadthFirst calls f for each item in the worklist. // Any items returned by f are added to the worklist. // f is called at most once for each item. func breadthFirst(f func(item string) []string, worklist []string) { seen := make(map[string]bool) for len(worklist) > 0 { items := worklist worklist = nil for _, item := range items { if !seen[item] { seen[item] = true worklist = append(worklist, f(item)...) } } } } //!-breadthFirst //!+crawl func crawl(url string) []string { fmt.Println(url) list, err := links.Extract(url) if err != nil { log.Print(err) } return list } //!-crawl //!+main func main() { // Crawl the web breadth-first, // starting from the command-line arguments. breadthFirst(crawl, os.Args[1:]) } //!-main ================================================ FILE: ch5/links/links.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 138. //!+Extract // Package links provides a link-extraction function. package links import ( "fmt" "net/http" "golang.org/x/net/html" ) // Extract makes an HTTP GET request to the specified URL, parses // the response as HTML, and returns the links in the HTML document. func Extract(url string) ([]string, error) { resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("getting %s: %s", url, resp.Status) } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url, err) } var links []string visitNode := func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key != "href" { continue } link, err := resp.Request.URL.Parse(a.Val) if err != nil { continue // ignore bad URLs } links = append(links, link.String()) } } } forEachNode(doc, visitNode, nil) return links, nil } //!-Extract // Copied from gopl.io/ch5/outline2. func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } } ================================================ FILE: ch5/outline/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 123. // Outline prints the outline of an HTML document tree. package main import ( "fmt" "os" "golang.org/x/net/html" ) //!+ func main() { doc, err := html.Parse(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "outline: %v\n", err) os.Exit(1) } outline(nil, doc) } func outline(stack []string, n *html.Node) { if n.Type == html.ElementNode { stack = append(stack, n.Data) // push tag fmt.Println(stack) } for c := n.FirstChild; c != nil; c = c.NextSibling { outline(stack, c) } } //!- ================================================ FILE: ch5/outline2/outline.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 133. // Outline prints the outline of an HTML document tree. package main import ( "fmt" "net/http" "os" "golang.org/x/net/html" ) func main() { for _, url := range os.Args[1:] { outline(url) } } func outline(url string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() doc, err := html.Parse(resp.Body) if err != nil { return err } //!+call forEachNode(doc, startElement, endElement) //!-call return nil } //!+forEachNode // forEachNode calls the functions pre(x) and post(x) for each node // x in the tree rooted at n. Both functions are optional. // pre is called before the children are visited (preorder) and // post is called after (postorder). func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } } //!-forEachNode //!+startend var depth int func startElement(n *html.Node) { if n.Type == html.ElementNode { fmt.Printf("%*s<%s>\n", depth*2, "", n.Data) depth++ } } func endElement(n *html.Node) { if n.Type == html.ElementNode { depth-- fmt.Printf("%*s\n", depth*2, "", n.Data) } } //!-startend ================================================ FILE: ch5/squares/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 135. // The squares program demonstrates a function value with state. package main import "fmt" //!+ // squares returns a function that returns // the next square number each time it is called. func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" } //!- ================================================ FILE: ch5/sum/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 142. // The sum program demonstrates a variadic function. package main import "fmt" //!+ func sum(vals ...int) int { total := 0 for _, val := range vals { total += val } return total } //!- func main() { //!+main fmt.Println(sum()) // "0" fmt.Println(sum(3)) // "3" fmt.Println(sum(1, 2, 3, 4)) // "10" //!-main //!+slice values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10" //!-slice } ================================================ FILE: ch5/title1/title.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 144. // Title1 prints the title of an HTML document specified by a URL. package main /* //!+output $ go build gopl.io/ch5/title1 $ ./title1 http://gopl.io The Go Programming Language $ ./title1 https://golang.org/doc/effective_go.html Effective Go - The Go Programming Language $ ./title1 https://golang.org/doc/gopher/frontpage.png title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html //!-output */ import ( "fmt" "net/http" "os" "strings" "golang.org/x/net/html" ) // Copied from gopl.io/ch5/outline2. func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } } //!+ func title(url string) error { resp, err := http.Get(url) if err != nil { return err } // Check Content-Type is HTML (e.g., "text/html; charset=utf-8"). ct := resp.Header.Get("Content-Type") if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") { resp.Body.Close() return fmt.Errorf("%s has type %s, not text/html", url, ct) } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return fmt.Errorf("parsing %s as HTML: %v", url, err) } visitNode := func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { fmt.Println(n.FirstChild.Data) } } forEachNode(doc, visitNode, nil) return nil } //!- func main() { for _, arg := range os.Args[1:] { if err := title(arg); err != nil { fmt.Fprintf(os.Stderr, "title: %v\n", err) } } } ================================================ FILE: ch5/title2/title.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 145. // Title2 prints the title of an HTML document specified by a URL. // It uses defer to simplify closing the response body stream. package main import ( "fmt" "net/http" "os" "strings" "golang.org/x/net/html" ) // Copied from gopl.io/ch5/outline2. func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } } //!+ func title(url string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() ct := resp.Header.Get("Content-Type") if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") { return fmt.Errorf("%s has type %s, not text/html", url, ct) } doc, err := html.Parse(resp.Body) if err != nil { return fmt.Errorf("parsing %s as HTML: %v", url, err) } // ...print doc's title element... //!- visitNode := func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { fmt.Println(n.FirstChild.Data) } } forEachNode(doc, visitNode, nil) //!+ return nil } //!- func main() { for _, arg := range os.Args[1:] { if err := title(arg); err != nil { fmt.Fprintf(os.Stderr, "title: %v\n", err) } } } ================================================ FILE: ch5/title3/title.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 153. // Title3 prints the title of an HTML document specified by a URL. package main import ( "fmt" "net/http" "os" "strings" "golang.org/x/net/html" ) // Copied from gopl.io/ch5/outline2. func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } } //!+ // soleTitle returns the text of the first non-empty title element // in doc, and an error if there was not exactly one. func soleTitle(doc *html.Node) (title string, err error) { type bailout struct{} defer func() { switch p := recover(); p { case nil: // no panic case bailout{}: // "expected" panic err = fmt.Errorf("multiple title elements") default: panic(p) // unexpected panic; carry on panicking } }() // Bail out of recursion if we find more than one non-empty title. forEachNode(doc, func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { if title != "" { panic(bailout{}) // multiple title elements } title = n.FirstChild.Data } }, nil) if title == "" { return "", fmt.Errorf("no title element") } return title, nil } //!- func title(url string) error { resp, err := http.Get(url) if err != nil { return err } // Check Content-Type is HTML (e.g., "text/html; charset=utf-8"). ct := resp.Header.Get("Content-Type") if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") { resp.Body.Close() return fmt.Errorf("%s has type %s, not text/html", url, ct) } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return fmt.Errorf("parsing %s as HTML: %v", url, err) } title, err := soleTitle(doc) if err != nil { return err } fmt.Println(title) return nil } func main() { for _, arg := range os.Args[1:] { if err := title(arg); err != nil { fmt.Fprintf(os.Stderr, "title: %v\n", err) } } } ================================================ FILE: ch5/toposort/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 136. // The toposort program prints the nodes of a DAG in topological order. package main import ( "fmt" "sort" ) //!+table // prereqs maps computer science courses to their prerequisites. var prereqs = map[string][]string{ "algorithms": {"data structures"}, "calculus": {"linear algebra"}, "compilers": { "data structures", "formal languages", "computer organization", }, "data structures": {"discrete math"}, "databases": {"data structures"}, "discrete math": {"intro to programming"}, "formal languages": {"discrete math"}, "networks": {"operating systems"}, "operating systems": {"data structures", "computer organization"}, "programming languages": {"data structures", "computer organization"}, } //!-table //!+main func main() { for i, course := range topoSort(prereqs) { fmt.Printf("%d:\t%s\n", i+1, course) } } func topoSort(m map[string][]string) []string { var order []string seen := make(map[string]bool) var visitAll func(items []string) visitAll = func(items []string) { for _, item := range items { if !seen[item] { seen[item] = true visitAll(m[item]) order = append(order, item) } } } var keys []string for key := range m { keys = append(keys, key) } sort.Strings(keys) visitAll(keys) return order } //!-main ================================================ FILE: ch5/trace/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 146. // The trace program uses defer to add entry/exit diagnostics to a function. package main import ( "log" "time" ) //!+main func bigSlowOperation() { defer trace("bigSlowOperation")() // don't forget the extra parentheses // ...lots of work... time.Sleep(10 * time.Second) // simulate slow operation by sleeping } func trace(msg string) func() { start := time.Now() log.Printf("enter %s", msg) return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) } } //!-main func main() { bigSlowOperation() } /* !+output $ go build gopl.io/ch5/trace $ ./trace 2015/11/18 09:53:26 enter bigSlowOperation 2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s) !-output */ ================================================ FILE: ch5/wait/wait.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 130. // The wait program waits for an HTTP server to start responding. package main import ( "fmt" "log" "net/http" "os" "time" ) //!+ // WaitForServer attempts to contact the server of a URL. // It tries for one minute using exponential back-off. // It reports an error if all attempts fail. func WaitForServer(url string) error { const timeout = 1 * time.Minute deadline := time.Now().Add(timeout) for tries := 0; time.Now().Before(deadline); tries++ { _, err := http.Head(url) if err == nil { return nil // success } log.Printf("server not responding (%s); retrying...", err) time.Sleep(time.Second << uint(tries)) // exponential back-off } return fmt.Errorf("server %s failed to respond after %s", url, timeout) } //!- func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "usage: wait url\n") os.Exit(1) } url := os.Args[1] //!+main // (In function main.) if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) os.Exit(1) } //!-main } ================================================ FILE: ch6/coloredpoint/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 161. // Coloredpoint demonstrates struct embedding. package main import ( "fmt" "math" ) //!+decl import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA } //!-decl func (p Point) Distance(q Point) float64 { dX := q.X - p.X dY := q.Y - p.Y return math.Sqrt(dX*dX + dY*dY) } func (p *Point) ScaleBy(factor float64) { p.X *= factor p.Y *= factor } func main() { //!+main red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} var p = ColoredPoint{Point{1, 1}, red} var q = ColoredPoint{Point{5, 4}, blue} fmt.Println(p.Distance(q.Point)) // "5" p.ScaleBy(2) q.ScaleBy(2) fmt.Println(p.Distance(q.Point)) // "10" //!-main } /* //!+error p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point //!-error */ func init() { //!+methodexpr p := Point{1, 2} q := Point{4, 6} distance := Point.Distance // method expression fmt.Println(distance(p, q)) // "5" fmt.Printf("%T\n", distance) // "func(Point, Point) float64" scale := (*Point).ScaleBy scale(&p, 2) fmt.Println(p) // "{2 4}" fmt.Printf("%T\n", scale) // "func(*Point, float64)" //!-methodexpr } func init() { red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} //!+indirect type ColoredPoint struct { *Point Color color.RGBA } p := ColoredPoint{&Point{1, 1}, red} q := ColoredPoint{&Point{5, 4}, blue} fmt.Println(p.Distance(*q.Point)) // "5" q.Point = p.Point // p and q now share the same Point p.ScaleBy(2) fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}" //!-indirect } ================================================ FILE: ch6/geometry/geometry.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 156. // Package geometry defines simple types for plane geometry. //!+point package geometry import "math" type Point struct{ X, Y float64 } // traditional function func Distance(p, q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } // same thing, but as a method of the Point type func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } //!-point //!+path // A Path is a journey connecting the points with straight lines. type Path []Point // Distance returns the distance traveled along the path. func (path Path) Distance() float64 { sum := 0.0 for i := range path { if i > 0 { sum += path[i-1].Distance(path[i]) } } return sum } //!-path ================================================ FILE: ch6/intset/intset.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 165. // Package intset provides a set of integers based on a bit vector. package intset import ( "bytes" "fmt" ) //!+intset // An IntSet is a set of small non-negative integers. // Its zero value represents the empty set. type IntSet struct { words []uint64 } // Has reports whether the set contains the non-negative value x. func (s *IntSet) Has(x int) bool { word, bit := x/64, uint(x%64) return word < len(s.words) && s.words[word]&(1<= len(s.words) { s.words = append(s.words, 0) } s.words[word] |= 1 << bit } // UnionWith sets s to the union of s and t. func (s *IntSet) UnionWith(t *IntSet) { for i, tword := range t.words { if i < len(s.words) { s.words[i] |= tword } else { s.words = append(s.words, tword) } } } //!-intset //!+string // String returns the set as a string of the form "{1 2 3}". func (s *IntSet) String() string { var buf bytes.Buffer buf.WriteByte('{') for i, word := range s.words { if word == 0 { continue } for j := 0; j < 64; j++ { if word&(1< len("{") { buf.WriteByte(' ') } fmt.Fprintf(&buf, "%d", 64*i+j) } } } buf.WriteByte('}') return buf.String() } //!-string ================================================ FILE: ch6/intset/intset_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package intset import "fmt" func Example_one() { //!+main var x, y IntSet x.Add(1) x.Add(144) x.Add(9) fmt.Println(x.String()) // "{1 9 144}" y.Add(9) y.Add(42) fmt.Println(y.String()) // "{9 42}" x.UnionWith(&y) fmt.Println(x.String()) // "{1 9 42 144}" fmt.Println(x.Has(9), x.Has(123)) // "true false" //!-main // Output: // {1 9 144} // {9 42} // {1 9 42 144} // true false } func Example_two() { var x IntSet x.Add(1) x.Add(144) x.Add(9) x.Add(42) //!+note fmt.Println(&x) // "{1 9 42 144}" fmt.Println(x.String()) // "{1 9 42 144}" fmt.Println(x) // "{[4398046511618 0 65536]}" //!-note // Output: // {1 9 42 144} // {1 9 42 144} // {[4398046511618 0 65536]} } ================================================ FILE: ch6/urlvalues/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 160. // The urlvalues command demonstrates a map type with methods. package main /* //!+values package url // Values maps a string key to a list of values. type Values map[string][]string // Get returns the first value associated with the given key, // or "" if there are none. func (v Values) Get(key string) string { if vs := v[key]; len(vs) > 0 { return vs[0] } return "" } // Add adds the value to key. // It appends to any existing values associated with key. func (v Values) Add(key, value string) { v[key] = append(v[key], value) } //!-values */ import ( "fmt" "net/url" ) func main() { //!+main m := url.Values{"lang": {"en"}} // direct construction m.Add("item", "1") m.Add("item", "2") fmt.Println(m.Get("lang")) // "en" fmt.Println(m.Get("q")) // "" fmt.Println(m.Get("item")) // "1" (first value) fmt.Println(m["item"]) // "[1 2]" (direct map access) m = nil fmt.Println(m.Get("item")) // "" m.Add("item", "3") // panic: assignment to entry in nil map //!-main } ================================================ FILE: ch7/bytecounter/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 173. // Bytecounter demonstrates an implementation of io.Writer that counts bytes. package main import ( "fmt" ) //!+bytecounter type ByteCounter int func (c *ByteCounter) Write(p []byte) (int, error) { *c += ByteCounter(len(p)) // convert int to ByteCounter return len(p), nil } //!-bytecounter func main() { //!+main var c ByteCounter c.Write([]byte("hello")) fmt.Println(c) // "5", = len("hello") c = 0 // reset the counter var name = "Dolly" fmt.Fprintf(&c, "hello, %s", name) fmt.Println(c) // "12", = len("hello, Dolly") //!-main } ================================================ FILE: ch7/eval/ast.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval // An Expr is an arithmetic expression. type Expr interface { // Eval returns the value of this Expr in the environment env. Eval(env Env) float64 // Check reports errors in this Expr and adds its Vars to the set. Check(vars map[Var]bool) error } //!+ast // A Var identifies a variable, e.g., x. type Var string // A literal is a numeric constant, e.g., 3.141. type literal float64 // A unary represents a unary operator expression, e.g., -x. type unary struct { op rune // one of '+', '-' x Expr } // A binary represents a binary operator expression, e.g., x+y. type binary struct { op rune // one of '+', '-', '*', '/' x, y Expr } // A call represents a function call expression, e.g., sin(x). type call struct { fn string // one of "pow", "sin", "sqrt" args []Expr } //!-ast ================================================ FILE: ch7/eval/check.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval import ( "fmt" "strings" ) //!+Check func (v Var) Check(vars map[Var]bool) error { vars[v] = true return nil } func (literal) Check(vars map[Var]bool) error { return nil } func (u unary) Check(vars map[Var]bool) error { if !strings.ContainsRune("+-", u.op) { return fmt.Errorf("unexpected unary op %q", u.op) } return u.x.Check(vars) } func (b binary) Check(vars map[Var]bool) error { if !strings.ContainsRune("+-*/", b.op) { return fmt.Errorf("unexpected binary op %q", b.op) } if err := b.x.Check(vars); err != nil { return err } return b.y.Check(vars) } func (c call) Check(vars map[Var]bool) error { arity, ok := numParams[c.fn] if !ok { return fmt.Errorf("unknown function %q", c.fn) } if len(c.args) != arity { return fmt.Errorf("call to %s has %d args, want %d", c.fn, len(c.args), arity) } for _, arg := range c.args { if err := arg.Check(vars); err != nil { return err } } return nil } var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1} //!-Check ================================================ FILE: ch7/eval/coverage_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval import ( "fmt" "math" "testing" ) //!+TestCoverage func TestCoverage(t *testing.T) { var tests = []struct { input string env Env want string // expected error from Parse/Check or result from Eval }{ {"x % 2", nil, "unexpected '%'"}, {"!true", nil, "unexpected '!'"}, {"log(10)", nil, `unknown function "log"`}, {"sqrt(1, 2)", nil, "call to sqrt has 2 args, want 1"}, {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"}, {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"}, {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"}, } for _, test := range tests { expr, err := Parse(test.input) if err == nil { err = expr.Check(map[Var]bool{}) } if err != nil { if err.Error() != test.want { t.Errorf("%s: got %q, want %q", test.input, err, test.want) } continue } got := fmt.Sprintf("%.6g", expr.Eval(test.env)) if got != test.want { t.Errorf("%s: %v => %s, want %s", test.input, test.env, got, test.want) } } } //!-TestCoverage ================================================ FILE: ch7/eval/eval.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 198. // Package eval provides an expression evaluator. package eval import ( "fmt" "math" ) //!+env type Env map[Var]float64 //!-env //!+Eval1 func (v Var) Eval(env Env) float64 { return env[v] } func (l literal) Eval(_ Env) float64 { return float64(l) } //!-Eval1 //!+Eval2 func (u unary) Eval(env Env) float64 { switch u.op { case '+': return +u.x.Eval(env) case '-': return -u.x.Eval(env) } panic(fmt.Sprintf("unsupported unary operator: %q", u.op)) } func (b binary) Eval(env Env) float64 { switch b.op { case '+': return b.x.Eval(env) + b.y.Eval(env) case '-': return b.x.Eval(env) - b.y.Eval(env) case '*': return b.x.Eval(env) * b.y.Eval(env) case '/': return b.x.Eval(env) / b.y.Eval(env) } panic(fmt.Sprintf("unsupported binary operator: %q", b.op)) } func (c call) Eval(env Env) float64 { switch c.fn { case "pow": return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env)) case "sin": return math.Sin(c.args[0].Eval(env)) case "sqrt": return math.Sqrt(c.args[0].Eval(env)) } panic(fmt.Sprintf("unsupported function call: %s", c.fn)) } //!-Eval2 ================================================ FILE: ch7/eval/eval_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval import ( "fmt" "math" "testing" ) //!+Eval func TestEval(t *testing.T) { tests := []struct { expr string env Env want string }{ {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"}, {"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"}, {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"}, {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"}, {"5 / 9 * (F - 32)", Env{"F": 32}, "0"}, {"5 / 9 * (F - 32)", Env{"F": 212}, "100"}, //!-Eval // additional tests that don't appear in the book {"-1 + -x", Env{"x": 1}, "-2"}, {"-1 - x", Env{"x": 1}, "-2"}, //!+Eval } var prevExpr string for _, test := range tests { // Print expr only when it changes. if test.expr != prevExpr { fmt.Printf("\n%s\n", test.expr) prevExpr = test.expr } expr, err := Parse(test.expr) if err != nil { t.Error(err) // parse error continue } got := fmt.Sprintf("%.6g", expr.Eval(test.env)) fmt.Printf("\t%v => %s\n", test.env, got) if got != test.want { t.Errorf("%s.Eval() in %v = %q, want %q\n", test.expr, test.env, got, test.want) } } } //!-Eval /* //!+output sqrt(A / pi) map[A:87616 pi:3.141592653589793] => 167 pow(x, 3) + pow(y, 3) map[x:12 y:1] => 1729 map[x:9 y:10] => 1729 5 / 9 * (F - 32) map[F:-40] => -40 map[F:32] => 0 map[F:212] => 100 //!-output // Additional outputs that don't appear in the book. -1 - x map[x:1] => -2 -1 + -x map[x:1] => -2 */ func TestErrors(t *testing.T) { for _, test := range []struct{ expr, wantErr string }{ {"x % 2", "unexpected '%'"}, {"math.Pi", "unexpected '.'"}, {"!true", "unexpected '!'"}, {`"hello"`, "unexpected '\"'"}, {"log(10)", `unknown function "log"`}, {"sqrt(1, 2)", "call to sqrt has 2 args, want 1"}, } { expr, err := Parse(test.expr) if err == nil { vars := make(map[Var]bool) err = expr.Check(vars) if err == nil { t.Errorf("unexpected success: %s", test.expr) continue } } fmt.Printf("%-20s%v\n", test.expr, err) // (for book) if err.Error() != test.wantErr { t.Errorf("got error %s, want %s", err, test.wantErr) } } } /* //!+errors x % 2 unexpected '%' math.Pi unexpected '.' !true unexpected '!' "hello" unexpected '"' log(10) unknown function "log" sqrt(1, 2) call to sqrt has 2 args, want 1 //!-errors */ ================================================ FILE: ch7/eval/parse.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval import ( "fmt" "strconv" "strings" "text/scanner" ) // ---- lexer ---- // This lexer is similar to the one described in Chapter 13. type lexer struct { scan scanner.Scanner token rune // current lookahead token } func (lex *lexer) next() { lex.token = lex.scan.Scan() } func (lex *lexer) text() string { return lex.scan.TokenText() } type lexPanic string // describe returns a string describing the current token, for use in errors. func (lex *lexer) describe() string { switch lex.token { case scanner.EOF: return "end of file" case scanner.Ident: return fmt.Sprintf("identifier %s", lex.text()) case scanner.Int, scanner.Float: return fmt.Sprintf("number %s", lex.text()) } return fmt.Sprintf("%q", rune(lex.token)) // any other rune } func precedence(op rune) int { switch op { case '*', '/': return 2 case '+', '-': return 1 } return 0 } // ---- parser ---- // Parse parses the input string as an arithmetic expression. // // expr = num a literal number, e.g., 3.14159 // | id a variable name, e.g., x // | id '(' expr ',' ... ')' a function call // | '-' expr a unary operator (+-) // | expr '+' expr a binary operator (+-*/) // func Parse(input string) (_ Expr, err error) { defer func() { switch x := recover().(type) { case nil: // no panic case lexPanic: err = fmt.Errorf("%s", x) default: // unexpected panic: resume state of panic. panic(x) } }() lex := new(lexer) lex.scan.Init(strings.NewReader(input)) lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats lex.next() // initial lookahead e := parseExpr(lex) if lex.token != scanner.EOF { return nil, fmt.Errorf("unexpected %s", lex.describe()) } return e, nil } func parseExpr(lex *lexer) Expr { return parseBinary(lex, 1) } // binary = unary ('+' binary)* // parseBinary stops when it encounters an // operator of lower precedence than prec1. func parseBinary(lex *lexer, prec1 int) Expr { lhs := parseUnary(lex) for prec := precedence(lex.token); prec >= prec1; prec-- { for precedence(lex.token) == prec { op := lex.token lex.next() // consume operator rhs := parseBinary(lex, prec+1) lhs = binary{op, lhs, rhs} } } return lhs } // unary = '+' expr | primary func parseUnary(lex *lexer) Expr { if lex.token == '+' || lex.token == '-' { op := lex.token lex.next() // consume '+' or '-' return unary{op, parseUnary(lex)} } return parsePrimary(lex) } // primary = id // | id '(' expr ',' ... ',' expr ')' // | num // | '(' expr ')' func parsePrimary(lex *lexer) Expr { switch lex.token { case scanner.Ident: id := lex.text() lex.next() // consume Ident if lex.token != '(' { return Var(id) } lex.next() // consume '(' var args []Expr if lex.token != ')' { for { args = append(args, parseExpr(lex)) if lex.token != ',' { break } lex.next() // consume ',' } if lex.token != ')' { msg := fmt.Sprintf("got %s, want ')'", lex.describe()) panic(lexPanic(msg)) } } lex.next() // consume ')' return call{id, args} case scanner.Int, scanner.Float: f, err := strconv.ParseFloat(lex.text(), 64) if err != nil { panic(lexPanic(err.Error())) } lex.next() // consume number return literal(f) case '(': lex.next() // consume '(' e := parseExpr(lex) if lex.token != ')' { msg := fmt.Sprintf("got %s, want ')'", lex.describe()) panic(lexPanic(msg)) } lex.next() // consume ')' return e } msg := fmt.Sprintf("unexpected %s", lex.describe()) panic(lexPanic(msg)) } ================================================ FILE: ch7/eval/print.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package eval import ( "bytes" "fmt" ) // Format formats an expression as a string. // It does not attempt to remove unnecessary parens. func Format(e Expr) string { var buf bytes.Buffer write(&buf, e) return buf.String() } func write(buf *bytes.Buffer, e Expr) { switch e := e.(type) { case literal: fmt.Fprintf(buf, "%g", e) case Var: fmt.Fprintf(buf, "%s", e) case unary: fmt.Fprintf(buf, "(%c", e.op) write(buf, e.x) buf.WriteByte(')') case binary: buf.WriteByte('(') write(buf, e.x) fmt.Fprintf(buf, " %c ", e.op) write(buf, e.y) buf.WriteByte(')') case call: fmt.Fprintf(buf, "%s(", e.fn) for i, arg := range e.args { if i > 0 { buf.WriteString(", ") } write(buf, arg) } buf.WriteByte(')') default: panic(fmt.Sprintf("unknown Expr: %T", e)) } } ================================================ FILE: ch7/http1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 191. // Http1 is a rudimentary e-commerce server. package main import ( "fmt" "log" "net/http" ) //!+main func main() { db := database{"shoes": 50, "socks": 5} log.Fatal(http.ListenAndServe("localhost:8000", db)) } type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } //!-main /* //!+handler package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error //!-handler */ ================================================ FILE: ch7/http2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 192. // Http2 is an e-commerce server with /list and /price endpoints. package main import ( "fmt" "log" "net/http" ) func main() { db := database{"shoes": 50, "socks": 5} log.Fatal(http.ListenAndServe("localhost:8000", db)) } type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars //!+handler func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/list": for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } case "/price": item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) default: w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such page: %s\n", req.URL) } } //!-handler ================================================ FILE: ch7/http3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 194. // Http3 is an e-commerce server that registers the /list and /price // endpoints by calling (*http.ServeMux).Handle. package main import ( "fmt" "log" "net/http" ) type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } //!+main func main() { db := database{"shoes": 50, "socks": 5} mux := http.NewServeMux() mux.Handle("/list", http.HandlerFunc(db.list)) mux.Handle("/price", http.HandlerFunc(db.price)) log.Fatal(http.ListenAndServe("localhost:8000", mux)) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) } //!-main /* //!+handlerfunc package http type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } //!-handlerfunc */ ================================================ FILE: ch7/http3a/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 195. // Http3a is an e-commerce server that registers the /list and /price // endpoints by calling (*http.ServeMux).HandleFunc. package main import ( "fmt" "log" "net/http" ) func main() { db := database{"shoes": 50, "socks": 5} mux := http.NewServeMux() //!+main mux.HandleFunc("/list", db.list) mux.HandleFunc("/price", db.price) //!-main log.Fatal(http.ListenAndServe("localhost:8000", mux)) } type database map[string]int func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: $%d\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") if price, ok := db[item]; ok { fmt.Fprintf(w, "$%d\n", price) } else { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) } } /* //!+handlerfunc package http type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } //!-handlerfunc */ ================================================ FILE: ch7/http4/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 195. // Http4 is an e-commerce server that registers the /list and /price // endpoint by calling http.HandleFunc. package main import ( "fmt" "log" "net/http" ) //!+main func main() { db := database{"shoes": 50, "socks": 5} http.HandleFunc("/list", db.list) http.HandleFunc("/price", db.price) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } //!-main type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") if price, ok := db[item]; ok { fmt.Fprintf(w, "%s\n", price) } else { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) } } ================================================ FILE: ch7/sleep/sleep.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 179. // The sleep program sleeps for a specified period of time. package main import ( "flag" "fmt" "time" ) //!+sleep var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { flag.Parse() fmt.Printf("Sleeping for %v...", *period) time.Sleep(*period) fmt.Println() } //!-sleep ================================================ FILE: ch7/sorting/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 187. // Sorting sorts a music playlist into a variety of orders. package main import ( "fmt" "os" "sort" "text/tabwriter" "time" ) //!+main type Track struct { Title string Artist string Album string Year int Length time.Duration } var tracks = []*Track{ {"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")}, {"Go", "Moby", "Moby", 1992, length("3m37s")}, {"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")}, {"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")}, } func length(s string) time.Duration { d, err := time.ParseDuration(s) if err != nil { panic(s) } return d } //!-main //!+printTracks func printTracks(tracks []*Track) { const format = "%v\t%v\t%v\t%v\t%v\t\n" tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0) fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length") fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------") for _, t := range tracks { fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length) } tw.Flush() // calculate column widths and print table } //!-printTracks //!+artistcode type byArtist []*Track func (x byArtist) Len() int { return len(x) } func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist } func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] } //!-artistcode //!+yearcode type byYear []*Track func (x byYear) Len() int { return len(x) } func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year } func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] } //!-yearcode func main() { fmt.Println("byArtist:") sort.Sort(byArtist(tracks)) printTracks(tracks) fmt.Println("\nReverse(byArtist):") sort.Sort(sort.Reverse(byArtist(tracks))) printTracks(tracks) fmt.Println("\nbyYear:") sort.Sort(byYear(tracks)) printTracks(tracks) fmt.Println("\nCustom:") //!+customcall sort.Sort(customSort{tracks, func(x, y *Track) bool { if x.Title != y.Title { return x.Title < y.Title } if x.Year != y.Year { return x.Year < y.Year } if x.Length != y.Length { return x.Length < y.Length } return false }}) //!-customcall printTracks(tracks) } /* //!+artistoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Ahead Alicia Keys As I Am 2007 4m36s Go Delilah From the Roots Up 2012 3m38s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Moby Moby 1992 3m37s //!-artistoutput //!+artistrevoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s Go Ahead Alicia Keys As I Am 2007 4m36s //!-artistrevoutput //!+yearoutput Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Go Ahead Alicia Keys As I Am 2007 4m36s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s //!-yearoutput //!+customout Title Artist Album Year Length ----- ------ ----- ---- ------ Go Moby Moby 1992 3m37s Go Delilah From the Roots Up 2012 3m38s Go Ahead Alicia Keys As I Am 2007 4m36s Ready 2 Go Martin Solveig Smash 2011 4m24s //!-customout */ //!+customcode type customSort struct { t []*Track less func(x, y *Track) bool } func (x customSort) Len() int { return len(x.t) } func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) } func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] } //!-customcode func init() { //!+ints values := []int{3, 1, 4, 1} fmt.Println(sort.IntsAreSorted(values)) // "false" sort.Ints(values) fmt.Println(values) // "[1 1 3 4]" fmt.Println(sort.IntsAreSorted(values)) // "true" sort.Sort(sort.Reverse(sort.IntSlice(values))) fmt.Println(values) // "[4 3 1 1]" fmt.Println(sort.IntsAreSorted(values)) // "false" //!-ints } ================================================ FILE: ch7/surface/surface.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 203. // The surface program plots the 3-D surface of a user-provided function. package main import ( "fmt" "io" "log" "math" "net/http" ) //!+parseAndCheck import "gopl.io/ch7/eval" //!-parseAndCheck // -- copied from gopl.io/ch3/surface -- const ( width, height = 600, 320 // canvas size in pixels cells = 100 // number of grid cells xyrange = 30.0 // x, y axis range (-xyrange..+xyrange) xyscale = width / 2 / xyrange // pixels per x or y unit zscale = height * 0.4 // pixels per z unit ) var sin30, cos30 = 0.5, math.Sqrt(3.0 / 4.0) // sin(30°), cos(30°) func corner(f func(x, y float64) float64, i, j int) (float64, float64) { // find point (x,y) at corner of cell (i,j) x := xyrange * (float64(i)/cells - 0.5) y := xyrange * (float64(j)/cells - 0.5) z := f(x, y) // compute surface height z // project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy) sx := width/2 + (x-y)*cos30*xyscale sy := height/2 + (x+y)*sin30*xyscale - z*zscale return sx, sy } func surface(w io.Writer, f func(x, y float64) float64) { fmt.Fprintf(w, "", width, height) for i := 0; i < cells; i++ { for j := 0; j < cells; j++ { ax, ay := corner(f, i+1, j) bx, by := corner(f, i, j) cx, cy := corner(f, i, j+1) dx, dy := corner(f, i+1, j+1) fmt.Fprintf(w, "\n", ax, ay, bx, by, cx, cy, dx, dy) } } fmt.Fprintln(w, "") } // -- main code for gopl.io/ch7/surface -- //!+parseAndCheck func parseAndCheck(s string) (eval.Expr, error) { if s == "" { return nil, fmt.Errorf("empty expression") } expr, err := eval.Parse(s) if err != nil { return nil, err } vars := make(map[eval.Var]bool) if err := expr.Check(vars); err != nil { return nil, err } for v := range vars { if v != "x" && v != "y" && v != "r" { return nil, fmt.Errorf("undefined variable: %s", v) } } return expr, nil } //!-parseAndCheck //!+plot func plot(w http.ResponseWriter, r *http.Request) { r.ParseForm() expr, err := parseAndCheck(r.Form.Get("expr")) if err != nil { http.Error(w, "bad expr: "+err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "image/svg+xml") surface(w, func(x, y float64) float64 { r := math.Hypot(x, y) // distance from (0,0) return expr.Eval(eval.Env{"x": x, "y": y, "r": r}) }) } //!-plot //!+main func main() { http.HandleFunc("/plot", plot) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } //!-main ================================================ FILE: ch7/tempconv/tempconv.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 180. // Package tempconv performs Celsius and Fahrenheit temperature computations. package tempconv import ( "flag" "fmt" ) type Celsius float64 type Fahrenheit float64 func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) } func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } /* //!+flagvalue package flag // Value is the interface to the value stored in a flag. type Value interface { String() string Set(string) error } //!-flagvalue */ //!+celsiusFlag // *celsiusFlag satisfies the flag.Value interface. type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { var unit string var value float64 fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed switch unit { case "C", "°C": f.Celsius = Celsius(value) return nil case "F", "°F": f.Celsius = FToC(Fahrenheit(value)) return nil } return fmt.Errorf("invalid temperature %q", s) } //!-celsiusFlag //!+CelsiusFlag // CelsiusFlag defines a Celsius flag with the specified name, // default value, and usage, and returns the address of the flag variable. // The flag argument must have a quantity and a unit, e.g., "100C". func CelsiusFlag(name string, value Celsius, usage string) *Celsius { f := celsiusFlag{value} flag.CommandLine.Var(&f, name, usage) return &f.Celsius } //!-CelsiusFlag ================================================ FILE: ch7/tempconv/tempconv.go.~master~ ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // Package tempconv performs Celsius and Fahrenheit temperature computations. package tempconv import ( "flag" "fmt" ) type Celsius float64 type Fahrenheit float64 func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) } func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } /* //!+flagvalue package flag // Value is the interface to the value stored in a flag. type Value interface { String() string Set(string) error } //!-flagvalue */ //!+celsiusflag // *celsiusFlag satisfies the flag.Value interface. type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { var unit string var value float64 fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed switch unit { case "C", "°C": f.Celsius = Celsius(value) return nil case "F", "°F": f.Celsius = FToC(Fahrenheit(value)) return nil } return fmt.Errorf("invalid temperature %q", s) } //!-celsiusflag //!+Celsiusflag // CelsiusFlag defines a Celsius flag with the specified name, // default value, and usage, and returns the address of the flag variable. // The flag argument must have a quantity and a unit, e.g., "100C". func CelsiusFlag(name string, value Celsius, usage string) *Celsius { f := celsiusFlag{value} flag.CommandLine.Var(&f, name, usage) return &f.Celsius } //!-Celsiusflag ================================================ FILE: ch7/tempflag/tempflag.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 181. // Tempflag prints the value of its -temp (temperature) flag. package main import ( "flag" "fmt" "gopl.io/ch7/tempconv" ) //!+ var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") func main() { flag.Parse() fmt.Println(*temp) } //!- ================================================ FILE: ch7/xmlselect/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 214. //!+ // Xmlselect prints the text of selected elements of an XML document. package main import ( "encoding/xml" "fmt" "io" "os" "strings" ) func main() { dec := xml.NewDecoder(os.Stdin) var stack []string // stack of element names for { tok, err := dec.Token() if err == io.EOF { break } else if err != nil { fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err) os.Exit(1) } switch tok := tok.(type) { case xml.StartElement: stack = append(stack, tok.Name.Local) // push case xml.EndElement: stack = stack[:len(stack)-1] // pop case xml.CharData: if containsAll(stack, os.Args[1:]) { fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok) } } } } // containsAll reports whether x contains the elements of y, in order. func containsAll(x, y []string) bool { for len(y) <= len(x) { if len(y) == 0 { return true } if x[0] == y[0] { y = y[1:] } x = x[1:] } return false } //!- ================================================ FILE: ch8/cake/cake.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 234. // Package cake provides a simulation of // a concurrent cake shop with numerous parameters. // // Use this command to run the benchmarks: // $ go test -bench=. gopl.io/ch8/cake package cake import ( "fmt" "math/rand" "time" ) type Shop struct { Verbose bool Cakes int // number of cakes to bake BakeTime time.Duration // time to bake one cake BakeStdDev time.Duration // standard deviation of baking time BakeBuf int // buffer slots between baking and icing NumIcers int // number of cooks doing icing IceTime time.Duration // time to ice one cake IceStdDev time.Duration // standard deviation of icing time IceBuf int // buffer slots between icing and inscribing InscribeTime time.Duration // time to inscribe one cake InscribeStdDev time.Duration // standard deviation of inscribing time } type cake int func (s *Shop) baker(baked chan<- cake) { for i := 0; i < s.Cakes; i++ { c := cake(i) if s.Verbose { fmt.Println("baking", c) } work(s.BakeTime, s.BakeStdDev) baked <- c } close(baked) } func (s *Shop) icer(iced chan<- cake, baked <-chan cake) { for c := range baked { if s.Verbose { fmt.Println("icing", c) } work(s.IceTime, s.IceStdDev) iced <- c } } func (s *Shop) inscriber(iced <-chan cake) { for i := 0; i < s.Cakes; i++ { c := <-iced if s.Verbose { fmt.Println("inscribing", c) } work(s.InscribeTime, s.InscribeStdDev) if s.Verbose { fmt.Println("finished", c) } } } // Work runs the simulation 'runs' times. func (s *Shop) Work(runs int) { for run := 0; run < runs; run++ { baked := make(chan cake, s.BakeBuf) iced := make(chan cake, s.IceBuf) go s.baker(baked) for i := 0; i < s.NumIcers; i++ { go s.icer(iced, baked) } s.inscriber(iced) } } // work blocks the calling goroutine for a period of time // that is normally distributed around d // with a standard deviation of stddev. func work(d, stddev time.Duration) { delay := d + time.Duration(rand.NormFloat64()*float64(stddev)) time.Sleep(delay) } ================================================ FILE: ch8/cake/cake_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package cake_test import ( "testing" "time" "gopl.io/ch8/cake" ) var defaults = cake.Shop{ Verbose: testing.Verbose(), Cakes: 20, BakeTime: 10 * time.Millisecond, NumIcers: 1, IceTime: 10 * time.Millisecond, InscribeTime: 10 * time.Millisecond, } func Benchmark(b *testing.B) { // Baseline: one baker, one icer, one inscriber. // Each step takes exactly 10ms. No buffers. cakeshop := defaults cakeshop.Work(b.N) // 224 ms } func BenchmarkBuffers(b *testing.B) { // Adding buffers has no effect. cakeshop := defaults cakeshop.BakeBuf = 10 cakeshop.IceBuf = 10 cakeshop.Work(b.N) // 224 ms } func BenchmarkVariable(b *testing.B) { // Adding variability to rate of each step // increases total time due to channel delays. cakeshop := defaults cakeshop.BakeStdDev = cakeshop.BakeTime / 4 cakeshop.IceStdDev = cakeshop.IceTime / 4 cakeshop.InscribeStdDev = cakeshop.InscribeTime / 4 cakeshop.Work(b.N) // 259 ms } func BenchmarkVariableBuffers(b *testing.B) { // Adding channel buffers reduces // delays resulting from variability. cakeshop := defaults cakeshop.BakeStdDev = cakeshop.BakeTime / 4 cakeshop.IceStdDev = cakeshop.IceTime / 4 cakeshop.InscribeStdDev = cakeshop.InscribeTime / 4 cakeshop.BakeBuf = 10 cakeshop.IceBuf = 10 cakeshop.Work(b.N) // 244 ms } func BenchmarkSlowIcing(b *testing.B) { // Making the middle stage slower // adds directly to the critical path. cakeshop := defaults cakeshop.IceTime = 50 * time.Millisecond cakeshop.Work(b.N) // 1.032 s } func BenchmarkSlowIcingManyIcers(b *testing.B) { // Adding more icing cooks reduces the cost of icing // to its sequential component, following Amdahl's Law. cakeshop := defaults cakeshop.IceTime = 50 * time.Millisecond cakeshop.NumIcers = 5 cakeshop.Work(b.N) // 288ms } ================================================ FILE: ch8/chat/chat.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 254. //!+ // Chat is a server that lets clients chat with each other. package main import ( "bufio" "fmt" "log" "net" ) //!+broadcaster type client chan<- string // an outgoing message channel var ( entering = make(chan client) leaving = make(chan client) messages = make(chan string) // all incoming client messages ) func broadcaster() { clients := make(map[client]bool) // all connected clients for { select { case msg := <-messages: // Broadcast incoming message to all // clients' outgoing message channels. for cli := range clients { cli <- msg } case cli := <-entering: clients[cli] = true case cli := <-leaving: delete(clients, cli) close(cli) } } } //!-broadcaster //!+handleConn func handleConn(conn net.Conn) { ch := make(chan string) // outgoing client messages go clientWriter(conn, ch) who := conn.RemoteAddr().String() ch <- "You are " + who messages <- who + " has arrived" entering <- ch input := bufio.NewScanner(conn) for input.Scan() { messages <- who + ": " + input.Text() } // NOTE: ignoring potential errors from input.Err() leaving <- ch messages <- who + " has left" conn.Close() } func clientWriter(conn net.Conn, ch <-chan string) { for msg := range ch { fmt.Fprintln(conn, msg) // NOTE: ignoring network errors } } //!-handleConn //!+main func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } go broadcaster() for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } } //!-main ================================================ FILE: ch8/chat/chat.go.~master~ ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ //!+ // Chat is a server that lets clients chat with each other. package main import ( "bufio" "fmt" "log" "net" ) //!+broadcaster type client chan<- string // an outgoing message channel var ( entering = make(chan client) leaving = make(chan client) messages = make(chan string) // all incoming client messages ) func broadcaster() { clients := make(map[client]bool) // all connected clients for { select { case msg := <-messages: // Broadcast incoming message to all // clients' outgoing message channels. for cli := range clients { cli <- msg } case cli := <-entering: clients[cli] = true case cli := <-leaving: delete(clients, cli) close(cli) } } } //!-broadcaster //!+handleConn func handleConn(conn net.Conn) { ch := make(chan string) // outgoing client messages go clientWriter(conn, ch) who := conn.RemoteAddr().String() entering <- ch messages <- who + " has arrived" input := bufio.NewScanner(conn) for input.Scan() { messages <- who + ": " + input.Text() } messages <- who + " has left" leaving <- ch conn.Close() } func clientWriter(conn net.Conn, ch <-chan string) { for msg := range ch { fmt.Fprintln(conn, msg) } } //!-handleConn //!+main func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } go broadcaster() for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } } //!-main ================================================ FILE: ch8/clock1/clock.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 219. //!+ // Clock1 is a TCP server that periodically writes the time. package main import ( "io" "log" "net" "time" ) func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { log.Print(err) // e.g., connection aborted continue } handleConn(conn) // handle one connection at a time } } func handleConn(c net.Conn) { defer c.Close() for { _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) if err != nil { return // e.g., client disconnected } time.Sleep(1 * time.Second) } } //!- ================================================ FILE: ch8/clock2/clock.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 222. // Clock is a TCP server that periodically writes the time. package main import ( "io" "log" "net" "time" ) func handleConn(c net.Conn) { defer c.Close() for { _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) if err != nil { return // e.g., client disconnected } time.Sleep(1 * time.Second) } } func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } //!+ for { conn, err := listener.Accept() if err != nil { log.Print(err) // e.g., connection aborted continue } go handleConn(conn) // handle connections concurrently } //!- } ================================================ FILE: ch8/countdown1/countdown.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 244. // Countdown implements the countdown for a rocket launch. package main import ( "fmt" "time" ) //!+ func main() { fmt.Println("Commencing countdown.") tick := time.Tick(1 * time.Second) for countdown := 10; countdown > 0; countdown-- { fmt.Println(countdown) <-tick } launch() } //!- func launch() { fmt.Println("Lift off!") } ================================================ FILE: ch8/countdown2/countdown.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 244. // Countdown implements the countdown for a rocket launch. package main import ( "fmt" "os" "time" ) //!+ func main() { // ...create abort channel... //!- //!+abort abort := make(chan struct{}) go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte abort <- struct{}{} }() //!-abort //!+ fmt.Println("Commencing countdown. Press return to abort.") select { case <-time.After(10 * time.Second): // Do nothing. case <-abort: fmt.Println("Launch aborted!") return } launch() } //!- func launch() { fmt.Println("Lift off!") } ================================================ FILE: ch8/countdown3/countdown.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 246. // Countdown implements the countdown for a rocket launch. package main // NOTE: the ticker goroutine never terminates if the launch is aborted. // This is a "goroutine leak". import ( "fmt" "os" "time" ) //!+ func main() { // ...create abort channel... //!- abort := make(chan struct{}) go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte abort <- struct{}{} }() //!+ fmt.Println("Commencing countdown. Press return to abort.") tick := time.Tick(1 * time.Second) for countdown := 10; countdown > 0; countdown-- { fmt.Println(countdown) select { case <-tick: // Do nothing. case <-abort: fmt.Println("Launch aborted!") return } } launch() } //!- func launch() { fmt.Println("Lift off!") } ================================================ FILE: ch8/crawl1/findlinks.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 240. // Crawl1 crawls web links starting with the command-line arguments. // // This version quickly exhausts available file descriptors // due to excessive concurrent calls to links.Extract. // // Also, it never terminates because the worklist is never closed. package main import ( "fmt" "log" "os" "gopl.io/ch5/links" ) //!+crawl func crawl(url string) []string { fmt.Println(url) list, err := links.Extract(url) if err != nil { log.Print(err) } return list } //!-crawl //!+main func main() { worklist := make(chan []string) // Start with the command-line arguments. go func() { worklist <- os.Args[1:] }() // Crawl the web concurrently. seen := make(map[string]bool) for list := range worklist { for _, link := range list { if !seen[link] { seen[link] = true go func(link string) { worklist <- crawl(link) }(link) } } } } //!-main /* //!+output $ go build gopl.io/ch8/crawl1 $ ./crawl1 http://gopl.io/ http://gopl.io/ https://golang.org/help/ https://golang.org/doc/ https://golang.org/blog/ ... 2015/07/15 18:22:12 Get ...: dial tcp: lookup blog.golang.org: no such host 2015/07/15 18:22:12 Get ...: dial tcp 23.21.222.120:443: socket: too many open files ... //!-output */ ================================================ FILE: ch8/crawl2/findlinks.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 241. // Crawl2 crawls web links starting with the command-line arguments. // // This version uses a buffered channel as a counting semaphore // to limit the number of concurrent calls to links.Extract. package main import ( "fmt" "log" "os" "gopl.io/ch5/links" ) //!+sema // tokens is a counting semaphore used to // enforce a limit of 20 concurrent requests. var tokens = make(chan struct{}, 20) func crawl(url string) []string { fmt.Println(url) tokens <- struct{}{} // acquire a token list, err := links.Extract(url) <-tokens // release the token if err != nil { log.Print(err) } return list } //!-sema //!+ func main() { worklist := make(chan []string) var n int // number of pending sends to worklist // Start with the command-line arguments. n++ go func() { worklist <- os.Args[1:] }() // Crawl the web concurrently. seen := make(map[string]bool) for ; n > 0; n-- { list := <-worklist for _, link := range list { if !seen[link] { seen[link] = true n++ go func(link string) { worklist <- crawl(link) }(link) } } } } //!- ================================================ FILE: ch8/crawl3/findlinks.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 243. // Crawl3 crawls web links starting with the command-line arguments. // // This version uses bounded parallelism. // For simplicity, it does not address the termination problem. // package main import ( "fmt" "log" "os" "gopl.io/ch5/links" ) func crawl(url string) []string { fmt.Println(url) list, err := links.Extract(url) if err != nil { log.Print(err) } return list } //!+ func main() { worklist := make(chan []string) // lists of URLs, may have duplicates unseenLinks := make(chan string) // de-duplicated URLs // Add command-line arguments to worklist. go func() { worklist <- os.Args[1:] }() // Create 20 crawler goroutines to fetch each unseen link. for i := 0; i < 20; i++ { go func() { for link := range unseenLinks { foundLinks := crawl(link) go func() { worklist <- foundLinks }() } }() } // The main goroutine de-duplicates worklist items // and sends the unseen ones to the crawlers. seen := make(map[string]bool) for list := range worklist { for _, link := range list { if !seen[link] { seen[link] = true unseenLinks <- link } } } } //!- ================================================ FILE: ch8/du1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 247. //!+main // The du1 command computes the disk usage of the files in a directory. package main import ( "flag" "fmt" "io/ioutil" "os" "path/filepath" ) func main() { // Determine the initial directories. flag.Parse() roots := flag.Args() if len(roots) == 0 { roots = []string{"."} } // Traverse the file tree. fileSizes := make(chan int64) go func() { for _, root := range roots { walkDir(root, fileSizes) } close(fileSizes) }() // Print the results. var nfiles, nbytes int64 for size := range fileSizes { nfiles++ nbytes += size } printDiskUsage(nfiles, nbytes) } func printDiskUsage(nfiles, nbytes int64) { fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) } //!-main //!+walkDir // walkDir recursively walks the file tree rooted at dir // and sends the size of each found file on fileSizes. func walkDir(dir string, fileSizes chan<- int64) { for _, entry := range dirents(dir) { if entry.IsDir() { subdir := filepath.Join(dir, entry.Name()) walkDir(subdir, fileSizes) } else { fileSizes <- entry.Size() } } } // dirents returns the entries of directory dir. func dirents(dir string) []os.FileInfo { entries, err := ioutil.ReadDir(dir) if err != nil { fmt.Fprintf(os.Stderr, "du1: %v\n", err) return nil } return entries } //!-walkDir // The du1 variant uses two goroutines and // prints the total after every file is found. ================================================ FILE: ch8/du2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 249. // The du2 command computes the disk usage of the files in a directory. package main // The du2 variant uses select and a time.Ticker // to print the totals periodically if -v is set. import ( "flag" "fmt" "io/ioutil" "os" "path/filepath" "time" ) //!+ var verbose = flag.Bool("v", false, "show verbose progress messages") func main() { // ...start background goroutine... //!- // Determine the initial directories. flag.Parse() roots := flag.Args() if len(roots) == 0 { roots = []string{"."} } // Traverse the file tree. fileSizes := make(chan int64) go func() { for _, root := range roots { walkDir(root, fileSizes) } close(fileSizes) }() //!+ // Print the results periodically. var tick <-chan time.Time if *verbose { tick = time.Tick(500 * time.Millisecond) } var nfiles, nbytes int64 loop: for { select { case size, ok := <-fileSizes: if !ok { break loop // fileSizes was closed } nfiles++ nbytes += size case <-tick: printDiskUsage(nfiles, nbytes) } } printDiskUsage(nfiles, nbytes) // final totals } //!- func printDiskUsage(nfiles, nbytes int64) { fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) } // walkDir recursively walks the file tree rooted at dir // and sends the size of each found file on fileSizes. func walkDir(dir string, fileSizes chan<- int64) { for _, entry := range dirents(dir) { if entry.IsDir() { subdir := filepath.Join(dir, entry.Name()) walkDir(subdir, fileSizes) } else { fileSizes <- entry.Size() } } } // dirents returns the entries of directory dir. func dirents(dir string) []os.FileInfo { entries, err := ioutil.ReadDir(dir) if err != nil { fmt.Fprintf(os.Stderr, "du: %v\n", err) return nil } return entries } ================================================ FILE: ch8/du3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 250. // The du3 command computes the disk usage of the files in a directory. package main // The du3 variant traverses all directories in parallel. // It uses a concurrency-limiting counting semaphore // to avoid opening too many files at once. import ( "flag" "fmt" "io/ioutil" "os" "path/filepath" "sync" "time" ) var vFlag = flag.Bool("v", false, "show verbose progress messages") //!+ func main() { // ...determine roots... //!- flag.Parse() // Determine the initial directories. roots := flag.Args() if len(roots) == 0 { roots = []string{"."} } //!+ // Traverse each root of the file tree in parallel. fileSizes := make(chan int64) var n sync.WaitGroup for _, root := range roots { n.Add(1) go walkDir(root, &n, fileSizes) } go func() { n.Wait() close(fileSizes) }() //!- // Print the results periodically. var tick <-chan time.Time if *vFlag { tick = time.Tick(500 * time.Millisecond) } var nfiles, nbytes int64 loop: for { select { case size, ok := <-fileSizes: if !ok { break loop // fileSizes was closed } nfiles++ nbytes += size case <-tick: printDiskUsage(nfiles, nbytes) } } printDiskUsage(nfiles, nbytes) // final totals //!+ // ...select loop... } //!- func printDiskUsage(nfiles, nbytes int64) { fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) } // walkDir recursively walks the file tree rooted at dir // and sends the size of each found file on fileSizes. //!+walkDir func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { defer n.Done() for _, entry := range dirents(dir) { if entry.IsDir() { n.Add(1) subdir := filepath.Join(dir, entry.Name()) go walkDir(subdir, n, fileSizes) } else { fileSizes <- entry.Size() } } } //!-walkDir //!+sema // sema is a counting semaphore for limiting concurrency in dirents. var sema = make(chan struct{}, 20) // dirents returns the entries of directory dir. func dirents(dir string) []os.FileInfo { sema <- struct{}{} // acquire token defer func() { <-sema }() // release token // ... //!-sema entries, err := ioutil.ReadDir(dir) if err != nil { fmt.Fprintf(os.Stderr, "du: %v\n", err) return nil } return entries } ================================================ FILE: ch8/du4/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 251. // The du4 command computes the disk usage of the files in a directory. package main // The du4 variant includes cancellation: // it terminates quickly when the user hits return. import ( "fmt" "os" "path/filepath" "sync" "time" ) //!+1 var done = make(chan struct{}) func cancelled() bool { select { case <-done: return true default: return false } } //!-1 func main() { // Determine the initial directories. roots := os.Args[1:] if len(roots) == 0 { roots = []string{"."} } //!+2 // Cancel traversal when input is detected. go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte close(done) }() //!-2 // Traverse each root of the file tree in parallel. fileSizes := make(chan int64) var n sync.WaitGroup for _, root := range roots { n.Add(1) go walkDir(root, &n, fileSizes) } go func() { n.Wait() close(fileSizes) }() // Print the results periodically. tick := time.Tick(500 * time.Millisecond) var nfiles, nbytes int64 loop: //!+3 for { select { case <-done: // Drain fileSizes to allow existing goroutines to finish. for range fileSizes { // Do nothing. } return case size, ok := <-fileSizes: // ... //!-3 if !ok { break loop // fileSizes was closed } nfiles++ nbytes += size case <-tick: printDiskUsage(nfiles, nbytes) } } printDiskUsage(nfiles, nbytes) // final totals } func printDiskUsage(nfiles, nbytes int64) { fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) } // walkDir recursively walks the file tree rooted at dir // and sends the size of each found file on fileSizes. //!+4 func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { defer n.Done() if cancelled() { return } for _, entry := range dirents(dir) { // ... //!-4 if entry.IsDir() { n.Add(1) subdir := filepath.Join(dir, entry.Name()) go walkDir(subdir, n, fileSizes) } else { fileSizes <- entry.Size() } //!+4 } } //!-4 var sema = make(chan struct{}, 20) // concurrency-limiting counting semaphore // dirents returns the entries of directory dir. //!+5 func dirents(dir string) []os.FileInfo { select { case sema <- struct{}{}: // acquire token case <-done: return nil // cancelled } defer func() { <-sema }() // release token // ...read directory... //!-5 f, err := os.Open(dir) if err != nil { fmt.Fprintf(os.Stderr, "du: %v\n", err) return nil } defer f.Close() entries, err := f.Readdir(0) // 0 => no limit; read all entries if err != nil { fmt.Fprintf(os.Stderr, "du: %v\n", err) // Don't return: Readdir may return partial results. } return entries } ================================================ FILE: ch8/netcat1/netcat.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 221. //!+ // Netcat1 is a read-only TCP client. package main import ( "io" "log" "net" "os" ) func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } defer conn.Close() mustCopy(os.Stdout, conn) } func mustCopy(dst io.Writer, src io.Reader) { if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } //!- ================================================ FILE: ch8/netcat2/netcat.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 223. // Netcat is a simple read/write client for TCP servers. package main import ( "io" "log" "net" "os" ) //!+ func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } defer conn.Close() go mustCopy(os.Stdout, conn) mustCopy(conn, os.Stdin) } //!- func mustCopy(dst io.Writer, src io.Reader) { if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } ================================================ FILE: ch8/netcat3/netcat.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 227. // Netcat is a simple read/write client for TCP servers. package main import ( "io" "log" "net" "os" ) //!+ func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } done := make(chan struct{}) go func() { io.Copy(os.Stdout, conn) // NOTE: ignoring errors log.Println("done") done <- struct{}{} // signal the main goroutine }() mustCopy(conn, os.Stdin) conn.Close() <-done // wait for background goroutine to finish } //!- func mustCopy(dst io.Writer, src io.Reader) { if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } ================================================ FILE: ch8/pipeline1/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 228. // Pipeline1 demonstrates an infinite 3-stage pipeline. package main import "fmt" //!+ func main() { naturals := make(chan int) squares := make(chan int) // Counter go func() { for x := 0; ; x++ { naturals <- x } }() // Squarer go func() { for { x := <-naturals squares <- x * x } }() // Printer (in main goroutine) for { fmt.Println(<-squares) } } //!- ================================================ FILE: ch8/pipeline2/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 229. // Pipeline2 demonstrates a finite 3-stage pipeline. package main import "fmt" //!+ func main() { naturals := make(chan int) squares := make(chan int) // Counter go func() { for x := 0; x < 100; x++ { naturals <- x } close(naturals) }() // Squarer go func() { for x := range naturals { squares <- x * x } close(squares) }() // Printer (in main goroutine) for x := range squares { fmt.Println(x) } } //!- ================================================ FILE: ch8/pipeline3/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 231. // Pipeline3 demonstrates a finite 3-stage pipeline // with range, close, and unidirectional channel types. package main import "fmt" //!+ func counter(out chan<- int) { for x := 0; x < 100; x++ { out <- x } close(out) } func squarer(out chan<- int, in <-chan int) { for v := range in { out <- v * v } close(out) } func printer(in <-chan int) { for v := range in { fmt.Println(v) } } func main() { naturals := make(chan int) squares := make(chan int) go counter(naturals) go squarer(squares, naturals) printer(squares) } //!- ================================================ FILE: ch8/reverb1/reverb.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 223. // Reverb1 is a TCP server that simulates an echo. package main import ( "bufio" "fmt" "log" "net" "strings" "time" ) //!+ func echo(c net.Conn, shout string, delay time.Duration) { fmt.Fprintln(c, "\t", strings.ToUpper(shout)) time.Sleep(delay) fmt.Fprintln(c, "\t", shout) time.Sleep(delay) fmt.Fprintln(c, "\t", strings.ToLower(shout)) } func handleConn(c net.Conn) { input := bufio.NewScanner(c) for input.Scan() { echo(c, input.Text(), 1*time.Second) } // NOTE: ignoring potential errors from input.Err() c.Close() } //!- func main() { l, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { conn, err := l.Accept() if err != nil { log.Print(err) // e.g., connection aborted continue } go handleConn(conn) } } ================================================ FILE: ch8/reverb2/reverb.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 224. // Reverb2 is a TCP server that simulates an echo. package main import ( "bufio" "fmt" "log" "net" "strings" "time" ) func echo(c net.Conn, shout string, delay time.Duration) { fmt.Fprintln(c, "\t", strings.ToUpper(shout)) time.Sleep(delay) fmt.Fprintln(c, "\t", shout) time.Sleep(delay) fmt.Fprintln(c, "\t", strings.ToLower(shout)) } //!+ func handleConn(c net.Conn) { input := bufio.NewScanner(c) for input.Scan() { go echo(c, input.Text(), 1*time.Second) } // NOTE: ignoring potential errors from input.Err() c.Close() } //!- func main() { l, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { conn, err := l.Accept() if err != nil { log.Print(err) // e.g., connection aborted continue } go handleConn(conn) } } ================================================ FILE: ch8/spinner/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 218. // Spinner displays an animation while computing the 45th Fibonacci number. package main import ( "fmt" "time" ) //!+ func main() { go spinner(100 * time.Millisecond) const n = 45 fibN := fib(n) // slow fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) } func spinner(delay time.Duration) { for { for _, r := range `-\|/` { fmt.Printf("\r%c", r) time.Sleep(delay) } } } func fib(x int) int { if x < 2 { return x } return fib(x-1) + fib(x-2) } //!- ================================================ FILE: ch8/thumbnail/main.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // +build ignore // The thumbnail command produces thumbnails of JPEG files // whose names are provided on each line of the standard input. // // The "+build ignore" tag (see p.295) excludes this file from the // thumbnail package, but it can be compiled as a command and run like // this: // // Run with: // $ go run $GOPATH/src/gopl.io/ch8/thumbnail/main.go // foo.jpeg // ^D // package main import ( "bufio" "fmt" "log" "os" "gopl.io/ch8/thumbnail" ) func main() { input := bufio.NewScanner(os.Stdin) for input.Scan() { thumb, err := thumbnail.ImageFile(input.Text()) if err != nil { log.Print(err) continue } fmt.Println(thumb) } if err := input.Err(); err != nil { log.Fatal(err) } } ================================================ FILE: ch8/thumbnail/thumbnail.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 234. // The thumbnail package produces thumbnail-size images from // larger images. Only JPEG images are currently supported. package thumbnail import ( "fmt" "image" "image/jpeg" "io" "os" "path/filepath" "strings" ) // Image returns a thumbnail-size version of src. func Image(src image.Image) image.Image { // Compute thumbnail size, preserving aspect ratio. xs := src.Bounds().Size().X ys := src.Bounds().Size().Y width, height := 128, 128 if aspect := float64(xs) / float64(ys); aspect < 1.0 { width = int(128 * aspect) // portrait } else { height = int(128 / aspect) // landscape } xscale := float64(xs) / float64(width) yscale := float64(ys) / float64(height) dst := image.NewRGBA(image.Rect(0, 0, width, height)) // a very crude scaling algorithm for x := 0; x < width; x++ { for y := 0; y < height; y++ { srcx := int(float64(x) * xscale) srcy := int(float64(y) * yscale) dst.Set(x, y, src.At(srcx, srcy)) } } return dst } // ImageStream reads an image from r and // writes a thumbnail-size version of it to w. func ImageStream(w io.Writer, r io.Reader) error { src, _, err := image.Decode(r) if err != nil { return err } dst := Image(src) return jpeg.Encode(w, dst, nil) } // ImageFile2 reads an image from infile and writes // a thumbnail-size version of it to outfile. func ImageFile2(outfile, infile string) (err error) { in, err := os.Open(infile) if err != nil { return err } defer in.Close() out, err := os.Create(outfile) if err != nil { return err } if err := ImageStream(out, in); err != nil { out.Close() return fmt.Errorf("scaling %s to %s: %s", infile, outfile, err) } return out.Close() } // ImageFile reads an image from infile and writes // a thumbnail-size version of it in the same directory. // It returns the generated file name, e.g. "foo.thumb.jpeg". func ImageFile(infile string) (string, error) { ext := filepath.Ext(infile) // e.g., ".jpg", ".JPEG" outfile := strings.TrimSuffix(infile, ext) + ".thumb" + ext return outfile, ImageFile2(outfile, infile) } ================================================ FILE: ch8/thumbnail/thumbnail_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // This file is just a place to put example code from the book. // It does not actually run any code in gopl.io/ch8/thumbnail. package thumbnail_test import ( "log" "os" "sync" "gopl.io/ch8/thumbnail" ) //!+1 // makeThumbnails makes thumbnails of the specified files. func makeThumbnails(filenames []string) { for _, f := range filenames { if _, err := thumbnail.ImageFile(f); err != nil { log.Println(err) } } } //!-1 //!+2 // NOTE: incorrect! func makeThumbnails2(filenames []string) { for _, f := range filenames { go thumbnail.ImageFile(f) // NOTE: ignoring errors } } //!-2 //!+3 // makeThumbnails3 makes thumbnails of the specified files in parallel. func makeThumbnails3(filenames []string) { ch := make(chan struct{}) for _, f := range filenames { go func(f string) { thumbnail.ImageFile(f) // NOTE: ignoring errors ch <- struct{}{} }(f) } // Wait for goroutines to complete. for range filenames { <-ch } } //!-3 //!+4 // makeThumbnails4 makes thumbnails for the specified files in parallel. // It returns an error if any step failed. func makeThumbnails4(filenames []string) error { errors := make(chan error) for _, f := range filenames { go func(f string) { _, err := thumbnail.ImageFile(f) errors <- err }(f) } for range filenames { if err := <-errors; err != nil { return err // NOTE: incorrect: goroutine leak! } } return nil } //!-4 //!+5 // makeThumbnails5 makes thumbnails for the specified files in parallel. // It returns the generated file names in an arbitrary order, // or an error if any step failed. func makeThumbnails5(filenames []string) (thumbfiles []string, err error) { type item struct { thumbfile string err error } ch := make(chan item, len(filenames)) for _, f := range filenames { go func(f string) { var it item it.thumbfile, it.err = thumbnail.ImageFile(f) ch <- it }(f) } for range filenames { it := <-ch if it.err != nil { return nil, it.err } thumbfiles = append(thumbfiles, it.thumbfile) } return thumbfiles, nil } //!-5 //!+6 // makeThumbnails6 makes thumbnails for each file received from the channel. // It returns the number of bytes occupied by the files it creates. func makeThumbnails6(filenames <-chan string) int64 { sizes := make(chan int64) var wg sync.WaitGroup // number of working goroutines for f := range filenames { wg.Add(1) // worker go func(f string) { defer wg.Done() thumb, err := thumbnail.ImageFile(f) if err != nil { log.Println(err) return } info, _ := os.Stat(thumb) // OK to ignore error sizes <- info.Size() }(f) } // closer go func() { wg.Wait() close(sizes) }() var total int64 for size := range sizes { total += size } return total } //!-6 ================================================ FILE: ch9/bank1/bank.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 261. //!+ // Package bank provides a concurrency-safe bank with one account. package bank var deposits = make(chan int) // send amount to deposit var balances = make(chan int) // receive balance func Deposit(amount int) { deposits <- amount } func Balance() int { return <-balances } func teller() { var balance int // balance is confined to teller goroutine for { select { case amount := <-deposits: balance += amount case balances <- balance: } } } func init() { go teller() // start the monitor goroutine } //!- ================================================ FILE: ch9/bank1/bank_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package bank_test import ( "fmt" "testing" "gopl.io/ch9/bank1" ) func TestBank(t *testing.T) { done := make(chan struct{}) // Alice go func() { bank.Deposit(200) fmt.Println("=", bank.Balance()) done <- struct{}{} }() // Bob go func() { bank.Deposit(100) done <- struct{}{} }() // Wait for both transactions. <-done <-done if got, want := bank.Balance(), 300; got != want { t.Errorf("Balance = %d, want %d", got, want) } } ================================================ FILE: ch9/bank2/bank.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 262. // Package bank provides a concurrency-safe bank with one account. package bank //!+ var ( sema = make(chan struct{}, 1) // a binary semaphore guarding balance balance int ) func Deposit(amount int) { sema <- struct{}{} // acquire token balance = balance + amount <-sema // release token } func Balance() int { sema <- struct{}{} // acquire token b := balance <-sema // release token return b } //!- ================================================ FILE: ch9/bank2/bank_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package bank_test import ( "sync" "testing" "gopl.io/ch9/bank2" ) func TestBank(t *testing.T) { // Deposit [1..1000] concurrently. var n sync.WaitGroup for i := 1; i <= 1000; i++ { n.Add(1) go func(amount int) { bank.Deposit(amount) n.Done() }(i) } n.Wait() if got, want := bank.Balance(), (1000+1)*1000/2; got != want { t.Errorf("Balance = %d, want %d", got, want) } } ================================================ FILE: ch9/bank3/bank.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 263. // Package bank provides a concurrency-safe single-account bank. package bank //!+ import "sync" var ( mu sync.Mutex // guards balance balance int ) func Deposit(amount int) { mu.Lock() balance = balance + amount mu.Unlock() } func Balance() int { mu.Lock() b := balance mu.Unlock() return b } //!- ================================================ FILE: ch9/bank3/bank_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package bank_test import ( "sync" "testing" "gopl.io/ch9/bank3" ) func TestBank(t *testing.T) { // Deposit [1..1000] concurrently. var n sync.WaitGroup for i := 1; i <= 1000; i++ { n.Add(1) go func(amount int) { bank.Deposit(amount) n.Done() }(i) } n.Wait() if got, want := bank.Balance(), (1000+1)*1000/2; got != want { t.Errorf("Balance = %d, want %d", got, want) } } ================================================ FILE: ch9/memo1/memo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 272. //!+ // Package memo provides a concurrency-unsafe // memoization of a function of type Func. package memo // A Memo caches the results of calling a Func. type Memo struct { f Func cache map[string]result } // Func is the type of the function to memoize. type Func func(key string) (interface{}, error) type result struct { value interface{} err error } func New(f Func) *Memo { return &Memo{f: f, cache: make(map[string]result)} } // NOTE: not concurrency-safe! func (memo *Memo) Get(key string) (interface{}, error) { res, ok := memo.cache[key] if !ok { res.value, res.err = memo.f(key) memo.cache[key] = res } return res.value, res.err } //!- ================================================ FILE: ch9/memo1/memo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package memo_test import ( "testing" "gopl.io/ch9/memo1" "gopl.io/ch9/memotest" ) var httpGetBody = memotest.HTTPGetBody func Test(t *testing.T) { m := memo.New(httpGetBody) memotest.Sequential(t, m) } // NOTE: not concurrency-safe! Test fails. func TestConcurrent(t *testing.T) { m := memo.New(httpGetBody) memotest.Concurrent(t, m) } /* //!+output $ go test -v gopl.io/ch9/memo1 === RUN Test https://golang.org, 175.026418ms, 7537 bytes https://godoc.org, 172.686825ms, 6878 bytes https://play.golang.org, 115.762377ms, 5767 bytes http://gopl.io, 749.887242ms, 2856 bytes https://golang.org, 721ns, 7537 bytes https://godoc.org, 152ns, 6878 bytes https://play.golang.org, 205ns, 5767 bytes http://gopl.io, 326ns, 2856 bytes --- PASS: Test (1.21s) PASS ok gopl.io/ch9/memo1 1.257s //!-output */ /* //!+race $ go test -run=TestConcurrent -race -v gopl.io/ch9/memo1 === RUN TestConcurrent ... WARNING: DATA RACE Write by goroutine 36: runtime.mapassign1() ~/go/src/runtime/hashmap.go:411 +0x0 gopl.io/ch9/memo1.(*Memo).Get() ~/gobook2/src/gopl.io/ch9/memo1/memo.go:32 +0x205 ... Previous write by goroutine 35: runtime.mapassign1() ~/go/src/runtime/hashmap.go:411 +0x0 gopl.io/ch9/memo1.(*Memo).Get() ~/gobook2/src/gopl.io/ch9/memo1/memo.go:32 +0x205 ... Found 1 data race(s) FAIL gopl.io/ch9/memo1 2.393s //!-race */ ================================================ FILE: ch9/memo2/memo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 275. // Package memo provides a concurrency-safe memoization a function of // type Func. Concurrent requests are serialized by a Mutex. package memo import "sync" // Func is the type of the function to memoize. type Func func(string) (interface{}, error) type result struct { value interface{} err error } func New(f Func) *Memo { return &Memo{f: f, cache: make(map[string]result)} } //!+ type Memo struct { f Func mu sync.Mutex // guards cache cache map[string]result } // Get is concurrency-safe. func (memo *Memo) Get(key string) (value interface{}, err error) { memo.mu.Lock() res, ok := memo.cache[key] if !ok { res.value, res.err = memo.f(key) memo.cache[key] = res } memo.mu.Unlock() return res.value, res.err } //!- ================================================ FILE: ch9/memo2/memo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package memo_test import ( "testing" "gopl.io/ch9/memo2" "gopl.io/ch9/memotest" ) var httpGetBody = memotest.HTTPGetBody func Test(t *testing.T) { m := memo.New(httpGetBody) memotest.Sequential(t, m) } func TestConcurrent(t *testing.T) { m := memo.New(httpGetBody) memotest.Concurrent(t, m) } ================================================ FILE: ch9/memo3/memo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 276. // Package memo provides a concurrency-safe memoization a function of // type Func. Requests for different keys run concurrently. // Concurrent requests for the same key result in duplicate work. package memo import "sync" type Memo struct { f Func mu sync.Mutex // guards cache cache map[string]result } type Func func(string) (interface{}, error) type result struct { value interface{} err error } func New(f Func) *Memo { return &Memo{f: f, cache: make(map[string]result)} } //!+ func (memo *Memo) Get(key string) (value interface{}, err error) { memo.mu.Lock() res, ok := memo.cache[key] memo.mu.Unlock() if !ok { res.value, res.err = memo.f(key) // Between the two critical sections, several goroutines // may race to compute f(key) and update the map. memo.mu.Lock() memo.cache[key] = res memo.mu.Unlock() } return res.value, res.err } //!- ================================================ FILE: ch9/memo3/memo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package memo_test import ( "testing" "gopl.io/ch9/memo3" "gopl.io/ch9/memotest" ) var httpGetBody = memotest.HTTPGetBody func Test(t *testing.T) { m := memo.New(httpGetBody) memotest.Sequential(t, m) } func TestConcurrent(t *testing.T) { m := memo.New(httpGetBody) memotest.Concurrent(t, m) } ================================================ FILE: ch9/memo4/memo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 276. // Package memo provides a concurrency-safe memoization a function of // a function. Requests for different keys proceed in parallel. // Concurrent requests for the same key block until the first completes. // This implementation uses a Mutex. package memo import "sync" // Func is the type of the function to memoize. type Func func(string) (interface{}, error) type result struct { value interface{} err error } //!+ type entry struct { res result ready chan struct{} // closed when res is ready } func New(f Func) *Memo { return &Memo{f: f, cache: make(map[string]*entry)} } type Memo struct { f Func mu sync.Mutex // guards cache cache map[string]*entry } func (memo *Memo) Get(key string) (value interface{}, err error) { memo.mu.Lock() e := memo.cache[key] if e == nil { // This is the first request for this key. // This goroutine becomes responsible for computing // the value and broadcasting the ready condition. e = &entry{ready: make(chan struct{})} memo.cache[key] = e memo.mu.Unlock() e.res.value, e.res.err = memo.f(key) close(e.ready) // broadcast ready condition } else { // This is a repeat request for this key. memo.mu.Unlock() <-e.ready // wait for ready condition } return e.res.value, e.res.err } //!- ================================================ FILE: ch9/memo4/memo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package memo_test import ( "testing" "gopl.io/ch9/memo4" "gopl.io/ch9/memotest" ) var httpGetBody = memotest.HTTPGetBody func Test(t *testing.T) { m := memo.New(httpGetBody) memotest.Sequential(t, m) } func TestConcurrent(t *testing.T) { m := memo.New(httpGetBody) memotest.Concurrent(t, m) } ================================================ FILE: ch9/memo5/memo.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 278. // Package memo provides a concurrency-safe non-blocking memoization // of a function. Requests for different keys proceed in parallel. // Concurrent requests for the same key block until the first completes. // This implementation uses a monitor goroutine. package memo //!+Func // Func is the type of the function to memoize. type Func func(key string) (interface{}, error) // A result is the result of calling a Func. type result struct { value interface{} err error } type entry struct { res result ready chan struct{} // closed when res is ready } //!-Func //!+get // A request is a message requesting that the Func be applied to key. type request struct { key string response chan<- result // the client wants a single result } type Memo struct{ requests chan request } // New returns a memoization of f. Clients must subsequently call Close. func New(f Func) *Memo { memo := &Memo{requests: make(chan request)} go memo.server(f) return memo } func (memo *Memo) Get(key string) (interface{}, error) { response := make(chan result) memo.requests <- request{key, response} res := <-response return res.value, res.err } func (memo *Memo) Close() { close(memo.requests) } //!-get //!+monitor func (memo *Memo) server(f Func) { cache := make(map[string]*entry) for req := range memo.requests { e := cache[req.key] if e == nil { // This is the first request for this key. e = &entry{ready: make(chan struct{})} cache[req.key] = e go e.call(f, req.key) // call f(key) } go e.deliver(req.response) } } func (e *entry) call(f Func, key string) { // Evaluate the function. e.res.value, e.res.err = f(key) // Broadcast the ready condition. close(e.ready) } func (e *entry) deliver(response chan<- result) { // Wait for the ready condition. <-e.ready // Send the result to the client. response <- e.res } //!-monitor ================================================ FILE: ch9/memo5/memo_test.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ package memo_test import ( "testing" "gopl.io/ch9/memo5" "gopl.io/ch9/memotest" ) var httpGetBody = memotest.HTTPGetBody func Test(t *testing.T) { m := memo.New(httpGetBody) defer m.Close() memotest.Sequential(t, m) } func TestConcurrent(t *testing.T) { m := memo.New(httpGetBody) defer m.Close() memotest.Concurrent(t, m) } ================================================ FILE: ch9/memotest/memotest.go ================================================ // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 272. // Package memotest provides common functions for // testing various designs of the memo package. package memotest import ( "fmt" "io/ioutil" "log" "net/http" "sync" "testing" "time" ) //!+httpRequestBody func httpGetBody(url string) (interface{}, error) { resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } //!-httpRequestBody var HTTPGetBody = httpGetBody func incomingURLs() <-chan string { ch := make(chan string) go func() { for _, url := range []string{ "https://golang.org", "https://godoc.org", "https://play.golang.org", "http://gopl.io", "https://golang.org", "https://godoc.org", "https://play.golang.org", "http://gopl.io", } { ch <- url } close(ch) }() return ch } type M interface { Get(key string) (interface{}, error) } /* //!+seq m := memo.New(httpGetBody) //!-seq */ func Sequential(t *testing.T, m M) { //!+seq for url := range incomingURLs() { start := time.Now() value, err := m.Get(url) if err != nil { log.Print(err) continue } fmt.Printf("%s, %s, %d bytes\n", url, time.Since(start), len(value.([]byte))) } //!-seq } /* //!+conc m := memo.New(httpGetBody) //!-conc */ func Concurrent(t *testing.T, m M) { //!+conc var n sync.WaitGroup for url := range incomingURLs() { n.Add(1) go func(url string) { defer n.Done() start := time.Now() value, err := m.Get(url) if err != nil { log.Print(err) return } fmt.Printf("%s, %s, %d bytes\n", url, time.Since(start), len(value.([]byte))) }(url) } n.Wait() //!-conc } ================================================ FILE: go.mod ================================================ module gopl.io go 1.16 require golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 ================================================ FILE: go.sum ================================================ golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 h1:Z04ewVs7JhXaYkmDhBERPi41gnltfQpMWDnTnQbaCqk= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=