| // 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) |
| } |
| } |