187 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2013 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 exact
 | 
						|
 | 
						|
import (
 | 
						|
	"go/token"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
)
 | 
						|
 | 
						|
// TODO(gri) expand this test framework
 | 
						|
 | 
						|
var tests = []string{
 | 
						|
	// unary operations
 | 
						|
	`+ 0 = 0`,
 | 
						|
	`- 1 = -1`,
 | 
						|
 | 
						|
	`! true = false`,
 | 
						|
	`! false = true`,
 | 
						|
	// etc.
 | 
						|
 | 
						|
	// binary operations
 | 
						|
	`"" + "" = ""`,
 | 
						|
	`"foo" + "" = "foo"`,
 | 
						|
	`"" + "bar" = "bar"`,
 | 
						|
	`"foo" + "bar" = "foobar"`,
 | 
						|
 | 
						|
	`0 + 0 = 0`,
 | 
						|
	`0 + 0.1 = 0.1`,
 | 
						|
	`0 + 0.1i = 0.1i`,
 | 
						|
	`0.1 + 0.9 = 1`,
 | 
						|
	`1e100 + 1e100 = 2e100`,
 | 
						|
 | 
						|
	`0 - 0 = 0`,
 | 
						|
	`0 - 0.1 = -0.1`,
 | 
						|
	`0 - 0.1i = -0.1i`,
 | 
						|
	`1e100 - 1e100 = 0`,
 | 
						|
 | 
						|
	`0 * 0 = 0`,
 | 
						|
	`1 * 0.1 = 0.1`,
 | 
						|
	`1 * 0.1i = 0.1i`,
 | 
						|
	`1i * 1i = -1`,
 | 
						|
 | 
						|
	`0 / 0 = "division_by_zero"`,
 | 
						|
	`10 / 2 = 5`,
 | 
						|
	`5 / 3 = 5/3`,
 | 
						|
 | 
						|
	`0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for /
 | 
						|
	`10 % 3 = 1`,
 | 
						|
	// etc.
 | 
						|
 | 
						|
	// shifts
 | 
						|
	`0 << 0 = 0`,
 | 
						|
	`1 << 10 = 1024`,
 | 
						|
	// etc.
 | 
						|
 | 
						|
	// comparisons
 | 
						|
	`false == false = true`,
 | 
						|
	`false == true = false`,
 | 
						|
	`true == false = false`,
 | 
						|
	`true == true = true`,
 | 
						|
 | 
						|
	`false != false = false`,
 | 
						|
	`false != true = true`,
 | 
						|
	`true != false = true`,
 | 
						|
	`true != true = false`,
 | 
						|
 | 
						|
	`"foo" == "bar" = false`,
 | 
						|
	`"foo" != "bar" = true`,
 | 
						|
	`"foo" < "bar" = false`,
 | 
						|
	`"foo" <= "bar" = false`,
 | 
						|
	`"foo" > "bar" = true`,
 | 
						|
	`"foo" >= "bar" = true`,
 | 
						|
 | 
						|
	`0 != 0 = false`,
 | 
						|
 | 
						|
	// etc.
 | 
						|
}
 | 
						|
 | 
						|
func TestOps(t *testing.T) {
 | 
						|
	for _, test := range tests {
 | 
						|
		var got, want Value
 | 
						|
 | 
						|
		switch a := strings.Split(test, " "); len(a) {
 | 
						|
		case 4:
 | 
						|
			got = doOp(nil, op[a[0]], val(a[1]))
 | 
						|
			want = val(a[3])
 | 
						|
		case 5:
 | 
						|
			got = doOp(val(a[0]), op[a[1]], val(a[2]))
 | 
						|
			want = val(a[4])
 | 
						|
		default:
 | 
						|
			t.Errorf("invalid test case: %s", test)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if !Compare(got, token.EQL, want) {
 | 
						|
			t.Errorf("%s failed: got %s; want %s", test, got, want)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ----------------------------------------------------------------------------
 | 
						|
// Support functions
 | 
						|
 | 
						|
func val(lit string) Value {
 | 
						|
	if len(lit) == 0 {
 | 
						|
		return MakeUnknown()
 | 
						|
	}
 | 
						|
 | 
						|
	switch lit {
 | 
						|
	case "?":
 | 
						|
		return MakeUnknown()
 | 
						|
	case "nil":
 | 
						|
		return MakeNil()
 | 
						|
	case "true":
 | 
						|
		return MakeBool(true)
 | 
						|
	case "false":
 | 
						|
		return MakeBool(false)
 | 
						|
	}
 | 
						|
 | 
						|
	tok := token.FLOAT
 | 
						|
	switch first, last := lit[0], lit[len(lit)-1]; {
 | 
						|
	case first == '"' || first == '`':
 | 
						|
		tok = token.STRING
 | 
						|
		lit = strings.Replace(lit, "_", " ", -1)
 | 
						|
	case first == '\'':
 | 
						|
		tok = token.CHAR
 | 
						|
	case last == 'i':
 | 
						|
		tok = token.IMAG
 | 
						|
	}
 | 
						|
 | 
						|
	return MakeFromLiteral(lit, tok)
 | 
						|
}
 | 
						|
 | 
						|
var op = map[string]token.Token{
 | 
						|
	"!": token.NOT,
 | 
						|
 | 
						|
	"+": token.ADD,
 | 
						|
	"-": token.SUB,
 | 
						|
	"*": token.MUL,
 | 
						|
	"/": token.QUO,
 | 
						|
	"%": token.REM,
 | 
						|
 | 
						|
	"<<": token.SHL,
 | 
						|
	">>": token.SHR,
 | 
						|
 | 
						|
	"==": token.EQL,
 | 
						|
	"!=": token.NEQ,
 | 
						|
	"<":  token.LSS,
 | 
						|
	"<=": token.LEQ,
 | 
						|
	">":  token.GTR,
 | 
						|
	">=": token.GEQ,
 | 
						|
}
 | 
						|
 | 
						|
func panicHandler(v *Value) {
 | 
						|
	switch p := recover().(type) {
 | 
						|
	case nil:
 | 
						|
		// nothing to do
 | 
						|
	case string:
 | 
						|
		*v = MakeString(p)
 | 
						|
	case error:
 | 
						|
		*v = MakeString(p.Error())
 | 
						|
	default:
 | 
						|
		panic(p)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func doOp(x Value, op token.Token, y Value) (z Value) {
 | 
						|
	defer panicHandler(&z)
 | 
						|
 | 
						|
	if x == nil {
 | 
						|
		return UnaryOp(op, y, -1)
 | 
						|
	}
 | 
						|
 | 
						|
	switch op {
 | 
						|
	case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ:
 | 
						|
		return MakeBool(Compare(x, op, y))
 | 
						|
	case token.SHL, token.SHR:
 | 
						|
		s, _ := Int64Val(y)
 | 
						|
		return Shift(x, op, uint(s))
 | 
						|
	default:
 | 
						|
		return BinaryOp(x, op, y)
 | 
						|
	}
 | 
						|
}
 |