Update to gobook@c6d7a22edd03e7738c38d5baa2174ff325d8d863

This commit is contained in:
Alan Donovan 2015-10-28 14:20:59 -04:00
parent fce1727c4c
commit 30090035de
150 changed files with 8559 additions and 6 deletions

View File

@ -13,17 +13,21 @@ import (
"image/color"
"image/gif"
"io"
//!-main
"log"
//!+main
"math"
"math/rand"
//!-main
"net/http"
//!+main
"os"
)
//!-main
// Packages not needed by version in book.
import (
"log"
"net/http"
"time"
)
//!+main
var palette = []color.Color{color.White, color.Black}
const (
@ -33,6 +37,11 @@ const (
func main() {
//!-main
// The sequence of images is deterministic unless we seed
// the pseudo-random number generator using the current time.
// Thanks to Randall McPherson for pointing out the omission.
rand.Seed(time.Now().UTC().UnixNano())
if len(os.Args) > 1 && os.Args[1] == "web" {
//!+http
handler := func(w http.ResponseWriter, r *http.Request) {

19
ch10/cross/main.go Normal file
View File

@ -0,0 +1,19 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 295.
// The cross command prints the values of GOOS and GOARCH for this target.
package main
import (
"fmt"
"runtime"
)
//!+
func main() {
fmt.Println(runtime.GOOS, runtime.GOARCH)
}
//!-

52
ch10/jpeg/main.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 287.
//!+main
// The jpeg command reads a PNG image from the standard input
// and writes it as a JPEG image to the standard output.
package main
import (
"fmt"
"image"
"image/jpeg"
_ "image/png" // register PNG decoder
"io"
"os"
)
func main() {
if err := toJPEG(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
os.Exit(1)
}
}
func toJPEG(in io.Reader, out io.Writer) error {
img, kind, err := image.Decode(in)
if err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Input format =", kind)
return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}
//!-main
/*
//!+with
$ go build gopl.io/ch3/mandelbrot
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png
//!-with
//!+without
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format
//!-without
*/

41
ch11/echo/echo.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 308.
//!+
// Echo prints its command-line arguments.
package main
import (
"flag"
"fmt"
"io"
"os"
"strings"
)
var (
n = flag.Bool("n", false, "omit trailing newline")
s = flag.String("s", " ", "separator")
)
var out io.Writer = os.Stdout // modified during testing
func main() {
flag.Parse()
if err := echo(!*n, *s, flag.Args()); err != nil {
fmt.Fprintf(os.Stderr, "echo: %v\n", err)
os.Exit(1)
}
}
func echo(newline bool, sep string, args []string) error {
fmt.Fprint(out, strings.Join(args, sep))
if newline {
fmt.Fprintln(out)
}
return nil
}
//!-

45
ch11/echo/echo_test.go Normal file
View File

@ -0,0 +1,45 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Test of echo command. Run with: go test gopl.io/ch11/echo
//!+
package main
import (
"bytes"
"fmt"
"testing"
)
func TestEcho(t *testing.T) {
var tests = []struct {
newline bool
sep string
args []string
want string
}{
{true, "", []string{}, "\n"},
{false, "", []string{}, ""},
{true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"},
{true, ",", []string{"a", "b", "c"}, "a,b,c\n"},
{false, ":", []string{"1", "2", "3"}, "1:2:3"},
}
for _, test := range tests {
descr := fmt.Sprintf("echo(%v, %q, %q)",
test.newline, test.sep, test.args)
out = new(bytes.Buffer) // captured output
if err := echo(test.newline, test.sep, test.args); err != nil {
t.Errorf("%s failed: %v", descr, err)
continue
}
got := out.(*bytes.Buffer).String()
if got != test.want {
t.Errorf("%s = %q, want %q", descr, got, test.want)
}
}
}
//!-

43
ch11/storage1/storage.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 311.
// Package storage is part of a hypothetical cloud storage server.
//!+main
package storage
import (
"fmt"
"log"
"net/smtp"
)
func bytesInUse(username string) int64 { return 0 /* ... */ }
// Email sender configuration.
// NOTE: never put passwords in source code!
const sender = "notifications@example.com"
const password = "correcthorsebatterystaple"
const hostname = "smtp.example.com"
const template = `Warning: you are using %d bytes of storage,
%d%% of your quota.`
func CheckQuota(username string) {
used := bytesInUse(username)
const quota = 1000000000 // 1GB
percent := 100 * used / quota
if percent < 90 {
return // OK
}
msg := fmt.Sprintf(template, used, percent)
auth := smtp.PlainAuth("", sender, password, hostname)
err := smtp.SendMail(hostname+":587", auth, sender,
[]string{username}, []byte(msg))
if err != nil {
log.Printf("smtp.SendMail(%s) failed: %s", username, err)
}
}
//!-main

View File

@ -0,0 +1,56 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//!+test
package storage
import (
"strings"
"testing"
)
func TestCheckQuotaNotifiesUser(t *testing.T) {
var notifiedUser, notifiedMsg string
notifyUser = func(user, msg string) {
notifiedUser, notifiedMsg = user, msg
}
const user = "joe@example.org"
// Simulate a 980MB-used condition for this user.
// NOTE: this differs slightly from the printed version.
usage["joe@example.org"] = 980000000
CheckQuota(user)
if notifiedUser == "" && notifiedMsg == "" {
t.Fatalf("notifyUser not called")
}
if notifiedUser != user {
t.Errorf("wrong user (%s) notified, want %s",
notifiedUser, user)
}
const wantSubstring = "98% of your quota"
if !strings.Contains(notifiedMsg, wantSubstring) {
t.Errorf("unexpected notification message <<%s>>, "+
"want substring %q", notifiedMsg, wantSubstring)
}
}
//!-test
/*
//!+defer
func TestCheckQuotaNotifiesUser(t *testing.T) {
// Save and restore original notifyUser.
saved := notifyUser
defer func() { notifyUser = saved }()
// Install the test's fake notifyUser.
var notifiedUser, notifiedMsg string
notifyUser = func(user, msg string) {
notifiedUser, notifiedMsg = user, msg
}
// ...rest of test...
}
//!-defer
*/

51
ch11/storage2/storage.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 312.
// Package storage is part of a hypothetical cloud storage server.
//!+main
package storage
import (
"fmt"
"log"
"net/smtp"
)
// NOTE: this differs slightly from the printed version.
var usage = make(map[string]int64)
func bytesInUse(username string) int64 { return usage[username] }
// E-mail sender configuration.
// NOTE: never put passwords in source code!
const sender = "notifications@example.com"
const password = "correcthorsebatterystaple"
const hostname = "smtp.example.com"
const template = `Warning: you are using %d bytes of storage,
%d%% of your quota.`
//!+factored
var notifyUser = func(username, msg string) {
auth := smtp.PlainAuth("", sender, password, hostname)
err := smtp.SendMail(hostname+":587", auth, sender,
[]string{username}, []byte(msg))
if err != nil {
log.Printf("smtp.SendEmail(%s) failed: %s", username, err)
}
}
func CheckQuota(username string) {
used := bytesInUse(username)
const quota = 1000000000 // 1GB
percent := 100 * used / quota
if percent < 90 {
return // OK
}
msg := fmt.Sprintf(template, used, percent)
notifyUser(username, msg)
}
//!-factored

21
ch11/word1/word.go Normal file
View File

@ -0,0 +1,21 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 303.
//!+
// Package word provides utilities for word games.
package word
// IsPalindrome reports whether s reads the same forward and backward.
// (Our first attempt.)
func IsPalindrome(s string) bool {
for i := range s {
if s[i] != s[len(s)-1-i] {
return false
}
}
return true
}
//!-

43
ch11/word1/word_test.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//!+test
package word
import "testing"
func TestPalindrome(t *testing.T) {
if !IsPalindrome("detartrated") {
t.Error(`IsPalindrome("detartrated") = false`)
}
if !IsPalindrome("kayak") {
t.Error(`IsPalindrome("kayak") = false`)
}
}
func TestNonPalindrome(t *testing.T) {
if IsPalindrome("palindrome") {
t.Error(`IsPalindrome("palindrome") = true`)
}
}
//!-test
// The tests below are expected to fail.
// See package gopl.io/ch11/word2 for the fix.
//!+more
func TestFrenchPalindrome(t *testing.T) {
if !IsPalindrome("été") {
t.Error(`IsPalindrome("été") = false`)
}
}
func TestCanalPalindrome(t *testing.T) {
input := "A man, a plan, a canal: Panama"
if !IsPalindrome(input) {
t.Errorf(`IsPalindrome(%q) = false`, input)
}
}
//!-more

29
ch11/word2/word.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 305.
//!+
// Package word provides utilities for word games.
package word
import "unicode"
// IsPalindrome reports whether s reads the same forward and backward.
// Letter case is ignored, as are non-letters.
func IsPalindrome(s string) bool {
var letters []rune
for _, r := range s {
if unicode.IsLetter(r) {
letters = append(letters, unicode.ToLower(r))
}
}
for i := range letters {
if letters[i] != letters[len(letters)-1-i] {
return false
}
}
return true
}
//!-

148
ch11/word2/word_test.go Normal file
View File

@ -0,0 +1,148 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package word
import (
"fmt"
"math/rand"
"time"
)
//!+bench
import "testing"
//!-bench
//!+test
func TestIsPalindrome(t *testing.T) {
var tests = []struct {
input string
want bool
}{
{"", true},
{"a", true},
{"aa", true},
{"ab", false},
{"kayak", true},
{"detartrated", true},
{"A man, a plan, a canal: Panama", true},
{"Evil I did dwell; lewd did I live.", true},
{"Able was I ere I saw Elba", true},
{"été", true},
{"Et se resservir, ivresse reste.", true},
{"palindrome", false}, // non-palindrome
{"desserts", false}, // semi-palindrome
}
for _, test := range tests {
if got := IsPalindrome(test.input); got != test.want {
t.Errorf("IsPalindrome(%q) = %v", test.input, got)
}
}
}
//!-test
//!+bench
func BenchmarkIsPalindrome(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama")
}
}
//!-bench
//!+example
func ExampleIsPalindrome() {
fmt.Println(IsPalindrome("A man, a plan, a canal: Panama"))
fmt.Println(IsPalindrome("palindrome"))
// Output:
// true
// false
}
//!-example
/*
//!+random
import "math/rand"
//!-random
*/
//!+random
// randomPalindrome returns a palindrome whose length and contents
// are derived from the pseudo-random number generator rng.
func randomPalindrome(rng *rand.Rand) string {
n := rng.Intn(25) // random length up to 24
runes := make([]rune, n)
for i := 0; i < (n+1)/2; i++ {
r := rune(rng.Intn(0x1000)) // random rune up to '\u0999'
runes[i] = r
runes[n-1-i] = r
}
return string(runes)
}
func TestRandomPalindromes(t *testing.T) {
// Initialize a pseudo-random number generator.
seed := time.Now().UTC().UnixNano()
t.Logf("Random seed: %d", seed)
rng := rand.New(rand.NewSource(seed))
for i := 0; i < 1000; i++ {
p := randomPalindrome(rng)
if !IsPalindrome(p) {
t.Errorf("IsPalindrome(%q) = false", p)
}
}
}
//!-random
/*
// Answer for Exercicse 11.1: Modify randomPalindrome to exercise
// IsPalindrome's handling of punctuation and spaces.
// WARNING: the conversion r -> upper -> lower doesn't preserve
// the value of r in some cases, e.g., µ Μ, ſ S, ı I
// randomPalindrome returns a palindrome whose length and contents
// are derived from the pseudo-random number generator rng.
func randomNoisyPalindrome(rng *rand.Rand) string {
n := rng.Intn(25) // random length up to 24
runes := make([]rune, n)
for i := 0; i < (n+1)/2; i++ {
r := rune(rng.Intn(0x200)) // random rune up to \u99
runes[i] = r
r1 := r
if unicode.IsLetter(r) && unicode.IsLower(r) {
r = unicode.ToUpper(r)
if unicode.ToLower(r) != r1 {
fmt.Printf("cap? %c %c\n", r1, r)
}
}
runes[n-1-i] = r
}
return "?" + string(runes) + "!"
}
func TestRandomNoisyPalindromes(t *testing.T) {
// Initialize a pseudo-random number generator.
seed := time.Now().UTC().UnixNano()
t.Logf("Random seed: %d", seed)
rng := rand.New(rand.NewSource(seed))
n := 0
for i := 0; i < 1000; i++ {
p := randomNoisyPalindrome(rng)
if !IsPalindrome(p) {
t.Errorf("IsNoisyPalindrome(%q) = false", p)
n++
}
}
fmt.Fprintf(os.Stderr, "fail = %d\n", n)
}
*/

90
ch12/display/display.go Normal file
View File

@ -0,0 +1,90 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 333.
// Package display provides a means to display structured data.
package display
import (
"fmt"
"reflect"
"strconv"
)
//!+Display
func Display(name string, x interface{}) {
fmt.Printf("Display %s (%T):\n", name, x)
display(name, reflect.ValueOf(x))
}
//!-Display
// formatAtom formats a value without inspecting its internal structure.
// It is a copy of the the function in gopl.io/ch11/format.
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
if v.Bool() {
return "true"
}
return "false"
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr,
reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}
//!+display
func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf("%s = invalid\n", path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf("%s[%s]", path,
formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
display(fmt.Sprintf("(*%s)", path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
display(path+".value", v.Elem())
}
default: // basic types, channels, funcs
fmt.Printf("%s = %s\n", path, formatAtom(v))
}
}
//!-display

View File

@ -0,0 +1,259 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package display
import (
"io"
"net"
"os"
"reflect"
"sync"
"testing"
"gopl.io/ch7/eval"
)
// NOTE: we can't use !+..!- comments to excerpt these tests
// into the book because it defeats the Example mechanism,
// which requires the // Output comment to be at the end
// of the function.
func ExampleExpr() {
e, _ := eval.Parse("sqrt(A / pi)")
Display("e", e)
// Output:
// Display e (eval.call):
// e.fn = "sqrt"
// e.args[0].type = eval.binary
// e.args[0].value.op = 47
// e.args[0].value.x.type = eval.Var
// e.args[0].value.x.value = "A"
// e.args[0].value.y.type = eval.Var
// e.args[0].value.y.value = "pi"
}
func ExampleSlice() {
Display("slice", []*int{new(int), nil})
// Output:
// Display slice ([]*int):
// (*slice[0]) = 0
// slice[1] = nil
}
func ExampleNilInterface() {
var w io.Writer
Display("w", w)
// Output:
// Display w (<nil>):
// w = invalid
}
func ExamplePtrToInterface() {
var w io.Writer
Display("&w", &w)
// Output:
// Display &w (*io.Writer):
// (*&w) = nil
}
func ExampleStruct() {
Display("x", struct{ x interface{} }{3})
// Output:
// Display x (struct { x interface {} }):
// x.x.type = int
// x.x.value = 3
}
func ExampleInterface() {
var i interface{} = 3
Display("i", i)
// Output:
// Display i (int):
// i = 3
}
func ExamplePtrToInterface2() {
var i interface{} = 3
Display("&i", &i)
// Output:
// Display &i (*interface {}):
// (*&i).type = int
// (*&i).value = 3
}
func ExampleArray() {
Display("x", [1]interface{}{3})
// Output:
// Display x ([1]interface {}):
// x[0].type = int
// x[0].value = 3
}
func ExampleMovie() {
//!+movie
type Movie struct {
Title, Subtitle string
Year int
Color bool
Actor map[string]string
Oscars []string
Sequel *string
}
//!-movie
//!+strangelove
strangelove := Movie{
Title: "Dr. Strangelove",
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
Year: 1964,
Color: false,
Actor: map[string]string{
"Dr. Strangelove": "Peter Sellers",
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
"Pres. Merkin Muffley": "Peter Sellers",
"Gen. Buck Turgidson": "George C. Scott",
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
`Maj. T.J. "King" Kong`: "Slim Pickens",
},
Oscars: []string{
"Best Actor (Nomin.)",
"Best Adapted Screenplay (Nomin.)",
"Best Director (Nomin.)",
"Best Picture (Nomin.)",
},
}
//!-strangelove
Display("strangelove", strangelove)
// We don't use an Output: comment since displaying
// a map is nondeterministic.
/*
//!+output
Display strangelove (display.Movie):
strangelove.Title = "Dr. Strangelove"
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
strangelove.Year = 1964
strangelove.Color = false
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
strangelove.Oscars[0] = "Best Actor (Nomin.)"
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove.Oscars[2] = "Best Director (Nomin.)"
strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel = nil
//!-output
*/
}
// This test ensures that the program terminates without crashing.
func Test(t *testing.T) {
// Some other values (YMMV)
Display("os.Stderr", os.Stderr)
// Output:
// Display os.Stderr (*os.File):
// (*(*os.Stderr).file).fd = 2
// (*(*os.Stderr).file).name = "/dev/stderr"
// (*(*os.Stderr).file).nepipe = 0
var w io.Writer = os.Stderr
Display("&w", &w)
// Output:
// Display &w (*io.Writer):
// (*&w).type = *os.File
// (*(*(*&w).value).file).fd = 2
// (*(*(*&w).value).file).name = "/dev/stderr"
// (*(*(*&w).value).file).nepipe = 0
var locker sync.Locker = new(sync.Mutex)
Display("(&locker)", &locker)
// Output:
// Display (&locker) (*sync.Locker):
// (*(&locker)).type = *sync.Mutex
// (*(*(&locker)).value).state = 0
// (*(*(&locker)).value).sema = 0
Display("locker", locker)
// Output:
// Display locker (*sync.Mutex):
// (*locker).state = 0
// (*locker).sema = 0
// (*(&locker)) = nil
locker = nil
Display("(&locker)", &locker)
// Output:
// Display (&locker) (*sync.Locker):
// (*(&locker)) = nil
ips, _ := net.LookupHost("golang.org")
Display("ips", ips)
// Output:
// Display ips ([]string):
// ips[0] = "173.194.68.141"
// ips[1] = "2607:f8b0:400d:c06::8d"
// Even metarecursion! (YMMV)
Display("rV", reflect.ValueOf(os.Stderr))
// Output:
// Display rV (reflect.Value):
// (*rV.typ).size = 8
// (*rV.typ).ptrdata = 8
// (*rV.typ).hash = 871609668
// (*rV.typ)._ = 0
// ...
// a pointer that points to itself
type P *P
var p P
p = &p
if false {
Display("p", p)
// Output:
// Display p (display.P):
// ...stuck, no output...
}
// a map that contains itself
type M map[string]M
m := make(M)
m[""] = m
if false {
Display("m", m)
// Output:
// Display m (display.M):
// ...stuck, no output...
}
// a slice that contains itself
type S []S
s := make(S, 1)
s[0] = s
if false {
Display("s", s)
// Output:
// Display s (display.S):
// ...stuck, no output...
}
// a linked list that eats its own tail
type Cycle struct {
Value int
Tail *Cycle
}
var c Cycle
c = Cycle{42, &c}
if false {
Display("c", c)
// Output:
// Display c (display.Cycle):
// c.Value = 42
// (*c.Tail).Value = 42
// (*(*c.Tail).Tail).Value = 42
// ...ad infinitum...
}
}

44
ch12/format/format.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 332.
// Package format provides an Any function that can format any value.
//!+
package format
import (
"reflect"
"strconv"
)
// Any formats any value as a string.
func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
}
// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}
//!-

View File

@ -0,0 +1,24 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package format_test
import (
"fmt"
"testing"
"time"
"gopl.io/ch12/format"
)
func Test(t *testing.T) {
// The pointer values are just examples, and may vary from run to run.
//!+time
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x)) // "1"
fmt.Println(format.Any(d)) // "1"
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
//!-time
}

29
ch12/methods/methods.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 351.
// Package methods provides a function to print the methods of any value.
package methods
import (
"fmt"
"reflect"
"strings"
)
//!+print
// Print prints the method set of the value x.
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}
//!-print

View File

@ -0,0 +1,49 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package methods_test
import (
"strings"
"time"
"gopl.io/ch12/methods"
)
func ExamplePrintDuration() {
methods.Print(time.Hour)
// Output:
// type time.Duration
// func (time.Duration) Hours() float64
// func (time.Duration) Minutes() float64
// func (time.Duration) Nanoseconds() int64
// func (time.Duration) Seconds() float64
// func (time.Duration) String() string
}
func ExamplePrintReplacer() {
methods.Print(new(strings.Replacer))
// Output:
// type *strings.Replacer
// func (*strings.Replacer) Replace(string) string
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
}
/*
//!+output
methods.Print(time.Hour)
// Output:
// type time.Duration
// func (time.Duration) Hours() float64
// func (time.Duration) Minutes() float64
// func (time.Duration) Nanoseconds() int64
// func (time.Duration) Seconds() float64
// func (time.Duration) String() string
methods.Print(new(strings.Replacer))
// Output:
// type *strings.Replacer
// func (*strings.Replacer) Replace(string) string
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
//!-output
*/

90
ch12/params/params.go Normal file
View File

@ -0,0 +1,90 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 349.
// Package params provides a reflection-based parser for URL parameters.
package params
import (
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
)
//!+Unpack
// Unpack populates the fields of the struct pointed to by ptr
// from the HTTP request parameters in req.
func Unpack(req *http.Request, ptr interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
// Build map of fields keyed by effective name.
fields := make(map[string]reflect.Value)
v := reflect.ValueOf(ptr).Elem() // the struct variable
for i := 0; i < v.NumField(); i++ {
fieldInfo := v.Type().Field(i) // a reflect.StructField
tag := fieldInfo.Tag // a reflect.StructTag
name := tag.Get("http")
if name == "" {
name = strings.ToLower(fieldInfo.Name)
}
fields[name] = v.Field(i)
}
// Update struct field for each parameter in the request.
for name, values := range req.Form {
f := fields[name]
if !f.IsValid() {
continue // ignore unrecognized HTTP parameters
}
for _, value := range values {
if f.Kind() == reflect.Slice {
elem := reflect.New(f.Type().Elem()).Elem()
if err := populate(elem, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
f.Set(reflect.Append(f, elem))
} else {
if err := populate(f, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
}
}
}
return nil
}
//!-Unpack
//!+populate
func populate(v reflect.Value, value string) error {
switch v.Kind() {
case reflect.String:
v.SetString(value)
case reflect.Int:
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
v.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
v.SetBool(b)
default:
return fmt.Errorf("unsupported kind %s", v.Type())
}
return nil
}
//!-populate

60
ch12/search/main.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 348.
// Search is a demo of the params.Unpack function.
package main
import (
"fmt"
"log"
"net/http"
)
//!+
import "gopl.io/ch12/params"
// search implements the /search URL endpoint.
func search(resp http.ResponseWriter, req *http.Request) {
var data struct {
Labels []string `http:"l"`
MaxResults int `http:"max"`
Exact bool `http:"x"`
}
data.MaxResults = 10 // set default
if err := params.Unpack(req, &data); err != nil {
http.Error(resp, err.Error(), http.StatusBadRequest) // 400
return
}
// ...rest of handler...
fmt.Fprintf(resp, "Search: %+v\n", data)
}
//!-
func main() {
http.HandleFunc("/search", search)
log.Fatal(http.ListenAndServe(":12345", nil))
}
/*
//!+output
$ go build gopl.io/ch12/search
$ ./search &
$ ./fetch 'http://localhost:12345/search'
Search: {Labels:[] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100'
Search: {Labels:[golang programming] MaxResults:100 Exact:false}
$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:true}
$ ./fetch 'http://localhost:12345/search?q=hello&x=123'
x: strconv.ParseBool: parsing "123": invalid syntax
$ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
max: strconv.ParseInt: parsing "lots": invalid syntax
//!-output
*/

162
ch12/sexpr/decode.go Normal file
View File

@ -0,0 +1,162 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 344.
// Package sexpr provides a means for converting Go objects to and
// from S-expressions.
package sexpr
import (
"bytes"
"fmt"
"reflect"
"strconv"
"text/scanner"
)
//!+Unmarshal
// Unmarshal parses S-expression data and populates the variable
// whose address is in the non-nil pointer out.
func Unmarshal(data []byte, out interface{}) (err error) {
lex := &lexer{scan: scanner.Scanner{Mode: scanner.GoTokens}}
lex.scan.Init(bytes.NewReader(data))
lex.next() // get the first token
defer func() {
// NOTE: this is not an example of ideal error handling.
if x := recover(); x != nil {
err = fmt.Errorf("error at %s: %v", lex.scan.Position, x)
}
}()
read(lex, reflect.ValueOf(out).Elem())
return nil
}
//!-Unmarshal
//!+lexer
type lexer struct {
scan scanner.Scanner
token rune // the current token
}
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
func (lex *lexer) text() string { return lex.scan.TokenText() }
func (lex *lexer) consume(want rune) {
if lex.token != want { // NOTE: Not an example of good error handling.
panic(fmt.Sprintf("got %q, want %q", lex.text(), want))
}
lex.next()
}
//!-lexer
// The read function is a decoder for a small subset of well-formed
// S-expressions. For brevity of our example, it takes many dubious
// shortcuts.
//
// The parser assumes
// - that the S-expression input is well-formed; it does no error checking.
// - that the S-expression input corresponds to the type of the variable.
// - that all numbers in the input are non-negative decimal integers.
// - that all keys in ((key value) ...) struct syntax are unquoted symbols.
// - that the input does not contain dotted lists such as (1 2 . 3).
// - that the input does not contain Lisp reader macros such 'x and #'x.
//
// The reflection logic assumes
// - that v is always a variable of the appropriate type for the
// S-expression value. For example, v must not be a boolean,
// interface, channel, or function, and if v is an array, the input
// must have the correct number of elements.
// - that v in the top-level call to read has the zero value of its
// type and doesn't need clearing.
// - that if v is a numeric variable, it is a signed integer.
//!+read
func read(lex *lexer, v reflect.Value) {
switch lex.token {
case scanner.Ident:
// The only valid identifiers are
// "nil" and struct field names.
if lex.text() == "nil" {
v.Set(reflect.Zero(v.Type()))
lex.next()
return
}
case scanner.String:
s, _ := strconv.Unquote(lex.text()) // NOTE: ignoring errors
v.SetString(s)
lex.next()
return
case scanner.Int:
i, _ := strconv.Atoi(lex.text()) // NOTE: ignoring errors
v.SetInt(int64(i))
lex.next()
return
case '(':
lex.next()
readList(lex, v)
lex.next() // consume ')'
return
}
panic(fmt.Sprintf("unexpected token %q", lex.text()))
}
//!-read
//!+readlist
func readList(lex *lexer, v reflect.Value) {
switch v.Kind() {
case reflect.Array: // (item ...)
for i := 0; !endList(lex); i++ {
read(lex, v.Index(i))
}
case reflect.Slice: // (item ...)
for !endList(lex) {
item := reflect.New(v.Type().Elem()).Elem()
read(lex, item)
v.Set(reflect.Append(v, item))
}
case reflect.Struct: // ((name value) ...)
for !endList(lex) {
lex.consume('(')
if lex.token != scanner.Ident {
panic(fmt.Sprintf("got token %q, want field name", lex.text()))
}
name := lex.text()
lex.next()
read(lex, v.FieldByName(name))
lex.consume(')')
}
case reflect.Map: // ((key value) ...)
v.Set(reflect.MakeMap(v.Type()))
for !endList(lex) {
lex.consume('(')
key := reflect.New(v.Type().Key()).Elem()
read(lex, key)
value := reflect.New(v.Type().Elem()).Elem()
read(lex, value)
v.SetMapIndex(key, value)
lex.consume(')')
}
default:
panic(fmt.Sprintf("cannot decode list into %v", v.Type()))
}
}
func endList(lex *lexer) bool {
switch lex.token {
case scanner.EOF:
panic("end of file")
case ')':
return true
}
return false
}
//!-readlist

97
ch12/sexpr/encode.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 339.
package sexpr
import (
"bytes"
"fmt"
"reflect"
)
//!+Marshal
// Marshal encodes a Go value in S-expression form.
func Marshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer
if err := encode(&buf, reflect.ValueOf(v)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
//!-Marshal
// encode writes to buf an S-expression representation of v.
//!+encode
func encode(buf *bytes.Buffer, v reflect.Value) error {
switch v.Kind() {
case reflect.Invalid:
buf.WriteString("nil")
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
fmt.Fprintf(buf, "%d", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
fmt.Fprintf(buf, "%d", v.Uint())
case reflect.String:
fmt.Fprintf(buf, "%q", v.String())
case reflect.Ptr:
return encode(buf, v.Elem())
case reflect.Array, reflect.Slice: // (value ...)
buf.WriteByte('(')
for i := 0; i < v.Len(); i++ {
if i > 0 {
buf.WriteByte(' ')
}
if err := encode(buf, v.Index(i)); err != nil {
return err
}
}
buf.WriteByte(')')
case reflect.Struct: // ((name value) ...)
buf.WriteByte('(')
for i := 0; i < v.NumField(); i++ {
if i > 0 {
buf.WriteByte(' ')
}
fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name)
if err := encode(buf, v.Field(i)); err != nil {
return err
}
buf.WriteByte(')')
}
buf.WriteByte(')')
case reflect.Map: // ((key value) ...)
buf.WriteByte('(')
for i, key := range v.MapKeys() {
if i > 0 {
buf.WriteByte(' ')
}
buf.WriteByte('(')
if err := encode(buf, key); err != nil {
return err
}
buf.WriteByte(' ')
if err := encode(buf, v.MapIndex(key)); err != nil {
return err
}
buf.WriteByte(')')
}
buf.WriteByte(')')
default: // float, complex, bool, chan, func, interface
return fmt.Errorf("unsupported type: %s", v.Type())
}
return nil
}
//!-encode

183
ch12/sexpr/pretty.go Normal file
View File

@ -0,0 +1,183 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package sexpr
// This file implements the algorithm described in Derek C. Oppen's
// 1979 Stanford technical report, "Pretty Printing".
import (
"bytes"
"fmt"
"reflect"
)
func MarshalIndent(v interface{}) ([]byte, error) {
p := printer{width: margin}
if err := pretty(&p, reflect.ValueOf(v)); err != nil {
return nil, err
}
return p.Bytes(), nil
}
const margin = 80
type token struct {
kind rune // one of "s ()" (string, blank, start, end)
str string
size int
}
type printer struct {
tokens []*token // FIFO buffer
stack []*token // stack of open ' ' and '(' tokens
rtotal int // total number of spaces needed to print stream
bytes.Buffer
indents []int
width int // remaining space
}
func (p *printer) string(str string) {
tok := &token{kind: 's', str: str, size: len(str)}
if len(p.stack) == 0 {
p.print(tok)
} else {
p.tokens = append(p.tokens, tok)
p.rtotal += len(str)
}
}
func (p *printer) pop() (top *token) {
last := len(p.stack) - 1
top, p.stack = p.stack[last], p.stack[:last]
return
}
func (p *printer) begin() {
if len(p.stack) == 0 {
p.rtotal = 1
}
t := &token{kind: '(', size: -p.rtotal}
p.tokens = append(p.tokens, t)
p.stack = append(p.stack, t) // push
p.string("(")
}
func (p *printer) end() {
p.string(")")
p.tokens = append(p.tokens, &token{kind: ')'})
x := p.pop()
x.size += p.rtotal
if x.kind == ' ' {
p.pop().size += p.rtotal
}
if len(p.stack) == 0 {
for _, tok := range p.tokens {
p.print(tok)
}
p.tokens = nil
}
}
func (p *printer) space() {
last := len(p.stack) - 1
x := p.stack[last]
if x.kind == ' ' {
x.size += p.rtotal
p.stack = p.stack[:last] // pop
}
t := &token{kind: ' ', size: -p.rtotal}
p.tokens = append(p.tokens, t)
p.stack = append(p.stack, t)
p.rtotal++
}
func (p *printer) print(t *token) {
switch t.kind {
case 's':
p.WriteString(t.str)
p.width -= len(t.str)
case '(':
p.indents = append(p.indents, p.width)
case ')':
p.indents = p.indents[:len(p.indents)-1] // pop
case ' ':
if t.size > p.width {
p.width = p.indents[len(p.indents)-1] - 1
fmt.Fprintf(&p.Buffer, "\n%*s", margin-p.width, "")
} else {
p.WriteByte(' ')
p.width--
}
}
}
func (p *printer) stringf(format string, args ...interface{}) {
p.string(fmt.Sprintf(format, args...))
}
func pretty(p *printer, v reflect.Value) error {
switch v.Kind() {
case reflect.Invalid:
p.string("nil")
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
p.stringf("%d", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p.stringf("%d", v.Uint())
case reflect.String:
p.stringf("%q", v.String())
case reflect.Array, reflect.Slice: // (value ...)
p.begin()
for i := 0; i < v.Len(); i++ {
if i > 0 {
p.space()
}
if err := pretty(p, v.Index(i)); err != nil {
return err
}
}
p.end()
case reflect.Struct: // ((name value ...)
p.begin()
for i := 0; i < v.NumField(); i++ {
if i > 0 {
p.space()
}
p.begin()
p.string(v.Type().Field(i).Name)
p.space()
if err := pretty(p, v.Field(i)); err != nil {
return err
}
p.end()
}
p.end()
case reflect.Map: // ((key value ...)
p.begin()
for i, key := range v.MapKeys() {
if i > 0 {
p.space()
}
p.begin()
if err := pretty(p, key); err != nil {
return err
}
p.space()
if err := pretty(p, v.MapIndex(key)); err != nil {
return err
}
p.end()
}
p.end()
case reflect.Ptr:
return pretty(p, v.Elem())
default: // float, complex, bool, chan, func, interface
return fmt.Errorf("unsupported type: %s", v.Type())
}
return nil
}

74
ch12/sexpr/sexpr_test.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package sexpr
import (
"reflect"
"testing"
)
// Test verifies that encoding and decoding a complex data value
// produces an equal result.
//
// The test does not make direct assertions about the encoded output
// because the output depends on map iteration order, which is
// nondeterministic. The output of the t.Log statements can be
// inspected by running the test with the -v flag:
//
// $ go test -v gopl.io/ch12/sexpr
//
func Test(t *testing.T) {
type Movie struct {
Title, Subtitle string
Year int
Actor map[string]string
Oscars []string
Sequel *string
}
strangelove := Movie{
Title: "Dr. Strangelove",
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
Year: 1964,
Actor: map[string]string{
"Dr. Strangelove": "Peter Sellers",
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
"Pres. Merkin Muffley": "Peter Sellers",
"Gen. Buck Turgidson": "George C. Scott",
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
`Maj. T.J. "King" Kong`: "Slim Pickens",
},
Oscars: []string{
"Best Actor (Nomin.)",
"Best Adapted Screenplay (Nomin.)",
"Best Director (Nomin.)",
"Best Picture (Nomin.)",
},
}
// Encode it
data, err := Marshal(strangelove)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
t.Logf("Marshal() = %s\n", data)
// Decode it
var movie Movie
if err := Unmarshal(data, &movie); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
t.Logf("Unmarshal() = %+v\n", movie)
// Check equality.
if !reflect.DeepEqual(movie, strangelove) {
t.Fatal("not equal")
}
// Pretty-print it:
data, err = MarshalIndent(strangelove)
if err != nil {
t.Fatal(err)
}
t.Logf("MarshalIdent() = %s\n", data)
}

View File

@ -1,4 +1,7 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 362.
//!+
/* This file is gopl.io/ch13/bzip/bzip2.c, */

View File

@ -1,4 +1,7 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 362.
//!+
// Package bzip provides a writer that uses bzip2 compression (bzip.org).

View File

@ -1,4 +1,5 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package bzip_test

View File

@ -1,4 +1,8 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 365.
//!+
// Bzipper reads input, bzip2-compresses it, and writes it out.

127
ch13/equal/equal.go Normal file
View File

@ -0,0 +1,127 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 359.
// Package equal provides a deep equivalence relation for arbitrary values.
package equal
import (
"reflect"
"unsafe"
)
//!+
func equal(x, y reflect.Value, seen map[comparison]bool) bool {
if !x.IsValid() || !y.IsValid() {
return x.IsValid() == y.IsValid()
}
if x.Type() != y.Type() {
return false
}
// ...cycle check omitted (shown later)...
//!-
//!+cyclecheck
// cycle check
if x.CanAddr() && y.CanAddr() {
xptr := unsafe.Pointer(x.UnsafeAddr())
yptr := unsafe.Pointer(y.UnsafeAddr())
if xptr == yptr {
return true // identical references
}
c := comparison{xptr, yptr, x.Type()}
if seen[c] {
return true // already seen
}
seen[c] = true
}
//!-cyclecheck
//!+
switch x.Kind() {
case reflect.Bool:
return x.Bool() == y.Bool()
case reflect.String:
return x.String() == y.String()
// ...numeric cases omitted for brevity...
//!-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
return x.Int() == y.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64, reflect.Uintptr:
return x.Uint() == y.Uint()
case reflect.Float32, reflect.Float64:
return x.Float() == y.Float()
case reflect.Complex64, reflect.Complex128:
return x.Complex() == y.Complex()
//!+
case reflect.Chan, reflect.UnsafePointer, reflect.Func:
return x.Pointer() == y.Pointer()
case reflect.Ptr, reflect.Interface:
return equal(x.Elem(), y.Elem(), seen)
case reflect.Array, reflect.Slice:
if x.Len() != y.Len() {
return false
}
for i := 0; i < x.Len(); i++ {
if !equal(x.Index(i), y.Index(i), seen) {
return false
}
}
return true
// ...struct and map cases omitted for brevity...
//!-
case reflect.Struct:
for i, n := 0, x.NumField(); i < n; i++ {
if !equal(x.Field(i), y.Field(i), seen) {
return false
}
}
return true
case reflect.Map:
if x.Len() != y.Len() {
return false
}
for _, k := range x.MapKeys() {
if !equal(x.MapIndex(k), y.MapIndex(k), seen) {
return false
}
}
return true
//!+
}
panic("unreachable")
}
//!-
//!+comparison
// Equal reports whether x and y are deeply equal.
//!-comparison
//
// Map keys are always compared with ==, not deeply.
// (This matters for keys containing pointers or interfaces.)
//!+comparison
func Equal(x, y interface{}) bool {
seen := make(map[comparison]bool)
return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen)
}
type comparison struct {
x, y unsafe.Pointer
t reflect.Type
}
//!-comparison

133
ch13/equal/equal_test.go Normal file
View File

@ -0,0 +1,133 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package equal
import (
"bytes"
"fmt"
"testing"
)
func TestEqual(t *testing.T) {
one, oneAgain, two := 1, 1, 2
type CyclePtr *CyclePtr
var cyclePtr1, cyclePtr2 CyclePtr
cyclePtr1 = &cyclePtr1
cyclePtr2 = &cyclePtr2
type CycleSlice []CycleSlice
var cycleSlice CycleSlice
cycleSlice = append(cycleSlice, cycleSlice)
ch1, ch2 := make(chan int), make(chan int)
var ch1ro <-chan int = ch1
type mystring string
var iface1, iface1Again, iface2 interface{} = &one, &oneAgain, &two
for _, test := range []struct {
x, y interface{}
want bool
}{
// basic types
{1, 1, true},
{1, 2, false}, // different values
{1, 1.0, false}, // different types
{"foo", "foo", true},
{"foo", "bar", false},
{mystring("foo"), "foo", false}, // different types
// slices
{[]string{"foo"}, []string{"foo"}, true},
{[]string{"foo"}, []string{"bar"}, false},
{[]string{}, []string(nil), true},
// slice cycles
{cycleSlice, cycleSlice, true},
// maps
{
map[string][]int{"foo": {1, 2, 3}},
map[string][]int{"foo": {1, 2, 3}},
true,
},
{
map[string][]int{"foo": {1, 2, 3}},
map[string][]int{"foo": {1, 2, 3, 4}},
false,
},
{
map[string][]int{},
map[string][]int(nil),
true,
},
// pointers
{&one, &one, true},
{&one, &two, false},
{&one, &oneAgain, true},
{new(bytes.Buffer), new(bytes.Buffer), true},
// pointer cycles
{cyclePtr1, cyclePtr1, true},
{cyclePtr2, cyclePtr2, true},
{cyclePtr1, cyclePtr2, true}, // they're deeply equal
// functions
{(func())(nil), (func())(nil), true},
{(func())(nil), func() {}, false},
{func() {}, func() {}, false},
// arrays
{[...]int{1, 2, 3}, [...]int{1, 2, 3}, true},
{[...]int{1, 2, 3}, [...]int{1, 2, 4}, false},
// channels
{ch1, ch1, true},
{ch1, ch2, false},
{ch1ro, ch1, false}, // NOTE: not equal
// interfaces
{&iface1, &iface1, true},
{&iface1, &iface2, false},
{&iface1Again, &iface1, true},
} {
if Equal(test.x, test.y) != test.want {
t.Errorf("Equal(%v, %v) = %t",
test.x, test.y, !test.want)
}
}
}
func ExampleEqual() {
//!+
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true"
fmt.Println(Equal([]string{"foo"}, []string{"bar"})) // "false"
fmt.Println(Equal([]string(nil), []string{})) // "true"
fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
//!-
// Output:
// true
// false
// true
// true
}
func ExampleEqualCycle() {
//!+cycle
// Circular linked lists a -> b -> a and c -> c.
type link struct {
value string
tail *link
}
a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
a.tail, b.tail, c.tail = b, a, c
fmt.Println(Equal(a, a)) // "true"
fmt.Println(Equal(b, b)) // "true"
fmt.Println(Equal(c, c)) // "true"
fmt.Println(Equal(a, b)) // "false"
fmt.Println(Equal(a, c)) // "false"
//!-cycle
// Output:
// true
// true
// true
// false
// false
}

38
ch13/unsafeptr/main.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 357.
// Package unsafeptr demonstrates basic use of unsafe.Pointer.
package main
import (
"fmt"
"unsafe"
)
func main() {
//!+main
var x struct {
a bool
b int16
c []int
}
// equivalent to pb := &x.b
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
fmt.Println(x.b) // "42"
//!-main
}
/*
//!+wrong
// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
//!-wrong
*/

22
ch2/boiling/main.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 29.
//!+
// Boiling prints the boiling point of water.
package main
import "fmt"
const boilingF = 212.0
func main() {
var f = boilingF
var c = (f - 32) * 5 / 9
fmt.Printf("boiling point = %g°F or %g°C\n", f, c)
// Output:
// boiling point = 212°F or 100°C
}
//!-

32
ch2/cf/main.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 43.
//!+
// Cf converts its numeric argument to Celsius and Fahrenheit.
package main
import (
"fmt"
"os"
"strconv"
"gopl.io/ch2/tempconv"
)
func main() {
for _, arg := range os.Args[1:] {
t, err := strconv.ParseFloat(arg, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "cf: %v\n", err)
os.Exit(1)
}
f := tempconv.Fahrenheit(t)
c := tempconv.Celsius(t)
fmt.Printf("%s = %s, %s = %s\n",
f, tempconv.FToC(f), c, tempconv.CToF(c))
}
}
//!-

27
ch2/echo4/main.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 33.
//!+
// Echo4 prints its command-line arguments.
package main
import (
"flag"
"fmt"
"strings"
)
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")
func main() {
flag.Parse()
fmt.Print(strings.Join(flag.Args(), *sep))
if !*n {
fmt.Println()
}
}
//!-

22
ch2/ftoc/main.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 29.
//!+
// Ftoc prints two Fahrenheit-to-Celsius conversions.
package main
import "fmt"
func main() {
const freezingF, boilingF = 32.0, 212.0
fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C"
fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF)) // "212°F = 100°C"
}
func fToC(f float64) float64 {
return (f - 32) * 5 / 9
}
//!-

31
ch2/popcount/main.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 45.
// (Package doc comment intentionally malformed to demonstrate golint.)
//!+
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
//!-

View File

@ -0,0 +1,83 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package popcount_test
import (
"testing"
"gopl.io/ch2/popcount"
)
// -- Alternative implementations --
func BitCount(x uint64) int {
// Hacker's Delight, Figure 5-2.
x = x - ((x >> 1) & 0x5555555555555555)
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333)
x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f
x = x + (x >> 8)
x = x + (x >> 16)
x = x + (x >> 32)
return int(x & 0x7f)
}
func PopCountByClearing(x uint64) int {
n := 0
for x != 0 {
x = x & (x - 1) // clear rightmost non-zero bit
n++
}
return n
}
func PopCountByShifting(x uint64) int {
n := 0
for i := uint(0); i < 64; i++ {
if x&(1<<i) != 0 {
n++
}
}
return n
}
// -- Benchmarks --
func BenchmarkPopCount(b *testing.B) {
for i := 0; i < b.N; i++ {
popcount.PopCount(0x1234567890ABCDEF)
}
}
func BenchmarkBitCount(b *testing.B) {
for i := 0; i < b.N; i++ {
BitCount(0x1234567890ABCDEF)
}
}
func BenchmarkPopCountByClearing(b *testing.B) {
for i := 0; i < b.N; i++ {
PopCountByClearing(0x1234567890ABCDEF)
}
}
func BenchmarkPopCountByShifting(b *testing.B) {
for i := 0; i < b.N; i++ {
PopCountByShifting(0x1234567890ABCDEF)
}
}
// 2.67GHz Xeon
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// BenchmarkPopCount-4 200000000 6.30 ns/op
// BenchmarkBitCount-4 300000000 4.15 ns/op
// BenchmarkPopCountByClearing-4 30000000 45.2 ns/op
// BenchmarkPopCountByShifting-4 10000000 153 ns/op
//
// 2.5GHz Intel Core i5
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// testing: warning: no tests to run
// BenchmarkPopCount-4 200000000 7.52 ns/op
// BenchmarkBitCount-4 500000000 3.36 ns/op
// BenchmarkPopCountByClearing-4 50000000 34.3 ns/op
// BenchmarkPopCountByShifting-4 20000000 108 ns/op

16
ch2/tempconv/conv.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 41.
//!+
package tempconv
// CToF converts a Celsius temperature to Fahrenheit.
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
// FToC converts a Fahrenheit temperature to Celsius.
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
//!-

23
ch2/tempconv/tempconv.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//!+
// Package tempconv performs Celsius and Fahrenheit conversions.
package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
//!-

27
ch2/tempconv0/celsius.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 39.
//!+
// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
//!-
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }

View File

@ -0,0 +1,45 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package tempconv
import "fmt"
func Example1() {
{
//!+arith
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
//!-arith
}
/*
//!+arith
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
//!-arith
*/
// Output:
// 100
// 180
}
func Example2() {
//!+printf
c := FToC(212.0)
fmt.Println(c.String()) // "100°C"
fmt.Printf("%v\n", c) // "100°C"; no need to call String explicitly
fmt.Printf("%s\n", c) // "100°C"
fmt.Println(c) // "100°C"
fmt.Printf("%g\n", c) // "100"; does not call String
fmt.Println(float64(c)) // "100"; does not call String
//!-printf
// Output:
// 100°C
// 100°C
// 100°C
// 100°C
// 100
// 100
}

44
ch3/basename1/main.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 72.
// Basename1 reads file names from stdin and prints the base name of each one.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
fmt.Println(basename(input.Text()))
}
// NOTE: ignoring potential errors from input.Err()
}
//!+
// basename removes directory components and a .suffix.
// e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c
func basename(s string) string {
// Discard last '/' and everything before.
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' {
s = s[i+1:]
break
}
}
// Preserve everything before last '.'.
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '.' {
s = s[:i]
break
}
}
return s
}
//!-

36
ch3/basename2/main.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 72.
// Basename2 reads file names from stdin and prints the base name of each one.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
fmt.Println(basename(input.Text()))
}
// NOTE: ignoring potential errors from input.Err()
}
// basename removes directory components and a trailing .suffix.
// e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c
//!+
func basename(s string) string {
slash := strings.LastIndex(s, "/") // -1 if "/" not found
s = s[slash+1:]
if dot := strings.LastIndex(s, "."); dot >= 0 {
s = s[:dot]
}
return s
}
//!-

40
ch3/comma/main.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 73.
// Comma prints its argument numbers with a comma at each power of 1000.
//
// Example:
// $ go build gopl.io/ch3/comma
// $ ./comma 1 12 123 1234 1234567890
// 1
// 12
// 123
// 1,234
// 1,234,567,890
//
package main
import (
"fmt"
"os"
)
func main() {
for i := 1; i < len(os.Args); i++ {
fmt.Printf(" %s\n", comma(os.Args[i]))
}
}
//!+
// comma inserts commas in a non-negative decimal integer string.
func comma(s string) string {
n := len(s)
if n <= 3 {
return s
}
return comma(s[:n-3]) + "," + s[n-3:]
}
//!-

84
ch3/mandelbrot/main.go Normal file
View File

@ -0,0 +1,84 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 61.
//!+
// Mandelbrot emits a PNG image of the Mandelbrot fractal.
package main
import (
"image"
"image/color"
"image/png"
"math/cmplx"
"os"
)
func main() {
const (
xmin, ymin, xmax, ymax = -2, -2, +2, +2
width, height = 1024, 1024
)
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
z := complex(x, y)
// Image point (px, py) represents complex value z.
img.Set(px, py, mandelbrot(z))
}
}
png.Encode(os.Stdout, img) // NOTE: ignoring errors
}
func mandelbrot(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex128
for n := uint8(0); n < iterations; n++ {
v = v*v + z
if cmplx.Abs(v) > 2 {
return color.Gray{255 - contrast*n}
}
}
return color.Black
}
//!-
// Some other interesting functions:
func acos(z complex128) color.Color {
v := cmplx.Acos(z)
blue := uint8(real(v)*128) + 127
red := uint8(imag(v)*128) + 127
return color.YCbCr{192, blue, red}
}
func sqrt(z complex128) color.Color {
v := cmplx.Sqrt(z)
blue := uint8(real(v)*128) + 127
red := uint8(imag(v)*128) + 127
return color.YCbCr{128, blue, red}
}
// f(x) = x^4 - 1
//
// z' = z - f(z)/f'(z)
// = z - (z^4 - 1) / (4 * z^3)
// = z - (z - 1/z^3) / 4
func newton(z complex128) color.Color {
const iterations = 37
const contrast = 7
for i := uint8(0); i < iterations; i++ {
z -= (z - 1/(z*z*z)) / 4
if cmplx.Abs(z*z*z*z-1) < 1e-6 {
return color.Gray{255 - contrast*i}
}
}
return color.Black
}

30
ch3/netflag/netflag.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 77.
// Netflag demonstrates an integer type used as a bit field.
package main
import (
"fmt"
. "net"
)
//!+
func IsUp(v Flags) bool { return v&FlagUp == FlagUp }
func TurnDown(v *Flags) { *v &^= FlagUp }
func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 }
func main() {
var v Flags = FlagMulticast | FlagUp
fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
TurnDown(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
SetBroadcast(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false"
fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
}
//!-

33
ch3/printints/main.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 74.
// Printints demonstrates the use of bytes.Buffer to format a string.
package main
import (
"bytes"
"fmt"
)
//!+
// intsToString is like fmt.Sprintf(values) but adds commas.
func intsToString(values []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i, v := range values {
if i > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%d", v)
}
buf.WriteByte(']')
return buf.String()
}
func main() {
fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]"
}
//!-

62
ch3/surface/main.go Normal file
View File

@ -0,0 +1,62 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 58.
//!+
// Surface computes an SVG rendering of a 3-D surface function.
package main
import (
"fmt"
"math"
)
const (
width, height = 600, 320 // canvas size in pixels
cells = 100 // number of grid cells
xyrange = 30.0 // axis ranges (-xyrange..+xyrange)
xyscale = width / 2 / xyrange // pixels per x or y unit
zscale = height * 0.4 // pixels per z unit
angle = math.Pi / 6 // angle of x, y axes (=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)
func main() {
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='stroke: grey; fill: white; stroke-width: 0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
ax, ay, bx, by, cx, cy, dx, dy)
}
}
fmt.Println("</svg>")
}
func corner(i, j int) (float64, float64) {
// Find point (x,y) at corner of cell (i,j).
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
// Compute surface height z.
z := f(x, y)
// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {
r := math.Hypot(x, y) // distance from (0,0)
return math.Sin(r) / r
}
//!-

79
ch4/append/main.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 88.
// Append illustrates the behavior of the built-in append function.
package main
import "fmt"
func appendslice(x []int, y ...int) []int {
var z []int
zlen := len(x) + len(y)
if zlen <= cap(x) {
// There is room to expand the slice.
z = x[:zlen]
} else {
// There is insufficient space.
// Grow by doubling, for amortized linear complexity.
zcap := zlen
if zcap < 2*len(x) {
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x)
}
copy(z[len(x):], y)
return z
}
//!+append
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
// There is room to grow. Extend the slice.
z = x[:zlen]
} else {
// There is insufficient space. Allocate a new array.
// Grow by doubling, for amortized linear complexity.
zcap := zlen
if zcap < 2*len(x) {
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x) // a built-in function; see text
}
z[len(x)] = y
return z
}
//!-append
//!+growth
func main() {
var x, y []int
for i := 0; i < 10; i++ {
y = appendInt(x, i)
fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
x = y
}
}
//!-growth
/*
//!+output
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
//!-output
*/

30
ch4/autoescape/main.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 117.
// Autoescape demonstrates automatic HTML escaping in html/template.
package main
import (
"html/template"
"log"
"os"
)
//!+
func main() {
const templ = `<p>A: {{.A}}</p><p>B: {{.B}}</p>`
t := template.Must(template.New("escape").Parse(templ))
var data struct {
A string // untrusted plain text
B template.HTML // trusted HTML
}
data.A = "<b>Hello!</b>"
data.B = "<b>Hello!</b>"
if err := t.Execute(os.Stdout, data); err != nil {
log.Fatal(err)
}
}
//!-

56
ch4/charcount/main.go Normal file
View File

@ -0,0 +1,56 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 97.
//!+
// Charcount computes counts of Unicode characters.
package main
import (
"bufio"
"fmt"
"io"
"os"
"unicode"
"unicode/utf8"
)
func main() {
counts := make(map[rune]int) // counts of Unicode characters
var utflen [utf8.UTFMax + 1]int // count of lengths of UTF-8 encodings
invalid := 0 // count of invalid UTF-8 characters
in := bufio.NewReader(os.Stdin)
for {
r, n, err := in.ReadRune() // returns rune, nbytes, error
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "charcount: %v\n", err)
os.Exit(1)
}
if r == unicode.ReplacementChar && n == 1 {
invalid++
continue
}
counts[r]++
utflen[n]++
}
fmt.Printf("rune\tcount\n")
for c, n := range counts {
fmt.Printf("%q\t%d\n", c, n)
}
fmt.Print("\nlen\tcount\n")
for i, n := range utflen {
if i > 0 {
fmt.Printf("%d\t%d\n", i, n)
}
}
if invalid > 0 {
fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
}
}
//!-

33
ch4/dedup/main.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 96.
// Dedup prints only one instance of each line; duplicates are removed.
package main
import (
"bufio"
"fmt"
"os"
)
//!+
func main() {
seen := make(map[string]bool) // a set of strings
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
line := input.Text()
if !seen[line] {
seen[line] = true
fmt.Println(line)
}
}
if err := input.Err(); err != nil {
fmt.Fprintf(os.Stderr, "dedup: %v\n", err)
os.Exit(1)
}
}
//!-

46
ch4/embed/main.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 106.
// Embed demonstrates basic struct embedding.
package main
import "fmt"
type Point struct{ X, Y int }
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
func main() {
var w Wheel
//!+
w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
Point: Point{X: 8, Y: 8},
Radius: 5,
},
Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}
fmt.Printf("%#v\n", w)
// Output:
// Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20}
w.X = 42
fmt.Printf("%#v\n", w)
// Output:
// Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}
//!-
}

35
ch4/github/github.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 110.
//!+
// Package github provides a Go API for the GitHub issue tracker.
// See https://developer.github.com/v3/search/#search-issues.
package github
import "time"
const IssuesURL = "https://api.github.com/search/issues"
type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}
type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string // in Markdown format
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
//!-

53
ch4/github/search.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//!+
package github
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// SearchIssues queries the GitHub issue tracker.
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " "))
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil {
return nil, err
}
//!-
// For long-term stability, instead of http.Get, use the
// variant below which adds an HTTP request header indicating
// that only version 3 of the GitHub API is acceptable.
//
// req, err := http.NewRequest("GET", IssuesURL+"?q="+q, nil)
// if err != nil {
// return nil, err
// }
// req.Header.Set(
// "Accept", "application/vnd.github.v3.text-match+json")
// resp, err := http.DefaultClient.Do(req)
//!+
// We must close resp.Body on all execution paths.
// (Chapter 5 presents 'defer', which makes this simpler.)
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}
//!-

43
ch4/graph/main.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 99.
// Graph shows how to use a map of maps to represent a directed graph.
package main
import "fmt"
//!+
var graph = make(map[string]map[string]bool)
func addEdge(from, to string) {
edges := graph[from]
if edges == nil {
edges = make(map[string]bool)
graph[from] = edges
}
edges[to] = true
}
func hasEdge(from, to string) bool {
return graph[from][to]
}
//!-
func main() {
addEdge("a", "b")
addEdge("c", "d")
addEdge("a", "d")
addEdge("d", "a")
fmt.Println(hasEdge("a", "b"))
fmt.Println(hasEdge("c", "d"))
fmt.Println(hasEdge("a", "d"))
fmt.Println(hasEdge("d", "a"))
fmt.Println(hasEdge("x", "b"))
fmt.Println(hasEdge("c", "d"))
fmt.Println(hasEdge("x", "d"))
fmt.Println(hasEdge("d", "x"))
}

52
ch4/issues/main.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 112.
//!+
// Issues prints a table of GitHub issues matching the search terms.
package main
import (
"fmt"
"log"
"os"
"gopl.io/ch4/github"
)
//!+
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
}
//!-
/*
//!+textoutput
$ go build gopl.io/ch4/issues
$ ./issues repo:golang/go is:open json decoder
13 issues:
#5680 eaigner encoding/json: set key converter on en/decoder
#6050 gopherbot encoding/json: provide tokenizer
#8658 gopherbot encoding/json: use bufio
#8462 kortschak encoding/json: UnmarshalText confuses json.Unmarshal
#5901 rsc encoding/json: allow override type marshaling
#9812 klauspost encoding/json: string tag not symmetric
#7872 extempora encoding/json: Encoder internally buffers full output
#9650 cespare encoding/json: Decoding gives errPhase when unmarshalin
#6716 gopherbot encoding/json: include field name in unmarshal error me
#6901 lukescott encoding/json, encoding/xml: option to treat unknown fi
#6384 joeshaw encoding/json: encode precise floating point integers u
#6647 btracey x/tools/cmd/godoc: display type kind of each named type
#4237 gjemiller encoding/base64: URLEncoding padding is optional
//!-textoutput
*/

52
ch4/issueshtml/main.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 115.
// Issueshtml prints an HTML table of issues matching the search terms.
package main
import (
"log"
"os"
"gopl.io/ch4/github"
)
//!+template
import "html/template"
var issueList = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} issues</h1>
<table>
<tr style='text-align: left'>
<th>#</th>
<th>State</th>
<th>User</th>
<th>Title</th>
</tr>
{{range .Items}}
<tr>
<td><a href='{{.HTMLURL}}'>{{.Number}}</td>
<td>{{.State}}</td>
<td><a href='{{.User.HTMLURL}}'>{{.User.Login}}</a></td>
<td><a href='{{.HTMLURL}}'>{{.Title}}</a></td>
</tr>
{{end}}
</table>
`))
//!-template
//!+
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
if err := issueList.Execute(os.Stdout, result); err != nil {
log.Fatal(err)
}
}
//!-

89
ch4/issuesreport/main.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 113.
// Issuesreport prints a report of issues matching the search terms.
package main
import (
"log"
"os"
"text/template"
"time"
"gopl.io/ch4/github"
)
//!+template
const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{end}}`
//!-template
//!+daysAgo
func daysAgo(t time.Time) int {
return int(time.Since(t).Hours() / 24)
}
//!-daysAgo
//!+exec
var report = template.Must(template.New("issuelist").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ))
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
if err := report.Execute(os.Stdout, result); err != nil {
log.Fatal(err)
}
}
//!-exec
func noMust() {
//!+parse
report, err := template.New("report").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ)
if err != nil {
log.Fatal(err)
}
//!-parse
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
if err := report.Execute(os.Stdout, result); err != nil {
log.Fatal(err)
}
}
/*
//!+output
$ go build gopl.io/ch4/issuesreport
$ ./issuesreport repo:golang/go is:open json decoder
13 issues:
----------------------------------------
Number: 5680
User: eaigner
Title: encoding/json: set key converter on en/decoder
Age: 750 days
----------------------------------------
Number: 6050
User: gopherbot
Title: encoding/json: provide tokenizer
Age: 695 days
----------------------------------------
...
//!-output
*/

104
ch4/movie/main.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 108.
// Movie prints Movies as JSON.
package main
import (
"encoding/json"
"fmt"
"log"
)
//!+
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
{Title: "Cool Hand Luke", Year: 1967, Color: true,
Actors: []string{"Paul Newman"}},
{Title: "Bullitt", Year: 1968, Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
// ...
}
//!-
func main() {
{
//!+Marshal
data, err := json.Marshal(movies)
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
//!-Marshal
}
{
//!+MarshalIndent
data, err := json.MarshalIndent(movies, "", " ")
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
//!-MarshalIndent
//!+Unmarshal
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
//!-Unmarshal
}
}
/*
//!+output
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]
//!-output
*/
/*
//!+indented
[
{
"Title": "Casablanca",
"released": 1942,
"Actors": [
"Humphrey Bogart",
"Ingrid Bergman"
]
},
{
"Title": "Cool Hand Luke",
"released": 1967,
"color": true,
"Actors": [
"Paul Newman"
]
},
{
"Title": "Bullitt",
"released": 1968,
"color": true,
"Actors": [
"Steve McQueen",
"Jacqueline Bisset"
]
}
]
//!-indented
*/

47
ch4/nonempty/main.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 91.
//!+nonempty
// Nonempty is an example of an in-place slice algorithm.
package main
import "fmt"
// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
//!-nonempty
func main() {
//!+main
data := []string{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
//!-main
}
//!+alt
func nonempty2(strings []string) []string {
out := strings[:0] // zero-length slice of original
for _, s := range strings {
if s != "" {
out = append(out, s)
}
}
return out
}
//!-alt

60
ch4/rev/main.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 86.
// Rev reverses a slice.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
//!+array
a := [...]int{0, 1, 2, 3, 4, 5}
reverse(a[:])
fmt.Println(a) // "[5 4 3 2 1 0]"
//!-array
//!+slice
s := []int{0, 1, 2, 3, 4, 5}
// Rotate s left by two positions.
reverse(s[:2])
reverse(s[2:])
reverse(s)
fmt.Println(s) // "[2 3 4 5 0 1]"
//!-slice
// Interactive test of reverse.
input := bufio.NewScanner(os.Stdin)
outer:
for input.Scan() {
var ints []int
for _, s := range strings.Fields(input.Text()) {
x, err := strconv.ParseInt(s, 10, 64)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue outer
}
ints = append(ints, int(x))
}
reverse(ints)
fmt.Printf("%v\n", ints)
}
// NOTE: ignoring potential errors from input.Err()
}
//!+rev
// reverse reverses a slice of ints in place.
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
//!-rev

25
ch4/sha256/main.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 83.
// The sha256 command computes the SHA256 hash (an array) of a string.
package main
import "fmt"
//!+
import "crypto/sha256"
func main() {
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
// Output:
// 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
// 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
// false
// [32]uint8
}
//!-

50
ch4/treesort/sort.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 101.
// Package treesort provides insertion sort using an unbalanced binary tree.
package treesort
//!+
type tree struct {
value int
left, right *tree
}
// Sort sorts values in place.
func Sort(values []int) {
var root *tree
for _, v := range values {
root = add(root, v)
}
appendValues(values[:0], root)
}
// appendValues appends the elements of t to values in order
// and returns the resulting slice.
func appendValues(values []int, t *tree) []int {
if t != nil {
values = appendValues(values, t.left)
values = append(values, t.value)
values = appendValues(values, t.right)
}
return values
}
func add(t *tree, value int) *tree {
if t == nil {
// Equivalent to return &tree{value: value}.
t = new(tree)
t.value = value
return t
}
if value < t.value {
t.left = add(t.left, value)
} else {
t.right = add(t.right, value)
}
return t
}
//!-

23
ch4/treesort/sort_test.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package treesort_test
import (
"math/rand"
"sort"
"testing"
"gopl.io/ch4/treesort"
)
func TestSort(t *testing.T) {
data := make([]int, 50)
for i := range data {
data[i] = rand.Int() % 50
}
treesort.Sort(data)
if !sort.IntsAreSorted(data) {
t.Errorf("not sorted: %s", data)
}
}

48
ch5/defer1/defer.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 150.
// Defer1 demonstrates a deferred call being invoked during a panic.
package main
import "fmt"
//!+f
func main() {
f(3)
}
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
defer fmt.Printf("defer %d\n", x)
f(x - 1)
}
//!-f
/*
//!+stdout
f(3)
f(2)
f(1)
defer 1
defer 2
defer 3
//!-stdout
//!+stderr
panic: runtime error: integer divide by zero
main.f(0)
src/gopl.io/ch5/defer1/defer.go:14
main.f(1)
src/gopl.io/ch5/defer1/defer.go:16
main.f(2)
src/gopl.io/ch5/defer1/defer.go:16
main.f(3)
src/gopl.io/ch5/defer1/defer.go:16
main.main()
src/gopl.io/ch5/defer1/defer.go:10
//!-stderr
*/

51
ch5/defer2/defer.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 151.
// Defer2 demonstrates a deferred call to runtime.Stack during a panic.
package main
import (
"fmt"
"os"
"runtime"
)
//!+
func main() {
defer printStack()
f(3)
}
func printStack() {
var buf [4096]byte
n := runtime.Stack(buf[:], false)
os.Stdout.Write(buf[:n])
}
//!-
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
defer fmt.Printf("defer %d\n", x)
f(x - 1)
}
/*
//!+printstack
goroutine 1 [running]:
main.printStack()
src/gopl.io/ch5/defer2/defer.go:20
main.f(0)
src/gopl.io/ch5/defer2/defer.go:27
main.f(1)
src/gopl.io/ch5/defer2/defer.go:29
main.f(2)
src/gopl.io/ch5/defer2/defer.go:29
main.f(3)
src/gopl.io/ch5/defer2/defer.go:29
main.main()
src/gopl.io/ch5/defer2/defer.go:15
//!-printstack
*/

54
ch5/fetch/main.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 148.
// Fetch saves the contents of a URL into a local file.
package main
import (
"fmt"
"io"
"net/http"
"os"
"path"
)
//!+
// Fetch downloads the URL and returns the
// name and length of the local file.
func fetch(url string) (filename string, n int64, err error) {
resp, err := http.Get(url)
if err != nil {
return "", 0, err
}
defer resp.Body.Close()
local := path.Base(resp.Request.URL.Path)
if local == "/" {
local = "index.html"
}
f, err := os.Create(local)
if err != nil {
return "", 0, err
}
n, err = io.Copy(f, resp.Body)
// Close file, but prefer error from Copy, if any.
if closeErr := f.Close(); err == nil {
err = closeErr
}
return local, n, err
}
//!-
func main() {
for _, url := range os.Args[1:] {
local, n, err := fetch(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch %s: %v\n", url, err)
continue
}
fmt.Fprintf(os.Stderr, "%s => %s (%d bytes).\n", url, local, n)
}
}

View File

@ -1,4 +1,7 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 122.
//!+main
// Findlinks1 prints the links in an HTML document read from standard input.

69
ch5/findlinks2/main.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 125.
// Findlinks2 does an HTTP GET on each URL, parses the
// result as HTML, and prints the links within it.
//
// Usage:
// findlinks url ...
package main
import (
"fmt"
"net/http"
"os"
"golang.org/x/net/html"
)
// visit appends to links each link found in n, and returns the result.
func visit(links []string, n *html.Node) []string {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
links = append(links, a.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = visit(links, c)
}
return links
}
//!+
func main() {
for _, url := range os.Args[1:] {
links, err := findLinks(url)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err)
continue
}
for _, link := range links {
fmt.Println(link)
}
}
}
// findLinks performs an HTTP GET request for url, parses the
// response as HTML, and extracts and returns the links.
func findLinks(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}
return visit(nil, doc), nil
}
//!-

View File

@ -0,0 +1,56 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 139.
// Findlinks3 crawls the web, starting with the URLs on the command line.
package main
import (
"fmt"
"log"
"os"
"gopl.io/ch5/links"
)
//!+breadthFirst
// breadthFirst calls f for each item in the worklist.
// Any items returned by f are added to the worklist.
// f is called at most once for each item.
func breadthFirst(f func(item string) []string, worklist []string) {
seen := make(map[string]bool)
for len(worklist) > 0 {
items := worklist
worklist = nil
for _, item := range items {
if !seen[item] {
seen[item] = true
worklist = append(worklist, f(item)...)
}
}
}
}
//!-breadthFirst
//!+crawl
func crawl(url string) []string {
fmt.Println(url)
list, err := links.Extract(url)
if err != nil {
log.Print(err)
}
return list
}
//!-crawl
//!+main
func main() {
// Crawl the web breadth-first,
// starting from the command-line arguments.
breadthFirst(crawl, os.Args[1:])
}
//!-main

67
ch5/links/links.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 138.
//!+Extract
// Package links provides a link-extraction function.
package links
import (
"fmt"
"net/http"
"golang.org/x/net/html"
)
// Extract makes an HTTP GET request to the specified URL, parses
// the response as HTML, and returns the links in the HTML document.
func Extract(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}
var links []string
visitNode := func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key != "href" {
continue
}
link, err := resp.Request.URL.Parse(a.Val)
if err != nil {
continue // ignore bad URLs
}
links = append(links, link.String())
}
}
}
forEachNode(doc, visitNode, nil)
return links, nil
}
//!-Extract
// Copied from gopl.io/ch5/outline2.
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}

36
ch5/outline/main.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 123.
// Outline prints the outline of an HTML document tree.
package main
import (
"fmt"
"os"
"golang.org/x/net/html"
)
//!+
func main() {
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "outline: %v\n", err)
os.Exit(1)
}
outline(nil, doc)
}
func outline(stack []string, n *html.Node) {
if n.Type == html.ElementNode {
stack = append(stack, n.Data) // push tag
fmt.Println(stack)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
outline(stack, c)
}
}
//!-

80
ch5/outline2/outline.go Normal file
View File

@ -0,0 +1,80 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 133.
// Outline prints the outline of an HTML document tree.
package main
import (
"fmt"
"net/http"
"os"
"golang.org/x/net/html"
)
func main() {
for _, url := range os.Args[1:] {
outline(url)
}
}
func outline(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
return err
}
//!+call
forEachNode(doc, startElement, endElement)
//!-call
return nil
}
//!+forEachNode
// forEachNode calls the functions pre(x) and post(x) for each node
// x in the tree rooted at n. Both functions are optional.
// pre is called before the children are visited (preorder) and
// post is called after (postorder).
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
//!-forEachNode
//!+startend
var depth int
func startElement(n *html.Node) {
if n.Type == html.ElementNode {
fmt.Printf("%*s<%s>\n", depth*2, "", n.Data)
depth++
}
}
func endElement(n *html.Node) {
if n.Type == html.ElementNode {
depth--
fmt.Printf("%*s</%s>\n", depth*2, "", n.Data)
}
}
//!-startend

30
ch5/squares/main.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 135.
// The squares program demonstrates a function value with state.
package main
import "fmt"
//!+
// squares returns a function that returns
// the next square number each time it is called.
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
//!-

33
ch5/sum/main.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 142.
// The sum program demonstrates a variadic function.
package main
import "fmt"
//!+
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
//!-
func main() {
//!+main
fmt.Println(sum()) // "0"
fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
//!-main
//!+slice
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
//!-slice
}

82
ch5/title1/title.go Normal file
View File

@ -0,0 +1,82 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 144.
// Title1 prints the title of an HTML document specified by a URL.
package main
/*
//!+output
$ go build gopl.io/ch5/title1
$ ./title1 http://gopl.io
The Go Programming Language
$ ./title1 https://golang.org/doc/effective_go.html
Effective Go - The Go Programming Language
$ ./title1 https://golang.org/doc/gopher/frontpage.png
title: https://golang.org/doc/gopher/frontpage.png
has type image/png, not text/html
//!-output
*/
import (
"fmt"
"net/http"
"os"
"strings"
"golang.org/x/net/html"
)
// Copied from gopl.io/ch5/outline2.
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
//!+
func title(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
// Check Content-Type is HTML (e.g., "text/html; charset=utf-8").
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
resp.Body.Close()
return fmt.Errorf("%s has type %s, not text/html", url, ct)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return fmt.Errorf("parsing %s as HTML: %v", url, err)
}
visitNode := func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" &&
n.FirstChild != nil {
fmt.Println(n.FirstChild.Data)
}
}
forEachNode(doc, visitNode, nil)
return nil
}
//!-
func main() {
for _, arg := range os.Args[1:] {
if err := title(arg); err != nil {
fmt.Fprintf(os.Stderr, "title: %v\n", err)
}
}
}

72
ch5/title2/title.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 145.
// Title2 prints the title of an HTML document specified by a URL.
// It uses defer to simplify closing the response body stream.
package main
import (
"fmt"
"net/http"
"os"
"strings"
"golang.org/x/net/html"
)
// Copied from gopl.io/ch5/outline2.
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
//!+
func title(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
return fmt.Errorf("%s has type %s, not text/html", url, ct)
}
doc, err := html.Parse(resp.Body)
if err != nil {
return fmt.Errorf("parsing %s as HTML: %v", url, err)
}
// ...print doc's title element...
//!-
visitNode := func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" &&
n.FirstChild != nil {
fmt.Println(n.FirstChild.Data)
}
}
forEachNode(doc, visitNode, nil)
//!+
return nil
}
//!-
func main() {
for _, arg := range os.Args[1:] {
if err := title(arg); err != nil {
fmt.Fprintf(os.Stderr, "title: %v\n", err)
}
}
}

99
ch5/title3/title.go Normal file
View File

@ -0,0 +1,99 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 153.
// Title3 prints the title of an HTML document specified by a URL.
package main
import (
"fmt"
"net/http"
"os"
"strings"
"golang.org/x/net/html"
)
// Copied from gopl.io/ch5/outline2.
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
//!+
// soleTitle returns the text of the first non-empty title element
// in doc, and an error if there was not exactly one.
func soleTitle(doc *html.Node) (title string, err error) {
type bailout struct{}
defer func() {
switch p := recover(); p {
case nil:
// no panic
case bailout{}:
// "expected" panic
err = fmt.Errorf("multiple title elements")
default:
panic(p) // unexpected panic; carry on panicking
}
}()
// Bail out of recursion if we find more than one non-empty title.
forEachNode(doc, func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" &&
n.FirstChild != nil {
if title != "" {
panic(bailout{}) // multiple title elements
}
title = n.FirstChild.Data
}
}, nil)
if title == "" {
return "", fmt.Errorf("no title element")
}
return title, nil
}
//!-
func title(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
// Check Content-Type is HTML (e.g., "text/html; charset=utf-8").
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
resp.Body.Close()
return fmt.Errorf("%s has type %s, not text/html", url, ct)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return fmt.Errorf("parsing %s as HTML: %v", url, err)
}
title, err := soleTitle(doc)
if err != nil {
return err
}
fmt.Println(title)
return nil
}
func main() {
for _, arg := range os.Args[1:] {
if err := title(arg); err != nil {
fmt.Fprintf(os.Stderr, "title: %v\n", err)
}
}
}

69
ch5/toposort/main.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 136.
// The toposort program prints the nodes of a DAG in topological order.
package main
import (
"fmt"
"sort"
)
//!+table
// prereqs maps computer science courses to their prerequisites.
var prereqs = map[string][]string{
"algorithms": {"data structures"},
"calculus": {"linear algebra"},
"compilers": {
"data structures",
"formal languages",
"computer organization",
},
"data structures": {"discrete math"},
"databases": {"data structures"},
"discrete math": {"intro to programming"},
"formal languages": {"discrete math"},
"networks": {"operating systems"},
"operating systems": {"data structures", "computer organization"},
"programming languages": {"data structures", "computer organization"},
}
//!-table
//!+main
func main() {
for i, course := range topoSort(prereqs) {
fmt.Printf("%d:\t%s\n", i+1, course)
}
}
func topoSort(m map[string][]string) []string {
var order []string
seen := make(map[string]bool)
var visitAll func(items []string)
visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item])
order = append(order, item)
}
}
}
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
visitAll(keys)
return order
}
//!-main

40
ch5/trace/main.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 146.
// The trace program uses defer to add entry/exit diagnostics to a function.
package main
import (
"log"
"time"
)
//!+main
func bigSlowOperation() {
defer trace("bigSlowOperation")() // don't forget the extra parentheses
// ...lots of work...
time.Sleep(10 * time.Second) // simulate slow operation by sleeping
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) }
}
//!-main
func main() {
bigSlowOperation()
}
/*
!+output
$ go build gopl.io/ch5/trace
$ ./trace
2015/11/18 09:53:26 enter bigSlowOperation
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
!-output
*/

50
ch5/wait/wait.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 130.
// The wait program waits for an HTTP server to start responding.
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
)
//!+
// WaitForServer attempts to contact the server of a URL.
// It tries for one minute using exponential back-off.
// It reports an error if all attempts fail.
func WaitForServer(url string) error {
const timeout = 1 * time.Minute
deadline := time.Now().Add(timeout)
for tries := 0; time.Now().Before(deadline); tries++ {
_, err := http.Head(url)
if err == nil {
return nil // success
}
log.Printf("server not responding (%s); retrying...", err)
time.Sleep(time.Second << uint(tries)) // exponential back-off
}
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}
//!-
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: wait url\n")
os.Exit(1)
}
url := os.Args[1]
//!+main
// (In function main.)
if err := WaitForServer(url); err != nil {
fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
os.Exit(1)
}
//!-main
}

89
ch6/coloredpoint/main.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 161.
// Coloredpoint demonstrates struct embedding.
package main
import (
"fmt"
"math"
)
//!+decl
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
//!-decl
func (p Point) Distance(q Point) float64 {
dX := q.X - p.X
dY := q.Y - p.Y
return math.Sqrt(dX*dX + dY*dY)
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func main() {
//!+main
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
//!-main
}
/*
//!+error
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
//!-error
*/
func init() {
//!+methodexpr
p := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"
//!-methodexpr
}
func init() {
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
//!+indirect
type ColoredPoint struct {
*Point
Color color.RGBA
}
p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point // p and q now share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
//!-indirect
}

42
ch6/geometry/geometry.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 156.
// Package geometry defines simple types for plane geometry.
//!+point
package geometry
import "math"
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
//!-point
//!+path
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}
//!-path

73
ch6/intset/intset.go Normal file
View File

@ -0,0 +1,73 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 165.
// Package intset provides a set of integers based on a bit vector.
package intset
import (
"bytes"
"fmt"
)
//!+intset
// An IntSet is a set of small non-negative integers.
// Its zero value represents the empty set.
type IntSet struct {
words []uint64
}
// Has reports whether the set contains the non-negative value x.
func (s *IntSet) Has(x int) bool {
word, bit := x/64, uint(x%64)
return word < len(s.words) && s.words[word]&(1<<bit) != 0
}
// Add adds the non-negative value x to the set.
func (s *IntSet) Add(x int) {
word, bit := x/64, uint(x%64)
for word >= len(s.words) {
s.words = append(s.words, 0)
}
s.words[word] |= 1 << bit
}
// UnionWith sets s to the union of s and t.
func (s *IntSet) UnionWith(t *IntSet) {
for i, tword := range t.words {
if i < len(s.words) {
s.words[i] |= tword
} else {
s.words = append(s.words, tword)
}
}
}
//!-intset
//!+string
// String returns the set as a string of the form "{1 2 3}".
func (s *IntSet) String() string {
var buf bytes.Buffer
buf.WriteByte('{')
for i, word := range s.words {
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0 {
if buf.Len() > len("{") {
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%d", 64*i+j)
}
}
}
buf.WriteByte('}')
return buf.String()
}
//!-string

50
ch6/intset/intset_test.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package intset
import "fmt"
func Example1() {
//!+main
var x, y IntSet
x.Add(1)
x.Add(144)
x.Add(9)
fmt.Println(x.String()) // "{1 9 144}"
y.Add(9)
y.Add(42)
fmt.Println(y.String()) // "{9 42}"
x.UnionWith(&y)
fmt.Println(x.String()) // "{1 9 42 144}"
fmt.Println(x.Has(9), x.Has(123)) // "true false"
//!-main
// Output:
// {1 9 144}
// {9 42}
// {1 9 42 144}
// true false
}
func Example2() {
var x IntSet
x.Add(1)
x.Add(144)
x.Add(9)
x.Add(42)
//!+note
fmt.Println(&x) // "{1 9 42 144}"
fmt.Println(x.String()) // "{1 9 42 144}"
fmt.Println(x) // "{[4398046511618 0 65536]}"
//!-note
// Output:
// {1 9 42 144}
// {1 9 42 144}
// {[4398046511618 0 65536]}
}

53
ch6/urlvalues/main.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 160.
// The urlvalues command demonstrates a map type with methods.
package main
/*
//!+values
package url
// Values maps a string key to a list of values.
type Values map[string][]string
// Get returns the first value associated with the given key,
// or "" if there are none.
func (v Values) Get(key string) string {
if vs := v[key]; len(vs) > 0 {
return vs[0]
}
return ""
}
// Add adds the value to key.
// It appends to any existing values associated with key.
func (v Values) Add(key, value string) {
v[key] = append(v[key], value)
}
//!-values
*/
import (
"fmt"
"net/url"
)
func main() {
//!+main
m := url.Values{"lang": {"en"}} // direct construction
m.Add("item", "1")
m.Add("item", "2")
fmt.Println(m.Get("lang")) // "en"
fmt.Println(m.Get("q")) // ""
fmt.Println(m.Get("item")) // "1" (first value)
fmt.Println(m["item"]) // "[1 2]" (direct map access)
m = nil
fmt.Println(m.Get("item")) // ""
m.Add("item", "3") // panic: assignment to entry in nil map
//!-main
}

35
ch7/bytecounter/main.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 173.
// Bytecounter demonstrates an implementation of io.Writer that counts bytes.
package main
import (
"fmt"
)
//!+bytecounter
type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p)) // convert int to ByteCounter
return len(p), nil
}
//!-bytecounter
func main() {
//!+main
var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c) // "5", = len("hello")
c = 0 // reset the counter
var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c) // "12", = len("hello, Dolly")
//!-main
}

40
ch7/eval/ast.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
// An Expr is an arithmetic expression.
type Expr interface {
// Eval returns the value of this Expr in the environment env.
Eval(env Env) float64
// Check reports errors in this Expr and adds its Vars to the set.
Check(vars map[Var]bool) error
}
//!+ast
// A Var identifies a variable, e.g., x.
type Var string
// A literal is a numeric constant, e.g., 3.141.
type literal float64
// A unary represents a unary operator expression, e.g., -x.
type unary struct {
op rune // one of '+', '-'
x Expr
}
// A binary represents a binary operator expression, e.g., x+y.
type binary struct {
op rune // one of '+', '-', '*', '/'
x, y Expr
}
// A call represents a function call expression, e.g., sin(x).
type call struct {
fn string // one of "pow", "sin", "sqrt"
args []Expr
}
//!-ast

58
ch7/eval/check.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
import (
"fmt"
"strings"
)
//!+Check
func (v Var) Check(vars map[Var]bool) error {
vars[v] = true
return nil
}
func (literal) Check(vars map[Var]bool) error {
return nil
}
func (u unary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-", u.op) {
return fmt.Errorf("unexpected unary op %q", u.op)
}
return u.x.Check(vars)
}
func (b binary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-*/", b.op) {
return fmt.Errorf("unexpected binary op %q", b.op)
}
if err := b.x.Check(vars); err != nil {
return err
}
return b.y.Check(vars)
}
func (c call) Check(vars map[Var]bool) error {
arity, ok := numParams[c.fn]
if !ok {
return fmt.Errorf("unknown function %q", c.fn)
}
if len(c.args) != arity {
return fmt.Errorf("call to %s has %d args, want %d",
c.fn, len(c.args), arity)
}
for _, arg := range c.args {
if err := arg.Check(vars); err != nil {
return err
}
}
return nil
}
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
//!-Check

48
ch7/eval/coverage_test.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
import (
"fmt"
"math"
"testing"
)
//!+TestCoverage
func TestCoverage(t *testing.T) {
var tests = []struct {
input string
env Env
want string // expected error from Parse/Check or result from Eval
}{
{"x % 2", nil, "unexpected '%'"},
{"!true", nil, "unexpected '!'"},
{"log(10)", nil, `unknown function "log"`},
{"sqrt(1, 2)", nil, "call to sqrt has 2 args, want 1"},
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
}
for _, test := range tests {
expr, err := Parse(test.input)
if err == nil {
err = expr.Check(map[Var]bool{})
}
if err != nil {
if err.Error() != test.want {
t.Errorf("%s: got %q, want %q", test.input, err, test.want)
}
continue
}
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
if got != test.want {
t.Errorf("%s: %v => %s, want %s",
test.input, test.env, got, test.want)
}
}
}
//!-TestCoverage

70
ch7/eval/eval.go Normal file
View File

@ -0,0 +1,70 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 198.
// Package eval provides an expression evaluator.
package eval
import (
"fmt"
"math"
)
//!+env
type Env map[Var]float64
//!-env
//!+Eval1
func (v Var) Eval(env Env) float64 {
return env[v]
}
func (l literal) Eval(_ Env) float64 {
return float64(l)
}
//!-Eval1
//!+Eval2
func (u unary) Eval(env Env) float64 {
switch u.op {
case '+':
return +u.x.Eval(env)
case '-':
return -u.x.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}
func (b binary) Eval(env Env) float64 {
switch b.op {
case '+':
return b.x.Eval(env) + b.y.Eval(env)
case '-':
return b.x.Eval(env) - b.y.Eval(env)
case '*':
return b.x.Eval(env) * b.y.Eval(env)
case '/':
return b.x.Eval(env) / b.y.Eval(env)
}
panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
}
func (c call) Eval(env Env) float64 {
switch c.fn {
case "pow":
return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
case "sin":
return math.Sin(c.args[0].Eval(env))
case "sqrt":
return math.Sqrt(c.args[0].Eval(env))
}
panic(fmt.Sprintf("unsupported function call: %s", c.fn))
}
//!-Eval2

113
ch7/eval/eval_test.go Normal file
View File

@ -0,0 +1,113 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
import (
"fmt"
"math"
"testing"
)
//!+Eval
func TestEval(t *testing.T) {
tests := []struct {
expr string
env Env
want string
}{
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
{"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"},
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
{"5 / 9 * (F - 32)", Env{"F": 32}, "0"},
{"5 / 9 * (F - 32)", Env{"F": 212}, "100"},
//!-Eval
// additional tests that don't appear in the book
{"-1 + -x", Env{"x": 1}, "-2"},
{"-1 - x", Env{"x": 1}, "-2"},
//!+Eval
}
var prevExpr string
for _, test := range tests {
// Print expr only when it changes.
if test.expr != prevExpr {
fmt.Printf("\n%s\n", test.expr)
prevExpr = test.expr
}
expr, err := Parse(test.expr)
if err != nil {
t.Error(err) // parse error
continue
}
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
fmt.Printf("\t%v => %s\n", test.env, got)
if got != test.want {
t.Errorf("%s.Eval() in %s = %q, want %q\n",
test.expr, test.env, got, test.want)
}
}
}
//!-Eval
/*
//!+output
sqrt(A / pi)
map[A:87616 pi:3.141592653589793] => 167
pow(x, 3) + pow(y, 3)
map[x:12 y:1] => 1729
map[x:9 y:10] => 1729
5 / 9 * (F - 32)
map[F:-40] => -40
map[F:32] => 0
map[F:212] => 100
//!-output
// Additional outputs that don't appear in the book.
-1 - x
map[x:1] => -2
-1 + -x
map[x:1] => -2
*/
func TestErrors(t *testing.T) {
for _, test := range []struct{ expr, wantErr string }{
{"x % 2", "unexpected '%'"},
{"math.Pi", "unexpected '.'"},
{"!true", "unexpected '!'"},
{`"hello"`, "unexpected '\"'"},
{"log(10)", `unknown function "log"`},
{"sqrt(1, 2)", "call to sqrt has 2 args, want 1"},
} {
expr, err := Parse(test.expr)
if err == nil {
vars := make(map[Var]bool)
err = expr.Check(vars)
if err == nil {
t.Errorf("unexpected success: %s", test.expr)
continue
}
}
fmt.Printf("%-20s%v\n", test.expr, err) // (for book)
if err.Error() != test.wantErr {
t.Errorf("got error %s, want %s", err, test.wantErr)
}
}
}
/*
//!+errors
x % 2 unexpected '%'
math.Pi unexpected '.'
!true unexpected '!'
"hello" unexpected '"'
log(10) unknown function "log"
sqrt(1, 2) call to sqrt has 2 args, want 1
//!-errors
*/

160
ch7/eval/parse.go Normal file
View File

@ -0,0 +1,160 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
import (
"fmt"
"strconv"
"strings"
"text/scanner"
)
// ---- lexer ----
// This lexer is similar to the one described in Chapter 13.
type lexer struct {
scan scanner.Scanner
token rune // current lookahead token
}
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
func (lex *lexer) text() string { return lex.scan.TokenText() }
type lexPanic string
// describe returns a string describing the current token, for use in errors.
func (lex *lexer) describe() string {
switch lex.token {
case scanner.EOF:
return "end of file"
case scanner.Ident:
return fmt.Sprintf("identifier %s", lex.text())
case scanner.Int, scanner.Float:
return fmt.Sprintf("number %s", lex.text())
}
return fmt.Sprintf("%q", rune(lex.token)) // any other rune
}
func precedence(op rune) int {
switch op {
case '*', '/':
return 2
case '+', '-':
return 1
}
return 0
}
// ---- parser ----
// Parse parses the input string as an arithmetic expression.
//
// expr = num a literal number, e.g., 3.14159
// | id a variable name, e.g., x
// | id '(' expr ',' ... ')' a function call
// | '-' expr a unary operator (+-)
// | expr '+' expr a binary operator (+-*/)
//
func Parse(input string) (_ Expr, err error) {
defer func() {
switch x := recover().(type) {
case nil:
// no panic
case lexPanic:
err = fmt.Errorf("%s", x)
default:
// unexpected panic: resume state of panic.
panic(x)
}
}()
lex := new(lexer)
lex.scan.Init(strings.NewReader(input))
lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats
lex.next() // initial lookahead
e := parseExpr(lex)
if lex.token != scanner.EOF {
return nil, fmt.Errorf("unexpected %s", lex.describe())
}
return e, nil
}
func parseExpr(lex *lexer) Expr { return parseBinary(lex, 1) }
// binary = unary ('+' binary)*
// parseBinary stops when it encounters an
// operator of lower precedence than prec1.
func parseBinary(lex *lexer, prec1 int) Expr {
lhs := parseUnary(lex)
for prec := precedence(lex.token); prec >= prec1; prec-- {
for precedence(lex.token) == prec {
op := lex.token
lex.next() // consume operator
rhs := parseBinary(lex, prec+1)
lhs = binary{op, lhs, rhs}
}
}
return lhs
}
// unary = '+' expr | primary
func parseUnary(lex *lexer) Expr {
if lex.token == '+' || lex.token == '-' {
op := lex.token
lex.next() // consume '+' or '-'
return unary{op, parseUnary(lex)}
}
return parsePrimary(lex)
}
// primary = id
// | id '(' expr ',' ... ',' expr ')'
// | num
// | '(' expr ')'
func parsePrimary(lex *lexer) Expr {
switch lex.token {
case scanner.Ident:
id := lex.text()
lex.next() // consume Ident
if lex.token != '(' {
return Var(id)
}
lex.next() // consume '('
var args []Expr
if lex.token != ')' {
for {
args = append(args, parseExpr(lex))
if lex.token != ',' {
break
}
lex.next() // consume ','
}
if lex.token != ')' {
msg := fmt.Sprintf("got %s, want ')'", lex.token)
panic(lexPanic(msg))
}
}
lex.next() // consume ')'
return call{id, args}
case scanner.Int, scanner.Float:
f, err := strconv.ParseFloat(lex.text(), 64)
if err != nil {
panic(lexPanic(err.Error()))
}
lex.next() // consume number
return literal(f)
case '(':
lex.next() // consume ')'
e := parseExpr(lex)
if lex.token != ')' {
msg := fmt.Sprintf("got %s, want ')'", lex.describe())
panic(lexPanic(msg))
}
lex.next() // consume ')'
return e
}
msg := fmt.Sprintf("unexpected %s", lex.describe())
panic(lexPanic(msg))
}

51
ch7/eval/print.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//+build ignore
package eval
import (
"bytes"
"fmt"
)
// Format formats an expression as a string.
// It does not attempt to remove unnecessary parens.
func Format(e Expr) string {
var buf bytes.Buffer
write(&buf, e)
return buf.String()
}
func write(buf *bytes.Buffer, e Expr) {
switch e := e.(type) {
case literal:
fmt.Fprintf(buf, "%g", e)
case Var:
fmt.Fprintf(buf, "%s", e)
case *unary:
fmt.Fprintf(buf, "(%c", e.op)
write(buf, e.x)
buf.WriteByte(')')
case *binary:
buf.WriteByte('(')
write(buf, e.x)
fmt.Fprintf(buf, " %c ", e.op)
write(buf, e.y)
buf.WriteByte(')')
case *call:
fmt.Fprintf(buf, "%s(", e.fn)
for i, arg := range e.args {
if i > 0 {
buf.WriteString(", ")
}
write(buf, arg)
}
buf.WriteByte(')')
}
}

46
ch7/http1/main.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 191.
// Http1 is a rudimentary e-commerce server.
package main
import (
"fmt"
"log"
"net/http"
)
//!+main
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
//!-main
/*
//!+handler
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
//!-handler
*/

48
ch7/http2/main.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 192.
// Http2 is an e-commerce server with /list and /price endpoints.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
//!+handler
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/list":
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
//!-handler

61
ch7/http3/main.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 194.
// Http3 is an e-commerce server that registers the /list and /price
// endpoints by calling (*http.ServeMux).Handle.
package main
import (
"fmt"
"log"
"net/http"
)
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
//!+main
func main() {
db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
//!-main
/*
//!+handlerfunc
package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
//!-handlerfunc
*/

54
ch7/http3a/main.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 195.
// Http3a is an e-commerce server that registers the /list and /price
// endpoints by calling (*http.ServeMux).HandleFunc.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux()
//!+main
mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price)
//!-main
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
type database map[string]int
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: $%d\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
if price, ok := db[item]; ok {
fmt.Fprintf(w, "$%d\n", price)
} else {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
}
}
/*
//!+handlerfunc
package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
//!-handlerfunc
*/

47
ch7/http4/main.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 195.
// Http4 is an e-commerce server that registers the /list and /price
// endpoint by calling http.HandleFunc.
package main
import (
"fmt"
"log"
"net/http"
)
//!+main
func main() {
db := database{"shoes": 50, "socks": 5}
http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
//!-main
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
if price, ok := db[item]; ok {
fmt.Fprintf(w, "%s\n", price)
} else {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
}
}

Some files were not shown because too many files have changed in this diff Show More