Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ce6153110 | ||
|
|
ae81bd4940 | ||
|
|
43d95d8279 | ||
|
|
1766cdaa89 | ||
|
|
19ed894a35 | ||
|
|
a4814e6329 | ||
|
|
7cd442867e | ||
|
|
1bc589883b | ||
|
|
454f1d70cf | ||
|
|
702bce3ca2 | ||
|
|
e5d9cd6a54 | ||
|
|
78deb2c337 | ||
|
|
948171284d | ||
|
|
183cce4924 | ||
|
|
bc54a9a664 | ||
|
|
22227cdc85 | ||
|
|
21e81f03c7 | ||
|
|
0980424fec | ||
|
|
ef034483f5 | ||
|
|
fc5c7868a4 | ||
|
|
87ed772059 | ||
|
|
4fa21526d1 | ||
|
|
7dc9622c20 | ||
|
|
80848c4951 | ||
|
|
14c4582106 | ||
|
|
9f377a2807 | ||
|
|
b295060b17 | ||
|
|
596f0f593a | ||
|
|
a86ffcbd26 | ||
|
|
dfa59d4f93 | ||
|
|
d338cd8f57 | ||
|
|
ec29a7eae4 | ||
|
|
a136a4e275 | ||
|
|
fc06ce37a4 | ||
|
|
0c557838a0 | ||
|
|
920917cf0b | ||
|
|
1a0d7be7b2 | ||
|
|
1c09c66d10 | ||
|
|
fa151ca9ec | ||
|
|
0fb6c39f2b | ||
|
|
21532b271f | ||
|
|
c6c4553d3e | ||
|
|
37cbc8734f | ||
|
|
0ca38837c1 | ||
|
|
0a07735fed | ||
|
|
98dcdcd405 | ||
|
|
d9d0b8444b | ||
|
|
c75d53a12b | ||
|
|
ce556fed91 | ||
|
|
7c2d57b491 | ||
|
|
d9d2fa941d | ||
|
|
b0f318b662 | ||
|
|
616e67e520 | ||
|
|
ed686d57dd | ||
|
|
e7825bbc60 | ||
|
|
69e7cc004e | ||
|
|
367a1eb60b | ||
|
|
7d617913e3 | ||
|
|
49d13b285d | ||
|
|
12c2773ab8 | ||
|
|
3ba571f0ea | ||
|
|
0f4f273995 | ||
|
|
33a8b8234b | ||
|
|
ec0f9b68e3 | ||
|
|
6b9ccd4d02 | ||
|
|
b8fccaa364 | ||
|
|
2813bb5acd | ||
|
|
ec6ccbb388 | ||
|
|
3a81d6693c | ||
|
|
1b3a98efe5 | ||
|
|
caeef4440e | ||
|
|
1443b7e8ee | ||
|
|
049a9ddd4f | ||
|
|
f7c7c8072d | ||
|
|
b4380aebe6 | ||
|
|
bfc2459f75 | ||
|
|
fdbcef405b | ||
|
|
eed47457d3 | ||
|
|
ad82b31147 | ||
|
|
f426083363 | ||
|
|
2c5e4fffeb | ||
|
|
a0139b46de | ||
|
|
a4a4b5922f | ||
|
|
9702ea6cec | ||
|
|
c585d83b5b | ||
|
|
cab528650f | ||
|
|
2206fbf5a4 | ||
|
|
8d99d0ce95 | ||
|
|
2903d341a3 | ||
|
|
d40646a122 | ||
|
|
41e9b86501 | ||
|
|
0cf7137be4 | ||
|
|
7c15f49a80 | ||
|
|
acea355788 | ||
|
|
f8f8c46f79 | ||
|
|
7790674b88 | ||
|
|
d803838f5f | ||
|
|
54d720cbcc | ||
|
|
384a2e435e | ||
|
|
af4de7c8fc | ||
|
|
e666c18a3d | ||
|
|
795815a9c2 | ||
|
|
1491e474b3 | ||
|
|
12000cc59b | ||
|
|
67368a591f | ||
|
|
1df1bcddc5 | ||
|
|
d233b598ae | ||
|
|
0c59b98b4b | ||
|
|
1992731cf3 | ||
|
|
5b93d947d2 | ||
|
|
ef5c394845 | ||
|
|
53cc4ce581 | ||
|
|
1e1c740b99 | ||
|
|
96e17120e7 |
BIN
5stars.png
Normal file
BIN
5stars.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
21
README.md
21
README.md
@@ -1,21 +1,4 @@
|
|||||||
# The Go Programming Language
|
|
||||||
|
|
||||||
This repository provides the downloadable example programs
|
This branch holds the source for the http://www.gopl.io website.
|
||||||
for the book, "The Go Programming Language"; see http://www.gopl.io.
|
|
||||||
|
|
||||||
These example programs are licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.<br/>
|
|
||||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"/></a>
|
|
||||||
|
|
||||||
You can download, build, and run the programs with the following commands:
|
|
||||||
|
|
||||||
$ export GOPATH=$HOME/gobook # choose workspace directory
|
|
||||||
$ go get gopl.io/ch1/helloworld # fetch, build, install
|
|
||||||
$ $GOPATH/bin/helloworld # run
|
|
||||||
Hello, 世界
|
|
||||||
|
|
||||||
Many of the programs contain comments of the form `//!+` and `//!-`.
|
|
||||||
These comments bracket the parts of the programs that are excerpted in the
|
|
||||||
book; you can safely ignore them. In a few cases, programs
|
|
||||||
have been reformatted in an unnatural way so that they can be presented
|
|
||||||
in stages in the book.
|
|
||||||
|
|
||||||
|
This entire repo is public, so don't check in anything sensitive.
|
||||||
|
|||||||
BIN
barnesnoble.png
Normal file
BIN
barnesnoble.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
buyfromamazon.png
Normal file
BIN
buyfromamazon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -1,31 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 8.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Dup1 prints the text of each line that appears more than
|
|
||||||
// once in the standard input, preceded by its count.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
counts := make(map[string]int)
|
|
||||||
input := bufio.NewScanner(os.Stdin)
|
|
||||||
for input.Scan() {
|
|
||||||
counts[input.Text()]++
|
|
||||||
}
|
|
||||||
// NOTE: ignoring potential errors from input.Err()
|
|
||||||
for line, n := range counts {
|
|
||||||
if n > 1 {
|
|
||||||
fmt.Printf("%d\t%s\n", n, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 10.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Dup2 prints the count and text of lines that appear more than once
|
|
||||||
// in the input. It reads from stdin or from a list of named files.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
counts := make(map[string]int)
|
|
||||||
files := os.Args[1:]
|
|
||||||
if len(files) == 0 {
|
|
||||||
countLines(os.Stdin, counts)
|
|
||||||
} else {
|
|
||||||
for _, arg := range files {
|
|
||||||
f, err := os.Open(arg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
countLines(f, counts)
|
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for line, n := range counts {
|
|
||||||
if n > 1 {
|
|
||||||
fmt.Printf("%d\t%s\n", n, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func countLines(f *os.File, counts map[string]int) {
|
|
||||||
input := bufio.NewScanner(f)
|
|
||||||
for input.Scan() {
|
|
||||||
counts[input.Text()]++
|
|
||||||
}
|
|
||||||
// NOTE: ignoring potential errors from input.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 12.
|
|
||||||
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Dup3 prints the count and text of lines that
|
|
||||||
// appear more than once in the named input files.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
counts := make(map[string]int)
|
|
||||||
for _, filename := range os.Args[1:] {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
|
||||||
counts[line]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for line, n := range counts {
|
|
||||||
if n > 1 {
|
|
||||||
fmt.Printf("%d\t%s\n", n, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 4.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Echo1 prints its command-line arguments.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var s, sep string
|
|
||||||
for i := 1; i < len(os.Args); i++ {
|
|
||||||
s += sep + os.Args[i]
|
|
||||||
sep = " "
|
|
||||||
}
|
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 6.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Echo2 prints its command-line arguments.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
s, sep := "", ""
|
|
||||||
for _, arg := range os.Args[1:] {
|
|
||||||
s += sep + arg
|
|
||||||
sep = " "
|
|
||||||
}
|
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 8.
|
|
||||||
|
|
||||||
// Echo3 prints its command-line arguments.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//!+
|
|
||||||
func main() {
|
|
||||||
fmt.Println(strings.Join(os.Args[1:], " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 16.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Fetch prints the content found at each specified URL.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
for _, url := range os.Args[1:] {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("%s", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 17.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Fetchall fetches URLs in parallel and reports their times and sizes.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
start := time.Now()
|
|
||||||
ch := make(chan string)
|
|
||||||
for _, url := range os.Args[1:] {
|
|
||||||
go fetch(url, ch) // start a goroutine
|
|
||||||
}
|
|
||||||
for range os.Args[1:] {
|
|
||||||
fmt.Println(<-ch) // receive from channel ch
|
|
||||||
}
|
|
||||||
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch(url string, ch chan<- string) {
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
ch <- fmt.Sprint(err) // send to channel ch
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
|
|
||||||
resp.Body.Close() // don't leak resources
|
|
||||||
if err != nil {
|
|
||||||
ch <- fmt.Sprintf("while reading %s: %v", url, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
secs := time.Since(start).Seconds()
|
|
||||||
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 1.
|
|
||||||
|
|
||||||
// Helloworld is our first Go program.
|
|
||||||
//!+
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Hello, 世界")
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// Run with "web" command-line argument for web server.
|
|
||||||
// See page 13.
|
|
||||||
//!+main
|
|
||||||
|
|
||||||
// Lissajous generates GIF animations of random Lissajous figures.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/gif"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
//!-main
|
|
||||||
// Packages not needed by version in book.
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//!+main
|
|
||||||
|
|
||||||
var palette = []color.Color{color.White, color.Black}
|
|
||||||
|
|
||||||
const (
|
|
||||||
whiteIndex = 0 // first color in palette
|
|
||||||
blackIndex = 1 // next color in palette
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
//!-main
|
|
||||||
// The sequence of images is deterministic unless we seed
|
|
||||||
// the pseudo-random number generator using the current time.
|
|
||||||
// Thanks to Randall McPherson for pointing out the omission.
|
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
|
||||||
|
|
||||||
if len(os.Args) > 1 && os.Args[1] == "web" {
|
|
||||||
//!+http
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
lissajous(w)
|
|
||||||
}
|
|
||||||
http.HandleFunc("/", handler)
|
|
||||||
//!-http
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8000", nil))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//!+main
|
|
||||||
lissajous(os.Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lissajous(out io.Writer) {
|
|
||||||
const (
|
|
||||||
cycles = 5 // number of complete x oscillator revolutions
|
|
||||||
res = 0.001 // angular resolution
|
|
||||||
size = 100 // image canvas covers [-size..+size]
|
|
||||||
nframes = 64 // number of animation frames
|
|
||||||
delay = 8 // delay between frames in 10ms units
|
|
||||||
)
|
|
||||||
freq := rand.Float64() * 3.0 // relative frequency of y oscillator
|
|
||||||
anim := gif.GIF{LoopCount: nframes}
|
|
||||||
phase := 0.0 // phase difference
|
|
||||||
for i := 0; i < nframes; i++ {
|
|
||||||
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
|
|
||||||
img := image.NewPaletted(rect, palette)
|
|
||||||
for t := 0.0; t < cycles*2*math.Pi; t += res {
|
|
||||||
x := math.Sin(t)
|
|
||||||
y := math.Sin(t*freq + phase)
|
|
||||||
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
|
|
||||||
blackIndex)
|
|
||||||
}
|
|
||||||
phase += 0.1
|
|
||||||
anim.Delay = append(anim.Delay, delay)
|
|
||||||
anim.Image = append(anim.Image, img)
|
|
||||||
}
|
|
||||||
gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-main
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 19.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Server1 is a minimal "echo" server.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", handler) // each request calls handler
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8000", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler echoes the Path component of the requested URL.
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 20.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Server2 is a minimal "echo" and counter server.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
|
||||||
var count int
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", handler)
|
|
||||||
http.HandleFunc("/count", counter)
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8000", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler echoes the Path component of the requested URL.
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
mu.Lock()
|
|
||||||
count++
|
|
||||||
mu.Unlock()
|
|
||||||
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// counter echoes the number of calls so far.
|
|
||||||
func counter(w http.ResponseWriter, r *http.Request) {
|
|
||||||
mu.Lock()
|
|
||||||
fmt.Fprintf(w, "Count %d\n", count)
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 21.
|
|
||||||
|
|
||||||
// Server3 is an "echo" server that displays request parameters.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", handler)
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8000", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
//!+handler
|
|
||||||
// handler echoes the HTTP request.
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
|
|
||||||
for k, v := range r.Header {
|
|
||||||
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "Host = %q\n", r.Host)
|
|
||||||
fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
for k, v := range r.Form {
|
|
||||||
fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-handler
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 311.
|
|
||||||
|
|
||||||
// Package storage is part of a hypothetical cloud storage server.
|
|
||||||
//!+main
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/smtp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var usage = make(map[string]int64)
|
|
||||||
|
|
||||||
func bytesInUse(username string) int64 { return usage[username] }
|
|
||||||
|
|
||||||
// Email sender configuration.
|
|
||||||
// NOTE: never put passwords in source code!
|
|
||||||
const sender = "notifications@example.com"
|
|
||||||
const password = "correcthorsebatterystaple"
|
|
||||||
const hostname = "smtp.example.com"
|
|
||||||
|
|
||||||
const template = `Warning: you are using %d bytes of storage,
|
|
||||||
%d%% of your quota.`
|
|
||||||
|
|
||||||
func CheckQuota(username string) {
|
|
||||||
used := bytesInUse(username)
|
|
||||||
const quota = 1000000000 // 1GB
|
|
||||||
percent := 100 * used / quota
|
|
||||||
if percent < 90 {
|
|
||||||
return // OK
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf(template, used, percent)
|
|
||||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
|
||||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
|
||||||
[]string{username}, []byte(msg))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("smtp.SendMail(%s) failed: %s", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-main
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
//!+test
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
|
||||||
var notifiedUser, notifiedMsg string
|
|
||||||
notifyUser = func(user, msg string) {
|
|
||||||
notifiedUser, notifiedMsg = user, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = "joe@example.org"
|
|
||||||
usage[user] = 980000000 // simulate a 980MB-used condition
|
|
||||||
|
|
||||||
CheckQuota(user)
|
|
||||||
if notifiedUser == "" && notifiedMsg == "" {
|
|
||||||
t.Fatalf("notifyUser not called")
|
|
||||||
}
|
|
||||||
if notifiedUser != user {
|
|
||||||
t.Errorf("wrong user (%s) notified, want %s",
|
|
||||||
notifiedUser, user)
|
|
||||||
}
|
|
||||||
const wantSubstring = "98% of your quota"
|
|
||||||
if !strings.Contains(notifiedMsg, wantSubstring) {
|
|
||||||
t.Errorf("unexpected notification message <<%s>>, "+
|
|
||||||
"want substring %q", notifiedMsg, wantSubstring)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-test
|
|
||||||
|
|
||||||
/*
|
|
||||||
//!+defer
|
|
||||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
|
||||||
// Save and restore original notifyUser.
|
|
||||||
saved := notifyUser
|
|
||||||
defer func() { notifyUser = saved }()
|
|
||||||
|
|
||||||
// Install the test's fake notifyUser.
|
|
||||||
var notifiedUser, notifiedMsg string
|
|
||||||
notifyUser = func(user, msg string) {
|
|
||||||
notifiedUser, notifiedMsg = user, msg
|
|
||||||
}
|
|
||||||
// ...rest of test...
|
|
||||||
}
|
|
||||||
//!-defer
|
|
||||||
*/
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 312.
|
|
||||||
|
|
||||||
// Package storage is part of a hypothetical cloud storage server.
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/smtp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var usage = make(map[string]int64)
|
|
||||||
|
|
||||||
func bytesInUse(username string) int64 { return usage[username] }
|
|
||||||
|
|
||||||
// E-mail sender configuration.
|
|
||||||
// NOTE: never put passwords in source code!
|
|
||||||
const sender = "notifications@example.com"
|
|
||||||
const password = "correcthorsebatterystaple"
|
|
||||||
const hostname = "smtp.example.com"
|
|
||||||
|
|
||||||
const template = `Warning: you are using %d bytes of storage,
|
|
||||||
%d%% of your quota.`
|
|
||||||
|
|
||||||
//!+factored
|
|
||||||
var notifyUser = func(username, msg string) {
|
|
||||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
|
||||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
|
||||||
[]string{username}, []byte(msg))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("smtp.SendMail(%s) failed: %s", username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckQuota(username string) {
|
|
||||||
used := bytesInUse(username)
|
|
||||||
const quota = 1000000000 // 1GB
|
|
||||||
percent := 100 * used / quota
|
|
||||||
if percent < 90 {
|
|
||||||
return // OK
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf(template, used, percent)
|
|
||||||
notifyUser(username, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-factored
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package display
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopl.io/ch7/eval"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE: we can't use !+..!- comments to excerpt these tests
|
|
||||||
// into the book because it defeats the Example mechanism,
|
|
||||||
// which requires the // Output comment to be at the end
|
|
||||||
// of the function.
|
|
||||||
|
|
||||||
func Example_expr() {
|
|
||||||
e, _ := eval.Parse("sqrt(A / pi)")
|
|
||||||
Display("e", e)
|
|
||||||
// Output:
|
|
||||||
// Display e (eval.call):
|
|
||||||
// e.fn = "sqrt"
|
|
||||||
// e.args[0].type = eval.binary
|
|
||||||
// e.args[0].value.op = 47
|
|
||||||
// e.args[0].value.x.type = eval.Var
|
|
||||||
// e.args[0].value.x.value = "A"
|
|
||||||
// e.args[0].value.y.type = eval.Var
|
|
||||||
// e.args[0].value.y.value = "pi"
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_slice() {
|
|
||||||
Display("slice", []*int{new(int), nil})
|
|
||||||
// Output:
|
|
||||||
// Display slice ([]*int):
|
|
||||||
// (*slice[0]) = 0
|
|
||||||
// slice[1] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_nilInterface() {
|
|
||||||
var w io.Writer
|
|
||||||
Display("w", w)
|
|
||||||
// Output:
|
|
||||||
// Display w (<nil>):
|
|
||||||
// w = invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_ptrToInterface() {
|
|
||||||
var w io.Writer
|
|
||||||
Display("&w", &w)
|
|
||||||
// Output:
|
|
||||||
// Display &w (*io.Writer):
|
|
||||||
// (*&w) = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_struct() {
|
|
||||||
Display("x", struct{ x interface{} }{3})
|
|
||||||
// Output:
|
|
||||||
// Display x (struct { x interface {} }):
|
|
||||||
// x.x.type = int
|
|
||||||
// x.x.value = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_interface() {
|
|
||||||
var i interface{} = 3
|
|
||||||
Display("i", i)
|
|
||||||
// Output:
|
|
||||||
// Display i (int):
|
|
||||||
// i = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_ptrToInterface2() {
|
|
||||||
var i interface{} = 3
|
|
||||||
Display("&i", &i)
|
|
||||||
// Output:
|
|
||||||
// Display &i (*interface {}):
|
|
||||||
// (*&i).type = int
|
|
||||||
// (*&i).value = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_array() {
|
|
||||||
Display("x", [1]interface{}{3})
|
|
||||||
// Output:
|
|
||||||
// Display x ([1]interface {}):
|
|
||||||
// x[0].type = int
|
|
||||||
// x[0].value = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_movie() {
|
|
||||||
//!+movie
|
|
||||||
type Movie struct {
|
|
||||||
Title, Subtitle string
|
|
||||||
Year int
|
|
||||||
Color bool
|
|
||||||
Actor map[string]string
|
|
||||||
Oscars []string
|
|
||||||
Sequel *string
|
|
||||||
}
|
|
||||||
//!-movie
|
|
||||||
//!+strangelove
|
|
||||||
strangelove := Movie{
|
|
||||||
Title: "Dr. Strangelove",
|
|
||||||
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
|
|
||||||
Year: 1964,
|
|
||||||
Color: false,
|
|
||||||
Actor: map[string]string{
|
|
||||||
"Dr. Strangelove": "Peter Sellers",
|
|
||||||
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
|
|
||||||
"Pres. Merkin Muffley": "Peter Sellers",
|
|
||||||
"Gen. Buck Turgidson": "George C. Scott",
|
|
||||||
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
|
|
||||||
`Maj. T.J. "King" Kong`: "Slim Pickens",
|
|
||||||
},
|
|
||||||
|
|
||||||
Oscars: []string{
|
|
||||||
"Best Actor (Nomin.)",
|
|
||||||
"Best Adapted Screenplay (Nomin.)",
|
|
||||||
"Best Director (Nomin.)",
|
|
||||||
"Best Picture (Nomin.)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
//!-strangelove
|
|
||||||
Display("strangelove", strangelove)
|
|
||||||
|
|
||||||
// We don't use an Output: comment since displaying
|
|
||||||
// a map is nondeterministic.
|
|
||||||
/*
|
|
||||||
//!+output
|
|
||||||
Display strangelove (display.Movie):
|
|
||||||
strangelove.Title = "Dr. Strangelove"
|
|
||||||
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
|
|
||||||
strangelove.Year = 1964
|
|
||||||
strangelove.Color = false
|
|
||||||
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
|
|
||||||
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
|
|
||||||
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
|
|
||||||
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
|
|
||||||
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
|
|
||||||
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
|
|
||||||
strangelove.Oscars[0] = "Best Actor (Nomin.)"
|
|
||||||
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
|
|
||||||
strangelove.Oscars[2] = "Best Director (Nomin.)"
|
|
||||||
strangelove.Oscars[3] = "Best Picture (Nomin.)"
|
|
||||||
strangelove.Sequel = nil
|
|
||||||
//!-output
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test ensures that the program terminates without crashing.
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
// Some other values (YMMV)
|
|
||||||
Display("os.Stderr", os.Stderr)
|
|
||||||
// Output:
|
|
||||||
// Display os.Stderr (*os.File):
|
|
||||||
// (*(*os.Stderr).file).fd = 2
|
|
||||||
// (*(*os.Stderr).file).name = "/dev/stderr"
|
|
||||||
// (*(*os.Stderr).file).nepipe = 0
|
|
||||||
|
|
||||||
var w io.Writer = os.Stderr
|
|
||||||
Display("&w", &w)
|
|
||||||
// Output:
|
|
||||||
// Display &w (*io.Writer):
|
|
||||||
// (*&w).type = *os.File
|
|
||||||
// (*(*(*&w).value).file).fd = 2
|
|
||||||
// (*(*(*&w).value).file).name = "/dev/stderr"
|
|
||||||
// (*(*(*&w).value).file).nepipe = 0
|
|
||||||
|
|
||||||
var locker sync.Locker = new(sync.Mutex)
|
|
||||||
Display("(&locker)", &locker)
|
|
||||||
// Output:
|
|
||||||
// Display (&locker) (*sync.Locker):
|
|
||||||
// (*(&locker)).type = *sync.Mutex
|
|
||||||
// (*(*(&locker)).value).state = 0
|
|
||||||
// (*(*(&locker)).value).sema = 0
|
|
||||||
|
|
||||||
Display("locker", locker)
|
|
||||||
// Output:
|
|
||||||
// Display locker (*sync.Mutex):
|
|
||||||
// (*locker).state = 0
|
|
||||||
// (*locker).sema = 0
|
|
||||||
// (*(&locker)) = nil
|
|
||||||
|
|
||||||
locker = nil
|
|
||||||
Display("(&locker)", &locker)
|
|
||||||
// Output:
|
|
||||||
// Display (&locker) (*sync.Locker):
|
|
||||||
// (*(&locker)) = nil
|
|
||||||
|
|
||||||
ips, _ := net.LookupHost("golang.org")
|
|
||||||
Display("ips", ips)
|
|
||||||
// Output:
|
|
||||||
// Display ips ([]string):
|
|
||||||
// ips[0] = "173.194.68.141"
|
|
||||||
// ips[1] = "2607:f8b0:400d:c06::8d"
|
|
||||||
|
|
||||||
// Even metarecursion! (YMMV)
|
|
||||||
Display("rV", reflect.ValueOf(os.Stderr))
|
|
||||||
// Output:
|
|
||||||
// Display rV (reflect.Value):
|
|
||||||
// (*rV.typ).size = 8
|
|
||||||
// (*rV.typ).ptrdata = 8
|
|
||||||
// (*rV.typ).hash = 871609668
|
|
||||||
// (*rV.typ)._ = 0
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// a pointer that points to itself
|
|
||||||
type P *P
|
|
||||||
var p P
|
|
||||||
p = &p
|
|
||||||
if false {
|
|
||||||
Display("p", p)
|
|
||||||
// Output:
|
|
||||||
// Display p (display.P):
|
|
||||||
// ...stuck, no output...
|
|
||||||
}
|
|
||||||
|
|
||||||
// a map that contains itself
|
|
||||||
type M map[string]M
|
|
||||||
m := make(M)
|
|
||||||
m[""] = m
|
|
||||||
if false {
|
|
||||||
Display("m", m)
|
|
||||||
// Output:
|
|
||||||
// Display m (display.M):
|
|
||||||
// ...stuck, no output...
|
|
||||||
}
|
|
||||||
|
|
||||||
// a slice that contains itself
|
|
||||||
type S []S
|
|
||||||
s := make(S, 1)
|
|
||||||
s[0] = s
|
|
||||||
if false {
|
|
||||||
Display("s", s)
|
|
||||||
// Output:
|
|
||||||
// Display s (display.S):
|
|
||||||
// ...stuck, no output...
|
|
||||||
}
|
|
||||||
|
|
||||||
// a linked list that eats its own tail
|
|
||||||
type Cycle struct {
|
|
||||||
Value int
|
|
||||||
Tail *Cycle
|
|
||||||
}
|
|
||||||
var c Cycle
|
|
||||||
c = Cycle{42, &c}
|
|
||||||
if false {
|
|
||||||
Display("c", c)
|
|
||||||
// Output:
|
|
||||||
// Display c (display.Cycle):
|
|
||||||
// c.Value = 42
|
|
||||||
// (*c.Tail).Value = 42
|
|
||||||
// (*(*c.Tail).Tail).Value = 42
|
|
||||||
// ...ad infinitum...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// 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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 362.
|
|
||||||
// This is the version that appears in print,
|
|
||||||
// but it does not comply with the proposed
|
|
||||||
// rules for passing pointers between Go and C.
|
|
||||||
// (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md)
|
|
||||||
// See gopl.io/ch13/bzip for an updated version.
|
|
||||||
|
|
||||||
//!+
|
|
||||||
/* This file is gopl.io/ch13/bzip/bzip2.c, */
|
|
||||||
/* a simple wrapper for libbzip2 suitable for cgo. */
|
|
||||||
#include <bzlib.h>
|
|
||||||
|
|
||||||
int bz2compress(bz_stream *s, int action,
|
|
||||||
char *in, unsigned *inlen, char *out, unsigned *outlen) {
|
|
||||||
s->next_in = in;
|
|
||||||
s->avail_in = *inlen;
|
|
||||||
s->next_out = out;
|
|
||||||
s->avail_out = *outlen;
|
|
||||||
int r = BZ2_bzCompress(s, action);
|
|
||||||
*inlen -= s->avail_in;
|
|
||||||
*outlen -= s->avail_out;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 362.
|
|
||||||
// This is the version that appears in print,
|
|
||||||
// but it does not comply with the proposed
|
|
||||||
// rules for passing pointers between Go and C.
|
|
||||||
// (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md)
|
|
||||||
// See gopl.io/ch13/bzip for an updated version.
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
|
|
||||||
package bzip
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -I/usr/include
|
|
||||||
#cgo LDFLAGS: -L/usr/lib -lbz2
|
|
||||||
#include <bzlib.h>
|
|
||||||
int bz2compress(bz_stream *s, int action,
|
|
||||||
char *in, unsigned *inlen, char *out, unsigned *outlen);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type writer struct {
|
|
||||||
w io.Writer // underlying output stream
|
|
||||||
stream *C.bz_stream
|
|
||||||
outbuf [64 * 1024]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter returns a writer for bzip2-compressed streams.
|
|
||||||
func NewWriter(out io.Writer) io.WriteCloser {
|
|
||||||
const (
|
|
||||||
blockSize = 9
|
|
||||||
verbosity = 0
|
|
||||||
workFactor = 30
|
|
||||||
)
|
|
||||||
w := &writer{w: out, stream: new(C.bz_stream)}
|
|
||||||
C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
|
|
||||||
//!+write
|
|
||||||
func (w *writer) Write(data []byte) (int, error) {
|
|
||||||
if w.stream == nil {
|
|
||||||
panic("closed")
|
|
||||||
}
|
|
||||||
var total int // uncompressed bytes written
|
|
||||||
|
|
||||||
for len(data) > 0 {
|
|
||||||
inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
|
|
||||||
C.bz2compress(w.stream, C.BZ_RUN,
|
|
||||||
(*C.char)(unsafe.Pointer(&data[0])), &inlen,
|
|
||||||
(*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
|
|
||||||
total += int(inlen)
|
|
||||||
data = data[inlen:]
|
|
||||||
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
|
|
||||||
return total, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-write
|
|
||||||
|
|
||||||
//!+close
|
|
||||||
// Close flushes the compressed data and closes the stream.
|
|
||||||
// It does not close the underlying io.Writer.
|
|
||||||
func (w *writer) Close() error {
|
|
||||||
if w.stream == nil {
|
|
||||||
panic("closed")
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
C.BZ2_bzCompressEnd(w.stream)
|
|
||||||
w.stream = nil
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
inlen, outlen := C.uint(0), C.uint(cap(w.outbuf))
|
|
||||||
r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen,
|
|
||||||
(*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
|
|
||||||
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r == C.BZ_STREAM_END {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-close
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package bzip_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/bzip2" // reader
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopl.io/ch13/bzip" // writer
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBzip2(t *testing.T) {
|
|
||||||
var compressed, uncompressed bytes.Buffer
|
|
||||||
w := bzip.NewWriter(&compressed)
|
|
||||||
|
|
||||||
// Write a repetitive message in a million pieces,
|
|
||||||
// compressing one copy but not the other.
|
|
||||||
tee := io.MultiWriter(w, &uncompressed)
|
|
||||||
for i := 0; i < 1000000; i++ {
|
|
||||||
io.WriteString(tee, "hello")
|
|
||||||
}
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the size of the compressed stream.
|
|
||||||
if got, want := compressed.Len(), 255; got != want {
|
|
||||||
t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress and compare with original.
|
|
||||||
var decompressed bytes.Buffer
|
|
||||||
io.Copy(&decompressed, bzip2.NewReader(&compressed))
|
|
||||||
if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) {
|
|
||||||
t.Error("decompression yielded a different message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 362.
|
|
||||||
//
|
|
||||||
// The version of this program that appeared in the first and second
|
|
||||||
// printings did not comply with the proposed rules for passing
|
|
||||||
// pointers between Go and C, described here:
|
|
||||||
// https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md
|
|
||||||
//
|
|
||||||
// The version below, which appears in the third printing,
|
|
||||||
// has been corrected. See bzip2.go for explanation.
|
|
||||||
|
|
||||||
//!+
|
|
||||||
/* This file is gopl.io/ch13/bzip/bzip2.c, */
|
|
||||||
/* a simple wrapper for libbzip2 suitable for cgo. */
|
|
||||||
#include <bzlib.h>
|
|
||||||
|
|
||||||
int bz2compress(bz_stream *s, int action,
|
|
||||||
char *in, unsigned *inlen, char *out, unsigned *outlen) {
|
|
||||||
s->next_in = in;
|
|
||||||
s->avail_in = *inlen;
|
|
||||||
s->next_out = out;
|
|
||||||
s->avail_out = *outlen;
|
|
||||||
int r = BZ2_bzCompress(s, action);
|
|
||||||
*inlen -= s->avail_in;
|
|
||||||
*outlen -= s->avail_out;
|
|
||||||
s->next_in = s->next_out = NULL;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 362.
|
|
||||||
//
|
|
||||||
// The version of this program that appeared in the first and second
|
|
||||||
// printings did not comply with the proposed rules for passing
|
|
||||||
// pointers between Go and C, described here:
|
|
||||||
// https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md
|
|
||||||
//
|
|
||||||
// The rules forbid a C function like bz2compress from storing 'in'
|
|
||||||
// and 'out' (pointers to variables allocated by Go) into the Go
|
|
||||||
// variable 's', even temporarily.
|
|
||||||
//
|
|
||||||
// The version below, which appears in the third printing, has been
|
|
||||||
// corrected. To comply with the rules, the bz_stream variable must
|
|
||||||
// be allocated by C code. We have introduced two C functions,
|
|
||||||
// bz2alloc and bz2free, to allocate and free instances of the
|
|
||||||
// bz_stream type. Also, we have changed bz2compress so that before
|
|
||||||
// it returns, it clears the fields of the bz_stream that contain
|
|
||||||
// pointers to Go variables.
|
|
||||||
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
|
|
||||||
package bzip
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -I/usr/include
|
|
||||||
#cgo LDFLAGS: -L/usr/lib -lbz2
|
|
||||||
#include <bzlib.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); }
|
|
||||||
int bz2compress(bz_stream *s, int action,
|
|
||||||
char *in, unsigned *inlen, char *out, unsigned *outlen);
|
|
||||||
void bz2free(bz_stream* s) { free(s); }
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type writer struct {
|
|
||||||
w io.Writer // underlying output stream
|
|
||||||
stream *C.bz_stream
|
|
||||||
outbuf [64 * 1024]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter returns a writer for bzip2-compressed streams.
|
|
||||||
func NewWriter(out io.Writer) io.WriteCloser {
|
|
||||||
const blockSize = 9
|
|
||||||
const verbosity = 0
|
|
||||||
const workFactor = 30
|
|
||||||
w := &writer{w: out, stream: C.bz2alloc()}
|
|
||||||
C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
|
|
||||||
//!+write
|
|
||||||
func (w *writer) Write(data []byte) (int, error) {
|
|
||||||
if w.stream == nil {
|
|
||||||
panic("closed")
|
|
||||||
}
|
|
||||||
var total int // uncompressed bytes written
|
|
||||||
|
|
||||||
for len(data) > 0 {
|
|
||||||
inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
|
|
||||||
C.bz2compress(w.stream, C.BZ_RUN,
|
|
||||||
(*C.char)(unsafe.Pointer(&data[0])), &inlen,
|
|
||||||
(*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
|
|
||||||
total += int(inlen)
|
|
||||||
data = data[inlen:]
|
|
||||||
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
|
|
||||||
return total, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-write
|
|
||||||
|
|
||||||
//!+close
|
|
||||||
// Close flushes the compressed data and closes the stream.
|
|
||||||
// It does not close the underlying io.Writer.
|
|
||||||
func (w *writer) Close() error {
|
|
||||||
if w.stream == nil {
|
|
||||||
panic("closed")
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
C.BZ2_bzCompressEnd(w.stream)
|
|
||||||
C.bz2free(w.stream)
|
|
||||||
w.stream = nil
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
inlen, outlen := C.uint(0), C.uint(cap(w.outbuf))
|
|
||||||
r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen,
|
|
||||||
(*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
|
|
||||||
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r == C.BZ_STREAM_END {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-close
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package bzip_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/bzip2" // reader
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopl.io/ch13/bzip" // writer
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBzip2(t *testing.T) {
|
|
||||||
var compressed, uncompressed bytes.Buffer
|
|
||||||
w := bzip.NewWriter(&compressed)
|
|
||||||
|
|
||||||
// Write a repetitive message in a million pieces,
|
|
||||||
// compressing one copy but not the other.
|
|
||||||
tee := io.MultiWriter(w, &uncompressed)
|
|
||||||
for i := 0; i < 1000000; i++ {
|
|
||||||
io.WriteString(tee, "hello")
|
|
||||||
}
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the size of the compressed stream.
|
|
||||||
if got, want := compressed.Len(), 255; got != want {
|
|
||||||
t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress and compare with original.
|
|
||||||
var decompressed bytes.Buffer
|
|
||||||
io.Copy(&decompressed, bzip2.NewReader(&compressed))
|
|
||||||
if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) {
|
|
||||||
t.Error("decompression yielded a different message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 365.
|
|
||||||
|
|
||||||
//!+
|
|
||||||
|
|
||||||
// Bzipper reads input, bzip2-compresses it, and writes it out.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopl.io/ch13/bzip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
w := bzip.NewWriter(os.Stdout)
|
|
||||||
if _, err := io.Copy(w, os.Stdin); err != nil {
|
|
||||||
log.Fatalf("bzipper: %v\n", err)
|
|
||||||
}
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
log.Fatalf("bzipper: close: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package equal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEqual(t *testing.T) {
|
|
||||||
one, oneAgain, two := 1, 1, 2
|
|
||||||
|
|
||||||
type CyclePtr *CyclePtr
|
|
||||||
var cyclePtr1, cyclePtr2 CyclePtr
|
|
||||||
cyclePtr1 = &cyclePtr1
|
|
||||||
cyclePtr2 = &cyclePtr2
|
|
||||||
|
|
||||||
type CycleSlice []CycleSlice
|
|
||||||
var cycleSlice = make(CycleSlice, 1)
|
|
||||||
cycleSlice[0] = cycleSlice
|
|
||||||
|
|
||||||
ch1, ch2 := make(chan int), make(chan int)
|
|
||||||
var ch1ro <-chan int = ch1
|
|
||||||
|
|
||||||
type mystring string
|
|
||||||
|
|
||||||
var iface1, iface1Again, iface2 interface{} = &one, &oneAgain, &two
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
x, y interface{}
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
// basic types
|
|
||||||
{1, 1, true},
|
|
||||||
{1, 2, false}, // different values
|
|
||||||
{1, 1.0, false}, // different types
|
|
||||||
{"foo", "foo", true},
|
|
||||||
{"foo", "bar", false},
|
|
||||||
{mystring("foo"), "foo", false}, // different types
|
|
||||||
// slices
|
|
||||||
{[]string{"foo"}, []string{"foo"}, true},
|
|
||||||
{[]string{"foo"}, []string{"bar"}, false},
|
|
||||||
{[]string{}, []string(nil), true},
|
|
||||||
// slice cycles
|
|
||||||
{cycleSlice, cycleSlice, true},
|
|
||||||
// maps
|
|
||||||
{
|
|
||||||
map[string][]int{"foo": {1, 2, 3}},
|
|
||||||
map[string][]int{"foo": {1, 2, 3}},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string][]int{"foo": {1, 2, 3}},
|
|
||||||
map[string][]int{"foo": {1, 2, 3, 4}},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string][]int{},
|
|
||||||
map[string][]int(nil),
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
// pointers
|
|
||||||
{&one, &one, true},
|
|
||||||
{&one, &two, false},
|
|
||||||
{&one, &oneAgain, true},
|
|
||||||
{new(bytes.Buffer), new(bytes.Buffer), true},
|
|
||||||
// pointer cycles
|
|
||||||
{cyclePtr1, cyclePtr1, true},
|
|
||||||
{cyclePtr2, cyclePtr2, true},
|
|
||||||
{cyclePtr1, cyclePtr2, true}, // they're deeply equal
|
|
||||||
// functions
|
|
||||||
{(func())(nil), (func())(nil), true},
|
|
||||||
{(func())(nil), func() {}, false},
|
|
||||||
{func() {}, func() {}, false},
|
|
||||||
// arrays
|
|
||||||
{[...]int{1, 2, 3}, [...]int{1, 2, 3}, true},
|
|
||||||
{[...]int{1, 2, 3}, [...]int{1, 2, 4}, false},
|
|
||||||
// channels
|
|
||||||
{ch1, ch1, true},
|
|
||||||
{ch1, ch2, false},
|
|
||||||
{ch1ro, ch1, false}, // NOTE: not equal
|
|
||||||
// interfaces
|
|
||||||
{&iface1, &iface1, true},
|
|
||||||
{&iface1, &iface2, false},
|
|
||||||
{&iface1Again, &iface1, true},
|
|
||||||
} {
|
|
||||||
if Equal(test.x, test.y) != test.want {
|
|
||||||
t.Errorf("Equal(%v, %v) = %t",
|
|
||||||
test.x, test.y, !test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_equal() {
|
|
||||||
//!+
|
|
||||||
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true"
|
|
||||||
fmt.Println(Equal([]string{"foo"}, []string{"bar"})) // "false"
|
|
||||||
fmt.Println(Equal([]string(nil), []string{})) // "true"
|
|
||||||
fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
|
|
||||||
//!-
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
// false
|
|
||||||
// true
|
|
||||||
// true
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_equalCycle() {
|
|
||||||
//!+cycle
|
|
||||||
// Circular linked lists a -> b -> a and c -> c.
|
|
||||||
type link struct {
|
|
||||||
value string
|
|
||||||
tail *link
|
|
||||||
}
|
|
||||||
a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
|
|
||||||
a.tail, b.tail, c.tail = b, a, c
|
|
||||||
fmt.Println(Equal(a, a)) // "true"
|
|
||||||
fmt.Println(Equal(b, b)) // "true"
|
|
||||||
fmt.Println(Equal(c, c)) // "true"
|
|
||||||
fmt.Println(Equal(a, b)) // "false"
|
|
||||||
fmt.Println(Equal(a, c)) // "false"
|
|
||||||
//!-cycle
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
// true
|
|
||||||
// true
|
|
||||||
// false
|
|
||||||
// false
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// 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))])
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go 1.6, 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
|
|
||||||
//
|
|
||||||
// Go 1.6, 2.5GHz Intel Core i5
|
|
||||||
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// Go 1.7, 3.5GHz Xeon
|
|
||||||
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
|
|
||||||
// BenchmarkPopCount-12 2000000000 0.28 ns/op
|
|
||||||
// BenchmarkBitCount-12 2000000000 0.27 ns/op
|
|
||||||
// BenchmarkPopCountByClearing-12 100000000 18.5 ns/op
|
|
||||||
// BenchmarkPopCountByShifting-12 20000000 70.1 ns/op
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// 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) }
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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) }
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// 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) }
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package tempconv
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func Example_one() {
|
|
||||||
{
|
|
||||||
//!+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 Example_two() {
|
|
||||||
//!+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
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// 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:]
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// 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"
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 74.
|
|
||||||
|
|
||||||
// Printints demonstrates the use of bytes.Buffer to format a string.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
//!+
|
|
||||||
// intsToString is like fmt.Sprint(values) but adds commas.
|
|
||||||
func intsToString(values []int) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteByte('[')
|
|
||||||
for i, v := range values {
|
|
||||||
if i > 0 {
|
|
||||||
buf.WriteString(", ")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "%d", v)
|
|
||||||
}
|
|
||||||
buf.WriteByte(']')
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]"
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// 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}
|
|
||||||
//!-
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// 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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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"))
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// 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}}</a></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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
package treesort_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopl.io/ch4/treesort"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSort(t *testing.T) {
|
|
||||||
data := make([]int, 50)
|
|
||||||
for i := range data {
|
|
||||||
data[i] = rand.Int() % 50
|
|
||||||
}
|
|
||||||
treesort.Sort(data)
|
|
||||||
if !sort.IntsAreSorted(data) {
|
|
||||||
t.Errorf("not sorted: %v", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
|
||||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
||||||
|
|
||||||
// See page 122.
|
|
||||||
//!+main
|
|
||||||
|
|
||||||
// Findlinks1 prints the links in an HTML document read from standard input.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
doc, err := html.Parse(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
for _, link := range visit(nil, doc) {
|
|
||||||
fmt.Println(link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-main
|
|
||||||
|
|
||||||
//!+visit
|
|
||||||
// visit appends to links each link found in n and returns the result.
|
|
||||||
func visit(links []string, n *html.Node) []string {
|
|
||||||
if n.Type == html.ElementNode && n.Data == "a" {
|
|
||||||
for _, a := range n.Attr {
|
|
||||||
if a.Key == "href" {
|
|
||||||
links = append(links, a.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
||||||
links = visit(links, c)
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-visit
|
|
||||||
|
|
||||||
/*
|
|
||||||
//!+html
|
|
||||||
package html
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Type NodeType
|
|
||||||
Data string
|
|
||||||
Attr []Attribute
|
|
||||||
FirstChild, NextSibling *Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeType int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrorNode NodeType = iota
|
|
||||||
TextNode
|
|
||||||
DocumentNode
|
|
||||||
ElementNode
|
|
||||||
CommentNode
|
|
||||||
DoctypeNode
|
|
||||||
)
|
|
||||||
|
|
||||||
type Attribute struct {
|
|
||||||
Key, Val string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(r io.Reader) (*Node, error)
|
|
||||||
//!-html
|
|
||||||
*/
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// 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"
|
|
||||||
}
|
|
||||||
|
|
||||||
//!-
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user