Update to gobook@c6d7a22edd03e7738c38d5baa2174ff325d8d863
This commit is contained in:
parent
fce1727c4c
commit
30090035de
|
@ -13,17 +13,21 @@ import (
|
|||
"image/color"
|
||||
"image/gif"
|
||||
"io"
|
||||
//!-main
|
||||
"log"
|
||||
//!+main
|
||||
"math"
|
||||
"math/rand"
|
||||
//!-main
|
||||
"net/http"
|
||||
//!+main
|
||||
"os"
|
||||
)
|
||||
|
||||
//!-main
|
||||
// Packages not needed by version in book.
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//!+main
|
||||
|
||||
var palette = []color.Color{color.White, color.Black}
|
||||
|
||||
const (
|
||||
|
@ -33,6 +37,11 @@ const (
|
|||
|
||||
func main() {
|
||||
//!-main
|
||||
// The sequence of images is deterministic unless we seed
|
||||
// the pseudo-random number generator using the current time.
|
||||
// Thanks to Randall McPherson for pointing out the omission.
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "web" {
|
||||
//!+http
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 295.
|
||||
|
||||
// The cross command prints the values of GOOS and GOARCH for this target.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
fmt.Println(runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 287.
|
||||
|
||||
//!+main
|
||||
|
||||
// The jpeg command reads a PNG image from the standard input
|
||||
// and writes it as a JPEG image to the standard output.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
_ "image/png" // register PNG decoder
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := toJPEG(os.Stdin, os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func toJPEG(in io.Reader, out io.Writer) error {
|
||||
img, kind, err := image.Decode(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Input format =", kind)
|
||||
return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
|
||||
}
|
||||
|
||||
//!-main
|
||||
|
||||
/*
|
||||
//!+with
|
||||
$ go build gopl.io/ch3/mandelbrot
|
||||
$ go build gopl.io/ch10/jpeg
|
||||
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
|
||||
Input format = png
|
||||
//!-with
|
||||
|
||||
//!+without
|
||||
$ go build gopl.io/ch10/jpeg
|
||||
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
|
||||
jpeg: image: unknown format
|
||||
//!-without
|
||||
*/
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 308.
|
||||
//!+
|
||||
|
||||
// Echo prints its command-line arguments.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
n = flag.Bool("n", false, "omit trailing newline")
|
||||
s = flag.String("s", " ", "separator")
|
||||
)
|
||||
|
||||
var out io.Writer = os.Stdout // modified during testing
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := echo(!*n, *s, flag.Args()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "echo: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func echo(newline bool, sep string, args []string) error {
|
||||
fmt.Fprint(out, strings.Join(args, sep))
|
||||
if newline {
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// Test of echo command. Run with: go test gopl.io/ch11/echo
|
||||
|
||||
//!+
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEcho(t *testing.T) {
|
||||
var tests = []struct {
|
||||
newline bool
|
||||
sep string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{true, "", []string{}, "\n"},
|
||||
{false, "", []string{}, ""},
|
||||
{true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"},
|
||||
{true, ",", []string{"a", "b", "c"}, "a,b,c\n"},
|
||||
{false, ":", []string{"1", "2", "3"}, "1:2:3"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
descr := fmt.Sprintf("echo(%v, %q, %q)",
|
||||
test.newline, test.sep, test.args)
|
||||
|
||||
out = new(bytes.Buffer) // captured output
|
||||
if err := echo(test.newline, test.sep, test.args); err != nil {
|
||||
t.Errorf("%s failed: %v", descr, err)
|
||||
continue
|
||||
}
|
||||
got := out.(*bytes.Buffer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("%s = %q, want %q", descr, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 311.
|
||||
|
||||
// Package storage is part of a hypothetical cloud storage server.
|
||||
//!+main
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
func bytesInUse(username string) int64 { return 0 /* ... */ }
|
||||
|
||||
// Email sender configuration.
|
||||
// NOTE: never put passwords in source code!
|
||||
const sender = "notifications@example.com"
|
||||
const password = "correcthorsebatterystaple"
|
||||
const hostname = "smtp.example.com"
|
||||
|
||||
const template = `Warning: you are using %d bytes of storage,
|
||||
%d%% of your quota.`
|
||||
|
||||
func CheckQuota(username string) {
|
||||
used := bytesInUse(username)
|
||||
const quota = 1000000000 // 1GB
|
||||
percent := 100 * used / quota
|
||||
if percent < 90 {
|
||||
return // OK
|
||||
}
|
||||
msg := fmt.Sprintf(template, used, percent)
|
||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
||||
[]string{username}, []byte(msg))
|
||||
if err != nil {
|
||||
log.Printf("smtp.SendMail(%s) failed: %s", username, err)
|
||||
}
|
||||
}
|
||||
|
||||
//!-main
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
//!+test
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
||||
var notifiedUser, notifiedMsg string
|
||||
notifyUser = func(user, msg string) {
|
||||
notifiedUser, notifiedMsg = user, msg
|
||||
}
|
||||
|
||||
const user = "joe@example.org"
|
||||
|
||||
// Simulate a 980MB-used condition for this user.
|
||||
// NOTE: this differs slightly from the printed version.
|
||||
usage["joe@example.org"] = 980000000
|
||||
|
||||
CheckQuota(user)
|
||||
if notifiedUser == "" && notifiedMsg == "" {
|
||||
t.Fatalf("notifyUser not called")
|
||||
}
|
||||
if notifiedUser != user {
|
||||
t.Errorf("wrong user (%s) notified, want %s",
|
||||
notifiedUser, user)
|
||||
}
|
||||
const wantSubstring = "98% of your quota"
|
||||
if !strings.Contains(notifiedMsg, wantSubstring) {
|
||||
t.Errorf("unexpected notification message <<%s>>, "+
|
||||
"want substring %q", notifiedMsg, wantSubstring)
|
||||
}
|
||||
}
|
||||
|
||||
//!-test
|
||||
|
||||
/*
|
||||
//!+defer
|
||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
||||
// Save and restore original notifyUser.
|
||||
saved := notifyUser
|
||||
defer func() { notifyUser = saved }()
|
||||
|
||||
// Install the test's fake notifyUser.
|
||||
var notifiedUser, notifiedMsg string
|
||||
notifyUser = func(user, msg string) {
|
||||
notifiedUser, notifiedMsg = user, msg
|
||||
}
|
||||
// ...rest of test...
|
||||
}
|
||||
//!-defer
|
||||
*/
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 312.
|
||||
|
||||
// Package storage is part of a hypothetical cloud storage server.
|
||||
//!+main
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
// NOTE: this differs slightly from the printed version.
|
||||
var usage = make(map[string]int64)
|
||||
|
||||
func bytesInUse(username string) int64 { return usage[username] }
|
||||
|
||||
// E-mail sender configuration.
|
||||
// NOTE: never put passwords in source code!
|
||||
const sender = "notifications@example.com"
|
||||
const password = "correcthorsebatterystaple"
|
||||
const hostname = "smtp.example.com"
|
||||
|
||||
const template = `Warning: you are using %d bytes of storage,
|
||||
%d%% of your quota.`
|
||||
|
||||
//!+factored
|
||||
var notifyUser = func(username, msg string) {
|
||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
||||
[]string{username}, []byte(msg))
|
||||
if err != nil {
|
||||
log.Printf("smtp.SendEmail(%s) failed: %s", username, err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckQuota(username string) {
|
||||
used := bytesInUse(username)
|
||||
const quota = 1000000000 // 1GB
|
||||
percent := 100 * used / quota
|
||||
if percent < 90 {
|
||||
return // OK
|
||||
}
|
||||
msg := fmt.Sprintf(template, used, percent)
|
||||
notifyUser(username, msg)
|
||||
}
|
||||
|
||||
//!-factored
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 303.
|
||||
//!+
|
||||
|
||||
// Package word provides utilities for word games.
|
||||
package word
|
||||
|
||||
// IsPalindrome reports whether s reads the same forward and backward.
|
||||
// (Our first attempt.)
|
||||
func IsPalindrome(s string) bool {
|
||||
for i := range s {
|
||||
if s[i] != s[len(s)-1-i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
//!+test
|
||||
package word
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPalindrome(t *testing.T) {
|
||||
if !IsPalindrome("detartrated") {
|
||||
t.Error(`IsPalindrome("detartrated") = false`)
|
||||
}
|
||||
if !IsPalindrome("kayak") {
|
||||
t.Error(`IsPalindrome("kayak") = false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonPalindrome(t *testing.T) {
|
||||
if IsPalindrome("palindrome") {
|
||||
t.Error(`IsPalindrome("palindrome") = true`)
|
||||
}
|
||||
}
|
||||
|
||||
//!-test
|
||||
|
||||
// The tests below are expected to fail.
|
||||
// See package gopl.io/ch11/word2 for the fix.
|
||||
|
||||
//!+more
|
||||
func TestFrenchPalindrome(t *testing.T) {
|
||||
if !IsPalindrome("été") {
|
||||
t.Error(`IsPalindrome("été") = false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanalPalindrome(t *testing.T) {
|
||||
input := "A man, a plan, a canal: Panama"
|
||||
if !IsPalindrome(input) {
|
||||
t.Errorf(`IsPalindrome(%q) = false`, input)
|
||||
}
|
||||
}
|
||||
|
||||
//!-more
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 305.
|
||||
//!+
|
||||
|
||||
// Package word provides utilities for word games.
|
||||
package word
|
||||
|
||||
import "unicode"
|
||||
|
||||
// IsPalindrome reports whether s reads the same forward and backward.
|
||||
// Letter case is ignored, as are non-letters.
|
||||
func IsPalindrome(s string) bool {
|
||||
var letters []rune
|
||||
for _, r := range s {
|
||||
if unicode.IsLetter(r) {
|
||||
letters = append(letters, unicode.ToLower(r))
|
||||
}
|
||||
}
|
||||
for i := range letters {
|
||||
if letters[i] != letters[len(letters)-1-i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package word
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
//!+bench
|
||||
|
||||
import "testing"
|
||||
|
||||
//!-bench
|
||||
|
||||
//!+test
|
||||
func TestIsPalindrome(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"", true},
|
||||
{"a", true},
|
||||
{"aa", true},
|
||||
{"ab", false},
|
||||
{"kayak", true},
|
||||
{"detartrated", true},
|
||||
{"A man, a plan, a canal: Panama", true},
|
||||
{"Evil I did dwell; lewd did I live.", true},
|
||||
{"Able was I ere I saw Elba", true},
|
||||
{"été", true},
|
||||
{"Et se resservir, ivresse reste.", true},
|
||||
{"palindrome", false}, // non-palindrome
|
||||
{"desserts", false}, // semi-palindrome
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got := IsPalindrome(test.input); got != test.want {
|
||||
t.Errorf("IsPalindrome(%q) = %v", test.input, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-test
|
||||
|
||||
//!+bench
|
||||
func BenchmarkIsPalindrome(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsPalindrome("A man, a plan, a canal: Panama")
|
||||
}
|
||||
}
|
||||
|
||||
//!-bench
|
||||
|
||||
//!+example
|
||||
|
||||
func ExampleIsPalindrome() {
|
||||
fmt.Println(IsPalindrome("A man, a plan, a canal: Panama"))
|
||||
fmt.Println(IsPalindrome("palindrome"))
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
//!-example
|
||||
|
||||
/*
|
||||
//!+random
|
||||
import "math/rand"
|
||||
|
||||
//!-random
|
||||
*/
|
||||
|
||||
//!+random
|
||||
// randomPalindrome returns a palindrome whose length and contents
|
||||
// are derived from the pseudo-random number generator rng.
|
||||
func randomPalindrome(rng *rand.Rand) string {
|
||||
n := rng.Intn(25) // random length up to 24
|
||||
runes := make([]rune, n)
|
||||
for i := 0; i < (n+1)/2; i++ {
|
||||
r := rune(rng.Intn(0x1000)) // random rune up to '\u0999'
|
||||
runes[i] = r
|
||||
runes[n-1-i] = r
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func TestRandomPalindromes(t *testing.T) {
|
||||
// Initialize a pseudo-random number generator.
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
t.Logf("Random seed: %d", seed)
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
p := randomPalindrome(rng)
|
||||
if !IsPalindrome(p) {
|
||||
t.Errorf("IsPalindrome(%q) = false", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-random
|
||||
|
||||
/*
|
||||
// Answer for Exercicse 11.1: Modify randomPalindrome to exercise
|
||||
// IsPalindrome's handling of punctuation and spaces.
|
||||
|
||||
// WARNING: the conversion r -> upper -> lower doesn't preserve
|
||||
// the value of r in some cases, e.g., µ Μ, ſ S, ı I
|
||||
|
||||
// randomPalindrome returns a palindrome whose length and contents
|
||||
// are derived from the pseudo-random number generator rng.
|
||||
func randomNoisyPalindrome(rng *rand.Rand) string {
|
||||
n := rng.Intn(25) // random length up to 24
|
||||
runes := make([]rune, n)
|
||||
for i := 0; i < (n+1)/2; i++ {
|
||||
r := rune(rng.Intn(0x200)) // random rune up to \u99
|
||||
runes[i] = r
|
||||
r1 := r
|
||||
if unicode.IsLetter(r) && unicode.IsLower(r) {
|
||||
r = unicode.ToUpper(r)
|
||||
if unicode.ToLower(r) != r1 {
|
||||
fmt.Printf("cap? %c %c\n", r1, r)
|
||||
}
|
||||
}
|
||||
runes[n-1-i] = r
|
||||
}
|
||||
return "?" + string(runes) + "!"
|
||||
}
|
||||
|
||||
func TestRandomNoisyPalindromes(t *testing.T) {
|
||||
// Initialize a pseudo-random number generator.
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
t.Logf("Random seed: %d", seed)
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
n := 0
|
||||
for i := 0; i < 1000; i++ {
|
||||
p := randomNoisyPalindrome(rng)
|
||||
if !IsPalindrome(p) {
|
||||
t.Errorf("IsNoisyPalindrome(%q) = false", p)
|
||||
n++
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "fail = %d\n", n)
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 333.
|
||||
|
||||
// Package display provides a means to display structured data.
|
||||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//!+Display
|
||||
|
||||
func Display(name string, x interface{}) {
|
||||
fmt.Printf("Display %s (%T):\n", name, x)
|
||||
display(name, reflect.ValueOf(x))
|
||||
}
|
||||
|
||||
//!-Display
|
||||
|
||||
// formatAtom formats a value without inspecting its internal structure.
|
||||
// It is a copy of the the function in gopl.io/ch11/format.
|
||||
func formatAtom(v reflect.Value) string {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
return "invalid"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16,
|
||||
reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return strconv.FormatUint(v.Uint(), 10)
|
||||
// ...floating-point and complex cases omitted for brevity...
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case reflect.String:
|
||||
return strconv.Quote(v.String())
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr,
|
||||
reflect.Slice, reflect.Map:
|
||||
return v.Type().String() + " 0x" +
|
||||
strconv.FormatUint(uint64(v.Pointer()), 16)
|
||||
default: // reflect.Array, reflect.Struct, reflect.Interface
|
||||
return v.Type().String() + " value"
|
||||
}
|
||||
}
|
||||
|
||||
//!+display
|
||||
func display(path string, v reflect.Value) {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
fmt.Printf("%s = invalid\n", path)
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
|
||||
display(fieldPath, v.Field(i))
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range v.MapKeys() {
|
||||
display(fmt.Sprintf("%s[%s]", path,
|
||||
formatAtom(key)), v.MapIndex(key))
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
fmt.Printf("%s = nil\n", path)
|
||||
} else {
|
||||
display(fmt.Sprintf("(*%s)", path), v.Elem())
|
||||
}
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
fmt.Printf("%s = nil\n", path)
|
||||
} else {
|
||||
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
|
||||
display(path+".value", v.Elem())
|
||||
}
|
||||
default: // basic types, channels, funcs
|
||||
fmt.Printf("%s = %s\n", path, formatAtom(v))
|
||||
}
|
||||
}
|
||||
|
||||
//!-display
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package display
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"gopl.io/ch7/eval"
|
||||
)
|
||||
|
||||
// NOTE: we can't use !+..!- comments to excerpt these tests
|
||||
// into the book because it defeats the Example mechanism,
|
||||
// which requires the // Output comment to be at the end
|
||||
// of the function.
|
||||
|
||||
func ExampleExpr() {
|
||||
e, _ := eval.Parse("sqrt(A / pi)")
|
||||
Display("e", e)
|
||||
// Output:
|
||||
// Display e (eval.call):
|
||||
// e.fn = "sqrt"
|
||||
// e.args[0].type = eval.binary
|
||||
// e.args[0].value.op = 47
|
||||
// e.args[0].value.x.type = eval.Var
|
||||
// e.args[0].value.x.value = "A"
|
||||
// e.args[0].value.y.type = eval.Var
|
||||
// e.args[0].value.y.value = "pi"
|
||||
}
|
||||
|
||||
func ExampleSlice() {
|
||||
Display("slice", []*int{new(int), nil})
|
||||
// Output:
|
||||
// Display slice ([]*int):
|
||||
// (*slice[0]) = 0
|
||||
// slice[1] = nil
|
||||
}
|
||||
|
||||
func ExampleNilInterface() {
|
||||
var w io.Writer
|
||||
Display("w", w)
|
||||
// Output:
|
||||
// Display w (<nil>):
|
||||
// w = invalid
|
||||
}
|
||||
|
||||
func ExamplePtrToInterface() {
|
||||
var w io.Writer
|
||||
Display("&w", &w)
|
||||
// Output:
|
||||
// Display &w (*io.Writer):
|
||||
// (*&w) = nil
|
||||
}
|
||||
|
||||
func ExampleStruct() {
|
||||
Display("x", struct{ x interface{} }{3})
|
||||
// Output:
|
||||
// Display x (struct { x interface {} }):
|
||||
// x.x.type = int
|
||||
// x.x.value = 3
|
||||
}
|
||||
|
||||
func ExampleInterface() {
|
||||
var i interface{} = 3
|
||||
Display("i", i)
|
||||
// Output:
|
||||
// Display i (int):
|
||||
// i = 3
|
||||
}
|
||||
|
||||
func ExamplePtrToInterface2() {
|
||||
var i interface{} = 3
|
||||
Display("&i", &i)
|
||||
// Output:
|
||||
// Display &i (*interface {}):
|
||||
// (*&i).type = int
|
||||
// (*&i).value = 3
|
||||
}
|
||||
|
||||
func ExampleArray() {
|
||||
Display("x", [1]interface{}{3})
|
||||
// Output:
|
||||
// Display x ([1]interface {}):
|
||||
// x[0].type = int
|
||||
// x[0].value = 3
|
||||
}
|
||||
|
||||
func ExampleMovie() {
|
||||
//!+movie
|
||||
type Movie struct {
|
||||
Title, Subtitle string
|
||||
Year int
|
||||
Color bool
|
||||
Actor map[string]string
|
||||
Oscars []string
|
||||
Sequel *string
|
||||
}
|
||||
//!-movie
|
||||
//!+strangelove
|
||||
strangelove := Movie{
|
||||
Title: "Dr. Strangelove",
|
||||
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
|
||||
Year: 1964,
|
||||
Color: false,
|
||||
Actor: map[string]string{
|
||||
"Dr. Strangelove": "Peter Sellers",
|
||||
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
|
||||
"Pres. Merkin Muffley": "Peter Sellers",
|
||||
"Gen. Buck Turgidson": "George C. Scott",
|
||||
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
|
||||
`Maj. T.J. "King" Kong`: "Slim Pickens",
|
||||
},
|
||||
|
||||
Oscars: []string{
|
||||
"Best Actor (Nomin.)",
|
||||
"Best Adapted Screenplay (Nomin.)",
|
||||
"Best Director (Nomin.)",
|
||||
"Best Picture (Nomin.)",
|
||||
},
|
||||
}
|
||||
//!-strangelove
|
||||
Display("strangelove", strangelove)
|
||||
|
||||
// We don't use an Output: comment since displaying
|
||||
// a map is nondeterministic.
|
||||
/*
|
||||
//!+output
|
||||
Display strangelove (display.Movie):
|
||||
strangelove.Title = "Dr. Strangelove"
|
||||
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
|
||||
strangelove.Year = 1964
|
||||
strangelove.Color = false
|
||||
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
|
||||
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
|
||||
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
|
||||
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
|
||||
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
|
||||
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
|
||||
strangelove.Oscars[0] = "Best Actor (Nomin.)"
|
||||
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
|
||||
strangelove.Oscars[2] = "Best Director (Nomin.)"
|
||||
strangelove.Oscars[3] = "Best Picture (Nomin.)"
|
||||
strangelove.Sequel = nil
|
||||
//!-output
|
||||
*/
|
||||
}
|
||||
|
||||
// This test ensures that the program terminates without crashing.
|
||||
func Test(t *testing.T) {
|
||||
// Some other values (YMMV)
|
||||
Display("os.Stderr", os.Stderr)
|
||||
// Output:
|
||||
// Display os.Stderr (*os.File):
|
||||
// (*(*os.Stderr).file).fd = 2
|
||||
// (*(*os.Stderr).file).name = "/dev/stderr"
|
||||
// (*(*os.Stderr).file).nepipe = 0
|
||||
|
||||
var w io.Writer = os.Stderr
|
||||
Display("&w", &w)
|
||||
// Output:
|
||||
// Display &w (*io.Writer):
|
||||
// (*&w).type = *os.File
|
||||
// (*(*(*&w).value).file).fd = 2
|
||||
// (*(*(*&w).value).file).name = "/dev/stderr"
|
||||
// (*(*(*&w).value).file).nepipe = 0
|
||||
|
||||
var locker sync.Locker = new(sync.Mutex)
|
||||
Display("(&locker)", &locker)
|
||||
// Output:
|
||||
// Display (&locker) (*sync.Locker):
|
||||
// (*(&locker)).type = *sync.Mutex
|
||||
// (*(*(&locker)).value).state = 0
|
||||
// (*(*(&locker)).value).sema = 0
|
||||
|
||||
Display("locker", locker)
|
||||
// Output:
|
||||
// Display locker (*sync.Mutex):
|
||||
// (*locker).state = 0
|
||||
// (*locker).sema = 0
|
||||
// (*(&locker)) = nil
|
||||
|
||||
locker = nil
|
||||
Display("(&locker)", &locker)
|
||||
// Output:
|
||||
// Display (&locker) (*sync.Locker):
|
||||
// (*(&locker)) = nil
|
||||
|
||||
ips, _ := net.LookupHost("golang.org")
|
||||
Display("ips", ips)
|
||||
// Output:
|
||||
// Display ips ([]string):
|
||||
// ips[0] = "173.194.68.141"
|
||||
// ips[1] = "2607:f8b0:400d:c06::8d"
|
||||
|
||||
// Even metarecursion! (YMMV)
|
||||
Display("rV", reflect.ValueOf(os.Stderr))
|
||||
// Output:
|
||||
// Display rV (reflect.Value):
|
||||
// (*rV.typ).size = 8
|
||||
// (*rV.typ).ptrdata = 8
|
||||
// (*rV.typ).hash = 871609668
|
||||
// (*rV.typ)._ = 0
|
||||
// ...
|
||||
|
||||
// a pointer that points to itself
|
||||
type P *P
|
||||
var p P
|
||||
p = &p
|
||||
if false {
|
||||
Display("p", p)
|
||||
// Output:
|
||||
// Display p (display.P):
|
||||
// ...stuck, no output...
|
||||
}
|
||||
|
||||
// a map that contains itself
|
||||
type M map[string]M
|
||||
m := make(M)
|
||||
m[""] = m
|
||||
if false {
|
||||
Display("m", m)
|
||||
// Output:
|
||||
// Display m (display.M):
|
||||
// ...stuck, no output...
|
||||
}
|
||||
|
||||
// a slice that contains itself
|
||||
type S []S
|
||||
s := make(S, 1)
|
||||
s[0] = s
|
||||
if false {
|
||||
Display("s", s)
|
||||
// Output:
|
||||
// Display s (display.S):
|
||||
// ...stuck, no output...
|
||||
}
|
||||
|
||||
// a linked list that eats its own tail
|
||||
type Cycle struct {
|
||||
Value int
|
||||
Tail *Cycle
|
||||
}
|
||||
var c Cycle
|
||||
c = Cycle{42, &c}
|
||||
if false {
|
||||
Display("c", c)
|
||||
// Output:
|
||||
// Display c (display.Cycle):
|
||||
// c.Value = 42
|
||||
// (*c.Tail).Value = 42
|
||||
// (*(*c.Tail).Tail).Value = 42
|
||||
// ...ad infinitum...
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 332.
|
||||
|
||||
// Package format provides an Any function that can format any value.
|
||||
//!+
|
||||
package format
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Any formats any value as a string.
|
||||
func Any(value interface{}) string {
|
||||
return formatAtom(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// formatAtom formats a value without inspecting its internal structure.
|
||||
func formatAtom(v reflect.Value) string {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
return "invalid"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16,
|
||||
reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return strconv.FormatUint(v.Uint(), 10)
|
||||
// ...floating-point and complex cases omitted for brevity...
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(v.Bool())
|
||||
case reflect.String:
|
||||
return strconv.Quote(v.String())
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
|
||||
return v.Type().String() + " 0x" +
|
||||
strconv.FormatUint(uint64(v.Pointer()), 16)
|
||||
default: // reflect.Array, reflect.Struct, reflect.Interface
|
||||
return v.Type().String() + " value"
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package format_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopl.io/ch12/format"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// The pointer values are just examples, and may vary from run to run.
|
||||
//!+time
|
||||
var x int64 = 1
|
||||
var d time.Duration = 1 * time.Nanosecond
|
||||
fmt.Println(format.Any(x)) // "1"
|
||||
fmt.Println(format.Any(d)) // "1"
|
||||
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
|
||||
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
|
||||
//!-time
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 351.
|
||||
|
||||
// Package methods provides a function to print the methods of any value.
|
||||
package methods
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//!+print
|
||||
// Print prints the method set of the value x.
|
||||
func Print(x interface{}) {
|
||||
v := reflect.ValueOf(x)
|
||||
t := v.Type()
|
||||
fmt.Printf("type %s\n", t)
|
||||
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
methType := v.Method(i).Type()
|
||||
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
|
||||
strings.TrimPrefix(methType.String(), "func"))
|
||||
}
|
||||
}
|
||||
|
||||
//!-print
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package methods_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopl.io/ch12/methods"
|
||||
)
|
||||
|
||||
func ExamplePrintDuration() {
|
||||
methods.Print(time.Hour)
|
||||
// Output:
|
||||
// type time.Duration
|
||||
// func (time.Duration) Hours() float64
|
||||
// func (time.Duration) Minutes() float64
|
||||
// func (time.Duration) Nanoseconds() int64
|
||||
// func (time.Duration) Seconds() float64
|
||||
// func (time.Duration) String() string
|
||||
}
|
||||
|
||||
func ExamplePrintReplacer() {
|
||||
methods.Print(new(strings.Replacer))
|
||||
// Output:
|
||||
// type *strings.Replacer
|
||||
// func (*strings.Replacer) Replace(string) string
|
||||
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
||||
}
|
||||
|
||||
/*
|
||||
//!+output
|
||||
methods.Print(time.Hour)
|
||||
// Output:
|
||||
// type time.Duration
|
||||
// func (time.Duration) Hours() float64
|
||||
// func (time.Duration) Minutes() float64
|
||||
// func (time.Duration) Nanoseconds() int64
|
||||
// func (time.Duration) Seconds() float64
|
||||
// func (time.Duration) String() string
|
||||
|
||||
methods.Print(new(strings.Replacer))
|
||||
// Output:
|
||||
// type *strings.Replacer
|
||||
// func (*strings.Replacer) Replace(string) string
|
||||
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
|
||||
//!-output
|
||||
*/
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 349.
|
||||
|
||||
// Package params provides a reflection-based parser for URL parameters.
|
||||
package params
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//!+Unpack
|
||||
|
||||
// Unpack populates the fields of the struct pointed to by ptr
|
||||
// from the HTTP request parameters in req.
|
||||
func Unpack(req *http.Request, ptr interface{}) error {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build map of fields keyed by effective name.
|
||||
fields := make(map[string]reflect.Value)
|
||||
v := reflect.ValueOf(ptr).Elem() // the struct variable
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldInfo := v.Type().Field(i) // a reflect.StructField
|
||||
tag := fieldInfo.Tag // a reflect.StructTag
|
||||
name := tag.Get("http")
|
||||
if name == "" {
|
||||
name = strings.ToLower(fieldInfo.Name)
|
||||
}
|
||||
fields[name] = v.Field(i)
|
||||
}
|
||||
|
||||
// Update struct field for each parameter in the request.
|
||||
for name, values := range req.Form {
|
||||
f := fields[name]
|
||||
if !f.IsValid() {
|
||||
continue // ignore unrecognized HTTP parameters
|
||||
}
|
||||
for _, value := range values {
|
||||
if f.Kind() == reflect.Slice {
|
||||
elem := reflect.New(f.Type().Elem()).Elem()
|
||||
if err := populate(elem, value); err != nil {
|
||||
return fmt.Errorf("%s: %v", name, err)
|
||||
}
|
||||
f.Set(reflect.Append(f, elem))
|
||||
} else {
|
||||
if err := populate(f, value); err != nil {
|
||||
return fmt.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-Unpack
|
||||
|
||||
//!+populate
|
||||
func populate(v reflect.Value, value string) error {
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
v.SetString(value)
|
||||
|
||||
case reflect.Int:
|
||||
i, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetInt(i)
|
||||
|
||||
case reflect.Bool:
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetBool(b)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported kind %s", v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-populate
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 348.
|
||||
|
||||
// Search is a demo of the params.Unpack function.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//!+
|
||||
|
||||
import "gopl.io/ch12/params"
|
||||
|
||||
// search implements the /search URL endpoint.
|
||||
func search(resp http.ResponseWriter, req *http.Request) {
|
||||
var data struct {
|
||||
Labels []string `http:"l"`
|
||||
MaxResults int `http:"max"`
|
||||
Exact bool `http:"x"`
|
||||
}
|
||||
data.MaxResults = 10 // set default
|
||||
if err := params.Unpack(req, &data); err != nil {
|
||||
http.Error(resp, err.Error(), http.StatusBadRequest) // 400
|
||||
return
|
||||
}
|
||||
|
||||
// ...rest of handler...
|
||||
fmt.Fprintf(resp, "Search: %+v\n", data)
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/search", search)
|
||||
log.Fatal(http.ListenAndServe(":12345", nil))
|
||||
}
|
||||
|
||||
/*
|
||||
//!+output
|
||||
$ go build gopl.io/ch12/search
|
||||
$ ./search &
|
||||
$ ./fetch 'http://localhost:12345/search'
|
||||
Search: {Labels:[] MaxResults:10 Exact:false}
|
||||
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming'
|
||||
Search: {Labels:[golang programming] MaxResults:10 Exact:false}
|
||||
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100'
|
||||
Search: {Labels:[golang programming] MaxResults:100 Exact:false}
|
||||
$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming'
|
||||
Search: {Labels:[golang programming] MaxResults:10 Exact:true}
|
||||
$ ./fetch 'http://localhost:12345/search?q=hello&x=123'
|
||||
x: strconv.ParseBool: parsing "123": invalid syntax
|
||||
$ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
|
||||
max: strconv.ParseInt: parsing "lots": invalid syntax
|
||||
//!-output
|
||||
*/
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 344.
|
||||
|
||||
// Package sexpr provides a means for converting Go objects to and
|
||||
// from S-expressions.
|
||||
package sexpr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
//!+Unmarshal
|
||||
// Unmarshal parses S-expression data and populates the variable
|
||||
// whose address is in the non-nil pointer out.
|
||||
func Unmarshal(data []byte, out interface{}) (err error) {
|
||||
lex := &lexer{scan: scanner.Scanner{Mode: scanner.GoTokens}}
|
||||
lex.scan.Init(bytes.NewReader(data))
|
||||
lex.next() // get the first token
|
||||
defer func() {
|
||||
// NOTE: this is not an example of ideal error handling.
|
||||
if x := recover(); x != nil {
|
||||
err = fmt.Errorf("error at %s: %v", lex.scan.Position, x)
|
||||
}
|
||||
}()
|
||||
read(lex, reflect.ValueOf(out).Elem())
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-Unmarshal
|
||||
|
||||
//!+lexer
|
||||
type lexer struct {
|
||||
scan scanner.Scanner
|
||||
token rune // the current token
|
||||
}
|
||||
|
||||
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
|
||||
func (lex *lexer) text() string { return lex.scan.TokenText() }
|
||||
|
||||
func (lex *lexer) consume(want rune) {
|
||||
if lex.token != want { // NOTE: Not an example of good error handling.
|
||||
panic(fmt.Sprintf("got %q, want %q", lex.text(), want))
|
||||
}
|
||||
lex.next()
|
||||
}
|
||||
|
||||
//!-lexer
|
||||
|
||||
// The read function is a decoder for a small subset of well-formed
|
||||
// S-expressions. For brevity of our example, it takes many dubious
|
||||
// shortcuts.
|
||||
//
|
||||
// The parser assumes
|
||||
// - that the S-expression input is well-formed; it does no error checking.
|
||||
// - that the S-expression input corresponds to the type of the variable.
|
||||
// - that all numbers in the input are non-negative decimal integers.
|
||||
// - that all keys in ((key value) ...) struct syntax are unquoted symbols.
|
||||
// - that the input does not contain dotted lists such as (1 2 . 3).
|
||||
// - that the input does not contain Lisp reader macros such 'x and #'x.
|
||||
//
|
||||
// The reflection logic assumes
|
||||
// - that v is always a variable of the appropriate type for the
|
||||
// S-expression value. For example, v must not be a boolean,
|
||||
// interface, channel, or function, and if v is an array, the input
|
||||
// must have the correct number of elements.
|
||||
// - that v in the top-level call to read has the zero value of its
|
||||
// type and doesn't need clearing.
|
||||
// - that if v is a numeric variable, it is a signed integer.
|
||||
|
||||
//!+read
|
||||
func read(lex *lexer, v reflect.Value) {
|
||||
switch lex.token {
|
||||
case scanner.Ident:
|
||||
// The only valid identifiers are
|
||||
// "nil" and struct field names.
|
||||
if lex.text() == "nil" {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
lex.next()
|
||||
return
|
||||
}
|
||||
case scanner.String:
|
||||
s, _ := strconv.Unquote(lex.text()) // NOTE: ignoring errors
|
||||
v.SetString(s)
|
||||
lex.next()
|
||||
return
|
||||
case scanner.Int:
|
||||
i, _ := strconv.Atoi(lex.text()) // NOTE: ignoring errors
|
||||
v.SetInt(int64(i))
|
||||
lex.next()
|
||||
return
|
||||
case '(':
|
||||
lex.next()
|
||||
readList(lex, v)
|
||||
lex.next() // consume ')'
|
||||
return
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected token %q", lex.text()))
|
||||
}
|
||||
|
||||
//!-read
|
||||
|
||||
//!+readlist
|
||||
func readList(lex *lexer, v reflect.Value) {
|
||||
switch v.Kind() {
|
||||
case reflect.Array: // (item ...)
|
||||
for i := 0; !endList(lex); i++ {
|
||||
read(lex, v.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Slice: // (item ...)
|
||||
for !endList(lex) {
|
||||
item := reflect.New(v.Type().Elem()).Elem()
|
||||
read(lex, item)
|
||||
v.Set(reflect.Append(v, item))
|
||||
}
|
||||
|
||||
case reflect.Struct: // ((name value) ...)
|
||||
for !endList(lex) {
|
||||
lex.consume('(')
|
||||
if lex.token != scanner.Ident {
|
||||
panic(fmt.Sprintf("got token %q, want field name", lex.text()))
|
||||
}
|
||||
name := lex.text()
|
||||
lex.next()
|
||||
read(lex, v.FieldByName(name))
|
||||
lex.consume(')')
|
||||
}
|
||||
|
||||
case reflect.Map: // ((key value) ...)
|
||||
v.Set(reflect.MakeMap(v.Type()))
|
||||
for !endList(lex) {
|
||||
lex.consume('(')
|
||||
key := reflect.New(v.Type().Key()).Elem()
|
||||
read(lex, key)
|
||||
value := reflect.New(v.Type().Elem()).Elem()
|
||||
read(lex, value)
|
||||
v.SetMapIndex(key, value)
|
||||
lex.consume(')')
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("cannot decode list into %v", v.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
func endList(lex *lexer) bool {
|
||||
switch lex.token {
|
||||
case scanner.EOF:
|
||||
panic("end of file")
|
||||
case ')':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//!-readlist
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 339.
|
||||
|
||||
package sexpr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
//!+Marshal
|
||||
// Marshal encodes a Go value in S-expression form.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := encode(&buf, reflect.ValueOf(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
//!-Marshal
|
||||
|
||||
// encode writes to buf an S-expression representation of v.
|
||||
//!+encode
|
||||
func encode(buf *bytes.Buffer, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
buf.WriteString("nil")
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16,
|
||||
reflect.Int32, reflect.Int64:
|
||||
fmt.Fprintf(buf, "%d", v.Int())
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
fmt.Fprintf(buf, "%d", v.Uint())
|
||||
|
||||
case reflect.String:
|
||||
fmt.Fprintf(buf, "%q", v.String())
|
||||
|
||||
case reflect.Ptr:
|
||||
return encode(buf, v.Elem())
|
||||
|
||||
case reflect.Array, reflect.Slice: // (value ...)
|
||||
buf.WriteByte('(')
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if err := encode(buf, v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
case reflect.Struct: // ((name value) ...)
|
||||
buf.WriteByte('(')
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name)
|
||||
if err := encode(buf, v.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
case reflect.Map: // ((key value) ...)
|
||||
buf.WriteByte('(')
|
||||
for i, key := range v.MapKeys() {
|
||||
if i > 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
buf.WriteByte('(')
|
||||
if err := encode(buf, key); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
if err := encode(buf, v.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
default: // float, complex, bool, chan, func, interface
|
||||
return fmt.Errorf("unsupported type: %s", v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-encode
|
|
@ -0,0 +1,183 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package sexpr
|
||||
|
||||
// This file implements the algorithm described in Derek C. Oppen's
|
||||
// 1979 Stanford technical report, "Pretty Printing".
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func MarshalIndent(v interface{}) ([]byte, error) {
|
||||
p := printer{width: margin}
|
||||
if err := pretty(&p, reflect.ValueOf(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Bytes(), nil
|
||||
}
|
||||
|
||||
const margin = 80
|
||||
|
||||
type token struct {
|
||||
kind rune // one of "s ()" (string, blank, start, end)
|
||||
str string
|
||||
size int
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
tokens []*token // FIFO buffer
|
||||
stack []*token // stack of open ' ' and '(' tokens
|
||||
rtotal int // total number of spaces needed to print stream
|
||||
|
||||
bytes.Buffer
|
||||
indents []int
|
||||
width int // remaining space
|
||||
}
|
||||
|
||||
func (p *printer) string(str string) {
|
||||
tok := &token{kind: 's', str: str, size: len(str)}
|
||||
if len(p.stack) == 0 {
|
||||
p.print(tok)
|
||||
} else {
|
||||
p.tokens = append(p.tokens, tok)
|
||||
p.rtotal += len(str)
|
||||
}
|
||||
}
|
||||
func (p *printer) pop() (top *token) {
|
||||
last := len(p.stack) - 1
|
||||
top, p.stack = p.stack[last], p.stack[:last]
|
||||
return
|
||||
}
|
||||
func (p *printer) begin() {
|
||||
if len(p.stack) == 0 {
|
||||
p.rtotal = 1
|
||||
}
|
||||
t := &token{kind: '(', size: -p.rtotal}
|
||||
p.tokens = append(p.tokens, t)
|
||||
p.stack = append(p.stack, t) // push
|
||||
p.string("(")
|
||||
}
|
||||
func (p *printer) end() {
|
||||
p.string(")")
|
||||
p.tokens = append(p.tokens, &token{kind: ')'})
|
||||
x := p.pop()
|
||||
x.size += p.rtotal
|
||||
if x.kind == ' ' {
|
||||
p.pop().size += p.rtotal
|
||||
}
|
||||
if len(p.stack) == 0 {
|
||||
for _, tok := range p.tokens {
|
||||
p.print(tok)
|
||||
}
|
||||
p.tokens = nil
|
||||
}
|
||||
}
|
||||
func (p *printer) space() {
|
||||
last := len(p.stack) - 1
|
||||
x := p.stack[last]
|
||||
if x.kind == ' ' {
|
||||
x.size += p.rtotal
|
||||
p.stack = p.stack[:last] // pop
|
||||
}
|
||||
t := &token{kind: ' ', size: -p.rtotal}
|
||||
p.tokens = append(p.tokens, t)
|
||||
p.stack = append(p.stack, t)
|
||||
p.rtotal++
|
||||
}
|
||||
func (p *printer) print(t *token) {
|
||||
switch t.kind {
|
||||
case 's':
|
||||
p.WriteString(t.str)
|
||||
p.width -= len(t.str)
|
||||
case '(':
|
||||
p.indents = append(p.indents, p.width)
|
||||
case ')':
|
||||
p.indents = p.indents[:len(p.indents)-1] // pop
|
||||
case ' ':
|
||||
if t.size > p.width {
|
||||
p.width = p.indents[len(p.indents)-1] - 1
|
||||
fmt.Fprintf(&p.Buffer, "\n%*s", margin-p.width, "")
|
||||
} else {
|
||||
p.WriteByte(' ')
|
||||
p.width--
|
||||
}
|
||||
}
|
||||
}
|
||||
func (p *printer) stringf(format string, args ...interface{}) {
|
||||
p.string(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func pretty(p *printer, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
p.string("nil")
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16,
|
||||
reflect.Int32, reflect.Int64:
|
||||
p.stringf("%d", v.Int())
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
p.stringf("%d", v.Uint())
|
||||
|
||||
case reflect.String:
|
||||
p.stringf("%q", v.String())
|
||||
|
||||
case reflect.Array, reflect.Slice: // (value ...)
|
||||
p.begin()
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i > 0 {
|
||||
p.space()
|
||||
}
|
||||
if err := pretty(p, v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.end()
|
||||
|
||||
case reflect.Struct: // ((name value ...)
|
||||
p.begin()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if i > 0 {
|
||||
p.space()
|
||||
}
|
||||
p.begin()
|
||||
p.string(v.Type().Field(i).Name)
|
||||
p.space()
|
||||
if err := pretty(p, v.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
p.end()
|
||||
}
|
||||
p.end()
|
||||
|
||||
case reflect.Map: // ((key value ...)
|
||||
p.begin()
|
||||
for i, key := range v.MapKeys() {
|
||||
if i > 0 {
|
||||
p.space()
|
||||
}
|
||||
p.begin()
|
||||
if err := pretty(p, key); err != nil {
|
||||
return err
|
||||
}
|
||||
p.space()
|
||||
if err := pretty(p, v.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
p.end()
|
||||
}
|
||||
p.end()
|
||||
|
||||
case reflect.Ptr:
|
||||
return pretty(p, v.Elem())
|
||||
|
||||
default: // float, complex, bool, chan, func, interface
|
||||
return fmt.Errorf("unsupported type: %s", v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package sexpr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test verifies that encoding and decoding a complex data value
|
||||
// produces an equal result.
|
||||
//
|
||||
// The test does not make direct assertions about the encoded output
|
||||
// because the output depends on map iteration order, which is
|
||||
// nondeterministic. The output of the t.Log statements can be
|
||||
// inspected by running the test with the -v flag:
|
||||
//
|
||||
// $ go test -v gopl.io/ch12/sexpr
|
||||
//
|
||||
func Test(t *testing.T) {
|
||||
type Movie struct {
|
||||
Title, Subtitle string
|
||||
Year int
|
||||
Actor map[string]string
|
||||
Oscars []string
|
||||
Sequel *string
|
||||
}
|
||||
strangelove := Movie{
|
||||
Title: "Dr. Strangelove",
|
||||
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
|
||||
Year: 1964,
|
||||
Actor: map[string]string{
|
||||
"Dr. Strangelove": "Peter Sellers",
|
||||
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
|
||||
"Pres. Merkin Muffley": "Peter Sellers",
|
||||
"Gen. Buck Turgidson": "George C. Scott",
|
||||
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
|
||||
`Maj. T.J. "King" Kong`: "Slim Pickens",
|
||||
},
|
||||
Oscars: []string{
|
||||
"Best Actor (Nomin.)",
|
||||
"Best Adapted Screenplay (Nomin.)",
|
||||
"Best Director (Nomin.)",
|
||||
"Best Picture (Nomin.)",
|
||||
},
|
||||
}
|
||||
|
||||
// Encode it
|
||||
data, err := Marshal(strangelove)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal failed: %v", err)
|
||||
}
|
||||
t.Logf("Marshal() = %s\n", data)
|
||||
|
||||
// Decode it
|
||||
var movie Movie
|
||||
if err := Unmarshal(data, &movie); err != nil {
|
||||
t.Fatalf("Unmarshal failed: %v", err)
|
||||
}
|
||||
t.Logf("Unmarshal() = %+v\n", movie)
|
||||
|
||||
// Check equality.
|
||||
if !reflect.DeepEqual(movie, strangelove) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
|
||||
// Pretty-print it:
|
||||
data, err = MarshalIndent(strangelove)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("MarshalIdent() = %s\n", data)
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 362.
|
||||
|
||||
//!+
|
||||
/* This file is gopl.io/ch13/bzip/bzip2.c, */
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 362.
|
||||
//!+
|
||||
|
||||
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package bzip_test
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 365.
|
||||
|
||||
//!+
|
||||
|
||||
// Bzipper reads input, bzip2-compresses it, and writes it out.
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 359.
|
||||
|
||||
// Package equal provides a deep equivalence relation for arbitrary values.
|
||||
package equal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//!+
|
||||
func equal(x, y reflect.Value, seen map[comparison]bool) bool {
|
||||
if !x.IsValid() || !y.IsValid() {
|
||||
return x.IsValid() == y.IsValid()
|
||||
}
|
||||
if x.Type() != y.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
// ...cycle check omitted (shown later)...
|
||||
|
||||
//!-
|
||||
//!+cyclecheck
|
||||
// cycle check
|
||||
if x.CanAddr() && y.CanAddr() {
|
||||
xptr := unsafe.Pointer(x.UnsafeAddr())
|
||||
yptr := unsafe.Pointer(y.UnsafeAddr())
|
||||
if xptr == yptr {
|
||||
return true // identical references
|
||||
}
|
||||
c := comparison{xptr, yptr, x.Type()}
|
||||
if seen[c] {
|
||||
return true // already seen
|
||||
}
|
||||
seen[c] = true
|
||||
}
|
||||
//!-cyclecheck
|
||||
//!+
|
||||
switch x.Kind() {
|
||||
case reflect.Bool:
|
||||
return x.Bool() == y.Bool()
|
||||
|
||||
case reflect.String:
|
||||
return x.String() == y.String()
|
||||
|
||||
// ...numeric cases omitted for brevity...
|
||||
|
||||
//!-
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64:
|
||||
return x.Int() == y.Int()
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64, reflect.Uintptr:
|
||||
return x.Uint() == y.Uint()
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return x.Float() == y.Float()
|
||||
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return x.Complex() == y.Complex()
|
||||
//!+
|
||||
case reflect.Chan, reflect.UnsafePointer, reflect.Func:
|
||||
return x.Pointer() == y.Pointer()
|
||||
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return equal(x.Elem(), y.Elem(), seen)
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
if x.Len() != y.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if !equal(x.Index(i), y.Index(i), seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
// ...struct and map cases omitted for brevity...
|
||||
//!-
|
||||
case reflect.Struct:
|
||||
for i, n := 0, x.NumField(); i < n; i++ {
|
||||
if !equal(x.Field(i), y.Field(i), seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Map:
|
||||
if x.Len() != y.Len() {
|
||||
return false
|
||||
}
|
||||
for _, k := range x.MapKeys() {
|
||||
if !equal(x.MapIndex(k), y.MapIndex(k), seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
//!+
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
//!+comparison
|
||||
// Equal reports whether x and y are deeply equal.
|
||||
//!-comparison
|
||||
//
|
||||
// Map keys are always compared with ==, not deeply.
|
||||
// (This matters for keys containing pointers or interfaces.)
|
||||
//!+comparison
|
||||
func Equal(x, y interface{}) bool {
|
||||
seen := make(map[comparison]bool)
|
||||
return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen)
|
||||
}
|
||||
|
||||
type comparison struct {
|
||||
x, y unsafe.Pointer
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
//!-comparison
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package equal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
one, oneAgain, two := 1, 1, 2
|
||||
|
||||
type CyclePtr *CyclePtr
|
||||
var cyclePtr1, cyclePtr2 CyclePtr
|
||||
cyclePtr1 = &cyclePtr1
|
||||
cyclePtr2 = &cyclePtr2
|
||||
|
||||
type CycleSlice []CycleSlice
|
||||
var cycleSlice CycleSlice
|
||||
cycleSlice = append(cycleSlice, cycleSlice)
|
||||
|
||||
ch1, ch2 := make(chan int), make(chan int)
|
||||
var ch1ro <-chan int = ch1
|
||||
|
||||
type mystring string
|
||||
|
||||
var iface1, iface1Again, iface2 interface{} = &one, &oneAgain, &two
|
||||
|
||||
for _, test := range []struct {
|
||||
x, y interface{}
|
||||
want bool
|
||||
}{
|
||||
// basic types
|
||||
{1, 1, true},
|
||||
{1, 2, false}, // different values
|
||||
{1, 1.0, false}, // different types
|
||||
{"foo", "foo", true},
|
||||
{"foo", "bar", false},
|
||||
{mystring("foo"), "foo", false}, // different types
|
||||
// slices
|
||||
{[]string{"foo"}, []string{"foo"}, true},
|
||||
{[]string{"foo"}, []string{"bar"}, false},
|
||||
{[]string{}, []string(nil), true},
|
||||
// slice cycles
|
||||
{cycleSlice, cycleSlice, true},
|
||||
// maps
|
||||
{
|
||||
map[string][]int{"foo": {1, 2, 3}},
|
||||
map[string][]int{"foo": {1, 2, 3}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string][]int{"foo": {1, 2, 3}},
|
||||
map[string][]int{"foo": {1, 2, 3, 4}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string][]int{},
|
||||
map[string][]int(nil),
|
||||
true,
|
||||
},
|
||||
// pointers
|
||||
{&one, &one, true},
|
||||
{&one, &two, false},
|
||||
{&one, &oneAgain, true},
|
||||
{new(bytes.Buffer), new(bytes.Buffer), true},
|
||||
// pointer cycles
|
||||
{cyclePtr1, cyclePtr1, true},
|
||||
{cyclePtr2, cyclePtr2, true},
|
||||
{cyclePtr1, cyclePtr2, true}, // they're deeply equal
|
||||
// functions
|
||||
{(func())(nil), (func())(nil), true},
|
||||
{(func())(nil), func() {}, false},
|
||||
{func() {}, func() {}, false},
|
||||
// arrays
|
||||
{[...]int{1, 2, 3}, [...]int{1, 2, 3}, true},
|
||||
{[...]int{1, 2, 3}, [...]int{1, 2, 4}, false},
|
||||
// channels
|
||||
{ch1, ch1, true},
|
||||
{ch1, ch2, false},
|
||||
{ch1ro, ch1, false}, // NOTE: not equal
|
||||
// interfaces
|
||||
{&iface1, &iface1, true},
|
||||
{&iface1, &iface2, false},
|
||||
{&iface1Again, &iface1, true},
|
||||
} {
|
||||
if Equal(test.x, test.y) != test.want {
|
||||
t.Errorf("Equal(%v, %v) = %t",
|
||||
test.x, test.y, !test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleEqual() {
|
||||
//!+
|
||||
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3})) // "true"
|
||||
fmt.Println(Equal([]string{"foo"}, []string{"bar"})) // "false"
|
||||
fmt.Println(Equal([]string(nil), []string{})) // "true"
|
||||
fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
|
||||
//!-
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleEqualCycle() {
|
||||
//!+cycle
|
||||
// Circular linked lists a -> b -> a and c -> c.
|
||||
type link struct {
|
||||
value string
|
||||
tail *link
|
||||
}
|
||||
a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
|
||||
a.tail, b.tail, c.tail = b, a, c
|
||||
fmt.Println(Equal(a, a)) // "true"
|
||||
fmt.Println(Equal(b, b)) // "true"
|
||||
fmt.Println(Equal(c, c)) // "true"
|
||||
fmt.Println(Equal(a, b)) // "false"
|
||||
fmt.Println(Equal(a, c)) // "false"
|
||||
//!-cycle
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 357.
|
||||
|
||||
// Package unsafeptr demonstrates basic use of unsafe.Pointer.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
var x struct {
|
||||
a bool
|
||||
b int16
|
||||
c []int
|
||||
}
|
||||
|
||||
// equivalent to pb := &x.b
|
||||
pb := (*int16)(unsafe.Pointer(
|
||||
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
|
||||
*pb = 42
|
||||
|
||||
fmt.Println(x.b) // "42"
|
||||
//!-main
|
||||
}
|
||||
|
||||
/*
|
||||
//!+wrong
|
||||
// NOTE: subtly incorrect!
|
||||
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
|
||||
pb := (*int16)(unsafe.Pointer(tmp))
|
||||
*pb = 42
|
||||
//!-wrong
|
||||
*/
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 29.
|
||||
//!+
|
||||
|
||||
// Boiling prints the boiling point of water.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const boilingF = 212.0
|
||||
|
||||
func main() {
|
||||
var f = boilingF
|
||||
var c = (f - 32) * 5 / 9
|
||||
fmt.Printf("boiling point = %g°F or %g°C\n", f, c)
|
||||
// Output:
|
||||
// boiling point = 212°F or 100°C
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 43.
|
||||
//!+
|
||||
|
||||
// Cf converts its numeric argument to Celsius and Fahrenheit.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopl.io/ch2/tempconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
t, err := strconv.ParseFloat(arg, 64)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cf: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
f := tempconv.Fahrenheit(t)
|
||||
c := tempconv.Celsius(t)
|
||||
fmt.Printf("%s = %s, %s = %s\n",
|
||||
f, tempconv.FToC(f), c, tempconv.CToF(c))
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 33.
|
||||
//!+
|
||||
|
||||
// Echo4 prints its command-line arguments.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var n = flag.Bool("n", false, "omit trailing newline")
|
||||
var sep = flag.String("s", " ", "separator")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
fmt.Print(strings.Join(flag.Args(), *sep))
|
||||
if !*n {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 29.
|
||||
//!+
|
||||
|
||||
// Ftoc prints two Fahrenheit-to-Celsius conversions.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
const freezingF, boilingF = 32.0, 212.0
|
||||
fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C"
|
||||
fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF)) // "212°F = 100°C"
|
||||
}
|
||||
|
||||
func fToC(f float64) float64 {
|
||||
return (f - 32) * 5 / 9
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 45.
|
||||
|
||||
// (Package doc comment intentionally malformed to demonstrate golint.)
|
||||
//!+
|
||||
package popcount
|
||||
|
||||
// pc[i] is the population count of i.
|
||||
var pc [256]byte
|
||||
|
||||
func init() {
|
||||
for i := range pc {
|
||||
pc[i] = pc[i/2] + byte(i&1)
|
||||
}
|
||||
}
|
||||
|
||||
// PopCount returns the population count (number of set bits) of x.
|
||||
func PopCount(x uint64) int {
|
||||
return int(pc[byte(x>>(0*8))] +
|
||||
pc[byte(x>>(1*8))] +
|
||||
pc[byte(x>>(2*8))] +
|
||||
pc[byte(x>>(3*8))] +
|
||||
pc[byte(x>>(4*8))] +
|
||||
pc[byte(x>>(5*8))] +
|
||||
pc[byte(x>>(6*8))] +
|
||||
pc[byte(x>>(7*8))])
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package popcount_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gopl.io/ch2/popcount"
|
||||
)
|
||||
|
||||
// -- Alternative implementations --
|
||||
|
||||
func BitCount(x uint64) int {
|
||||
// Hacker's Delight, Figure 5-2.
|
||||
x = x - ((x >> 1) & 0x5555555555555555)
|
||||
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333)
|
||||
x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f
|
||||
x = x + (x >> 8)
|
||||
x = x + (x >> 16)
|
||||
x = x + (x >> 32)
|
||||
return int(x & 0x7f)
|
||||
}
|
||||
|
||||
func PopCountByClearing(x uint64) int {
|
||||
n := 0
|
||||
for x != 0 {
|
||||
x = x & (x - 1) // clear rightmost non-zero bit
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func PopCountByShifting(x uint64) int {
|
||||
n := 0
|
||||
for i := uint(0); i < 64; i++ {
|
||||
if x&(1<<i) != 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// -- Benchmarks --
|
||||
|
||||
func BenchmarkPopCount(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
popcount.PopCount(0x1234567890ABCDEF)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBitCount(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
BitCount(0x1234567890ABCDEF)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPopCountByClearing(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
PopCountByClearing(0x1234567890ABCDEF)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPopCountByShifting(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
PopCountByShifting(0x1234567890ABCDEF)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.67GHz Xeon
|
||||
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
|
||||
// BenchmarkPopCount-4 200000000 6.30 ns/op
|
||||
// BenchmarkBitCount-4 300000000 4.15 ns/op
|
||||
// BenchmarkPopCountByClearing-4 30000000 45.2 ns/op
|
||||
// BenchmarkPopCountByShifting-4 10000000 153 ns/op
|
||||
//
|
||||
// 2.5GHz Intel Core i5
|
||||
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
|
||||
// testing: warning: no tests to run
|
||||
// BenchmarkPopCount-4 200000000 7.52 ns/op
|
||||
// BenchmarkBitCount-4 500000000 3.36 ns/op
|
||||
// BenchmarkPopCountByClearing-4 50000000 34.3 ns/op
|
||||
// BenchmarkPopCountByShifting-4 20000000 108 ns/op
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 41.
|
||||
|
||||
//!+
|
||||
|
||||
package tempconv
|
||||
|
||||
// CToF converts a Celsius temperature to Fahrenheit.
|
||||
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
|
||||
|
||||
// FToC converts a Fahrenheit temperature to Celsius.
|
||||
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
|
||||
|
||||
//!-
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
//!+
|
||||
|
||||
// Package tempconv performs Celsius and Fahrenheit conversions.
|
||||
package tempconv
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Celsius float64
|
||||
type Fahrenheit float64
|
||||
|
||||
const (
|
||||
AbsoluteZeroC Celsius = -273.15
|
||||
FreezingC Celsius = 0
|
||||
BoilingC Celsius = 100
|
||||
)
|
||||
|
||||
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
|
||||
func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
|
||||
|
||||
//!-
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 39.
|
||||
//!+
|
||||
|
||||
// Package tempconv performs Celsius and Fahrenheit temperature computations.
|
||||
package tempconv
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Celsius float64
|
||||
type Fahrenheit float64
|
||||
|
||||
const (
|
||||
AbsoluteZeroC Celsius = -273.15
|
||||
FreezingC Celsius = 0
|
||||
BoilingC Celsius = 100
|
||||
)
|
||||
|
||||
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
|
||||
|
||||
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
|
||||
|
||||
//!-
|
||||
|
||||
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package tempconv
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Example1() {
|
||||
{
|
||||
//!+arith
|
||||
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
|
||||
boilingF := CToF(BoilingC)
|
||||
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
|
||||
//!-arith
|
||||
}
|
||||
/*
|
||||
//!+arith
|
||||
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
|
||||
//!-arith
|
||||
*/
|
||||
|
||||
// Output:
|
||||
// 100
|
||||
// 180
|
||||
}
|
||||
|
||||
func Example2() {
|
||||
//!+printf
|
||||
c := FToC(212.0)
|
||||
fmt.Println(c.String()) // "100°C"
|
||||
fmt.Printf("%v\n", c) // "100°C"; no need to call String explicitly
|
||||
fmt.Printf("%s\n", c) // "100°C"
|
||||
fmt.Println(c) // "100°C"
|
||||
fmt.Printf("%g\n", c) // "100"; does not call String
|
||||
fmt.Println(float64(c)) // "100"; does not call String
|
||||
//!-printf
|
||||
|
||||
// Output:
|
||||
// 100°C
|
||||
// 100°C
|
||||
// 100°C
|
||||
// 100°C
|
||||
// 100
|
||||
// 100
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 72.
|
||||
|
||||
// Basename1 reads file names from stdin and prints the base name of each one.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
for input.Scan() {
|
||||
fmt.Println(basename(input.Text()))
|
||||
}
|
||||
// NOTE: ignoring potential errors from input.Err()
|
||||
}
|
||||
|
||||
//!+
|
||||
// basename removes directory components and a .suffix.
|
||||
// e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c
|
||||
func basename(s string) string {
|
||||
// Discard last '/' and everything before.
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == '/' {
|
||||
s = s[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
// Preserve everything before last '.'.
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == '.' {
|
||||
s = s[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 72.
|
||||
|
||||
// Basename2 reads file names from stdin and prints the base name of each one.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
for input.Scan() {
|
||||
fmt.Println(basename(input.Text()))
|
||||
}
|
||||
// NOTE: ignoring potential errors from input.Err()
|
||||
}
|
||||
|
||||
// basename removes directory components and a trailing .suffix.
|
||||
// e.g., a => a, a.go => a, a/b/c.go => c, a/b.c.go => b.c
|
||||
//!+
|
||||
func basename(s string) string {
|
||||
slash := strings.LastIndex(s, "/") // -1 if "/" not found
|
||||
s = s[slash+1:]
|
||||
if dot := strings.LastIndex(s, "."); dot >= 0 {
|
||||
s = s[:dot]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 73.
|
||||
|
||||
// Comma prints its argument numbers with a comma at each power of 1000.
|
||||
//
|
||||
// Example:
|
||||
// $ go build gopl.io/ch3/comma
|
||||
// $ ./comma 1 12 123 1234 1234567890
|
||||
// 1
|
||||
// 12
|
||||
// 123
|
||||
// 1,234
|
||||
// 1,234,567,890
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
fmt.Printf(" %s\n", comma(os.Args[i]))
|
||||
}
|
||||
}
|
||||
|
||||
//!+
|
||||
// comma inserts commas in a non-negative decimal integer string.
|
||||
func comma(s string) string {
|
||||
n := len(s)
|
||||
if n <= 3 {
|
||||
return s
|
||||
}
|
||||
return comma(s[:n-3]) + "," + s[n-3:]
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 61.
|
||||
//!+
|
||||
|
||||
// Mandelbrot emits a PNG image of the Mandelbrot fractal.
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"math/cmplx"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const (
|
||||
xmin, ymin, xmax, ymax = -2, -2, +2, +2
|
||||
width, height = 1024, 1024
|
||||
)
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
for py := 0; py < height; py++ {
|
||||
y := float64(py)/height*(ymax-ymin) + ymin
|
||||
for px := 0; px < width; px++ {
|
||||
x := float64(px)/width*(xmax-xmin) + xmin
|
||||
z := complex(x, y)
|
||||
// Image point (px, py) represents complex value z.
|
||||
img.Set(px, py, mandelbrot(z))
|
||||
}
|
||||
}
|
||||
png.Encode(os.Stdout, img) // NOTE: ignoring errors
|
||||
}
|
||||
|
||||
func mandelbrot(z complex128) color.Color {
|
||||
const iterations = 200
|
||||
const contrast = 15
|
||||
|
||||
var v complex128
|
||||
for n := uint8(0); n < iterations; n++ {
|
||||
v = v*v + z
|
||||
if cmplx.Abs(v) > 2 {
|
||||
return color.Gray{255 - contrast*n}
|
||||
}
|
||||
}
|
||||
return color.Black
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
// Some other interesting functions:
|
||||
|
||||
func acos(z complex128) color.Color {
|
||||
v := cmplx.Acos(z)
|
||||
blue := uint8(real(v)*128) + 127
|
||||
red := uint8(imag(v)*128) + 127
|
||||
return color.YCbCr{192, blue, red}
|
||||
}
|
||||
|
||||
func sqrt(z complex128) color.Color {
|
||||
v := cmplx.Sqrt(z)
|
||||
blue := uint8(real(v)*128) + 127
|
||||
red := uint8(imag(v)*128) + 127
|
||||
return color.YCbCr{128, blue, red}
|
||||
}
|
||||
|
||||
// f(x) = x^4 - 1
|
||||
//
|
||||
// z' = z - f(z)/f'(z)
|
||||
// = z - (z^4 - 1) / (4 * z^3)
|
||||
// = z - (z - 1/z^3) / 4
|
||||
func newton(z complex128) color.Color {
|
||||
const iterations = 37
|
||||
const contrast = 7
|
||||
for i := uint8(0); i < iterations; i++ {
|
||||
z -= (z - 1/(z*z*z)) / 4
|
||||
if cmplx.Abs(z*z*z*z-1) < 1e-6 {
|
||||
return color.Gray{255 - contrast*i}
|
||||
}
|
||||
}
|
||||
return color.Black
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 77.
|
||||
|
||||
// Netflag demonstrates an integer type used as a bit field.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "net"
|
||||
)
|
||||
|
||||
//!+
|
||||
func IsUp(v Flags) bool { return v&FlagUp == FlagUp }
|
||||
func TurnDown(v *Flags) { *v &^= FlagUp }
|
||||
func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
|
||||
func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 }
|
||||
|
||||
func main() {
|
||||
var v Flags = FlagMulticast | FlagUp
|
||||
fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
|
||||
TurnDown(&v)
|
||||
fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
|
||||
SetBroadcast(&v)
|
||||
fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false"
|
||||
fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 74.
|
||||
|
||||
// Printints demonstrates the use of bytes.Buffer to format a string.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//!+
|
||||
// intsToString is like fmt.Sprintf(values) but adds commas.
|
||||
func intsToString(values []int) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
for i, v := range values {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%d", v)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]"
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 58.
|
||||
//!+
|
||||
|
||||
// Surface computes an SVG rendering of a 3-D surface function.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
width, height = 600, 320 // canvas size in pixels
|
||||
cells = 100 // number of grid cells
|
||||
xyrange = 30.0 // axis ranges (-xyrange..+xyrange)
|
||||
xyscale = width / 2 / xyrange // pixels per x or y unit
|
||||
zscale = height * 0.4 // pixels per z unit
|
||||
angle = math.Pi / 6 // angle of x, y axes (=30°)
|
||||
)
|
||||
|
||||
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
|
||||
"style='stroke: grey; fill: white; stroke-width: 0.7' "+
|
||||
"width='%d' height='%d'>", width, height)
|
||||
for i := 0; i < cells; i++ {
|
||||
for j := 0; j < cells; j++ {
|
||||
ax, ay := corner(i+1, j)
|
||||
bx, by := corner(i, j)
|
||||
cx, cy := corner(i, j+1)
|
||||
dx, dy := corner(i+1, j+1)
|
||||
fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
|
||||
ax, ay, bx, by, cx, cy, dx, dy)
|
||||
}
|
||||
}
|
||||
fmt.Println("</svg>")
|
||||
}
|
||||
|
||||
func corner(i, j int) (float64, float64) {
|
||||
// Find point (x,y) at corner of cell (i,j).
|
||||
x := xyrange * (float64(i)/cells - 0.5)
|
||||
y := xyrange * (float64(j)/cells - 0.5)
|
||||
|
||||
// Compute surface height z.
|
||||
z := f(x, y)
|
||||
|
||||
// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
|
||||
sx := width/2 + (x-y)*cos30*xyscale
|
||||
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
|
||||
return sx, sy
|
||||
}
|
||||
|
||||
func f(x, y float64) float64 {
|
||||
r := math.Hypot(x, y) // distance from (0,0)
|
||||
return math.Sin(r) / r
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 88.
|
||||
|
||||
// Append illustrates the behavior of the built-in append function.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func appendslice(x []int, y ...int) []int {
|
||||
var z []int
|
||||
zlen := len(x) + len(y)
|
||||
if zlen <= cap(x) {
|
||||
// There is room to expand the slice.
|
||||
z = x[:zlen]
|
||||
} else {
|
||||
// There is insufficient space.
|
||||
// Grow by doubling, for amortized linear complexity.
|
||||
zcap := zlen
|
||||
if zcap < 2*len(x) {
|
||||
zcap = 2 * len(x)
|
||||
}
|
||||
z = make([]int, zlen, zcap)
|
||||
copy(z, x)
|
||||
}
|
||||
copy(z[len(x):], y)
|
||||
return z
|
||||
}
|
||||
|
||||
//!+append
|
||||
func appendInt(x []int, y int) []int {
|
||||
var z []int
|
||||
zlen := len(x) + 1
|
||||
if zlen <= cap(x) {
|
||||
// There is room to grow. Extend the slice.
|
||||
z = x[:zlen]
|
||||
} else {
|
||||
// There is insufficient space. Allocate a new array.
|
||||
// Grow by doubling, for amortized linear complexity.
|
||||
zcap := zlen
|
||||
if zcap < 2*len(x) {
|
||||
zcap = 2 * len(x)
|
||||
}
|
||||
z = make([]int, zlen, zcap)
|
||||
copy(z, x) // a built-in function; see text
|
||||
}
|
||||
z[len(x)] = y
|
||||
return z
|
||||
}
|
||||
|
||||
//!-append
|
||||
|
||||
//!+growth
|
||||
func main() {
|
||||
var x, y []int
|
||||
for i := 0; i < 10; i++ {
|
||||
y = appendInt(x, i)
|
||||
fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
|
||||
x = y
|
||||
}
|
||||
}
|
||||
|
||||
//!-growth
|
||||
|
||||
/*
|
||||
//!+output
|
||||
0 cap=1 [0]
|
||||
1 cap=2 [0 1]
|
||||
2 cap=4 [0 1 2]
|
||||
3 cap=4 [0 1 2 3]
|
||||
4 cap=8 [0 1 2 3 4]
|
||||
5 cap=8 [0 1 2 3 4 5]
|
||||
6 cap=8 [0 1 2 3 4 5 6]
|
||||
7 cap=8 [0 1 2 3 4 5 6 7]
|
||||
8 cap=16 [0 1 2 3 4 5 6 7 8]
|
||||
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
|
||||
//!-output
|
||||
*/
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 117.
|
||||
|
||||
// Autoescape demonstrates automatic HTML escaping in html/template.
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
const templ = `<p>A: {{.A}}</p><p>B: {{.B}}</p>`
|
||||
t := template.Must(template.New("escape").Parse(templ))
|
||||
var data struct {
|
||||
A string // untrusted plain text
|
||||
B template.HTML // trusted HTML
|
||||
}
|
||||
data.A = "<b>Hello!</b>"
|
||||
data.B = "<b>Hello!</b>"
|
||||
if err := t.Execute(os.Stdout, data); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 97.
|
||||
//!+
|
||||
|
||||
// Charcount computes counts of Unicode characters.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func main() {
|
||||
counts := make(map[rune]int) // counts of Unicode characters
|
||||
var utflen [utf8.UTFMax + 1]int // count of lengths of UTF-8 encodings
|
||||
invalid := 0 // count of invalid UTF-8 characters
|
||||
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
r, n, err := in.ReadRune() // returns rune, nbytes, error
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "charcount: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if r == unicode.ReplacementChar && n == 1 {
|
||||
invalid++
|
||||
continue
|
||||
}
|
||||
counts[r]++
|
||||
utflen[n]++
|
||||
}
|
||||
fmt.Printf("rune\tcount\n")
|
||||
for c, n := range counts {
|
||||
fmt.Printf("%q\t%d\n", c, n)
|
||||
}
|
||||
fmt.Print("\nlen\tcount\n")
|
||||
for i, n := range utflen {
|
||||
if i > 0 {
|
||||
fmt.Printf("%d\t%d\n", i, n)
|
||||
}
|
||||
}
|
||||
if invalid > 0 {
|
||||
fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 96.
|
||||
|
||||
// Dedup prints only one instance of each line; duplicates are removed.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
seen := make(map[string]bool) // a set of strings
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
for input.Scan() {
|
||||
line := input.Text()
|
||||
if !seen[line] {
|
||||
seen[line] = true
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := input.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "dedup: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 106.
|
||||
|
||||
// Embed demonstrates basic struct embedding.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Point struct{ X, Y int }
|
||||
|
||||
type Circle struct {
|
||||
Point
|
||||
Radius int
|
||||
}
|
||||
|
||||
type Wheel struct {
|
||||
Circle
|
||||
Spokes int
|
||||
}
|
||||
|
||||
func main() {
|
||||
var w Wheel
|
||||
//!+
|
||||
w = Wheel{Circle{Point{8, 8}, 5}, 20}
|
||||
|
||||
w = Wheel{
|
||||
Circle: Circle{
|
||||
Point: Point{X: 8, Y: 8},
|
||||
Radius: 5,
|
||||
},
|
||||
Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v\n", w)
|
||||
// Output:
|
||||
// Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20}
|
||||
|
||||
w.X = 42
|
||||
|
||||
fmt.Printf("%#v\n", w)
|
||||
// Output:
|
||||
// Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}
|
||||
//!-
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 110.
|
||||
//!+
|
||||
|
||||
// Package github provides a Go API for the GitHub issue tracker.
|
||||
// See https://developer.github.com/v3/search/#search-issues.
|
||||
package github
|
||||
|
||||
import "time"
|
||||
|
||||
const IssuesURL = "https://api.github.com/search/issues"
|
||||
|
||||
type IssuesSearchResult struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Items []*Issue
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
Number int
|
||||
HTMLURL string `json:"html_url"`
|
||||
Title string
|
||||
State string
|
||||
User *User
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Body string // in Markdown format
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Login string
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
//!+
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SearchIssues queries the GitHub issue tracker.
|
||||
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
|
||||
q := url.QueryEscape(strings.Join(terms, " "))
|
||||
resp, err := http.Get(IssuesURL + "?q=" + q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//!-
|
||||
// For long-term stability, instead of http.Get, use the
|
||||
// variant below which adds an HTTP request header indicating
|
||||
// that only version 3 of the GitHub API is acceptable.
|
||||
//
|
||||
// req, err := http.NewRequest("GET", IssuesURL+"?q="+q, nil)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// req.Header.Set(
|
||||
// "Accept", "application/vnd.github.v3.text-match+json")
|
||||
// resp, err := http.DefaultClient.Do(req)
|
||||
//!+
|
||||
|
||||
// We must close resp.Body on all execution paths.
|
||||
// (Chapter 5 presents 'defer', which makes this simpler.)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("search query failed: %s", resp.Status)
|
||||
}
|
||||
|
||||
var result IssuesSearchResult
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 99.
|
||||
|
||||
// Graph shows how to use a map of maps to represent a directed graph.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//!+
|
||||
var graph = make(map[string]map[string]bool)
|
||||
|
||||
func addEdge(from, to string) {
|
||||
edges := graph[from]
|
||||
if edges == nil {
|
||||
edges = make(map[string]bool)
|
||||
graph[from] = edges
|
||||
}
|
||||
edges[to] = true
|
||||
}
|
||||
|
||||
func hasEdge(from, to string) bool {
|
||||
return graph[from][to]
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
addEdge("a", "b")
|
||||
addEdge("c", "d")
|
||||
addEdge("a", "d")
|
||||
addEdge("d", "a")
|
||||
fmt.Println(hasEdge("a", "b"))
|
||||
fmt.Println(hasEdge("c", "d"))
|
||||
fmt.Println(hasEdge("a", "d"))
|
||||
fmt.Println(hasEdge("d", "a"))
|
||||
fmt.Println(hasEdge("x", "b"))
|
||||
fmt.Println(hasEdge("c", "d"))
|
||||
fmt.Println(hasEdge("x", "d"))
|
||||
fmt.Println(hasEdge("d", "x"))
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 112.
|
||||
//!+
|
||||
|
||||
// Issues prints a table of GitHub issues matching the search terms.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopl.io/ch4/github"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
result, err := github.SearchIssues(os.Args[1:])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%d issues:\n", result.TotalCount)
|
||||
for _, item := range result.Items {
|
||||
fmt.Printf("#%-5d %9.9s %.55s\n",
|
||||
item.Number, item.User.Login, item.Title)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
/*
|
||||
//!+textoutput
|
||||
$ go build gopl.io/ch4/issues
|
||||
$ ./issues repo:golang/go is:open json decoder
|
||||
13 issues:
|
||||
#5680 eaigner encoding/json: set key converter on en/decoder
|
||||
#6050 gopherbot encoding/json: provide tokenizer
|
||||
#8658 gopherbot encoding/json: use bufio
|
||||
#8462 kortschak encoding/json: UnmarshalText confuses json.Unmarshal
|
||||
#5901 rsc encoding/json: allow override type marshaling
|
||||
#9812 klauspost encoding/json: string tag not symmetric
|
||||
#7872 extempora encoding/json: Encoder internally buffers full output
|
||||
#9650 cespare encoding/json: Decoding gives errPhase when unmarshalin
|
||||
#6716 gopherbot encoding/json: include field name in unmarshal error me
|
||||
#6901 lukescott encoding/json, encoding/xml: option to treat unknown fi
|
||||
#6384 joeshaw encoding/json: encode precise floating point integers u
|
||||
#6647 btracey x/tools/cmd/godoc: display type kind of each named type
|
||||
#4237 gjemiller encoding/base64: URLEncoding padding is optional
|
||||
//!-textoutput
|
||||
*/
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 115.
|
||||
|
||||
// Issueshtml prints an HTML table of issues matching the search terms.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopl.io/ch4/github"
|
||||
)
|
||||
|
||||
//!+template
|
||||
import "html/template"
|
||||
|
||||
var issueList = template.Must(template.New("issuelist").Parse(`
|
||||
<h1>{{.TotalCount}} issues</h1>
|
||||
<table>
|
||||
<tr style='text-align: left'>
|
||||
<th>#</th>
|
||||
<th>State</th>
|
||||
<th>User</th>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
{{range .Items}}
|
||||
<tr>
|
||||
<td><a href='{{.HTMLURL}}'>{{.Number}}</td>
|
||||
<td>{{.State}}</td>
|
||||
<td><a href='{{.User.HTMLURL}}'>{{.User.Login}}</a></td>
|
||||
<td><a href='{{.HTMLURL}}'>{{.Title}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
`))
|
||||
|
||||
//!-template
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
result, err := github.SearchIssues(os.Args[1:])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := issueList.Execute(os.Stdout, result); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 113.
|
||||
|
||||
// Issuesreport prints a report of issues matching the search terms.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gopl.io/ch4/github"
|
||||
)
|
||||
|
||||
//!+template
|
||||
const templ = `{{.TotalCount}} issues:
|
||||
{{range .Items}}----------------------------------------
|
||||
Number: {{.Number}}
|
||||
User: {{.User.Login}}
|
||||
Title: {{.Title | printf "%.64s"}}
|
||||
Age: {{.CreatedAt | daysAgo}} days
|
||||
{{end}}`
|
||||
|
||||
//!-template
|
||||
|
||||
//!+daysAgo
|
||||
func daysAgo(t time.Time) int {
|
||||
return int(time.Since(t).Hours() / 24)
|
||||
}
|
||||
|
||||
//!-daysAgo
|
||||
|
||||
//!+exec
|
||||
var report = template.Must(template.New("issuelist").
|
||||
Funcs(template.FuncMap{"daysAgo": daysAgo}).
|
||||
Parse(templ))
|
||||
|
||||
func main() {
|
||||
result, err := github.SearchIssues(os.Args[1:])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := report.Execute(os.Stdout, result); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
//!-exec
|
||||
|
||||
func noMust() {
|
||||
//!+parse
|
||||
report, err := template.New("report").
|
||||
Funcs(template.FuncMap{"daysAgo": daysAgo}).
|
||||
Parse(templ)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//!-parse
|
||||
result, err := github.SearchIssues(os.Args[1:])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := report.Execute(os.Stdout, result); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//!+output
|
||||
$ go build gopl.io/ch4/issuesreport
|
||||
$ ./issuesreport repo:golang/go is:open json decoder
|
||||
13 issues:
|
||||
----------------------------------------
|
||||
Number: 5680
|
||||
User: eaigner
|
||||
Title: encoding/json: set key converter on en/decoder
|
||||
Age: 750 days
|
||||
----------------------------------------
|
||||
Number: 6050
|
||||
User: gopherbot
|
||||
Title: encoding/json: provide tokenizer
|
||||
Age: 695 days
|
||||
----------------------------------------
|
||||
...
|
||||
//!-output
|
||||
*/
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 108.
|
||||
|
||||
// Movie prints Movies as JSON.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
//!+
|
||||
type Movie struct {
|
||||
Title string
|
||||
Year int `json:"released"`
|
||||
Color bool `json:"color,omitempty"`
|
||||
Actors []string
|
||||
}
|
||||
|
||||
var movies = []Movie{
|
||||
{Title: "Casablanca", Year: 1942, Color: false,
|
||||
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
|
||||
{Title: "Cool Hand Luke", Year: 1967, Color: true,
|
||||
Actors: []string{"Paul Newman"}},
|
||||
{Title: "Bullitt", Year: 1968, Color: true,
|
||||
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
|
||||
// ...
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
{
|
||||
//!+Marshal
|
||||
data, err := json.Marshal(movies)
|
||||
if err != nil {
|
||||
log.Fatalf("JSON marshaling failed: %s", err)
|
||||
}
|
||||
fmt.Printf("%s\n", data)
|
||||
//!-Marshal
|
||||
}
|
||||
|
||||
{
|
||||
//!+MarshalIndent
|
||||
data, err := json.MarshalIndent(movies, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("JSON marshaling failed: %s", err)
|
||||
}
|
||||
fmt.Printf("%s\n", data)
|
||||
//!-MarshalIndent
|
||||
|
||||
//!+Unmarshal
|
||||
var titles []struct{ Title string }
|
||||
if err := json.Unmarshal(data, &titles); err != nil {
|
||||
log.Fatalf("JSON unmarshaling failed: %s", err)
|
||||
}
|
||||
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
|
||||
//!-Unmarshal
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//!+output
|
||||
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
|
||||
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
|
||||
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
|
||||
Actors":["Steve McQueen","Jacqueline Bisset"]}]
|
||||
//!-output
|
||||
*/
|
||||
|
||||
/*
|
||||
//!+indented
|
||||
[
|
||||
{
|
||||
"Title": "Casablanca",
|
||||
"released": 1942,
|
||||
"Actors": [
|
||||
"Humphrey Bogart",
|
||||
"Ingrid Bergman"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Cool Hand Luke",
|
||||
"released": 1967,
|
||||
"color": true,
|
||||
"Actors": [
|
||||
"Paul Newman"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Bullitt",
|
||||
"released": 1968,
|
||||
"color": true,
|
||||
"Actors": [
|
||||
"Steve McQueen",
|
||||
"Jacqueline Bisset"
|
||||
]
|
||||
}
|
||||
]
|
||||
//!-indented
|
||||
*/
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 91.
|
||||
|
||||
//!+nonempty
|
||||
|
||||
// Nonempty is an example of an in-place slice algorithm.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// nonempty returns a slice holding only the non-empty strings.
|
||||
// The underlying array is modified during the call.
|
||||
func nonempty(strings []string) []string {
|
||||
i := 0
|
||||
for _, s := range strings {
|
||||
if s != "" {
|
||||
strings[i] = s
|
||||
i++
|
||||
}
|
||||
}
|
||||
return strings[:i]
|
||||
}
|
||||
|
||||
//!-nonempty
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
data := []string{"one", "", "three"}
|
||||
fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
|
||||
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
|
||||
//!-main
|
||||
}
|
||||
|
||||
//!+alt
|
||||
func nonempty2(strings []string) []string {
|
||||
out := strings[:0] // zero-length slice of original
|
||||
for _, s := range strings {
|
||||
if s != "" {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
//!-alt
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 86.
|
||||
|
||||
// Rev reverses a slice.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//!+array
|
||||
a := [...]int{0, 1, 2, 3, 4, 5}
|
||||
reverse(a[:])
|
||||
fmt.Println(a) // "[5 4 3 2 1 0]"
|
||||
//!-array
|
||||
|
||||
//!+slice
|
||||
s := []int{0, 1, 2, 3, 4, 5}
|
||||
// Rotate s left by two positions.
|
||||
reverse(s[:2])
|
||||
reverse(s[2:])
|
||||
reverse(s)
|
||||
fmt.Println(s) // "[2 3 4 5 0 1]"
|
||||
//!-slice
|
||||
|
||||
// Interactive test of reverse.
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
outer:
|
||||
for input.Scan() {
|
||||
var ints []int
|
||||
for _, s := range strings.Fields(input.Text()) {
|
||||
x, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
continue outer
|
||||
}
|
||||
ints = append(ints, int(x))
|
||||
}
|
||||
reverse(ints)
|
||||
fmt.Printf("%v\n", ints)
|
||||
}
|
||||
// NOTE: ignoring potential errors from input.Err()
|
||||
}
|
||||
|
||||
//!+rev
|
||||
// reverse reverses a slice of ints in place.
|
||||
func reverse(s []int) {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
}
|
||||
|
||||
//!-rev
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 83.
|
||||
|
||||
// The sha256 command computes the SHA256 hash (an array) of a string.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//!+
|
||||
import "crypto/sha256"
|
||||
|
||||
func main() {
|
||||
c1 := sha256.Sum256([]byte("x"))
|
||||
c2 := sha256.Sum256([]byte("X"))
|
||||
fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
|
||||
// Output:
|
||||
// 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
|
||||
// 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
|
||||
// false
|
||||
// [32]uint8
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 101.
|
||||
|
||||
// Package treesort provides insertion sort using an unbalanced binary tree.
|
||||
package treesort
|
||||
|
||||
//!+
|
||||
type tree struct {
|
||||
value int
|
||||
left, right *tree
|
||||
}
|
||||
|
||||
// Sort sorts values in place.
|
||||
func Sort(values []int) {
|
||||
var root *tree
|
||||
for _, v := range values {
|
||||
root = add(root, v)
|
||||
}
|
||||
appendValues(values[:0], root)
|
||||
}
|
||||
|
||||
// appendValues appends the elements of t to values in order
|
||||
// and returns the resulting slice.
|
||||
func appendValues(values []int, t *tree) []int {
|
||||
if t != nil {
|
||||
values = appendValues(values, t.left)
|
||||
values = append(values, t.value)
|
||||
values = appendValues(values, t.right)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func add(t *tree, value int) *tree {
|
||||
if t == nil {
|
||||
// Equivalent to return &tree{value: value}.
|
||||
t = new(tree)
|
||||
t.value = value
|
||||
return t
|
||||
}
|
||||
if value < t.value {
|
||||
t.left = add(t.left, value)
|
||||
} else {
|
||||
t.right = add(t.right, value)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package treesort_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"gopl.io/ch4/treesort"
|
||||
)
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
data := make([]int, 50)
|
||||
for i := range data {
|
||||
data[i] = rand.Int() % 50
|
||||
}
|
||||
treesort.Sort(data)
|
||||
if !sort.IntsAreSorted(data) {
|
||||
t.Errorf("not sorted: %s", data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 150.
|
||||
|
||||
// Defer1 demonstrates a deferred call being invoked during a panic.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//!+f
|
||||
func main() {
|
||||
f(3)
|
||||
}
|
||||
|
||||
func f(x int) {
|
||||
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
|
||||
defer fmt.Printf("defer %d\n", x)
|
||||
f(x - 1)
|
||||
}
|
||||
|
||||
//!-f
|
||||
|
||||
/*
|
||||
//!+stdout
|
||||
f(3)
|
||||
f(2)
|
||||
f(1)
|
||||
defer 1
|
||||
defer 2
|
||||
defer 3
|
||||
//!-stdout
|
||||
|
||||
//!+stderr
|
||||
panic: runtime error: integer divide by zero
|
||||
main.f(0)
|
||||
src/gopl.io/ch5/defer1/defer.go:14
|
||||
main.f(1)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
main.f(2)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
|
||||
main.f(3)
|
||||
src/gopl.io/ch5/defer1/defer.go:16
|
||||
main.main()
|
||||
src/gopl.io/ch5/defer1/defer.go:10
|
||||
//!-stderr
|
||||
*/
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 151.
|
||||
|
||||
// Defer2 demonstrates a deferred call to runtime.Stack during a panic.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
defer printStack()
|
||||
f(3)
|
||||
}
|
||||
|
||||
func printStack() {
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
os.Stdout.Write(buf[:n])
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func f(x int) {
|
||||
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
|
||||
defer fmt.Printf("defer %d\n", x)
|
||||
f(x - 1)
|
||||
}
|
||||
|
||||
/*
|
||||
//!+printstack
|
||||
goroutine 1 [running]:
|
||||
main.printStack()
|
||||
src/gopl.io/ch5/defer2/defer.go:20
|
||||
main.f(0)
|
||||
src/gopl.io/ch5/defer2/defer.go:27
|
||||
main.f(1)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.f(2)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.f(3)
|
||||
src/gopl.io/ch5/defer2/defer.go:29
|
||||
main.main()
|
||||
src/gopl.io/ch5/defer2/defer.go:15
|
||||
//!-printstack
|
||||
*/
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 148.
|
||||
|
||||
// Fetch saves the contents of a URL into a local file.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
//!+
|
||||
// Fetch downloads the URL and returns the
|
||||
// name and length of the local file.
|
||||
func fetch(url string) (filename string, n int64, err error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
local := path.Base(resp.Request.URL.Path)
|
||||
if local == "/" {
|
||||
local = "index.html"
|
||||
}
|
||||
f, err := os.Create(local)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
n, err = io.Copy(f, resp.Body)
|
||||
// Close file, but prefer error from Copy, if any.
|
||||
if closeErr := f.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
return local, n, err
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
for _, url := range os.Args[1:] {
|
||||
local, n, err := fetch(url)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fetch %s: %v\n", url, err)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s => %s (%d bytes).\n", url, local, n)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 122.
|
||||
//!+main
|
||||
|
||||
// Findlinks1 prints the links in an HTML document read from standard input.
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 125.
|
||||
|
||||
// Findlinks2 does an HTTP GET on each URL, parses the
|
||||
// result as HTML, and prints the links within it.
|
||||
//
|
||||
// Usage:
|
||||
// findlinks url ...
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// visit appends to links each link found in n, and returns the result.
|
||||
func visit(links []string, n *html.Node) []string {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == "href" {
|
||||
links = append(links, a.Val)
|
||||
}
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
links = visit(links, c)
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
for _, url := range os.Args[1:] {
|
||||
links, err := findLinks(url)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err)
|
||||
continue
|
||||
}
|
||||
for _, link := range links {
|
||||
fmt.Println(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findLinks performs an HTTP GET request for url, parses the
|
||||
// response as HTML, and extracts and returns the links.
|
||||
func findLinks(url string) ([]string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
|
||||
}
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
return visit(nil, doc), nil
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 139.
|
||||
|
||||
// Findlinks3 crawls the web, starting with the URLs on the command line.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopl.io/ch5/links"
|
||||
)
|
||||
|
||||
//!+breadthFirst
|
||||
// breadthFirst calls f for each item in the worklist.
|
||||
// Any items returned by f are added to the worklist.
|
||||
// f is called at most once for each item.
|
||||
func breadthFirst(f func(item string) []string, worklist []string) {
|
||||
seen := make(map[string]bool)
|
||||
for len(worklist) > 0 {
|
||||
items := worklist
|
||||
worklist = nil
|
||||
for _, item := range items {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
worklist = append(worklist, f(item)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-breadthFirst
|
||||
|
||||
//!+crawl
|
||||
func crawl(url string) []string {
|
||||
fmt.Println(url)
|
||||
list, err := links.Extract(url)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
//!-crawl
|
||||
|
||||
//!+main
|
||||
func main() {
|
||||
// Crawl the web breadth-first,
|
||||
// starting from the command-line arguments.
|
||||
breadthFirst(crawl, os.Args[1:])
|
||||
}
|
||||
|
||||
//!-main
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 138.
|
||||
//!+Extract
|
||||
|
||||
// Package links provides a link-extraction function.
|
||||
package links
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Extract makes an HTTP GET request to the specified URL, parses
|
||||
// the response as HTML, and returns the links in the HTML document.
|
||||
func Extract(url string) ([]string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
|
||||
}
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
|
||||
var links []string
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key != "href" {
|
||||
continue
|
||||
}
|
||||
link, err := resp.Request.URL.Parse(a.Val)
|
||||
if err != nil {
|
||||
continue // ignore bad URLs
|
||||
}
|
||||
links = append(links, link.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachNode(doc, visitNode, nil)
|
||||
return links, nil
|
||||
}
|
||||
|
||||
//!-Extract
|
||||
|
||||
// Copied from gopl.io/ch5/outline2.
|
||||
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
|
||||
if pre != nil {
|
||||
pre(n)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
forEachNode(c, pre, post)
|
||||
}
|
||||
if post != nil {
|
||||
post(n)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 123.
|
||||
|
||||
// Outline prints the outline of an HTML document tree.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
//!+
|
||||
func main() {
|
||||
doc, err := html.Parse(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "outline: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
outline(nil, doc)
|
||||
}
|
||||
|
||||
func outline(stack []string, n *html.Node) {
|
||||
if n.Type == html.ElementNode {
|
||||
stack = append(stack, n.Data) // push tag
|
||||
fmt.Println(stack)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
outline(stack, c)
|
||||
}
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 133.
|
||||
|
||||
// Outline prints the outline of an HTML document tree.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, url := range os.Args[1:] {
|
||||
outline(url)
|
||||
}
|
||||
}
|
||||
|
||||
func outline(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//!+call
|
||||
forEachNode(doc, startElement, endElement)
|
||||
//!-call
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//!+forEachNode
|
||||
// forEachNode calls the functions pre(x) and post(x) for each node
|
||||
// x in the tree rooted at n. Both functions are optional.
|
||||
// pre is called before the children are visited (preorder) and
|
||||
// post is called after (postorder).
|
||||
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
|
||||
if pre != nil {
|
||||
pre(n)
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
forEachNode(c, pre, post)
|
||||
}
|
||||
|
||||
if post != nil {
|
||||
post(n)
|
||||
}
|
||||
}
|
||||
|
||||
//!-forEachNode
|
||||
|
||||
//!+startend
|
||||
var depth int
|
||||
|
||||
func startElement(n *html.Node) {
|
||||
if n.Type == html.ElementNode {
|
||||
fmt.Printf("%*s<%s>\n", depth*2, "", n.Data)
|
||||
depth++
|
||||
}
|
||||
}
|
||||
|
||||
func endElement(n *html.Node) {
|
||||
if n.Type == html.ElementNode {
|
||||
depth--
|
||||
fmt.Printf("%*s</%s>\n", depth*2, "", n.Data)
|
||||
}
|
||||
}
|
||||
|
||||
//!-startend
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 135.
|
||||
|
||||
// The squares program demonstrates a function value with state.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//!+
|
||||
// squares returns a function that returns
|
||||
// the next square number each time it is called.
|
||||
func squares() func() int {
|
||||
var x int
|
||||
return func() int {
|
||||
x++
|
||||
return x * x
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := squares()
|
||||
fmt.Println(f()) // "1"
|
||||
fmt.Println(f()) // "4"
|
||||
fmt.Println(f()) // "9"
|
||||
fmt.Println(f()) // "16"
|
||||
}
|
||||
|
||||
//!-
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 142.
|
||||
|
||||
// The sum program demonstrates a variadic function.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//!+
|
||||
func sum(vals ...int) int {
|
||||
total := 0
|
||||
for _, val := range vals {
|
||||
total += val
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
fmt.Println(sum()) // "0"
|
||||
fmt.Println(sum(3)) // "3"
|
||||
fmt.Println(sum(1, 2, 3, 4)) // "10"
|
||||
//!-main
|
||||
|
||||
//!+slice
|
||||
values := []int{1, 2, 3, 4}
|
||||
fmt.Println(sum(values...)) // "10"
|
||||
//!-slice
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 144.
|
||||
|
||||
// Title1 prints the title of an HTML document specified by a URL.
|
||||
package main
|
||||
|
||||
/*
|
||||
//!+output
|
||||
$ go build gopl.io/ch5/title1
|
||||
$ ./title1 http://gopl.io
|
||||
The Go Programming Language
|
||||
$ ./title1 https://golang.org/doc/effective_go.html
|
||||
Effective Go - The Go Programming Language
|
||||
$ ./title1 https://golang.org/doc/gopher/frontpage.png
|
||||
title: https://golang.org/doc/gopher/frontpage.png
|
||||
has type image/png, not text/html
|
||||
//!-output
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Copied from gopl.io/ch5/outline2.
|
||||
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
|
||||
if pre != nil {
|
||||
pre(n)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
forEachNode(c, pre, post)
|
||||
}
|
||||
if post != nil {
|
||||
post(n)
|
||||
}
|
||||
}
|
||||
|
||||
//!+
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check Content-Type is HTML (e.g., "text/html; charset=utf-8").
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
|
||||
resp.Body.Close()
|
||||
return fmt.Errorf("%s has type %s, not text/html", url, ct)
|
||||
}
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "title" &&
|
||||
n.FirstChild != nil {
|
||||
fmt.Println(n.FirstChild.Data)
|
||||
}
|
||||
}
|
||||
forEachNode(doc, visitNode, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if err := title(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "title: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 145.
|
||||
|
||||
// Title2 prints the title of an HTML document specified by a URL.
|
||||
// It uses defer to simplify closing the response body stream.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Copied from gopl.io/ch5/outline2.
|
||||
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
|
||||
if pre != nil {
|
||||
pre(n)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
forEachNode(c, pre, post)
|
||||
}
|
||||
if post != nil {
|
||||
post(n)
|
||||
}
|
||||
}
|
||||
|
||||
//!+
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
|
||||
return fmt.Errorf("%s has type %s, not text/html", url, ct)
|
||||
}
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
|
||||
// ...print doc's title element...
|
||||
//!-
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "title" &&
|
||||
n.FirstChild != nil {
|
||||
fmt.Println(n.FirstChild.Data)
|
||||
}
|
||||
}
|
||||
forEachNode(doc, visitNode, nil)
|
||||
//!+
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if err := title(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "title: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 153.
|
||||
|
||||
// Title3 prints the title of an HTML document specified by a URL.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Copied from gopl.io/ch5/outline2.
|
||||
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
|
||||
if pre != nil {
|
||||
pre(n)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
forEachNode(c, pre, post)
|
||||
}
|
||||
if post != nil {
|
||||
post(n)
|
||||
}
|
||||
}
|
||||
|
||||
//!+
|
||||
// soleTitle returns the text of the first non-empty title element
|
||||
// in doc, and an error if there was not exactly one.
|
||||
func soleTitle(doc *html.Node) (title string, err error) {
|
||||
type bailout struct{}
|
||||
|
||||
defer func() {
|
||||
switch p := recover(); p {
|
||||
case nil:
|
||||
// no panic
|
||||
case bailout{}:
|
||||
// "expected" panic
|
||||
err = fmt.Errorf("multiple title elements")
|
||||
default:
|
||||
panic(p) // unexpected panic; carry on panicking
|
||||
}
|
||||
}()
|
||||
|
||||
// Bail out of recursion if we find more than one non-empty title.
|
||||
forEachNode(doc, func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "title" &&
|
||||
n.FirstChild != nil {
|
||||
if title != "" {
|
||||
panic(bailout{}) // multiple title elements
|
||||
}
|
||||
title = n.FirstChild.Data
|
||||
}
|
||||
}, nil)
|
||||
if title == "" {
|
||||
return "", fmt.Errorf("no title element")
|
||||
}
|
||||
return title, nil
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check Content-Type is HTML (e.g., "text/html; charset=utf-8").
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
|
||||
resp.Body.Close()
|
||||
return fmt.Errorf("%s has type %s, not text/html", url, ct)
|
||||
}
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
title, err := soleTitle(doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(title)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if err := title(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "title: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 136.
|
||||
|
||||
// The toposort program prints the nodes of a DAG in topological order.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
//!+table
|
||||
// prereqs maps computer science courses to their prerequisites.
|
||||
var prereqs = map[string][]string{
|
||||
"algorithms": {"data structures"},
|
||||
"calculus": {"linear algebra"},
|
||||
|
||||
"compilers": {
|
||||
"data structures",
|
||||
"formal languages",
|
||||
"computer organization",
|
||||
},
|
||||
|
||||
"data structures": {"discrete math"},
|
||||
"databases": {"data structures"},
|
||||
"discrete math": {"intro to programming"},
|
||||
"formal languages": {"discrete math"},
|
||||
"networks": {"operating systems"},
|
||||
"operating systems": {"data structures", "computer organization"},
|
||||
"programming languages": {"data structures", "computer organization"},
|
||||
}
|
||||
|
||||
//!-table
|
||||
|
||||
//!+main
|
||||
func main() {
|
||||
for i, course := range topoSort(prereqs) {
|
||||
fmt.Printf("%d:\t%s\n", i+1, course)
|
||||
}
|
||||
}
|
||||
|
||||
func topoSort(m map[string][]string) []string {
|
||||
var order []string
|
||||
seen := make(map[string]bool)
|
||||
var visitAll func(items []string)
|
||||
|
||||
visitAll = func(items []string) {
|
||||
for _, item := range items {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
visitAll(m[item])
|
||||
order = append(order, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
visitAll(keys)
|
||||
return order
|
||||
}
|
||||
|
||||
//!-main
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 146.
|
||||
|
||||
// The trace program uses defer to add entry/exit diagnostics to a function.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
//!+main
|
||||
func bigSlowOperation() {
|
||||
defer trace("bigSlowOperation")() // don't forget the extra parentheses
|
||||
// ...lots of work...
|
||||
time.Sleep(10 * time.Second) // simulate slow operation by sleeping
|
||||
}
|
||||
|
||||
func trace(msg string) func() {
|
||||
start := time.Now()
|
||||
log.Printf("enter %s", msg)
|
||||
return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) }
|
||||
}
|
||||
|
||||
//!-main
|
||||
|
||||
func main() {
|
||||
bigSlowOperation()
|
||||
}
|
||||
|
||||
/*
|
||||
!+output
|
||||
$ go build gopl.io/ch5/trace
|
||||
$ ./trace
|
||||
2015/11/18 09:53:26 enter bigSlowOperation
|
||||
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
|
||||
!-output
|
||||
*/
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 130.
|
||||
|
||||
// The wait program waits for an HTTP server to start responding.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//!+
|
||||
// WaitForServer attempts to contact the server of a URL.
|
||||
// It tries for one minute using exponential back-off.
|
||||
// It reports an error if all attempts fail.
|
||||
func WaitForServer(url string) error {
|
||||
const timeout = 1 * time.Minute
|
||||
deadline := time.Now().Add(timeout)
|
||||
for tries := 0; time.Now().Before(deadline); tries++ {
|
||||
_, err := http.Head(url)
|
||||
if err == nil {
|
||||
return nil // success
|
||||
}
|
||||
log.Printf("server not responding (%s); retrying...", err)
|
||||
time.Sleep(time.Second << uint(tries)) // exponential back-off
|
||||
}
|
||||
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
|
||||
}
|
||||
|
||||
//!-
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "usage: wait url\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
url := os.Args[1]
|
||||
//!+main
|
||||
// (In function main.)
|
||||
if err := WaitForServer(url); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
//!-main
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 161.
|
||||
|
||||
// Coloredpoint demonstrates struct embedding.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
//!+decl
|
||||
import "image/color"
|
||||
|
||||
type Point struct{ X, Y float64 }
|
||||
|
||||
type ColoredPoint struct {
|
||||
Point
|
||||
Color color.RGBA
|
||||
}
|
||||
|
||||
//!-decl
|
||||
|
||||
func (p Point) Distance(q Point) float64 {
|
||||
dX := q.X - p.X
|
||||
dY := q.Y - p.Y
|
||||
return math.Sqrt(dX*dX + dY*dY)
|
||||
}
|
||||
|
||||
func (p *Point) ScaleBy(factor float64) {
|
||||
p.X *= factor
|
||||
p.Y *= factor
|
||||
}
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
red := color.RGBA{255, 0, 0, 255}
|
||||
blue := color.RGBA{0, 0, 255, 255}
|
||||
var p = ColoredPoint{Point{1, 1}, red}
|
||||
var q = ColoredPoint{Point{5, 4}, blue}
|
||||
fmt.Println(p.Distance(q.Point)) // "5"
|
||||
p.ScaleBy(2)
|
||||
q.ScaleBy(2)
|
||||
fmt.Println(p.Distance(q.Point)) // "10"
|
||||
//!-main
|
||||
}
|
||||
|
||||
/*
|
||||
//!+error
|
||||
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
|
||||
//!-error
|
||||
*/
|
||||
|
||||
func init() {
|
||||
//!+methodexpr
|
||||
p := Point{1, 2}
|
||||
q := Point{4, 6}
|
||||
|
||||
distance := Point.Distance // method expression
|
||||
fmt.Println(distance(p, q)) // "5"
|
||||
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
|
||||
|
||||
scale := (*Point).ScaleBy
|
||||
scale(&p, 2)
|
||||
fmt.Println(p) // "{2 4}"
|
||||
fmt.Printf("%T\n", scale) // "func(*Point, float64)"
|
||||
//!-methodexpr
|
||||
}
|
||||
|
||||
func init() {
|
||||
red := color.RGBA{255, 0, 0, 255}
|
||||
blue := color.RGBA{0, 0, 255, 255}
|
||||
|
||||
//!+indirect
|
||||
type ColoredPoint struct {
|
||||
*Point
|
||||
Color color.RGBA
|
||||
}
|
||||
|
||||
p := ColoredPoint{&Point{1, 1}, red}
|
||||
q := ColoredPoint{&Point{5, 4}, blue}
|
||||
fmt.Println(p.Distance(*q.Point)) // "5"
|
||||
q.Point = p.Point // p and q now share the same Point
|
||||
p.ScaleBy(2)
|
||||
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
|
||||
//!-indirect
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 156.
|
||||
|
||||
// Package geometry defines simple types for plane geometry.
|
||||
//!+point
|
||||
package geometry
|
||||
|
||||
import "math"
|
||||
|
||||
type Point struct{ X, Y float64 }
|
||||
|
||||
// traditional function
|
||||
func Distance(p, q Point) float64 {
|
||||
return math.Hypot(q.X-p.X, q.Y-p.Y)
|
||||
}
|
||||
|
||||
// same thing, but as a method of the Point type
|
||||
func (p Point) Distance(q Point) float64 {
|
||||
return math.Hypot(q.X-p.X, q.Y-p.Y)
|
||||
}
|
||||
|
||||
//!-point
|
||||
|
||||
//!+path
|
||||
|
||||
// A Path is a journey connecting the points with straight lines.
|
||||
type Path []Point
|
||||
|
||||
// Distance returns the distance traveled along the path.
|
||||
func (path Path) Distance() float64 {
|
||||
sum := 0.0
|
||||
for i := range path {
|
||||
if i > 0 {
|
||||
sum += path[i-1].Distance(path[i])
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
//!-path
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 165.
|
||||
|
||||
// Package intset provides a set of integers based on a bit vector.
|
||||
package intset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//!+intset
|
||||
|
||||
// An IntSet is a set of small non-negative integers.
|
||||
// Its zero value represents the empty set.
|
||||
type IntSet struct {
|
||||
words []uint64
|
||||
}
|
||||
|
||||
// Has reports whether the set contains the non-negative value x.
|
||||
func (s *IntSet) Has(x int) bool {
|
||||
word, bit := x/64, uint(x%64)
|
||||
return word < len(s.words) && s.words[word]&(1<<bit) != 0
|
||||
}
|
||||
|
||||
// Add adds the non-negative value x to the set.
|
||||
func (s *IntSet) Add(x int) {
|
||||
word, bit := x/64, uint(x%64)
|
||||
for word >= len(s.words) {
|
||||
s.words = append(s.words, 0)
|
||||
}
|
||||
s.words[word] |= 1 << bit
|
||||
}
|
||||
|
||||
// UnionWith sets s to the union of s and t.
|
||||
func (s *IntSet) UnionWith(t *IntSet) {
|
||||
for i, tword := range t.words {
|
||||
if i < len(s.words) {
|
||||
s.words[i] |= tword
|
||||
} else {
|
||||
s.words = append(s.words, tword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-intset
|
||||
|
||||
//!+string
|
||||
|
||||
// String returns the set as a string of the form "{1 2 3}".
|
||||
func (s *IntSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
for i, word := range s.words {
|
||||
if word == 0 {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < 64; j++ {
|
||||
if word&(1<<uint(j)) != 0 {
|
||||
if buf.Len() > len("{") {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
fmt.Fprintf(&buf, "%d", 64*i+j)
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
//!-string
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package intset
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Example1() {
|
||||
//!+main
|
||||
var x, y IntSet
|
||||
x.Add(1)
|
||||
x.Add(144)
|
||||
x.Add(9)
|
||||
fmt.Println(x.String()) // "{1 9 144}"
|
||||
|
||||
y.Add(9)
|
||||
y.Add(42)
|
||||
fmt.Println(y.String()) // "{9 42}"
|
||||
|
||||
x.UnionWith(&y)
|
||||
fmt.Println(x.String()) // "{1 9 42 144}"
|
||||
|
||||
fmt.Println(x.Has(9), x.Has(123)) // "true false"
|
||||
//!-main
|
||||
|
||||
// Output:
|
||||
// {1 9 144}
|
||||
// {9 42}
|
||||
// {1 9 42 144}
|
||||
// true false
|
||||
}
|
||||
|
||||
func Example2() {
|
||||
var x IntSet
|
||||
x.Add(1)
|
||||
x.Add(144)
|
||||
x.Add(9)
|
||||
x.Add(42)
|
||||
|
||||
//!+note
|
||||
fmt.Println(&x) // "{1 9 42 144}"
|
||||
fmt.Println(x.String()) // "{1 9 42 144}"
|
||||
fmt.Println(x) // "{[4398046511618 0 65536]}"
|
||||
//!-note
|
||||
|
||||
// Output:
|
||||
// {1 9 42 144}
|
||||
// {1 9 42 144}
|
||||
// {[4398046511618 0 65536]}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 160.
|
||||
|
||||
// The urlvalues command demonstrates a map type with methods.
|
||||
package main
|
||||
|
||||
/*
|
||||
//!+values
|
||||
package url
|
||||
|
||||
// Values maps a string key to a list of values.
|
||||
type Values map[string][]string
|
||||
|
||||
// Get returns the first value associated with the given key,
|
||||
// or "" if there are none.
|
||||
func (v Values) Get(key string) string {
|
||||
if vs := v[key]; len(vs) > 0 {
|
||||
return vs[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Add adds the value to key.
|
||||
// It appends to any existing values associated with key.
|
||||
func (v Values) Add(key, value string) {
|
||||
v[key] = append(v[key], value)
|
||||
}
|
||||
//!-values
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
m := url.Values{"lang": {"en"}} // direct construction
|
||||
m.Add("item", "1")
|
||||
m.Add("item", "2")
|
||||
|
||||
fmt.Println(m.Get("lang")) // "en"
|
||||
fmt.Println(m.Get("q")) // ""
|
||||
fmt.Println(m.Get("item")) // "1" (first value)
|
||||
fmt.Println(m["item"]) // "[1 2]" (direct map access)
|
||||
|
||||
m = nil
|
||||
fmt.Println(m.Get("item")) // ""
|
||||
m.Add("item", "3") // panic: assignment to entry in nil map
|
||||
//!-main
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 173.
|
||||
|
||||
// Bytecounter demonstrates an implementation of io.Writer that counts bytes.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//!+bytecounter
|
||||
|
||||
type ByteCounter int
|
||||
|
||||
func (c *ByteCounter) Write(p []byte) (int, error) {
|
||||
*c += ByteCounter(len(p)) // convert int to ByteCounter
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
//!-bytecounter
|
||||
|
||||
func main() {
|
||||
//!+main
|
||||
var c ByteCounter
|
||||
c.Write([]byte("hello"))
|
||||
fmt.Println(c) // "5", = len("hello")
|
||||
|
||||
c = 0 // reset the counter
|
||||
var name = "Dolly"
|
||||
fmt.Fprintf(&c, "hello, %s", name)
|
||||
fmt.Println(c) // "12", = len("hello, Dolly")
|
||||
//!-main
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
// An Expr is an arithmetic expression.
|
||||
type Expr interface {
|
||||
// Eval returns the value of this Expr in the environment env.
|
||||
Eval(env Env) float64
|
||||
// Check reports errors in this Expr and adds its Vars to the set.
|
||||
Check(vars map[Var]bool) error
|
||||
}
|
||||
|
||||
//!+ast
|
||||
|
||||
// A Var identifies a variable, e.g., x.
|
||||
type Var string
|
||||
|
||||
// A literal is a numeric constant, e.g., 3.141.
|
||||
type literal float64
|
||||
|
||||
// A unary represents a unary operator expression, e.g., -x.
|
||||
type unary struct {
|
||||
op rune // one of '+', '-'
|
||||
x Expr
|
||||
}
|
||||
|
||||
// A binary represents a binary operator expression, e.g., x+y.
|
||||
type binary struct {
|
||||
op rune // one of '+', '-', '*', '/'
|
||||
x, y Expr
|
||||
}
|
||||
|
||||
// A call represents a function call expression, e.g., sin(x).
|
||||
type call struct {
|
||||
fn string // one of "pow", "sin", "sqrt"
|
||||
args []Expr
|
||||
}
|
||||
|
||||
//!-ast
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//!+Check
|
||||
|
||||
func (v Var) Check(vars map[Var]bool) error {
|
||||
vars[v] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (literal) Check(vars map[Var]bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u unary) Check(vars map[Var]bool) error {
|
||||
if !strings.ContainsRune("+-", u.op) {
|
||||
return fmt.Errorf("unexpected unary op %q", u.op)
|
||||
}
|
||||
return u.x.Check(vars)
|
||||
}
|
||||
|
||||
func (b binary) Check(vars map[Var]bool) error {
|
||||
if !strings.ContainsRune("+-*/", b.op) {
|
||||
return fmt.Errorf("unexpected binary op %q", b.op)
|
||||
}
|
||||
if err := b.x.Check(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.y.Check(vars)
|
||||
}
|
||||
|
||||
func (c call) Check(vars map[Var]bool) error {
|
||||
arity, ok := numParams[c.fn]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown function %q", c.fn)
|
||||
}
|
||||
if len(c.args) != arity {
|
||||
return fmt.Errorf("call to %s has %d args, want %d",
|
||||
c.fn, len(c.args), arity)
|
||||
}
|
||||
for _, arg := range c.args {
|
||||
if err := arg.Check(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
|
||||
|
||||
//!-Check
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//!+TestCoverage
|
||||
func TestCoverage(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
env Env
|
||||
want string // expected error from Parse/Check or result from Eval
|
||||
}{
|
||||
{"x % 2", nil, "unexpected '%'"},
|
||||
{"!true", nil, "unexpected '!'"},
|
||||
{"log(10)", nil, `unknown function "log"`},
|
||||
{"sqrt(1, 2)", nil, "call to sqrt has 2 args, want 1"},
|
||||
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expr, err := Parse(test.input)
|
||||
if err == nil {
|
||||
err = expr.Check(map[Var]bool{})
|
||||
}
|
||||
if err != nil {
|
||||
if err.Error() != test.want {
|
||||
t.Errorf("%s: got %q, want %q", test.input, err, test.want)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
|
||||
if got != test.want {
|
||||
t.Errorf("%s: %v => %s, want %s",
|
||||
test.input, test.env, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-TestCoverage
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 198.
|
||||
|
||||
// Package eval provides an expression evaluator.
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
//!+env
|
||||
|
||||
type Env map[Var]float64
|
||||
|
||||
//!-env
|
||||
|
||||
//!+Eval1
|
||||
|
||||
func (v Var) Eval(env Env) float64 {
|
||||
return env[v]
|
||||
}
|
||||
|
||||
func (l literal) Eval(_ Env) float64 {
|
||||
return float64(l)
|
||||
}
|
||||
|
||||
//!-Eval1
|
||||
|
||||
//!+Eval2
|
||||
|
||||
func (u unary) Eval(env Env) float64 {
|
||||
switch u.op {
|
||||
case '+':
|
||||
return +u.x.Eval(env)
|
||||
case '-':
|
||||
return -u.x.Eval(env)
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
|
||||
}
|
||||
|
||||
func (b binary) Eval(env Env) float64 {
|
||||
switch b.op {
|
||||
case '+':
|
||||
return b.x.Eval(env) + b.y.Eval(env)
|
||||
case '-':
|
||||
return b.x.Eval(env) - b.y.Eval(env)
|
||||
case '*':
|
||||
return b.x.Eval(env) * b.y.Eval(env)
|
||||
case '/':
|
||||
return b.x.Eval(env) / b.y.Eval(env)
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
|
||||
}
|
||||
|
||||
func (c call) Eval(env Env) float64 {
|
||||
switch c.fn {
|
||||
case "pow":
|
||||
return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
|
||||
case "sin":
|
||||
return math.Sin(c.args[0].Eval(env))
|
||||
case "sqrt":
|
||||
return math.Sqrt(c.args[0].Eval(env))
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported function call: %s", c.fn))
|
||||
}
|
||||
|
||||
//!-Eval2
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//!+Eval
|
||||
func TestEval(t *testing.T) {
|
||||
tests := []struct {
|
||||
expr string
|
||||
env Env
|
||||
want string
|
||||
}{
|
||||
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": 32}, "0"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": 212}, "100"},
|
||||
//!-Eval
|
||||
// additional tests that don't appear in the book
|
||||
{"-1 + -x", Env{"x": 1}, "-2"},
|
||||
{"-1 - x", Env{"x": 1}, "-2"},
|
||||
//!+Eval
|
||||
}
|
||||
var prevExpr string
|
||||
for _, test := range tests {
|
||||
// Print expr only when it changes.
|
||||
if test.expr != prevExpr {
|
||||
fmt.Printf("\n%s\n", test.expr)
|
||||
prevExpr = test.expr
|
||||
}
|
||||
expr, err := Parse(test.expr)
|
||||
if err != nil {
|
||||
t.Error(err) // parse error
|
||||
continue
|
||||
}
|
||||
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
|
||||
fmt.Printf("\t%v => %s\n", test.env, got)
|
||||
if got != test.want {
|
||||
t.Errorf("%s.Eval() in %s = %q, want %q\n",
|
||||
test.expr, test.env, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-Eval
|
||||
|
||||
/*
|
||||
//!+output
|
||||
sqrt(A / pi)
|
||||
map[A:87616 pi:3.141592653589793] => 167
|
||||
|
||||
pow(x, 3) + pow(y, 3)
|
||||
map[x:12 y:1] => 1729
|
||||
map[x:9 y:10] => 1729
|
||||
|
||||
5 / 9 * (F - 32)
|
||||
map[F:-40] => -40
|
||||
map[F:32] => 0
|
||||
map[F:212] => 100
|
||||
//!-output
|
||||
|
||||
// Additional outputs that don't appear in the book.
|
||||
|
||||
-1 - x
|
||||
map[x:1] => -2
|
||||
|
||||
-1 + -x
|
||||
map[x:1] => -2
|
||||
*/
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, test := range []struct{ expr, wantErr string }{
|
||||
{"x % 2", "unexpected '%'"},
|
||||
{"math.Pi", "unexpected '.'"},
|
||||
{"!true", "unexpected '!'"},
|
||||
{`"hello"`, "unexpected '\"'"},
|
||||
{"log(10)", `unknown function "log"`},
|
||||
{"sqrt(1, 2)", "call to sqrt has 2 args, want 1"},
|
||||
} {
|
||||
expr, err := Parse(test.expr)
|
||||
if err == nil {
|
||||
vars := make(map[Var]bool)
|
||||
err = expr.Check(vars)
|
||||
if err == nil {
|
||||
t.Errorf("unexpected success: %s", test.expr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Printf("%-20s%v\n", test.expr, err) // (for book)
|
||||
if err.Error() != test.wantErr {
|
||||
t.Errorf("got error %s, want %s", err, test.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//!+errors
|
||||
x % 2 unexpected '%'
|
||||
math.Pi unexpected '.'
|
||||
!true unexpected '!'
|
||||
"hello" unexpected '"'
|
||||
|
||||
log(10) unknown function "log"
|
||||
sqrt(1, 2) call to sqrt has 2 args, want 1
|
||||
//!-errors
|
||||
*/
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
// ---- lexer ----
|
||||
|
||||
// This lexer is similar to the one described in Chapter 13.
|
||||
type lexer struct {
|
||||
scan scanner.Scanner
|
||||
token rune // current lookahead token
|
||||
}
|
||||
|
||||
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
|
||||
func (lex *lexer) text() string { return lex.scan.TokenText() }
|
||||
|
||||
type lexPanic string
|
||||
|
||||
// describe returns a string describing the current token, for use in errors.
|
||||
func (lex *lexer) describe() string {
|
||||
switch lex.token {
|
||||
case scanner.EOF:
|
||||
return "end of file"
|
||||
case scanner.Ident:
|
||||
return fmt.Sprintf("identifier %s", lex.text())
|
||||
case scanner.Int, scanner.Float:
|
||||
return fmt.Sprintf("number %s", lex.text())
|
||||
}
|
||||
return fmt.Sprintf("%q", rune(lex.token)) // any other rune
|
||||
}
|
||||
|
||||
func precedence(op rune) int {
|
||||
switch op {
|
||||
case '*', '/':
|
||||
return 2
|
||||
case '+', '-':
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ---- parser ----
|
||||
|
||||
// Parse parses the input string as an arithmetic expression.
|
||||
//
|
||||
// expr = num a literal number, e.g., 3.14159
|
||||
// | id a variable name, e.g., x
|
||||
// | id '(' expr ',' ... ')' a function call
|
||||
// | '-' expr a unary operator (+-)
|
||||
// | expr '+' expr a binary operator (+-*/)
|
||||
//
|
||||
func Parse(input string) (_ Expr, err error) {
|
||||
defer func() {
|
||||
switch x := recover().(type) {
|
||||
case nil:
|
||||
// no panic
|
||||
case lexPanic:
|
||||
err = fmt.Errorf("%s", x)
|
||||
default:
|
||||
// unexpected panic: resume state of panic.
|
||||
panic(x)
|
||||
}
|
||||
}()
|
||||
lex := new(lexer)
|
||||
lex.scan.Init(strings.NewReader(input))
|
||||
lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats
|
||||
lex.next() // initial lookahead
|
||||
e := parseExpr(lex)
|
||||
if lex.token != scanner.EOF {
|
||||
return nil, fmt.Errorf("unexpected %s", lex.describe())
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func parseExpr(lex *lexer) Expr { return parseBinary(lex, 1) }
|
||||
|
||||
// binary = unary ('+' binary)*
|
||||
// parseBinary stops when it encounters an
|
||||
// operator of lower precedence than prec1.
|
||||
func parseBinary(lex *lexer, prec1 int) Expr {
|
||||
lhs := parseUnary(lex)
|
||||
for prec := precedence(lex.token); prec >= prec1; prec-- {
|
||||
for precedence(lex.token) == prec {
|
||||
op := lex.token
|
||||
lex.next() // consume operator
|
||||
rhs := parseBinary(lex, prec+1)
|
||||
lhs = binary{op, lhs, rhs}
|
||||
}
|
||||
}
|
||||
return lhs
|
||||
}
|
||||
|
||||
// unary = '+' expr | primary
|
||||
func parseUnary(lex *lexer) Expr {
|
||||
if lex.token == '+' || lex.token == '-' {
|
||||
op := lex.token
|
||||
lex.next() // consume '+' or '-'
|
||||
return unary{op, parseUnary(lex)}
|
||||
}
|
||||
return parsePrimary(lex)
|
||||
}
|
||||
|
||||
// primary = id
|
||||
// | id '(' expr ',' ... ',' expr ')'
|
||||
// | num
|
||||
// | '(' expr ')'
|
||||
func parsePrimary(lex *lexer) Expr {
|
||||
switch lex.token {
|
||||
case scanner.Ident:
|
||||
id := lex.text()
|
||||
lex.next() // consume Ident
|
||||
if lex.token != '(' {
|
||||
return Var(id)
|
||||
}
|
||||
lex.next() // consume '('
|
||||
var args []Expr
|
||||
if lex.token != ')' {
|
||||
for {
|
||||
args = append(args, parseExpr(lex))
|
||||
if lex.token != ',' {
|
||||
break
|
||||
}
|
||||
lex.next() // consume ','
|
||||
}
|
||||
if lex.token != ')' {
|
||||
msg := fmt.Sprintf("got %s, want ')'", lex.token)
|
||||
panic(lexPanic(msg))
|
||||
}
|
||||
}
|
||||
lex.next() // consume ')'
|
||||
return call{id, args}
|
||||
|
||||
case scanner.Int, scanner.Float:
|
||||
f, err := strconv.ParseFloat(lex.text(), 64)
|
||||
if err != nil {
|
||||
panic(lexPanic(err.Error()))
|
||||
}
|
||||
lex.next() // consume number
|
||||
return literal(f)
|
||||
|
||||
case '(':
|
||||
lex.next() // consume ')'
|
||||
e := parseExpr(lex)
|
||||
if lex.token != ')' {
|
||||
msg := fmt.Sprintf("got %s, want ')'", lex.describe())
|
||||
panic(lexPanic(msg))
|
||||
}
|
||||
lex.next() // consume ')'
|
||||
return e
|
||||
}
|
||||
msg := fmt.Sprintf("unexpected %s", lex.describe())
|
||||
panic(lexPanic(msg))
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
//+build ignore
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Format formats an expression as a string.
|
||||
// It does not attempt to remove unnecessary parens.
|
||||
func Format(e Expr) string {
|
||||
var buf bytes.Buffer
|
||||
write(&buf, e)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func write(buf *bytes.Buffer, e Expr) {
|
||||
switch e := e.(type) {
|
||||
case literal:
|
||||
fmt.Fprintf(buf, "%g", e)
|
||||
|
||||
case Var:
|
||||
fmt.Fprintf(buf, "%s", e)
|
||||
|
||||
case *unary:
|
||||
fmt.Fprintf(buf, "(%c", e.op)
|
||||
write(buf, e.x)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *binary:
|
||||
buf.WriteByte('(')
|
||||
write(buf, e.x)
|
||||
fmt.Fprintf(buf, " %c ", e.op)
|
||||
write(buf, e.y)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *call:
|
||||
fmt.Fprintf(buf, "%s(", e.fn)
|
||||
for i, arg := range e.args {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
write(buf, arg)
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 191.
|
||||
|
||||
// Http1 is a rudimentary e-commerce server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//!+main
|
||||
|
||||
func main() {
|
||||
db := database{"shoes": 50, "socks": 5}
|
||||
log.Fatal(http.ListenAndServe("localhost:8000", db))
|
||||
}
|
||||
|
||||
type dollars float32
|
||||
|
||||
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
|
||||
|
||||
type database map[string]dollars
|
||||
|
||||
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
for item, price := range db {
|
||||
fmt.Fprintf(w, "%s: %s\n", item, price)
|
||||
}
|
||||
}
|
||||
|
||||
//!-main
|
||||
|
||||
/*
|
||||
//!+handler
|
||||
package http
|
||||
|
||||
type Handler interface {
|
||||
ServeHTTP(w ResponseWriter, r *Request)
|
||||
}
|
||||
|
||||
func ListenAndServe(address string, h Handler) error
|
||||
//!-handler
|
||||
*/
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 192.
|
||||
|
||||
// Http2 is an e-commerce server with /list and /price endpoints.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db := database{"shoes": 50, "socks": 5}
|
||||
log.Fatal(http.ListenAndServe("localhost:8000", db))
|
||||
}
|
||||
|
||||
type dollars float32
|
||||
|
||||
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
|
||||
|
||||
type database map[string]dollars
|
||||
|
||||
//!+handler
|
||||
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
switch req.URL.Path {
|
||||
case "/list":
|
||||
for item, price := range db {
|
||||
fmt.Fprintf(w, "%s: %s\n", item, price)
|
||||
}
|
||||
case "/price":
|
||||
item := req.URL.Query().Get("item")
|
||||
price, ok := db[item]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusNotFound) // 404
|
||||
fmt.Fprintf(w, "no such item: %q\n", item)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", price)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound) // 404
|
||||
fmt.Fprintf(w, "no such page: %s\n", req.URL)
|
||||
}
|
||||
}
|
||||
|
||||
//!-handler
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 194.
|
||||
|
||||
// Http3 is an e-commerce server that registers the /list and /price
|
||||
// endpoints by calling (*http.ServeMux).Handle.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type dollars float32
|
||||
|
||||
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
|
||||
|
||||
//!+main
|
||||
|
||||
func main() {
|
||||
db := database{"shoes": 50, "socks": 5}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/list", http.HandlerFunc(db.list))
|
||||
mux.Handle("/price", http.HandlerFunc(db.price))
|
||||
log.Fatal(http.ListenAndServe("localhost:8000", mux))
|
||||
}
|
||||
|
||||
type database map[string]dollars
|
||||
|
||||
func (db database) list(w http.ResponseWriter, req *http.Request) {
|
||||
for item, price := range db {
|
||||
fmt.Fprintf(w, "%s: %s\n", item, price)
|
||||
}
|
||||
}
|
||||
|
||||
func (db database) price(w http.ResponseWriter, req *http.Request) {
|
||||
item := req.URL.Query().Get("item")
|
||||
price, ok := db[item]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusNotFound) // 404
|
||||
fmt.Fprintf(w, "no such item: %q\n", item)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", price)
|
||||
}
|
||||
|
||||
//!-main
|
||||
|
||||
/*
|
||||
//!+handlerfunc
|
||||
package http
|
||||
|
||||
type HandlerFunc func(w ResponseWriter, r *Request)
|
||||
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
//!-handlerfunc
|
||||
*/
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 195.
|
||||
|
||||
// Http3a is an e-commerce server that registers the /list and /price
|
||||
// endpoints by calling (*http.ServeMux).HandleFunc.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db := database{"shoes": 50, "socks": 5}
|
||||
mux := http.NewServeMux()
|
||||
//!+main
|
||||
mux.HandleFunc("/list", db.list)
|
||||
mux.HandleFunc("/price", db.price)
|
||||
//!-main
|
||||
log.Fatal(http.ListenAndServe("localhost:8000", mux))
|
||||
}
|
||||
|
||||
type database map[string]int
|
||||
|
||||
func (db database) list(w http.ResponseWriter, req *http.Request) {
|
||||
for item, price := range db {
|
||||
fmt.Fprintf(w, "%s: $%d\n", item, price)
|
||||
}
|
||||
}
|
||||
|
||||
func (db database) price(w http.ResponseWriter, req *http.Request) {
|
||||
item := req.URL.Query().Get("item")
|
||||
if price, ok := db[item]; ok {
|
||||
fmt.Fprintf(w, "$%d\n", price)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound) // 404
|
||||
fmt.Fprintf(w, "no such item: %q\n", item)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//!+handlerfunc
|
||||
package http
|
||||
|
||||
type HandlerFunc func(w ResponseWriter, r *Request)
|
||||
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
//!-handlerfunc
|
||||
*/
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 195.
|
||||
|
||||
// Http4 is an e-commerce server that registers the /list and /price
|
||||
// endpoint by calling http.HandleFunc.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//!+main
|
||||
|
||||
func main() {
|
||||
db := database{"shoes": 50, "socks": 5}
|
||||
http.HandleFunc("/list", db.list)
|
||||
http.HandleFunc("/price", db.price)
|
||||
log.Fatal(http.ListenAndServe("localhost:8000", nil))
|
||||
}
|
||||
|
||||
//!-main
|
||||
|
||||
type dollars float32
|
||||
|
||||
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
|
||||
|
||||
type database map[string]dollars
|
||||
|
||||
func (db database) list(w http.ResponseWriter, req *http.Request) {
|
||||
for item, price := range db {
|
||||
fmt.Fprintf(w, "%s: %s\n", item, price)
|
||||
}
|
||||
}
|
||||
|
||||
func (db database) price(w http.ResponseWriter, req *http.Request) {
|
||||
item := req.URL.Query().Get("item")
|
||||
if price, ok := db[item]; ok {
|
||||
fmt.Fprintf(w, "%s\n", price)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound) // 404
|
||||
fmt.Fprintf(w, "no such item: %q\n", item)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue