├── .gitignore ├── README.md └── src ├── Chapter3 ├── doc.go └── main.go ├── Chapter5-Func ├── doc.go └── main.go ├── Chpter4 ├── doc.go └── main.go ├── Echo1 ├── doc.go └── main.go ├── HellowWorld ├── doc.go └── main.go ├── Tests ├── doc.go └── main.go ├── ch1 ├── dup1 │ └── main.go ├── dup2 │ ├── 111.txt │ └── main.go ├── dup3 │ └── main.go ├── echo1 │ └── main.go ├── echo2 │ └── main.go ├── echo3 │ └── main.go ├── f_all │ └── f_all.go ├── fetch │ └── main.go ├── fetchall │ └── main.go ├── helloworld │ └── main.go ├── lissajous │ ├── 1.gif │ ├── 2.gif │ ├── 3.gif │ └── 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-print │ ├── bzip2.c │ ├── bzip2.go │ └── bzip2_test.go ├── bzip │ ├── 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 /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.html 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GolangBook 2 | Примеры и задачи из книги Донован А. Керниган Б. "Язык программирования Go" 3 | -------------------------------------------------------------------------------- /src/Chapter3/doc.go: -------------------------------------------------------------------------------- 1 | // Chapter3 project doc.go 2 | 3 | /* 4 | Chapter3 document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/Chapter3/main.go: -------------------------------------------------------------------------------- 1 | // Chapter3 project main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | 10 | for i := 1; i <= 100; i++ { 11 | 12 | // switch i % 3 { 13 | // case 0: 14 | // fmt.Println("Zero") 15 | // case 1: 16 | // fmt.Println("One") 17 | // case 2: 18 | // fmt.Println("Two") 19 | // case 3: 20 | // fmt.Println("Three") 21 | // case 4: 22 | // fmt.Println("Four") 23 | // case 5: 24 | // fmt.Println("Five") 25 | // default: 26 | // fmt.Println("Unknown Number") 27 | // } 28 | 29 | if i%3 == 0 { 30 | if i%5 == 0 { 31 | fmt.Println(i, " - FizzBuzz") 32 | } else { 33 | fmt.Println(i, " - Fizz") 34 | } 35 | } else if i%5 == 0 { 36 | fmt.Println(i, " - Buzz") 37 | } /*else { 38 | fmt.Println(i) 39 | }*/ 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Chapter5-Func/doc.go: -------------------------------------------------------------------------------- 1 | // Chapter5-Func project doc.go 2 | 3 | /* 4 | Chapter5-Func document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/Chapter5-Func/main.go: -------------------------------------------------------------------------------- 1 | // Chapter5-Func project main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | xs := []float64{98, 93, 77, 82, 83} 10 | 11 | fmt.Println(average(xs)) 12 | } 13 | 14 | func average(xs []float64) float64 { 15 | 16 | total := 0.0 17 | for _, v := range xs { 18 | total += v 19 | } 20 | return total / float64(len(xs)) 21 | // panic("Not Implemented") 22 | } 23 | -------------------------------------------------------------------------------- /src/Chpter4/doc.go: -------------------------------------------------------------------------------- 1 | // Chpter4 project doc.go 2 | 3 | /* 4 | Chpter4 document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/Chpter4/main.go: -------------------------------------------------------------------------------- 1 | // Chpter4 project main.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | func main() { 7 | // var x [5]float64 8 | // x[0] = 98 9 | // x[1] = 93 10 | // x[2] = 77 11 | // x[3] = 82 12 | // x[4] = 83 13 | 14 | // var total float64 = 0 15 | // for _, value := range x { 16 | // total += value 17 | // } 18 | // fmt.Println(total / float64(len(x))) 19 | // x := make(map[string]int) 20 | // x["key"] = 10 21 | // fmt.Println(x["key"]) 22 | // elements := map[string]string{ 23 | // "H": "Hydrogen", 24 | // "He": "Helium", 25 | // "Li": "Lithium", 26 | // "Be": "Beryllium", 27 | // "B": "Boron", 28 | // "C": "Carbon", 29 | // "N": "Nitrogen", 30 | // "O": "Oxygen", 31 | // "F": "Fluorine", 32 | // "Ne": "Neon", 33 | // } 34 | // name, ok := elements["Li"] 35 | // x := [6]string{"a", "b", "c", "d", "e", "f"} 36 | // fmt.Println(x[2:5]) 37 | x := []int{ 38 | 48, 96, 86, 68, 39 | 57, 82, 63, 70, 40 | 37, 34, 83, 27, 41 | 19, 97, 9, 17, 42 | } 43 | small := &x[0] 44 | for i, _ := range x { 45 | if *small > x[i] { 46 | small = &x[i] 47 | } 48 | } 49 | fmt.Println(*small) 50 | } 51 | -------------------------------------------------------------------------------- /src/Echo1/doc.go: -------------------------------------------------------------------------------- 1 | // Echo1 project doc.go 2 | 3 | /* 4 | Echo1 document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/Echo1/main.go: -------------------------------------------------------------------------------- 1 | // Echo1 project main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("Hello World!") 10 | } 11 | -------------------------------------------------------------------------------- /src/HellowWorld/doc.go: -------------------------------------------------------------------------------- 1 | // HellowWorld project doc.go 2 | 3 | /* 4 | HellowWorld document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/HellowWorld/main.go: -------------------------------------------------------------------------------- 1 | // HellowWorld project main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("Hello World!") 10 | } 11 | -------------------------------------------------------------------------------- /src/Tests/doc.go: -------------------------------------------------------------------------------- 1 | // Tests project doc.go 2 | 3 | /* 4 | Tests document 5 | */ 6 | package main 7 | -------------------------------------------------------------------------------- /src/Tests/main.go: -------------------------------------------------------------------------------- 1 | // Tests project main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | func main() { 10 | // xs := []float64{98, 93, 77, 82, 83} 11 | // fmt.Println(sum(1, 2, 3, 4, 5)) 12 | str1 := "Привет всем!" 13 | // strlen := len(str1) 14 | // runeStr := []rune(str1) 15 | fmt.Print("Строка \"", str1, "\" имеет длинну ", strconv.Itoa(len([]rune(str1))), "\n") 16 | } 17 | 18 | //func sum(x1 []float64) int { 19 | // var sum int 20 | // for _, a := range x1 { 21 | // if maximum < a { 22 | // maximum = a 23 | // } 24 | // } 25 | // return maximum 26 | //} 27 | -------------------------------------------------------------------------------- /src/ch1/dup1/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 8. 5 | //!+ 6 | 7 | // Dup1 prints the text of each line that appears more than 8 | // once in the standard input, preceded by its count. 9 | package main 10 | 11 | import ( 12 | "bufio" 13 | "fmt" 14 | "os" 15 | ) 16 | 17 | func main() { 18 | counts := make(map[string]int) 19 | input := bufio.NewScanner(os.Stdin) 20 | for input.Scan() { 21 | if input.Text() == "" { 22 | break 23 | } 24 | counts[input.Text()]++ 25 | } 26 | // NOTE: ignoring potential errors from input.Err() 27 | for line, n := range counts { 28 | if n > 1 { 29 | fmt.Printf("%d\t%s\n", n, line) 30 | } 31 | } 32 | } 33 | 34 | //!- 35 | -------------------------------------------------------------------------------- /src/ch1/dup2/111.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 65 7 | 8 | 7 9 | 8 10 | -------------------------------------------------------------------------------- /src/ch1/dup2/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 10. 5 | //!+ 6 | 7 | // Dup2 prints the count and text of lines that appear more than once 8 | // in the input. It reads from stdin or from a list of named files. 9 | package main 10 | 11 | import ( 12 | "bufio" 13 | "fmt" 14 | "os" 15 | ) 16 | 17 | func main() { 18 | counts := make(map[string]int) 19 | files := os.Args[1:] 20 | if len(files) == 0 { 21 | countLines(os.Stdin, counts) 22 | } else { 23 | for _, arg := range files { 24 | f, err := os.Open(arg) 25 | if err != nil { 26 | fmt.Fprintf(os.Stderr, "dup2: %v\n", err) 27 | continue 28 | } 29 | countLines(f, counts) 30 | for _, n := range counts { 31 | if n > 1 { 32 | fmt.Printf("%s\n", arg) 33 | break 34 | } 35 | } 36 | counts = make(map[string]int) 37 | f.Close() 38 | } 39 | } 40 | // for line, n := range counts { 41 | // if n > 1 { 42 | // fmt.Printf("%d\t%s\n", n, line) 43 | // } 44 | // } 45 | } 46 | 47 | func countLines(f *os.File, counts map[string]int) { 48 | input := bufio.NewScanner(f) 49 | for input.Scan() { 50 | // if input.Text() == "" { 51 | // break 52 | // } 53 | counts[input.Text()]++ 54 | } 55 | // NOTE: ignoring potential errors from input.Err() 56 | } 57 | 58 | //!- 59 | -------------------------------------------------------------------------------- /src/ch1/dup3/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 12. 5 | 6 | //!+ 7 | 8 | // Dup3 prints the count and text of lines that 9 | // appear more than once in the named input files. 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "io/ioutil" 15 | "os" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // test 21 | func main() { 22 | counts := make(map[string]int) 23 | for _, filename := range os.Args[1:] { 24 | data, err := ioutil.ReadFile(filename) 25 | if err != nil { 26 | fmt.Fprintf(os.Stderr, "dup3: %v\n", err) 27 | continue 28 | } 29 | for _, line := range strings.Split(string(data), "\n") { 30 | counts[line]++ 31 | } 32 | } 33 | start := time.Now() 34 | for line, n := range counts { 35 | if n > 1 { 36 | fmt.Printf("%d\t%s\n", n, line) 37 | } 38 | } 39 | secs := time.Since(start).Seconds() 40 | fmt.Printf("%.20fs", secs) 41 | } 42 | 43 | // test 44 | //!- 45 | -------------------------------------------------------------------------------- /src/ch1/echo1/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 4. 5 | //!+ 6 | 7 | // Echo1 prints its command-line arguments. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | var s, sep string 17 | for i := 1; i < len(os.Args); i++ { 18 | s += sep + os.Args[i] 19 | sep = " " 20 | } 21 | fmt.Println(s) 22 | } 23 | 24 | //!- 25 | -------------------------------------------------------------------------------- /src/ch1/echo2/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 6. 5 | //!+ 6 | 7 | // Echo2 prints its command-line arguments. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | t0 := time.Now() 18 | s, sep := "", "" 19 | for i, arg := range os.Args[:] { 20 | s += sep + arg 21 | sep = " " 22 | fmt.Println("Индекс аргумента:", i, "Значение аргумента:", arg) 23 | } 24 | fmt.Println(s) 25 | t1 := time.Nanosecond.(Now().Sub(t0)) 26 | fmt.Println("The call took to run.\n", t1) 27 | } 28 | 29 | //!- 30 | -------------------------------------------------------------------------------- /src/ch1/echo3/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 8. 5 | 6 | // Echo3 prints its command-line arguments. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | //!+ 16 | func main() { 17 | fmt.Println(strings.Join(os.Args[1:], " ")) 18 | } 19 | 20 | //!- 21 | -------------------------------------------------------------------------------- /src/ch1/f_all/f_all.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 16. 5 | //!+ 6 | 7 | // Fetch prints the content found at each specified URL. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "net/http" 15 | "os" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | var ( 22 | countByte int 23 | filename *os.File 24 | err error 25 | ) 26 | ch := make(chan string) 27 | if len(os.Args[2:]) == 0 { 28 | fmt.Println("Usage: fetch ...") 29 | } 30 | if Exists(os.Args[1]) { 31 | filename, err = os.OpenFile(os.Args[1], os.O_APPEND, os.ModeAppend /*0644*/) 32 | if err != nil { 33 | fmt.Fprintf(os.Stderr, "fetch1o: open %s: %v\n", os.Args[1], err) 34 | os.Exit(1) 35 | } 36 | } else { 37 | filename, err = os.Create(os.Args[1]) 38 | if err != nil { 39 | fmt.Fprintf(os.Stderr, "fetch1c: create %s: %v\n", os.Args[1], err) 40 | os.Exit(1) 41 | } 42 | } 43 | defer filename.Close() 44 | startglob := time.Now() 45 | for _, url := range os.Args[2:] { 46 | if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { 47 | url = "http://" + url 48 | } 49 | go fetch(url, ch) // start a goroutine 50 | // countByte += b 51 | // fmt.Println(url, resp.Status, secs) 52 | } 53 | for _, url := range os.Args[2:] { 54 | // fmt.Println(<-ch) // receive from channel ch 55 | b, err := io.WriteString(filename, <-ch) 56 | if err != nil { 57 | fmt.Fprintf(os.Stderr, "fetch3: reading %s: %v\n", url, err) 58 | os.Exit(1) 59 | } 60 | countByte += b 61 | secsglob := time.Since(startglob).Seconds() 62 | fmt.Printf("\nЗаписано из %s %d байт за %f секунд\n", url, b, secsglob) 63 | } 64 | secsglob := time.Since(startglob).Seconds() 65 | fmt.Printf("\nВсего записано %d байт за %f секунд\n", countByte, secsglob) 66 | } 67 | 68 | // Exists reports whether the named file or directory exists. 69 | func Exists(name string) bool { 70 | if _, err := os.Stat(name); err != nil { 71 | if os.IsNotExist(err) { 72 | return false 73 | } 74 | } 75 | return true 76 | } 77 | 78 | func fetch(url string, ch chan<- string) { 79 | start := time.Now() 80 | resp, err := http.Get(url) 81 | if err != nil { 82 | ch <- fmt.Sprint(err) // send to channel ch 83 | return 84 | } 85 | 86 | // nbytes, err := io.Copy(ch <-, resp.Body) 87 | b, err := ioutil.ReadAll(resp.Body) 88 | resp.Body.Close() // don't leak resources 89 | if err != nil { 90 | ch <- fmt.Sprintf("while reading %s: %v", url, err) 91 | return 92 | } 93 | secs := time.Since(start).Seconds() 94 | fmt.Printf("%.2fs %s", secs, url) 95 | ch <- "--------- " + url + " ---------\n" + string(b) + "\n" 96 | 97 | } 98 | 99 | //!- 100 | -------------------------------------------------------------------------------- /src/ch1/fetch/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 16. 5 | //!+ 6 | 7 | // Fetch prints the content found at each specified URL. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "net/http" 14 | "os" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | func main() { 20 | var ( 21 | countByte int64 22 | filename *os.File 23 | err error 24 | ) 25 | if len(os.Args[2:]) == 0 { 26 | fmt.Println("Usage: fetch ...") 27 | } 28 | if Exists(os.Args[1]) { 29 | filename, err = os.OpenFile(os.Args[1], os.O_APPEND, os.ModeAppend /*0644*/) 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "fetch1o: open %s: %v\n", os.Args[1], err) 32 | os.Exit(1) 33 | } 34 | } else { 35 | filename, err = os.Create(os.Args[1]) 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "fetch1c: create %s: %v\n", os.Args[1], err) 38 | os.Exit(1) 39 | } 40 | } 41 | defer filename.Close() 42 | startglob := time.Now() 43 | for _, url := range os.Args[2:] { 44 | if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { 45 | url = "http://" + url 46 | } 47 | resp, err := http.Get(url) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "fetch2: %v\n", err) 50 | os.Exit(1) 51 | } 52 | _, _ = filename.WriteString("--------- " + url + " ---------\n") 53 | // if err != nil { 54 | // fmt.Fprintf(os.Stderr, "fetch2.5: write sep %s: %v\n", url, err) 55 | // os.Exit(1) 56 | // } 57 | start := time.Now() 58 | b, err := io.Copy(filename, resp.Body) 59 | resp.Body.Close() 60 | if err != nil { 61 | fmt.Fprintf(os.Stderr, "fetch3: reading %s: %v\n", url, err) 62 | os.Exit(1) 63 | } 64 | secs := time.Since(start).Seconds() 65 | _, _ = filename.WriteString("\n") 66 | countByte += b 67 | fmt.Println(url, resp.Status, secs) 68 | } 69 | secsglob := time.Since(startglob).Seconds() 70 | fmt.Printf("\nЗаписано %d байт за %f секунд\n", countByte, secsglob) 71 | } 72 | 73 | // Exists reports whether the named file or directory exists. 74 | func Exists(name string) bool { 75 | if _, err := os.Stat(name); err != nil { 76 | if os.IsNotExist(err) { 77 | return false 78 | } 79 | } 80 | return true 81 | } 82 | 83 | //!- 84 | -------------------------------------------------------------------------------- /src/ch1/fetchall/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 17. 5 | //!+ 6 | 7 | // Fetchall fetches URLs in parallel and reports their times and sizes. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "net/http" 15 | "os" 16 | "time" 17 | ) 18 | 19 | func main() { 20 | start := time.Now() 21 | ch := make(chan string) 22 | for _, url := range os.Args[1:] { 23 | go fetch(url, ch) // start a goroutine 24 | } 25 | for range os.Args[1:] { 26 | fmt.Println(<-ch) // receive from channel ch 27 | } 28 | fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds()) 29 | } 30 | 31 | func fetch(url string, ch chan<- string) { 32 | start := time.Now() 33 | resp, err := http.Get(url) 34 | if err != nil { 35 | ch <- fmt.Sprint(err) // send to channel ch 36 | return 37 | } 38 | 39 | nbytes, err := io.Copy(ioutil.Discard, resp.Body) 40 | resp.Body.Close() // don't leak resources 41 | if err != nil { 42 | ch <- fmt.Sprintf("while reading %s: %v", url, err) 43 | return 44 | } 45 | secs := time.Since(start).Seconds() 46 | ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url) 47 | } 48 | 49 | //!- 50 | -------------------------------------------------------------------------------- /src/ch1/helloworld/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 1. 5 | 6 | // Helloworld is our first Go program. 7 | //!+ 8 | package main 9 | 10 | import "fmt" 11 | 12 | func main() { 13 | fmt.Println("Hello, 世界") 14 | } 15 | 16 | //!- 17 | -------------------------------------------------------------------------------- /src/ch1/lissajous/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicodin77/GolangBook/5959b40338924fedc149e959468c21cdf6d91592/src/ch1/lissajous/1.gif -------------------------------------------------------------------------------- /src/ch1/lissajous/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicodin77/GolangBook/5959b40338924fedc149e959468c21cdf6d91592/src/ch1/lissajous/2.gif -------------------------------------------------------------------------------- /src/ch1/lissajous/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicodin77/GolangBook/5959b40338924fedc149e959468c21cdf6d91592/src/ch1/lissajous/3.gif -------------------------------------------------------------------------------- /src/ch1/lissajous/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // Run with "web" command-line argument for web server. 5 | // See page 13. 6 | //!+main 7 | 8 | // Lissajous generates GIF animations of random Lissajous figures. 9 | package main 10 | 11 | import ( 12 | "image" 13 | "image/color" 14 | "image/gif" 15 | "io" 16 | "math" 17 | "math/rand" 18 | "os" 19 | ) 20 | 21 | //!-main 22 | // Packages not needed by version in book. 23 | import ( 24 | "log" 25 | "net/http" 26 | // "time" 27 | ) 28 | 29 | //!+main 30 | 31 | var palette = []color.Color{color.Black, color.RGBA{0x00, 0xFF, 0x00, 0xff}, color.RGBA{0xFF, 0x00, 0x00, 0xff}, color.RGBA{0x00, 0x00, 0xFF, 0xff}} 32 | 33 | const ( 34 | whiteIndex = 0 // first color in palette 35 | blackIndex = 1 // next color in palette 36 | ) 37 | 38 | func main() { 39 | //!-main 40 | // The sequence of images is deterministic unless we seed 41 | // the pseudo-random number generator using the current time. 42 | // Thanks to Randall McPherson for pointing out the omission. 43 | // rand.Seed(time.Now().UTC().UnixNano()) 44 | 45 | if len(os.Args) > 1 && os.Args[1] == "web" { 46 | //!+http 47 | handler := func(w http.ResponseWriter, r *http.Request) { 48 | lissajous(w) 49 | } 50 | http.HandleFunc("/", handler) 51 | //!-http 52 | log.Fatal(http.ListenAndServe("localhost:8000", nil)) 53 | return 54 | } 55 | //!+main 56 | lissajous(os.Stdout) 57 | } 58 | 59 | func lissajous(out io.Writer) { 60 | const ( 61 | cycles = 5 // number of complete x oscillator revolutions 62 | res = 0.001 // angular resolution 63 | size = 100 // image canvas covers [-size..+size] 64 | nframes = 64 // number of animation frames 65 | delay = 8 // delay between frames in 10ms units 66 | ) 67 | freq := rand.Float64() * 3.0 // relative frequency of y oscillator 68 | anim := gif.GIF{LoopCount: nframes} 69 | phase := 0.0 // phase difference 70 | for i := 0; i < nframes; i++ { 71 | rect := image.Rect(0, 0, 2*size+1, 2*size+1) 72 | img := image.NewPaletted(rect, palette) 73 | for t := 0.0; t < cycles*2*math.Pi; t += res { 74 | x := math.Sin(t) 75 | y := math.Sin(t*freq + phase) 76 | img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), 77 | blackIndex+uint8(rand.Float64()*3.0)) 78 | } 79 | phase += 0.1 80 | anim.Delay = append(anim.Delay, delay) 81 | anim.Image = append(anim.Image, img) 82 | } 83 | gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors 84 | } 85 | 86 | //!-main 87 | -------------------------------------------------------------------------------- /src/ch1/server1/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 19. 5 | //!+ 6 | 7 | // Server1 is a minimal "echo" server. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "net/http" 14 | ) 15 | 16 | func main() { 17 | http.HandleFunc("/", handler) // each request calls handler 18 | log.Fatal(http.ListenAndServe("localhost:8000", nil)) 19 | } 20 | 21 | // handler echoes the Path component of the requested URL. 22 | func handler(w http.ResponseWriter, r *http.Request) { 23 | fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) 24 | } 25 | 26 | //!- 27 | -------------------------------------------------------------------------------- /src/ch1/server2/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 20. 5 | //!+ 6 | 7 | // Server2 is a minimal "echo" and counter server. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "net/http" 14 | "sync" 15 | ) 16 | 17 | var mu sync.Mutex 18 | var count int 19 | 20 | func main() { 21 | http.HandleFunc("/", handler) 22 | http.HandleFunc("/count", counter) 23 | log.Fatal(http.ListenAndServe("localhost:8000", nil)) 24 | } 25 | 26 | // handler echoes the Path component of the requested URL. 27 | func handler(w http.ResponseWriter, r *http.Request) { 28 | mu.Lock() 29 | count++ 30 | mu.Unlock() 31 | fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) 32 | } 33 | 34 | // counter echoes the number of calls so far. 35 | func counter(w http.ResponseWriter, r *http.Request) { 36 | mu.Lock() 37 | fmt.Fprintf(w, "Count %d\n", count) 38 | mu.Unlock() 39 | } 40 | 41 | //!- 42 | -------------------------------------------------------------------------------- /src/ch1/server3/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 21. 5 | 6 | // Server3 is an "echo" server that displays request parameters. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | func main() { 16 | http.HandleFunc("/", handler) 17 | log.Fatal(http.ListenAndServe("localhost:8000", nil)) 18 | } 19 | 20 | //!+handler 21 | // handler echoes the HTTP request. 22 | func handler(w http.ResponseWriter, r *http.Request) { 23 | fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto) 24 | for k, v := range r.Header { 25 | fmt.Fprintf(w, "Header[%q] = %q\n", k, v) 26 | } 27 | fmt.Fprintf(w, "Host = %q\n", r.Host) 28 | fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr) 29 | if err := r.ParseForm(); err != nil { 30 | log.Print(err) 31 | } 32 | for k, v := range r.Form { 33 | fmt.Fprintf(w, "Form[%q] = %q\n", k, v) 34 | } 35 | } 36 | 37 | //!-handler 38 | -------------------------------------------------------------------------------- /src/ch10/cross/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 295. 5 | 6 | // The cross command prints the values of GOOS and GOARCH for this target. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "runtime" 12 | ) 13 | 14 | //!+ 15 | func main() { 16 | fmt.Println(runtime.GOOS, runtime.GOARCH) 17 | } 18 | 19 | //!- 20 | -------------------------------------------------------------------------------- /src/ch10/jpeg/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 287. 5 | 6 | //!+main 7 | 8 | // The jpeg command reads a PNG image from the standard input 9 | // and writes it as a JPEG image to the standard output. 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "image" 15 | "image/jpeg" 16 | _ "image/png" // register PNG decoder 17 | "io" 18 | "os" 19 | ) 20 | 21 | func main() { 22 | if err := toJPEG(os.Stdin, os.Stdout); err != nil { 23 | fmt.Fprintf(os.Stderr, "jpeg: %v\n", err) 24 | os.Exit(1) 25 | } 26 | } 27 | 28 | func toJPEG(in io.Reader, out io.Writer) error { 29 | img, kind, err := image.Decode(in) 30 | if err != nil { 31 | return err 32 | } 33 | fmt.Fprintln(os.Stderr, "Input format =", kind) 34 | return jpeg.Encode(out, img, &jpeg.Options{Quality: 95}) 35 | } 36 | 37 | //!-main 38 | 39 | /* 40 | //!+with 41 | $ go build gopl.io/ch3/mandelbrot 42 | $ go build gopl.io/ch10/jpeg 43 | $ ./mandelbrot | ./jpeg >mandelbrot.jpg 44 | Input format = png 45 | //!-with 46 | 47 | //!+without 48 | $ go build gopl.io/ch10/jpeg 49 | $ ./mandelbrot | ./jpeg >mandelbrot.jpg 50 | jpeg: image: unknown format 51 | //!-without 52 | */ 53 | -------------------------------------------------------------------------------- /src/ch11/echo/echo.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 308. 5 | //!+ 6 | 7 | // Echo prints its command-line arguments. 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "io" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | var ( 19 | n = flag.Bool("n", false, "omit trailing newline") 20 | s = flag.String("s", " ", "separator") 21 | ) 22 | 23 | var out io.Writer = os.Stdout // modified during testing 24 | 25 | func main() { 26 | flag.Parse() 27 | if err := echo(!*n, *s, flag.Args()); err != nil { 28 | fmt.Fprintf(os.Stderr, "echo: %v\n", err) 29 | os.Exit(1) 30 | } 31 | } 32 | 33 | func echo(newline bool, sep string, args []string) error { 34 | fmt.Fprint(out, strings.Join(args, sep)) 35 | if newline { 36 | fmt.Fprintln(out) 37 | } 38 | return nil 39 | } 40 | 41 | //!- 42 | -------------------------------------------------------------------------------- /src/ch11/echo/echo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // Test of echo command. Run with: go test gopl.io/ch11/echo 5 | 6 | //!+ 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "testing" 13 | ) 14 | 15 | func TestEcho(t *testing.T) { 16 | var tests = []struct { 17 | newline bool 18 | sep string 19 | args []string 20 | want string 21 | }{ 22 | {true, "", []string{}, "\n"}, 23 | {false, "", []string{}, ""}, 24 | {true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"}, 25 | {true, ",", []string{"a", "b", "c"}, "a,b,c\n"}, 26 | {false, ":", []string{"1", "2", "3"}, "1:2:3"}, 27 | } 28 | 29 | for _, test := range tests { 30 | descr := fmt.Sprintf("echo(%v, %q, %q)", 31 | test.newline, test.sep, test.args) 32 | 33 | out = new(bytes.Buffer) // captured output 34 | if err := echo(test.newline, test.sep, test.args); err != nil { 35 | t.Errorf("%s failed: %v", descr, err) 36 | continue 37 | } 38 | got := out.(*bytes.Buffer).String() 39 | if got != test.want { 40 | t.Errorf("%s = %q, want %q", descr, got, test.want) 41 | } 42 | } 43 | } 44 | 45 | //!- 46 | -------------------------------------------------------------------------------- /src/ch11/storage1/storage.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 311. 5 | 6 | // Package storage is part of a hypothetical cloud storage server. 7 | //!+main 8 | package storage 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "net/smtp" 14 | ) 15 | 16 | var usage = make(map[string]int64) 17 | 18 | func bytesInUse(username string) int64 { return usage[username] } 19 | 20 | // Email sender configuration. 21 | // NOTE: never put passwords in source code! 22 | const sender = "notifications@example.com" 23 | const password = "correcthorsebatterystaple" 24 | const hostname = "smtp.example.com" 25 | 26 | const template = `Warning: you are using %d bytes of storage, 27 | %d%% of your quota.` 28 | 29 | func CheckQuota(username string) { 30 | used := bytesInUse(username) 31 | const quota = 1000000000 // 1GB 32 | percent := 100 * used / quota 33 | if percent < 90 { 34 | return // OK 35 | } 36 | msg := fmt.Sprintf(template, used, percent) 37 | auth := smtp.PlainAuth("", sender, password, hostname) 38 | err := smtp.SendMail(hostname+":587", auth, sender, 39 | []string{username}, []byte(msg)) 40 | if err != nil { 41 | log.Printf("smtp.SendMail(%s) failed: %s", username, err) 42 | } 43 | } 44 | 45 | //!-main 46 | -------------------------------------------------------------------------------- /src/ch11/storage2/quota_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | //!+test 5 | package storage 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestCheckQuotaNotifiesUser(t *testing.T) { 13 | var notifiedUser, notifiedMsg string 14 | notifyUser = func(user, msg string) { 15 | notifiedUser, notifiedMsg = user, msg 16 | } 17 | 18 | const user = "joe@example.org" 19 | usage[user] = 980000000 // simulate a 980MB-used condition 20 | 21 | CheckQuota(user) 22 | if notifiedUser == "" && notifiedMsg == "" { 23 | t.Fatalf("notifyUser not called") 24 | } 25 | if notifiedUser != user { 26 | t.Errorf("wrong user (%s) notified, want %s", 27 | notifiedUser, user) 28 | } 29 | const wantSubstring = "98% of your quota" 30 | if !strings.Contains(notifiedMsg, wantSubstring) { 31 | t.Errorf("unexpected notification message <<%s>>, "+ 32 | "want substring %q", notifiedMsg, wantSubstring) 33 | } 34 | } 35 | 36 | //!-test 37 | 38 | /* 39 | //!+defer 40 | func TestCheckQuotaNotifiesUser(t *testing.T) { 41 | // Save and restore original notifyUser. 42 | saved := notifyUser 43 | defer func() { notifyUser = saved }() 44 | 45 | // Install the test's fake notifyUser. 46 | var notifiedUser, notifiedMsg string 47 | notifyUser = func(user, msg string) { 48 | notifiedUser, notifiedMsg = user, msg 49 | } 50 | // ...rest of test... 51 | } 52 | //!-defer 53 | */ 54 | -------------------------------------------------------------------------------- /src/ch11/storage2/storage.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 312. 5 | 6 | // Package storage is part of a hypothetical cloud storage server. 7 | package storage 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "net/smtp" 13 | ) 14 | 15 | var usage = make(map[string]int64) 16 | 17 | func bytesInUse(username string) int64 { return usage[username] } 18 | 19 | // E-mail sender configuration. 20 | // NOTE: never put passwords in source code! 21 | const sender = "notifications@example.com" 22 | const password = "correcthorsebatterystaple" 23 | const hostname = "smtp.example.com" 24 | 25 | const template = `Warning: you are using %d bytes of storage, 26 | %d%% of your quota.` 27 | 28 | //!+factored 29 | var notifyUser = func(username, msg string) { 30 | auth := smtp.PlainAuth("", sender, password, hostname) 31 | err := smtp.SendMail(hostname+":587", auth, sender, 32 | []string{username}, []byte(msg)) 33 | if err != nil { 34 | log.Printf("smtp.SendEmail(%s) failed: %s", username, err) 35 | } 36 | } 37 | 38 | func CheckQuota(username string) { 39 | used := bytesInUse(username) 40 | const quota = 1000000000 // 1GB 41 | percent := 100 * used / quota 42 | if percent < 90 { 43 | return // OK 44 | } 45 | msg := fmt.Sprintf(template, used, percent) 46 | notifyUser(username, msg) 47 | } 48 | 49 | //!-factored 50 | -------------------------------------------------------------------------------- /src/ch11/word1/word.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 303. 5 | //!+ 6 | 7 | // Package word provides utilities for word games. 8 | package word 9 | 10 | // IsPalindrome reports whether s reads the same forward and backward. 11 | // (Our first attempt.) 12 | func IsPalindrome(s string) bool { 13 | for i := range s { 14 | if s[i] != s[len(s)-1-i] { 15 | return false 16 | } 17 | } 18 | return true 19 | } 20 | 21 | //!- 22 | -------------------------------------------------------------------------------- /src/ch11/word1/word_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | //!+test 5 | package word 6 | 7 | import "testing" 8 | 9 | func TestPalindrome(t *testing.T) { 10 | if !IsPalindrome("detartrated") { 11 | t.Error(`IsPalindrome("detartrated") = false`) 12 | } 13 | if !IsPalindrome("kayak") { 14 | t.Error(`IsPalindrome("kayak") = false`) 15 | } 16 | } 17 | 18 | func TestNonPalindrome(t *testing.T) { 19 | if IsPalindrome("palindrome") { 20 | t.Error(`IsPalindrome("palindrome") = true`) 21 | } 22 | } 23 | 24 | //!-test 25 | 26 | // The tests below are expected to fail. 27 | // See package gopl.io/ch11/word2 for the fix. 28 | 29 | //!+more 30 | func TestFrenchPalindrome(t *testing.T) { 31 | if !IsPalindrome("été") { 32 | t.Error(`IsPalindrome("été") = false`) 33 | } 34 | } 35 | 36 | func TestCanalPalindrome(t *testing.T) { 37 | input := "A man, a plan, a canal: Panama" 38 | if !IsPalindrome(input) { 39 | t.Errorf(`IsPalindrome(%q) = false`, input) 40 | } 41 | } 42 | 43 | //!-more 44 | -------------------------------------------------------------------------------- /src/ch11/word2/word.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 305. 5 | //!+ 6 | 7 | // Package word provides utilities for word games. 8 | package word 9 | 10 | import "unicode" 11 | 12 | // IsPalindrome reports whether s reads the same forward and backward. 13 | // Letter case is ignored, as are non-letters. 14 | func IsPalindrome(s string) bool { 15 | var letters []rune 16 | for _, r := range s { 17 | if unicode.IsLetter(r) { 18 | letters = append(letters, unicode.ToLower(r)) 19 | } 20 | } 21 | for i := range letters { 22 | if letters[i] != letters[len(letters)-1-i] { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | 29 | //!- 30 | -------------------------------------------------------------------------------- /src/ch12/display/display.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 333. 5 | 6 | // Package display provides a means to display structured data. 7 | package display 8 | 9 | import ( 10 | "fmt" 11 | "reflect" 12 | "strconv" 13 | ) 14 | 15 | //!+Display 16 | 17 | func Display(name string, x interface{}) { 18 | fmt.Printf("Display %s (%T):\n", name, x) 19 | display(name, reflect.ValueOf(x)) 20 | } 21 | 22 | //!-Display 23 | 24 | // formatAtom formats a value without inspecting its internal structure. 25 | // It is a copy of the the function in gopl.io/ch11/format. 26 | func formatAtom(v reflect.Value) string { 27 | switch v.Kind() { 28 | case reflect.Invalid: 29 | return "invalid" 30 | case reflect.Int, reflect.Int8, reflect.Int16, 31 | reflect.Int32, reflect.Int64: 32 | return strconv.FormatInt(v.Int(), 10) 33 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 34 | reflect.Uint32, reflect.Uint64, reflect.Uintptr: 35 | return strconv.FormatUint(v.Uint(), 10) 36 | // ...floating-point and complex cases omitted for brevity... 37 | case reflect.Bool: 38 | if v.Bool() { 39 | return "true" 40 | } 41 | return "false" 42 | case reflect.String: 43 | return strconv.Quote(v.String()) 44 | case reflect.Chan, reflect.Func, reflect.Ptr, 45 | reflect.Slice, reflect.Map: 46 | return v.Type().String() + " 0x" + 47 | strconv.FormatUint(uint64(v.Pointer()), 16) 48 | default: // reflect.Array, reflect.Struct, reflect.Interface 49 | return v.Type().String() + " value" 50 | } 51 | } 52 | 53 | //!+display 54 | func display(path string, v reflect.Value) { 55 | switch v.Kind() { 56 | case reflect.Invalid: 57 | fmt.Printf("%s = invalid\n", path) 58 | case reflect.Slice, reflect.Array: 59 | for i := 0; i < v.Len(); i++ { 60 | display(fmt.Sprintf("%s[%d]", path, i), v.Index(i)) 61 | } 62 | case reflect.Struct: 63 | for i := 0; i < v.NumField(); i++ { 64 | fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name) 65 | display(fieldPath, v.Field(i)) 66 | } 67 | case reflect.Map: 68 | for _, key := range v.MapKeys() { 69 | display(fmt.Sprintf("%s[%s]", path, 70 | formatAtom(key)), v.MapIndex(key)) 71 | } 72 | case reflect.Ptr: 73 | if v.IsNil() { 74 | fmt.Printf("%s = nil\n", path) 75 | } else { 76 | display(fmt.Sprintf("(*%s)", path), v.Elem()) 77 | } 78 | case reflect.Interface: 79 | if v.IsNil() { 80 | fmt.Printf("%s = nil\n", path) 81 | } else { 82 | fmt.Printf("%s.type = %s\n", path, v.Elem().Type()) 83 | display(path+".value", v.Elem()) 84 | } 85 | default: // basic types, channels, funcs 86 | fmt.Printf("%s = %s\n", path, formatAtom(v)) 87 | } 88 | } 89 | 90 | //!-display 91 | -------------------------------------------------------------------------------- /src/ch12/format/format.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 332. 5 | 6 | // Package format provides an Any function that can format any value. 7 | //!+ 8 | package format 9 | 10 | import ( 11 | "reflect" 12 | "strconv" 13 | ) 14 | 15 | // Any formats any value as a string. 16 | func Any(value interface{}) string { 17 | return formatAtom(reflect.ValueOf(value)) 18 | } 19 | 20 | // formatAtom formats a value without inspecting its internal structure. 21 | func formatAtom(v reflect.Value) string { 22 | switch v.Kind() { 23 | case reflect.Invalid: 24 | return "invalid" 25 | case reflect.Int, reflect.Int8, reflect.Int16, 26 | reflect.Int32, reflect.Int64: 27 | return strconv.FormatInt(v.Int(), 10) 28 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 29 | reflect.Uint32, reflect.Uint64, reflect.Uintptr: 30 | return strconv.FormatUint(v.Uint(), 10) 31 | // ...floating-point and complex cases omitted for brevity... 32 | case reflect.Bool: 33 | return strconv.FormatBool(v.Bool()) 34 | case reflect.String: 35 | return strconv.Quote(v.String()) 36 | case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: 37 | return v.Type().String() + " 0x" + 38 | strconv.FormatUint(uint64(v.Pointer()), 16) 39 | default: // reflect.Array, reflect.Struct, reflect.Interface 40 | return v.Type().String() + " value" 41 | } 42 | } 43 | 44 | //!- 45 | -------------------------------------------------------------------------------- /src/ch12/format/format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package format_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | 11 | "gopl.io/ch12/format" 12 | ) 13 | 14 | func Test(t *testing.T) { 15 | // The pointer values are just examples, and may vary from run to run. 16 | //!+time 17 | var x int64 = 1 18 | var d time.Duration = 1 * time.Nanosecond 19 | fmt.Println(format.Any(x)) // "1" 20 | fmt.Println(format.Any(d)) // "1" 21 | fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0" 22 | fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0" 23 | //!-time 24 | } 25 | -------------------------------------------------------------------------------- /src/ch12/methods/methods.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 351. 5 | 6 | // Package methods provides a function to print the methods of any value. 7 | package methods 8 | 9 | import ( 10 | "fmt" 11 | "reflect" 12 | "strings" 13 | ) 14 | 15 | //!+print 16 | // Print prints the method set of the value x. 17 | func Print(x interface{}) { 18 | v := reflect.ValueOf(x) 19 | t := v.Type() 20 | fmt.Printf("type %s\n", t) 21 | 22 | for i := 0; i < v.NumMethod(); i++ { 23 | methType := v.Method(i).Type() 24 | fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name, 25 | strings.TrimPrefix(methType.String(), "func")) 26 | } 27 | } 28 | 29 | //!-print 30 | -------------------------------------------------------------------------------- /src/ch12/methods/methods_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package methods_test 5 | 6 | import ( 7 | "strings" 8 | "time" 9 | 10 | "gopl.io/ch12/methods" 11 | ) 12 | 13 | func ExamplePrintDuration() { 14 | methods.Print(time.Hour) 15 | // Output: 16 | // type time.Duration 17 | // func (time.Duration) Hours() float64 18 | // func (time.Duration) Minutes() float64 19 | // func (time.Duration) Nanoseconds() int64 20 | // func (time.Duration) Seconds() float64 21 | // func (time.Duration) String() string 22 | } 23 | 24 | func ExamplePrintReplacer() { 25 | methods.Print(new(strings.Replacer)) 26 | // Output: 27 | // type *strings.Replacer 28 | // func (*strings.Replacer) Replace(string) string 29 | // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) 30 | } 31 | 32 | /* 33 | //!+output 34 | methods.Print(time.Hour) 35 | // Output: 36 | // type time.Duration 37 | // func (time.Duration) Hours() float64 38 | // func (time.Duration) Minutes() float64 39 | // func (time.Duration) Nanoseconds() int64 40 | // func (time.Duration) Seconds() float64 41 | // func (time.Duration) String() string 42 | 43 | methods.Print(new(strings.Replacer)) 44 | // Output: 45 | // type *strings.Replacer 46 | // func (*strings.Replacer) Replace(string) string 47 | // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) 48 | //!-output 49 | */ 50 | -------------------------------------------------------------------------------- /src/ch12/params/params.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 349. 5 | 6 | // Package params provides a reflection-based parser for URL parameters. 7 | package params 8 | 9 | import ( 10 | "fmt" 11 | "net/http" 12 | "reflect" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | //!+Unpack 18 | 19 | // Unpack populates the fields of the struct pointed to by ptr 20 | // from the HTTP request parameters in req. 21 | func Unpack(req *http.Request, ptr interface{}) error { 22 | if err := req.ParseForm(); err != nil { 23 | return err 24 | } 25 | 26 | // Build map of fields keyed by effective name. 27 | fields := make(map[string]reflect.Value) 28 | v := reflect.ValueOf(ptr).Elem() // the struct variable 29 | for i := 0; i < v.NumField(); i++ { 30 | fieldInfo := v.Type().Field(i) // a reflect.StructField 31 | tag := fieldInfo.Tag // a reflect.StructTag 32 | name := tag.Get("http") 33 | if name == "" { 34 | name = strings.ToLower(fieldInfo.Name) 35 | } 36 | fields[name] = v.Field(i) 37 | } 38 | 39 | // Update struct field for each parameter in the request. 40 | for name, values := range req.Form { 41 | f := fields[name] 42 | if !f.IsValid() { 43 | continue // ignore unrecognized HTTP parameters 44 | } 45 | for _, value := range values { 46 | if f.Kind() == reflect.Slice { 47 | elem := reflect.New(f.Type().Elem()).Elem() 48 | if err := populate(elem, value); err != nil { 49 | return fmt.Errorf("%s: %v", name, err) 50 | } 51 | f.Set(reflect.Append(f, elem)) 52 | } else { 53 | if err := populate(f, value); err != nil { 54 | return fmt.Errorf("%s: %v", name, err) 55 | } 56 | } 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | //!-Unpack 63 | 64 | //!+populate 65 | func populate(v reflect.Value, value string) error { 66 | switch v.Kind() { 67 | case reflect.String: 68 | v.SetString(value) 69 | 70 | case reflect.Int: 71 | i, err := strconv.ParseInt(value, 10, 64) 72 | if err != nil { 73 | return err 74 | } 75 | v.SetInt(i) 76 | 77 | case reflect.Bool: 78 | b, err := strconv.ParseBool(value) 79 | if err != nil { 80 | return err 81 | } 82 | v.SetBool(b) 83 | 84 | default: 85 | return fmt.Errorf("unsupported kind %s", v.Type()) 86 | } 87 | return nil 88 | } 89 | 90 | //!-populate 91 | -------------------------------------------------------------------------------- /src/ch12/search/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 348. 5 | 6 | // Search is a demo of the params.Unpack function. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | //!+ 16 | 17 | import "gopl.io/ch12/params" 18 | 19 | // search implements the /search URL endpoint. 20 | func search(resp http.ResponseWriter, req *http.Request) { 21 | var data struct { 22 | Labels []string `http:"l"` 23 | MaxResults int `http:"max"` 24 | Exact bool `http:"x"` 25 | } 26 | data.MaxResults = 10 // set default 27 | if err := params.Unpack(req, &data); err != nil { 28 | http.Error(resp, err.Error(), http.StatusBadRequest) // 400 29 | return 30 | } 31 | 32 | // ...rest of handler... 33 | fmt.Fprintf(resp, "Search: %+v\n", data) 34 | } 35 | 36 | //!- 37 | 38 | func main() { 39 | http.HandleFunc("/search", search) 40 | log.Fatal(http.ListenAndServe(":12345", nil)) 41 | } 42 | 43 | /* 44 | //!+output 45 | $ go build gopl.io/ch12/search 46 | $ ./search & 47 | $ ./fetch 'http://localhost:12345/search' 48 | Search: {Labels:[] MaxResults:10 Exact:false} 49 | $ ./fetch 'http://localhost:12345/search?l=golang&l=programming' 50 | Search: {Labels:[golang programming] MaxResults:10 Exact:false} 51 | $ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100' 52 | Search: {Labels:[golang programming] MaxResults:100 Exact:false} 53 | $ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming' 54 | Search: {Labels:[golang programming] MaxResults:10 Exact:true} 55 | $ ./fetch 'http://localhost:12345/search?q=hello&x=123' 56 | x: strconv.ParseBool: parsing "123": invalid syntax 57 | $ ./fetch 'http://localhost:12345/search?q=hello&max=lots' 58 | max: strconv.ParseInt: parsing "lots": invalid syntax 59 | //!-output 60 | */ 61 | -------------------------------------------------------------------------------- /src/ch12/sexpr/encode.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 339. 5 | 6 | package sexpr 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "reflect" 12 | ) 13 | 14 | //!+Marshal 15 | // Marshal encodes a Go value in S-expression form. 16 | func Marshal(v interface{}) ([]byte, error) { 17 | var buf bytes.Buffer 18 | if err := encode(&buf, reflect.ValueOf(v)); err != nil { 19 | return nil, err 20 | } 21 | return buf.Bytes(), nil 22 | } 23 | 24 | //!-Marshal 25 | 26 | // encode writes to buf an S-expression representation of v. 27 | //!+encode 28 | func encode(buf *bytes.Buffer, v reflect.Value) error { 29 | switch v.Kind() { 30 | case reflect.Invalid: 31 | buf.WriteString("nil") 32 | 33 | case reflect.Int, reflect.Int8, reflect.Int16, 34 | reflect.Int32, reflect.Int64: 35 | fmt.Fprintf(buf, "%d", v.Int()) 36 | 37 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 38 | reflect.Uint32, reflect.Uint64, reflect.Uintptr: 39 | fmt.Fprintf(buf, "%d", v.Uint()) 40 | 41 | case reflect.String: 42 | fmt.Fprintf(buf, "%q", v.String()) 43 | 44 | case reflect.Ptr: 45 | return encode(buf, v.Elem()) 46 | 47 | case reflect.Array, reflect.Slice: // (value ...) 48 | buf.WriteByte('(') 49 | for i := 0; i < v.Len(); i++ { 50 | if i > 0 { 51 | buf.WriteByte(' ') 52 | } 53 | if err := encode(buf, v.Index(i)); err != nil { 54 | return err 55 | } 56 | } 57 | buf.WriteByte(')') 58 | 59 | case reflect.Struct: // ((name value) ...) 60 | buf.WriteByte('(') 61 | for i := 0; i < v.NumField(); i++ { 62 | if i > 0 { 63 | buf.WriteByte(' ') 64 | } 65 | fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name) 66 | if err := encode(buf, v.Field(i)); err != nil { 67 | return err 68 | } 69 | buf.WriteByte(')') 70 | } 71 | buf.WriteByte(')') 72 | 73 | case reflect.Map: // ((key value) ...) 74 | buf.WriteByte('(') 75 | for i, key := range v.MapKeys() { 76 | if i > 0 { 77 | buf.WriteByte(' ') 78 | } 79 | buf.WriteByte('(') 80 | if err := encode(buf, key); err != nil { 81 | return err 82 | } 83 | buf.WriteByte(' ') 84 | if err := encode(buf, v.MapIndex(key)); err != nil { 85 | return err 86 | } 87 | buf.WriteByte(')') 88 | } 89 | buf.WriteByte(')') 90 | 91 | default: // float, complex, bool, chan, func, interface 92 | return fmt.Errorf("unsupported type: %s", v.Type()) 93 | } 94 | return nil 95 | } 96 | 97 | //!-encode 98 | -------------------------------------------------------------------------------- /src/ch12/sexpr/sexpr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package sexpr 5 | 6 | import ( 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | // Test verifies that encoding and decoding a complex data value 12 | // produces an equal result. 13 | // 14 | // The test does not make direct assertions about the encoded output 15 | // because the output depends on map iteration order, which is 16 | // nondeterministic. The output of the t.Log statements can be 17 | // inspected by running the test with the -v flag: 18 | // 19 | // $ go test -v gopl.io/ch12/sexpr 20 | // 21 | func Test(t *testing.T) { 22 | type Movie struct { 23 | Title, Subtitle string 24 | Year int 25 | Actor map[string]string 26 | Oscars []string 27 | Sequel *string 28 | } 29 | strangelove := Movie{ 30 | Title: "Dr. Strangelove", 31 | Subtitle: "How I Learned to Stop Worrying and Love the Bomb", 32 | Year: 1964, 33 | Actor: map[string]string{ 34 | "Dr. Strangelove": "Peter Sellers", 35 | "Grp. Capt. Lionel Mandrake": "Peter Sellers", 36 | "Pres. Merkin Muffley": "Peter Sellers", 37 | "Gen. Buck Turgidson": "George C. Scott", 38 | "Brig. Gen. Jack D. Ripper": "Sterling Hayden", 39 | `Maj. T.J. "King" Kong`: "Slim Pickens", 40 | }, 41 | Oscars: []string{ 42 | "Best Actor (Nomin.)", 43 | "Best Adapted Screenplay (Nomin.)", 44 | "Best Director (Nomin.)", 45 | "Best Picture (Nomin.)", 46 | }, 47 | } 48 | 49 | // Encode it 50 | data, err := Marshal(strangelove) 51 | if err != nil { 52 | t.Fatalf("Marshal failed: %v", err) 53 | } 54 | t.Logf("Marshal() = %s\n", data) 55 | 56 | // Decode it 57 | var movie Movie 58 | if err := Unmarshal(data, &movie); err != nil { 59 | t.Fatalf("Unmarshal failed: %v", err) 60 | } 61 | t.Logf("Unmarshal() = %+v\n", movie) 62 | 63 | // Check equality. 64 | if !reflect.DeepEqual(movie, strangelove) { 65 | t.Fatal("not equal") 66 | } 67 | 68 | // Pretty-print it: 69 | data, err = MarshalIndent(strangelove) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | t.Logf("MarshalIdent() = %s\n", data) 74 | } 75 | -------------------------------------------------------------------------------- /src/ch13/bzip-print/bzip2.c: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 362. 5 | // This is the version that appears in print, 6 | // but it does not comply with the proposed 7 | // rules for passing pointers between Go and C. 8 | // (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) 9 | // See gopl.io/ch13/bzip for an updated version. 10 | 11 | //!+ 12 | /* This file is gopl.io/ch13/bzip/bzip2.c, */ 13 | /* a simple wrapper for libbzip2 suitable for cgo. */ 14 | #include 15 | 16 | int bz2compress(bz_stream *s, int action, 17 | char *in, unsigned *inlen, char *out, unsigned *outlen) { 18 | s->next_in = in; 19 | s->avail_in = *inlen; 20 | s->next_out = out; 21 | s->avail_out = *outlen; 22 | int r = BZ2_bzCompress(s, action); 23 | *inlen -= s->avail_in; 24 | *outlen -= s->avail_out; 25 | return r; 26 | } 27 | 28 | //!- 29 | -------------------------------------------------------------------------------- /src/ch13/bzip-print/bzip2.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 362. 5 | // This is the version that appears in print, 6 | // but it does not comply with the proposed 7 | // rules for passing pointers between Go and C. 8 | // (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) 9 | // See gopl.io/ch13/bzip for an updated version. 10 | //!+ 11 | 12 | // Package bzip provides a writer that uses bzip2 compression (bzip.org). 13 | package bzip 14 | 15 | /* 16 | #cgo CFLAGS: -I/usr/include 17 | #cgo LDFLAGS: -L/usr/lib -lbz2 18 | #include 19 | int bz2compress(bz_stream *s, int action, 20 | char *in, unsigned *inlen, char *out, unsigned *outlen); 21 | */ 22 | import "C" 23 | 24 | import ( 25 | "io" 26 | "unsafe" 27 | ) 28 | 29 | type writer struct { 30 | w io.Writer // underlying output stream 31 | stream *C.bz_stream 32 | outbuf [64 * 1024]byte 33 | } 34 | 35 | // NewWriter returns a writer for bzip2-compressed streams. 36 | func NewWriter(out io.Writer) io.WriteCloser { 37 | const ( 38 | blockSize = 9 39 | verbosity = 0 40 | workFactor = 30 41 | ) 42 | w := &writer{w: out, stream: new(C.bz_stream)} 43 | C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor) 44 | return w 45 | } 46 | 47 | //!- 48 | 49 | //!+write 50 | func (w *writer) Write(data []byte) (int, error) { 51 | if w.stream == nil { 52 | panic("closed") 53 | } 54 | var total int // uncompressed bytes written 55 | 56 | for len(data) > 0 { 57 | inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf)) 58 | C.bz2compress(w.stream, C.BZ_RUN, 59 | (*C.char)(unsafe.Pointer(&data[0])), &inlen, 60 | (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) 61 | total += int(inlen) 62 | data = data[inlen:] 63 | if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { 64 | return total, err 65 | } 66 | } 67 | return total, nil 68 | } 69 | 70 | //!-write 71 | 72 | //!+close 73 | // Close flushes the compressed data and closes the stream. 74 | // It does not close the underlying io.Writer. 75 | func (w *writer) Close() error { 76 | if w.stream == nil { 77 | panic("closed") 78 | } 79 | defer func() { 80 | C.BZ2_bzCompressEnd(w.stream) 81 | w.stream = nil 82 | }() 83 | for { 84 | inlen, outlen := C.uint(0), C.uint(cap(w.outbuf)) 85 | r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen, 86 | (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) 87 | if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { 88 | return err 89 | } 90 | if r == C.BZ_STREAM_END { 91 | return nil 92 | } 93 | } 94 | } 95 | 96 | //!-close 97 | -------------------------------------------------------------------------------- /src/ch13/bzip-print/bzip2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package bzip_test 5 | 6 | import ( 7 | "bytes" 8 | "compress/bzip2" // reader 9 | "io" 10 | "testing" 11 | 12 | "gopl.io/ch13/bzip" // writer 13 | ) 14 | 15 | func TestBzip2(t *testing.T) { 16 | var compressed, uncompressed bytes.Buffer 17 | w := bzip.NewWriter(&compressed) 18 | 19 | // Write a repetitive message in a million pieces, 20 | // compressing one copy but not the other. 21 | tee := io.MultiWriter(w, &uncompressed) 22 | for i := 0; i < 1000000; i++ { 23 | io.WriteString(tee, "hello") 24 | } 25 | if err := w.Close(); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | // Check the size of the compressed stream. 30 | if got, want := compressed.Len(), 255; got != want { 31 | t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want) 32 | } 33 | 34 | // Decompress and compare with original. 35 | var decompressed bytes.Buffer 36 | io.Copy(&decompressed, bzip2.NewReader(&compressed)) 37 | if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) { 38 | t.Error("decompression yielded a different message") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ch13/bzip/bzip2.c: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 362. 5 | // 6 | // The version of this program that appeared in the first and second 7 | // printings did not comply with the proposed rules for passing 8 | // pointers between Go and C, described here: 9 | // https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md 10 | // 11 | // The version below, which appears in the third printing, 12 | // has been corrected. See bzip2.go for explanation. 13 | 14 | //!+ 15 | /* This file is gopl.io/ch13/bzip/bzip2.c, */ 16 | /* a simple wrapper for libbzip2 suitable for cgo. */ 17 | #include 18 | 19 | int bz2compress(bz_stream *s, int action, 20 | char *in, unsigned *inlen, char *out, unsigned *outlen) { 21 | s->next_in = in; 22 | s->avail_in = *inlen; 23 | s->next_out = out; 24 | s->avail_out = *outlen; 25 | int r = BZ2_bzCompress(s, action); 26 | *inlen -= s->avail_in; 27 | *outlen -= s->avail_out; 28 | s->next_in = s->next_out = NULL; 29 | return r; 30 | } 31 | 32 | //!- 33 | -------------------------------------------------------------------------------- /src/ch13/bzip/bzip2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package bzip_test 5 | 6 | import ( 7 | "bytes" 8 | "compress/bzip2" // reader 9 | "io" 10 | "testing" 11 | 12 | "gopl.io/ch13/bzip" // writer 13 | ) 14 | 15 | func TestBzip2(t *testing.T) { 16 | var compressed, uncompressed bytes.Buffer 17 | w := bzip.NewWriter(&compressed) 18 | 19 | // Write a repetitive message in a million pieces, 20 | // compressing one copy but not the other. 21 | tee := io.MultiWriter(w, &uncompressed) 22 | for i := 0; i < 1000000; i++ { 23 | io.WriteString(tee, "hello") 24 | } 25 | if err := w.Close(); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | // Check the size of the compressed stream. 30 | if got, want := compressed.Len(), 255; got != want { 31 | t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want) 32 | } 33 | 34 | // Decompress and compare with original. 35 | var decompressed bytes.Buffer 36 | io.Copy(&decompressed, bzip2.NewReader(&compressed)) 37 | if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) { 38 | t.Error("decompression yielded a different message") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ch13/bzipper/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 365. 5 | 6 | //!+ 7 | 8 | // Bzipper reads input, bzip2-compresses it, and writes it out. 9 | package main 10 | 11 | import ( 12 | "io" 13 | "log" 14 | "os" 15 | 16 | "gopl.io/ch13/bzip" 17 | ) 18 | 19 | func main() { 20 | w := bzip.NewWriter(os.Stdout) 21 | if _, err := io.Copy(w, os.Stdin); err != nil { 22 | log.Fatalf("bzipper: %v\n", err) 23 | } 24 | if err := w.Close(); err != nil { 25 | log.Fatalf("bzipper: close: %v\n", err) 26 | } 27 | } 28 | 29 | //!- 30 | -------------------------------------------------------------------------------- /src/ch13/equal/equal.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 359. 5 | 6 | // Package equal provides a deep equivalence relation for arbitrary values. 7 | package equal 8 | 9 | import ( 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | //!+ 15 | func equal(x, y reflect.Value, seen map[comparison]bool) bool { 16 | if !x.IsValid() || !y.IsValid() { 17 | return x.IsValid() == y.IsValid() 18 | } 19 | if x.Type() != y.Type() { 20 | return false 21 | } 22 | 23 | // ...cycle check omitted (shown later)... 24 | 25 | //!- 26 | //!+cyclecheck 27 | // cycle check 28 | if x.CanAddr() && y.CanAddr() { 29 | xptr := unsafe.Pointer(x.UnsafeAddr()) 30 | yptr := unsafe.Pointer(y.UnsafeAddr()) 31 | if xptr == yptr { 32 | return true // identical references 33 | } 34 | c := comparison{xptr, yptr, x.Type()} 35 | if seen[c] { 36 | return true // already seen 37 | } 38 | seen[c] = true 39 | } 40 | //!-cyclecheck 41 | //!+ 42 | switch x.Kind() { 43 | case reflect.Bool: 44 | return x.Bool() == y.Bool() 45 | 46 | case reflect.String: 47 | return x.String() == y.String() 48 | 49 | // ...numeric cases omitted for brevity... 50 | 51 | //!- 52 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 53 | reflect.Int64: 54 | return x.Int() == y.Int() 55 | 56 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 57 | reflect.Uint64, reflect.Uintptr: 58 | return x.Uint() == y.Uint() 59 | 60 | case reflect.Float32, reflect.Float64: 61 | return x.Float() == y.Float() 62 | 63 | case reflect.Complex64, reflect.Complex128: 64 | return x.Complex() == y.Complex() 65 | //!+ 66 | case reflect.Chan, reflect.UnsafePointer, reflect.Func: 67 | return x.Pointer() == y.Pointer() 68 | 69 | case reflect.Ptr, reflect.Interface: 70 | return equal(x.Elem(), y.Elem(), seen) 71 | 72 | case reflect.Array, reflect.Slice: 73 | if x.Len() != y.Len() { 74 | return false 75 | } 76 | for i := 0; i < x.Len(); i++ { 77 | if !equal(x.Index(i), y.Index(i), seen) { 78 | return false 79 | } 80 | } 81 | return true 82 | 83 | // ...struct and map cases omitted for brevity... 84 | //!- 85 | case reflect.Struct: 86 | for i, n := 0, x.NumField(); i < n; i++ { 87 | if !equal(x.Field(i), y.Field(i), seen) { 88 | return false 89 | } 90 | } 91 | return true 92 | 93 | case reflect.Map: 94 | if x.Len() != y.Len() { 95 | return false 96 | } 97 | for _, k := range x.MapKeys() { 98 | if !equal(x.MapIndex(k), y.MapIndex(k), seen) { 99 | return false 100 | } 101 | } 102 | return true 103 | //!+ 104 | } 105 | panic("unreachable") 106 | } 107 | 108 | //!- 109 | 110 | //!+comparison 111 | // Equal reports whether x and y are deeply equal. 112 | //!-comparison 113 | // 114 | // Map keys are always compared with ==, not deeply. 115 | // (This matters for keys containing pointers or interfaces.) 116 | //!+comparison 117 | func Equal(x, y interface{}) bool { 118 | seen := make(map[comparison]bool) 119 | return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen) 120 | } 121 | 122 | type comparison struct { 123 | x, y unsafe.Pointer 124 | t reflect.Type 125 | } 126 | 127 | //!-comparison 128 | -------------------------------------------------------------------------------- /src/ch13/unsafeptr/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 357. 5 | 6 | // Package unsafeptr demonstrates basic use of unsafe.Pointer. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "unsafe" 12 | ) 13 | 14 | func main() { 15 | //!+main 16 | var x struct { 17 | a bool 18 | b int16 19 | c []int 20 | } 21 | 22 | // equivalent to pb := &x.b 23 | pb := (*int16)(unsafe.Pointer( 24 | uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))) 25 | *pb = 42 26 | 27 | fmt.Println(x.b) // "42" 28 | //!-main 29 | } 30 | 31 | /* 32 | //!+wrong 33 | // NOTE: subtly incorrect! 34 | tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b) 35 | pb := (*int16)(unsafe.Pointer(tmp)) 36 | *pb = 42 37 | //!-wrong 38 | */ 39 | -------------------------------------------------------------------------------- /src/ch2/boiling/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 29. 5 | //!+ 6 | 7 | // Boiling prints the boiling point of water. 8 | package main 9 | 10 | import "fmt" 11 | 12 | const boilingF = 212.0 13 | 14 | func main() { 15 | var f = boilingF 16 | var c = (f - 32) * 5 / 9 17 | fmt.Printf("boiling point = %g°F or %g°C\n", f, c) 18 | // Output: 19 | // boiling point = 212°F or 100°C 20 | } 21 | 22 | //!- 23 | -------------------------------------------------------------------------------- /src/ch2/cf/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 43. 5 | //!+ 6 | 7 | // Cf converts its numeric argument to Celsius and Fahrenheit. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "strconv" 14 | 15 | "gopl.io/ch2/tempconv" 16 | ) 17 | 18 | func main() { 19 | for _, arg := range os.Args[1:] { 20 | t, err := strconv.ParseFloat(arg, 64) 21 | if err != nil { 22 | fmt.Fprintf(os.Stderr, "cf: %v\n", err) 23 | os.Exit(1) 24 | } 25 | f := tempconv.Fahrenheit(t) 26 | c := tempconv.Celsius(t) 27 | fmt.Printf("%s = %s, %s = %s\n", 28 | f, tempconv.FToC(f), c, tempconv.CToF(c)) 29 | } 30 | } 31 | 32 | //!- 33 | -------------------------------------------------------------------------------- /src/ch2/echo4/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 33. 5 | //!+ 6 | 7 | // Echo4 prints its command-line arguments. 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "strings" 14 | ) 15 | 16 | var n = flag.Bool("n", false, "omit trailing newline") 17 | var sep = flag.String("s", " ", "separator") 18 | 19 | func main() { 20 | flag.Parse() 21 | fmt.Print(strings.Join(flag.Args(), *sep)) 22 | if !*n { 23 | fmt.Println() 24 | } 25 | } 26 | 27 | //!- 28 | -------------------------------------------------------------------------------- /src/ch2/ftoc/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 29. 5 | //!+ 6 | 7 | // Ftoc prints two Fahrenheit-to-Celsius conversions. 8 | package main 9 | 10 | import "fmt" 11 | 12 | func main() { 13 | const freezingF, boilingF = 32.0, 212.0 14 | fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C" 15 | fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF)) // "212°F = 100°C" 16 | } 17 | 18 | func fToC(f float64) float64 { 19 | return (f - 32) * 5 / 9 20 | } 21 | 22 | //!- 23 | -------------------------------------------------------------------------------- /src/ch2/popcount/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 45. 5 | 6 | // (Package doc comment intentionally malformed to demonstrate golint.) 7 | //!+ 8 | package popcount 9 | 10 | // pc[i] is the population count of i. 11 | var pc [256]byte 12 | 13 | func init() { 14 | for i := range pc { 15 | pc[i] = pc[i/2] + byte(i&1) 16 | } 17 | } 18 | 19 | // PopCount returns the population count (number of set bits) of x. 20 | func PopCount(x uint64) int { 21 | return int(pc[byte(x>>(0*8))] + 22 | pc[byte(x>>(1*8))] + 23 | pc[byte(x>>(2*8))] + 24 | pc[byte(x>>(3*8))] + 25 | pc[byte(x>>(4*8))] + 26 | pc[byte(x>>(5*8))] + 27 | pc[byte(x>>(6*8))] + 28 | pc[byte(x>>(7*8))]) 29 | } 30 | 31 | //!- 32 | -------------------------------------------------------------------------------- /src/ch2/popcount/popcount_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | package popcount_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "gopl.io/ch2/popcount" 10 | ) 11 | 12 | // -- Alternative implementations -- 13 | 14 | func BitCount(x uint64) int { 15 | // Hacker's Delight, Figure 5-2. 16 | x = x - ((x >> 1) & 0x5555555555555555) 17 | x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333) 18 | x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f 19 | x = x + (x >> 8) 20 | x = x + (x >> 16) 21 | x = x + (x >> 32) 22 | return int(x & 0x7f) 23 | } 24 | 25 | func PopCountByClearing(x uint64) int { 26 | n := 0 27 | for x != 0 { 28 | x = x & (x - 1) // clear rightmost non-zero bit 29 | n++ 30 | } 31 | return n 32 | } 33 | 34 | func PopCountByShifting(x uint64) int { 35 | n := 0 36 | for i := uint(0); i < 64; i++ { 37 | if x&(1< a, a.go => a, a/b/c.go => c, a/b.c.go => b.c 26 | func basename(s string) string { 27 | // Discard last '/' and everything before. 28 | for i := len(s) - 1; i >= 0; i-- { 29 | if s[i] == '/' { 30 | s = s[i+1:] 31 | break 32 | } 33 | } 34 | // Preserve everything before last '.'. 35 | for i := len(s) - 1; i >= 0; i-- { 36 | if s[i] == '.' { 37 | s = s[:i] 38 | break 39 | } 40 | } 41 | return s 42 | } 43 | 44 | //!- 45 | -------------------------------------------------------------------------------- /src/ch3/basename2/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 72. 5 | 6 | // Basename2 reads file names from stdin and prints the base name of each one. 7 | package main 8 | 9 | import ( 10 | "bufio" 11 | "fmt" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | func main() { 17 | input := bufio.NewScanner(os.Stdin) 18 | for input.Scan() { 19 | fmt.Println(basename(input.Text())) 20 | } 21 | // NOTE: ignoring potential errors from input.Err() 22 | } 23 | 24 | // basename removes directory components and a trailing .suffix. 25 | // e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c 26 | //!+ 27 | func basename(s string) string { 28 | slash := strings.LastIndex(s, "/") // -1 if "/" not found 29 | s = s[slash+1:] 30 | if dot := strings.LastIndex(s, "."); dot >= 0 { 31 | s = s[:dot] 32 | } 33 | return s 34 | } 35 | 36 | //!- 37 | -------------------------------------------------------------------------------- /src/ch3/comma/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 73. 5 | 6 | // Comma prints its argument numbers with a comma at each power of 1000. 7 | // 8 | // Example: 9 | // $ go build gopl.io/ch3/comma 10 | // $ ./comma 1 12 123 1234 1234567890 11 | // 1 12 | // 12 13 | // 123 14 | // 1,234 15 | // 1,234,567,890 16 | // 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | ) 23 | 24 | func main() { 25 | for i := 1; i < len(os.Args); i++ { 26 | fmt.Printf(" %s\n", comma(os.Args[i])) 27 | } 28 | } 29 | 30 | //!+ 31 | // comma inserts commas in a non-negative decimal integer string. 32 | func comma(s string) string { 33 | n := len(s) 34 | if n <= 3 { 35 | return s 36 | } 37 | return comma(s[:n-3]) + "," + s[n-3:] 38 | } 39 | 40 | //!- 41 | -------------------------------------------------------------------------------- /src/ch3/mandelbrot/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 61. 5 | //!+ 6 | 7 | // Mandelbrot emits a PNG image of the Mandelbrot fractal. 8 | package main 9 | 10 | import ( 11 | "image" 12 | "image/color" 13 | "image/png" 14 | "math/cmplx" 15 | "os" 16 | ) 17 | 18 | func main() { 19 | const ( 20 | xmin, ymin, xmax, ymax = -2, -2, +2, +2 21 | width, height = 1024, 1024 22 | ) 23 | 24 | img := image.NewRGBA(image.Rect(0, 0, width, height)) 25 | for py := 0; py < height; py++ { 26 | y := float64(py)/height*(ymax-ymin) + ymin 27 | for px := 0; px < width; px++ { 28 | x := float64(px)/width*(xmax-xmin) + xmin 29 | z := complex(x, y) 30 | // Image point (px, py) represents complex value z. 31 | img.Set(px, py, mandelbrot(z)) 32 | } 33 | } 34 | png.Encode(os.Stdout, img) // NOTE: ignoring errors 35 | } 36 | 37 | func mandelbrot(z complex128) color.Color { 38 | const iterations = 200 39 | const contrast = 15 40 | 41 | var v complex128 42 | for n := uint8(0); n < iterations; n++ { 43 | v = v*v + z 44 | if cmplx.Abs(v) > 2 { 45 | return color.Gray{255 - contrast*n} 46 | } 47 | } 48 | return color.Black 49 | } 50 | 51 | //!- 52 | 53 | // Some other interesting functions: 54 | 55 | func acos(z complex128) color.Color { 56 | v := cmplx.Acos(z) 57 | blue := uint8(real(v)*128) + 127 58 | red := uint8(imag(v)*128) + 127 59 | return color.YCbCr{192, blue, red} 60 | } 61 | 62 | func sqrt(z complex128) color.Color { 63 | v := cmplx.Sqrt(z) 64 | blue := uint8(real(v)*128) + 127 65 | red := uint8(imag(v)*128) + 127 66 | return color.YCbCr{128, blue, red} 67 | } 68 | 69 | // f(x) = x^4 - 1 70 | // 71 | // z' = z - f(z)/f'(z) 72 | // = z - (z^4 - 1) / (4 * z^3) 73 | // = z - (z - 1/z^3) / 4 74 | func newton(z complex128) color.Color { 75 | const iterations = 37 76 | const contrast = 7 77 | for i := uint8(0); i < iterations; i++ { 78 | z -= (z - 1/(z*z*z)) / 4 79 | if cmplx.Abs(z*z*z*z-1) < 1e-6 { 80 | return color.Gray{255 - contrast*i} 81 | } 82 | } 83 | return color.Black 84 | } 85 | -------------------------------------------------------------------------------- /src/ch3/netflag/netflag.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 77. 5 | 6 | // Netflag demonstrates an integer type used as a bit field. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | . "net" 12 | ) 13 | 14 | //!+ 15 | func IsUp(v Flags) bool { return v&FlagUp == FlagUp } 16 | func TurnDown(v *Flags) { *v &^= FlagUp } 17 | func SetBroadcast(v *Flags) { *v |= FlagBroadcast } 18 | func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 } 19 | 20 | func main() { 21 | var v Flags = FlagMulticast | FlagUp 22 | fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true" 23 | TurnDown(&v) 24 | fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false" 25 | SetBroadcast(&v) 26 | fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false" 27 | fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true" 28 | } 29 | 30 | //!- 31 | -------------------------------------------------------------------------------- /src/ch3/printints/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 74. 5 | 6 | // Printints demonstrates the use of bytes.Buffer to format a string. 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | ) 13 | 14 | //!+ 15 | // intsToString is like fmt.Sprint(values) but adds commas. 16 | func intsToString(values []int) string { 17 | var buf bytes.Buffer 18 | buf.WriteByte('[') 19 | for i, v := range values { 20 | if i > 0 { 21 | buf.WriteString(", ") 22 | } 23 | fmt.Fprintf(&buf, "%d", v) 24 | } 25 | buf.WriteByte(']') 26 | return buf.String() 27 | } 28 | 29 | func main() { 30 | fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]" 31 | } 32 | 33 | //!- 34 | -------------------------------------------------------------------------------- /src/ch3/surface/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 58. 5 | //!+ 6 | 7 | // Surface computes an SVG rendering of a 3-D surface function. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "math" 13 | ) 14 | 15 | const ( 16 | width, height = 600, 320 // canvas size in pixels 17 | cells = 100 // number of grid cells 18 | xyrange = 30.0 // axis ranges (-xyrange..+xyrange) 19 | xyscale = width / 2 / xyrange // pixels per x or y unit 20 | zscale = height * 0.4 // pixels per z unit 21 | angle = math.Pi / 6 // angle of x, y axes (=30°) 22 | ) 23 | 24 | var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°) 25 | 26 | func main() { 27 | fmt.Printf("", width, height) 30 | for i := 0; i < cells; i++ { 31 | for j := 0; j < cells; j++ { 32 | ax, ay := corner(i+1, j) 33 | bx, by := corner(i, j) 34 | cx, cy := corner(i, j+1) 35 | dx, dy := corner(i+1, j+1) 36 | fmt.Printf("\n", 37 | ax, ay, bx, by, cx, cy, dx, dy) 38 | } 39 | } 40 | fmt.Println("") 41 | } 42 | 43 | func corner(i, j int) (float64, float64) { 44 | // Find point (x,y) at corner of cell (i,j). 45 | x := xyrange * (float64(i)/cells - 0.5) 46 | y := xyrange * (float64(j)/cells - 0.5) 47 | 48 | // Compute surface height z. 49 | z := f(x, y) 50 | 51 | // Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy). 52 | sx := width/2 + (x-y)*cos30*xyscale 53 | sy := height/2 + (x+y)*sin30*xyscale - z*zscale 54 | return sx, sy 55 | } 56 | 57 | func f(x, y float64) float64 { 58 | r := math.Hypot(x, y) // distance from (0,0) 59 | return math.Sin(r) / r 60 | } 61 | 62 | //!- 63 | -------------------------------------------------------------------------------- /src/ch4/append/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 88. 5 | 6 | // Append illustrates the behavior of the built-in append function. 7 | package main 8 | 9 | import "fmt" 10 | 11 | func appendslice(x []int, y ...int) []int { 12 | var z []int 13 | zlen := len(x) + len(y) 14 | if zlen <= cap(x) { 15 | // There is room to expand the slice. 16 | z = x[:zlen] 17 | } else { 18 | // There is insufficient space. 19 | // Grow by doubling, for amortized linear complexity. 20 | zcap := zlen 21 | if zcap < 2*len(x) { 22 | zcap = 2 * len(x) 23 | } 24 | z = make([]int, zlen, zcap) 25 | copy(z, x) 26 | } 27 | copy(z[len(x):], y) 28 | return z 29 | } 30 | 31 | //!+append 32 | func appendInt(x []int, y int) []int { 33 | var z []int 34 | zlen := len(x) + 1 35 | if zlen <= cap(x) { 36 | // There is room to grow. Extend the slice. 37 | z = x[:zlen] 38 | } else { 39 | // There is insufficient space. Allocate a new array. 40 | // Grow by doubling, for amortized linear complexity. 41 | zcap := zlen 42 | if zcap < 2*len(x) { 43 | zcap = 2 * len(x) 44 | } 45 | z = make([]int, zlen, zcap) 46 | copy(z, x) // a built-in function; see text 47 | } 48 | z[len(x)] = y 49 | return z 50 | } 51 | 52 | //!-append 53 | 54 | //!+growth 55 | func main() { 56 | var x, y []int 57 | for i := 0; i < 10; i++ { 58 | y = appendInt(x, i) 59 | fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y) 60 | x = y 61 | } 62 | } 63 | 64 | //!-growth 65 | 66 | /* 67 | //!+output 68 | 0 cap=1 [0] 69 | 1 cap=2 [0 1] 70 | 2 cap=4 [0 1 2] 71 | 3 cap=4 [0 1 2 3] 72 | 4 cap=8 [0 1 2 3 4] 73 | 5 cap=8 [0 1 2 3 4 5] 74 | 6 cap=8 [0 1 2 3 4 5 6] 75 | 7 cap=8 [0 1 2 3 4 5 6 7] 76 | 8 cap=16 [0 1 2 3 4 5 6 7 8] 77 | 9 cap=16 [0 1 2 3 4 5 6 7 8 9] 78 | //!-output 79 | */ 80 | -------------------------------------------------------------------------------- /src/ch4/autoescape/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 117. 5 | 6 | // Autoescape demonstrates automatic HTML escaping in html/template. 7 | package main 8 | 9 | import ( 10 | "html/template" 11 | "log" 12 | "os" 13 | ) 14 | 15 | //!+ 16 | func main() { 17 | const templ = `

A: {{.A}}

B: {{.B}}

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

{{.TotalCount}} issues

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