mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-18 19:54:21 +08:00
good good study, day day up!
This commit is contained in:
40
vendor/gopl.io/ch7/eval/ast.go
generated
vendored
Normal file
40
vendor/gopl.io/ch7/eval/ast.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
// An Expr is an arithmetic expression.
|
||||
type Expr interface {
|
||||
// Eval returns the value of this Expr in the environment env.
|
||||
Eval(env Env) float64
|
||||
// Check reports errors in this Expr and adds its Vars to the set.
|
||||
Check(vars map[Var]bool) error
|
||||
}
|
||||
|
||||
//!+ast
|
||||
|
||||
// A Var identifies a variable, e.g., x.
|
||||
type Var string
|
||||
|
||||
// A literal is a numeric constant, e.g., 3.141.
|
||||
type literal float64
|
||||
|
||||
// A unary represents a unary operator expression, e.g., -x.
|
||||
type unary struct {
|
||||
op rune // one of '+', '-'
|
||||
x Expr
|
||||
}
|
||||
|
||||
// A binary represents a binary operator expression, e.g., x+y.
|
||||
type binary struct {
|
||||
op rune // one of '+', '-', '*', '/'
|
||||
x, y Expr
|
||||
}
|
||||
|
||||
// A call represents a function call expression, e.g., sin(x).
|
||||
type call struct {
|
||||
fn string // one of "pow", "sin", "sqrt"
|
||||
args []Expr
|
||||
}
|
||||
|
||||
//!-ast
|
||||
58
vendor/gopl.io/ch7/eval/check.go
generated
vendored
Normal file
58
vendor/gopl.io/ch7/eval/check.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//!+Check
|
||||
|
||||
func (v Var) Check(vars map[Var]bool) error {
|
||||
vars[v] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (literal) Check(vars map[Var]bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u unary) Check(vars map[Var]bool) error {
|
||||
if !strings.ContainsRune("+-", u.op) {
|
||||
return fmt.Errorf("unexpected unary op %q", u.op)
|
||||
}
|
||||
return u.x.Check(vars)
|
||||
}
|
||||
|
||||
func (b binary) Check(vars map[Var]bool) error {
|
||||
if !strings.ContainsRune("+-*/", b.op) {
|
||||
return fmt.Errorf("unexpected binary op %q", b.op)
|
||||
}
|
||||
if err := b.x.Check(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.y.Check(vars)
|
||||
}
|
||||
|
||||
func (c call) Check(vars map[Var]bool) error {
|
||||
arity, ok := numParams[c.fn]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown function %q", c.fn)
|
||||
}
|
||||
if len(c.args) != arity {
|
||||
return fmt.Errorf("call to %s has %d args, want %d",
|
||||
c.fn, len(c.args), arity)
|
||||
}
|
||||
for _, arg := range c.args {
|
||||
if err := arg.Check(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
|
||||
|
||||
//!-Check
|
||||
48
vendor/gopl.io/ch7/eval/coverage_test.go
generated
vendored
Normal file
48
vendor/gopl.io/ch7/eval/coverage_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//!+TestCoverage
|
||||
func TestCoverage(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
env Env
|
||||
want string // expected error from Parse/Check or result from Eval
|
||||
}{
|
||||
{"x % 2", nil, "unexpected '%'"},
|
||||
{"!true", nil, "unexpected '!'"},
|
||||
{"log(10)", nil, `unknown function "log"`},
|
||||
{"sqrt(1, 2)", nil, "call to sqrt has 2 args, want 1"},
|
||||
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expr, err := Parse(test.input)
|
||||
if err == nil {
|
||||
err = expr.Check(map[Var]bool{})
|
||||
}
|
||||
if err != nil {
|
||||
if err.Error() != test.want {
|
||||
t.Errorf("%s: got %q, want %q", test.input, err, test.want)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
|
||||
if got != test.want {
|
||||
t.Errorf("%s: %v => %s, want %s",
|
||||
test.input, test.env, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-TestCoverage
|
||||
70
vendor/gopl.io/ch7/eval/eval.go
generated
vendored
Normal file
70
vendor/gopl.io/ch7/eval/eval.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
// See page 198.
|
||||
|
||||
// Package eval provides an expression evaluator.
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
//!+env
|
||||
|
||||
type Env map[Var]float64
|
||||
|
||||
//!-env
|
||||
|
||||
//!+Eval1
|
||||
|
||||
func (v Var) Eval(env Env) float64 {
|
||||
return env[v]
|
||||
}
|
||||
|
||||
func (l literal) Eval(_ Env) float64 {
|
||||
return float64(l)
|
||||
}
|
||||
|
||||
//!-Eval1
|
||||
|
||||
//!+Eval2
|
||||
|
||||
func (u unary) Eval(env Env) float64 {
|
||||
switch u.op {
|
||||
case '+':
|
||||
return +u.x.Eval(env)
|
||||
case '-':
|
||||
return -u.x.Eval(env)
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
|
||||
}
|
||||
|
||||
func (b binary) Eval(env Env) float64 {
|
||||
switch b.op {
|
||||
case '+':
|
||||
return b.x.Eval(env) + b.y.Eval(env)
|
||||
case '-':
|
||||
return b.x.Eval(env) - b.y.Eval(env)
|
||||
case '*':
|
||||
return b.x.Eval(env) * b.y.Eval(env)
|
||||
case '/':
|
||||
return b.x.Eval(env) / b.y.Eval(env)
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
|
||||
}
|
||||
|
||||
func (c call) Eval(env Env) float64 {
|
||||
switch c.fn {
|
||||
case "pow":
|
||||
return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
|
||||
case "sin":
|
||||
return math.Sin(c.args[0].Eval(env))
|
||||
case "sqrt":
|
||||
return math.Sqrt(c.args[0].Eval(env))
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported function call: %s", c.fn))
|
||||
}
|
||||
|
||||
//!-Eval2
|
||||
113
vendor/gopl.io/ch7/eval/eval_test.go
generated
vendored
Normal file
113
vendor/gopl.io/ch7/eval/eval_test.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//!+Eval
|
||||
func TestEval(t *testing.T) {
|
||||
tests := []struct {
|
||||
expr string
|
||||
env Env
|
||||
want string
|
||||
}{
|
||||
{"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"},
|
||||
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": 32}, "0"},
|
||||
{"5 / 9 * (F - 32)", Env{"F": 212}, "100"},
|
||||
//!-Eval
|
||||
// additional tests that don't appear in the book
|
||||
{"-1 + -x", Env{"x": 1}, "-2"},
|
||||
{"-1 - x", Env{"x": 1}, "-2"},
|
||||
//!+Eval
|
||||
}
|
||||
var prevExpr string
|
||||
for _, test := range tests {
|
||||
// Print expr only when it changes.
|
||||
if test.expr != prevExpr {
|
||||
fmt.Printf("\n%s\n", test.expr)
|
||||
prevExpr = test.expr
|
||||
}
|
||||
expr, err := Parse(test.expr)
|
||||
if err != nil {
|
||||
t.Error(err) // parse error
|
||||
continue
|
||||
}
|
||||
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
|
||||
fmt.Printf("\t%v => %s\n", test.env, got)
|
||||
if got != test.want {
|
||||
t.Errorf("%s.Eval() in %v = %q, want %q\n",
|
||||
test.expr, test.env, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//!-Eval
|
||||
|
||||
/*
|
||||
//!+output
|
||||
sqrt(A / pi)
|
||||
map[A:87616 pi:3.141592653589793] => 167
|
||||
|
||||
pow(x, 3) + pow(y, 3)
|
||||
map[x:12 y:1] => 1729
|
||||
map[x:9 y:10] => 1729
|
||||
|
||||
5 / 9 * (F - 32)
|
||||
map[F:-40] => -40
|
||||
map[F:32] => 0
|
||||
map[F:212] => 100
|
||||
//!-output
|
||||
|
||||
// Additional outputs that don't appear in the book.
|
||||
|
||||
-1 - x
|
||||
map[x:1] => -2
|
||||
|
||||
-1 + -x
|
||||
map[x:1] => -2
|
||||
*/
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, test := range []struct{ expr, wantErr string }{
|
||||
{"x % 2", "unexpected '%'"},
|
||||
{"math.Pi", "unexpected '.'"},
|
||||
{"!true", "unexpected '!'"},
|
||||
{`"hello"`, "unexpected '\"'"},
|
||||
{"log(10)", `unknown function "log"`},
|
||||
{"sqrt(1, 2)", "call to sqrt has 2 args, want 1"},
|
||||
} {
|
||||
expr, err := Parse(test.expr)
|
||||
if err == nil {
|
||||
vars := make(map[Var]bool)
|
||||
err = expr.Check(vars)
|
||||
if err == nil {
|
||||
t.Errorf("unexpected success: %s", test.expr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Printf("%-20s%v\n", test.expr, err) // (for book)
|
||||
if err.Error() != test.wantErr {
|
||||
t.Errorf("got error %s, want %s", err, test.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//!+errors
|
||||
x % 2 unexpected '%'
|
||||
math.Pi unexpected '.'
|
||||
!true unexpected '!'
|
||||
"hello" unexpected '"'
|
||||
|
||||
log(10) unknown function "log"
|
||||
sqrt(1, 2) call to sqrt has 2 args, want 1
|
||||
//!-errors
|
||||
*/
|
||||
160
vendor/gopl.io/ch7/eval/parse.go
generated
vendored
Normal file
160
vendor/gopl.io/ch7/eval/parse.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
// ---- lexer ----
|
||||
|
||||
// This lexer is similar to the one described in Chapter 13.
|
||||
type lexer struct {
|
||||
scan scanner.Scanner
|
||||
token rune // current lookahead token
|
||||
}
|
||||
|
||||
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
|
||||
func (lex *lexer) text() string { return lex.scan.TokenText() }
|
||||
|
||||
type lexPanic string
|
||||
|
||||
// describe returns a string describing the current token, for use in errors.
|
||||
func (lex *lexer) describe() string {
|
||||
switch lex.token {
|
||||
case scanner.EOF:
|
||||
return "end of file"
|
||||
case scanner.Ident:
|
||||
return fmt.Sprintf("identifier %s", lex.text())
|
||||
case scanner.Int, scanner.Float:
|
||||
return fmt.Sprintf("number %s", lex.text())
|
||||
}
|
||||
return fmt.Sprintf("%q", rune(lex.token)) // any other rune
|
||||
}
|
||||
|
||||
func precedence(op rune) int {
|
||||
switch op {
|
||||
case '*', '/':
|
||||
return 2
|
||||
case '+', '-':
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ---- parser ----
|
||||
|
||||
// Parse parses the input string as an arithmetic expression.
|
||||
//
|
||||
// expr = num a literal number, e.g., 3.14159
|
||||
// | id a variable name, e.g., x
|
||||
// | id '(' expr ',' ... ')' a function call
|
||||
// | '-' expr a unary operator (+-)
|
||||
// | expr '+' expr a binary operator (+-*/)
|
||||
//
|
||||
func Parse(input string) (_ Expr, err error) {
|
||||
defer func() {
|
||||
switch x := recover().(type) {
|
||||
case nil:
|
||||
// no panic
|
||||
case lexPanic:
|
||||
err = fmt.Errorf("%s", x)
|
||||
default:
|
||||
// unexpected panic: resume state of panic.
|
||||
panic(x)
|
||||
}
|
||||
}()
|
||||
lex := new(lexer)
|
||||
lex.scan.Init(strings.NewReader(input))
|
||||
lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats
|
||||
lex.next() // initial lookahead
|
||||
e := parseExpr(lex)
|
||||
if lex.token != scanner.EOF {
|
||||
return nil, fmt.Errorf("unexpected %s", lex.describe())
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func parseExpr(lex *lexer) Expr { return parseBinary(lex, 1) }
|
||||
|
||||
// binary = unary ('+' binary)*
|
||||
// parseBinary stops when it encounters an
|
||||
// operator of lower precedence than prec1.
|
||||
func parseBinary(lex *lexer, prec1 int) Expr {
|
||||
lhs := parseUnary(lex)
|
||||
for prec := precedence(lex.token); prec >= prec1; prec-- {
|
||||
for precedence(lex.token) == prec {
|
||||
op := lex.token
|
||||
lex.next() // consume operator
|
||||
rhs := parseBinary(lex, prec+1)
|
||||
lhs = binary{op, lhs, rhs}
|
||||
}
|
||||
}
|
||||
return lhs
|
||||
}
|
||||
|
||||
// unary = '+' expr | primary
|
||||
func parseUnary(lex *lexer) Expr {
|
||||
if lex.token == '+' || lex.token == '-' {
|
||||
op := lex.token
|
||||
lex.next() // consume '+' or '-'
|
||||
return unary{op, parseUnary(lex)}
|
||||
}
|
||||
return parsePrimary(lex)
|
||||
}
|
||||
|
||||
// primary = id
|
||||
// | id '(' expr ',' ... ',' expr ')'
|
||||
// | num
|
||||
// | '(' expr ')'
|
||||
func parsePrimary(lex *lexer) Expr {
|
||||
switch lex.token {
|
||||
case scanner.Ident:
|
||||
id := lex.text()
|
||||
lex.next() // consume Ident
|
||||
if lex.token != '(' {
|
||||
return Var(id)
|
||||
}
|
||||
lex.next() // consume '('
|
||||
var args []Expr
|
||||
if lex.token != ')' {
|
||||
for {
|
||||
args = append(args, parseExpr(lex))
|
||||
if lex.token != ',' {
|
||||
break
|
||||
}
|
||||
lex.next() // consume ','
|
||||
}
|
||||
if lex.token != ')' {
|
||||
msg := fmt.Sprintf("got %q, 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))
|
||||
}
|
||||
52
vendor/gopl.io/ch7/eval/print.go
generated
vendored
Normal file
52
vendor/gopl.io/ch7/eval/print.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Format formats an expression as a string.
|
||||
// It does not attempt to remove unnecessary parens.
|
||||
func Format(e Expr) string {
|
||||
var buf bytes.Buffer
|
||||
write(&buf, e)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func write(buf *bytes.Buffer, e Expr) {
|
||||
switch e := e.(type) {
|
||||
case literal:
|
||||
fmt.Fprintf(buf, "%g", e)
|
||||
|
||||
case Var:
|
||||
fmt.Fprintf(buf, "%s", e)
|
||||
|
||||
case unary:
|
||||
fmt.Fprintf(buf, "(%c", e.op)
|
||||
write(buf, e.x)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case binary:
|
||||
buf.WriteByte('(')
|
||||
write(buf, e.x)
|
||||
fmt.Fprintf(buf, " %c ", e.op)
|
||||
write(buf, e.y)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case call:
|
||||
fmt.Fprintf(buf, "%s(", e.fn)
|
||||
for i, arg := range e.args {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
write(buf, arg)
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown Expr: %T", e))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user