// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bufio" "fmt" "os" "strconv" "strings" ) // Generic expression parser/evaluator type Value interface { String() string BinaryOp(op string, y Value) Value } type Parser struct { precTab map[string]int newVal func(string) Value src string pos int tok string } const alphanum = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func (p *Parser) stop(c uint8) bool { switch { case p.pos >= len(p.src): return true case c == '"': if p.src[p.pos] == '"' { p.pos++ return true } return false case strings.IndexRune(alphanum, int(c)) >= 0: return strings.IndexRune(alphanum, int(p.src[p.pos])) < 0 } return true } func (p *Parser) next() { // skip blanks for ; p.pos < len(p.src) && p.src[p.pos] <= ' '; p.pos++ { } if p.pos >= len(p.src) { p.tok = "" return } start := p.pos c := p.src[p.pos] for p.pos < len(p.src) { p.pos++ if p.stop(c) { break } } p.tok = p.src[start:p.pos] } func (p *Parser) binaryExpr(prec1 int) Value { x := p.newVal(p.tok) p.next() for prec := p.precTab[p.tok]; prec >= prec1; prec-- { for p.precTab[p.tok] == prec { op := p.tok p.next() y := p.binaryExpr(prec + 1) x = x.BinaryOp(op, y) } } return x } func Eval(precTab map[string]int, newVal func(string) Value, src string) Value { var p Parser p.precTab = precTab p.newVal = newVal p.src = src p.next() return p.binaryExpr(1) } // Command-line expression evaluator func main() { r := bufio.NewReader(os.Stdin) for { fmt.Printf("> ") line, err := r.ReadString('\n') if err != nil { break } fmt.Printf("%s\n", Eval(precTab, trace(newVal), line)) } } // Custom grammar and values var precTab = map[string]int{ "&&": 1, "||": 2, "==": 3, "!=": 3, "<": 3, "<=": 3, ">": 3, ">=": 3, "+": 4, "-": 4, "*": 5, "/": 5, "%": 5, } func newVal(lit string) Value { x, err := strconv.Atoi(lit) if err == nil { return Int(x) } b, err := strconv.ParseBool(lit) if err == nil { return Bool(b) } s, err := strconv.Unquote(lit) if err == nil { return String(s) } return Error(fmt.Sprintf("illegal literal '%s'", lit)) } type Error string func (e Error) String() string { return string(e) } func (e Error) BinaryOp(op string, y Value) Value { return e } type Int int func (x Int) String() string { return strconv.Itoa(int(x)) } func (x Int) BinaryOp(op string, y Value) Value { switch y := y.(type) { case Error: return y case String: switch op { case "*": return String(strings.Repeat(string(y), int(x))) } case Int: switch op { case "+": return x + y case "-": return x - y case "*": return x * y case "/": return x / y case "%": return x % y case "==": return Bool(x == y) case "!=": return Bool(x != y) case "<": return Bool(x < y) case "<=": return Bool(x <= y) case ">": return Bool(x > y) case ">=": return Bool(x >= y) } } return Error(fmt.Sprintf("illegal operation: '%v %s %v'", x, op, y)) } type Bool bool func (x Bool) String() string { return strconv.FormatBool(bool(x)) } func (x Bool) BinaryOp(op string, y Value) Value { switch y := y.(type) { case Error: return y case Bool: switch op { case "&&": return Bool(x && y) case "||": return Bool(x || y) case "==": return Bool(x == y) case "!=": return Bool(x != y) } } return Error(fmt.Sprintf("illegal operation: '%v %s %v'", x, op, y)) } type String string func (x String) String() string { return strconv.Quote(string(x)) } func (x String) BinaryOp(op string, y Value) Value { switch y := y.(type) { case Error: return y case Int: switch op { case "*": return String(strings.Repeat(string(x), int(y))) } case String: switch op { case "+": return x + y case "<": return Bool(x < y) } } return Error(fmt.Sprintf("illegal operation: '%v %s %v'", x, op, y)) } func trace(newVal func(string) Value) func(string) Value { return func(s string) Value { v := newVal(s) fmt.Printf("\tnewVal(%q) = %s\n", s, fmtv(v)) return &traceValue{v} } } type traceValue struct { Value } func (x *traceValue) BinaryOp(op string, y Value) Value { z := x.Value.BinaryOp(op, y.(*traceValue).Value) fmt.Printf("\t%s.BinaryOp(%q, %s) = %s\n", fmtv(x.Value), op, fmtv(y.(*traceValue).Value), fmtv(z)) return &traceValue{z} } func (x *traceValue) String() string { s := x.Value.String() fmt.Printf("\t%s.String() = %#v\n", fmtv(x.Value), s) return s } func fmtv(v Value) string { t := fmt.Sprintf("%T", v) if i := strings.LastIndex(t, "."); i >= 0 { // strip package t = t[i+1:] } return fmt.Sprintf("%s(%#v)", t, v) }