| // Copyright 2017 The Puffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package check |
| |
| import ( |
| "bytes" |
| "fmt" |
| "math/big" |
| "reflect" |
| "sort" |
| "strings" |
| "testing" |
| |
| "github.com/google/puffs/lang/ast" |
| "github.com/google/puffs/lang/parse" |
| "github.com/google/puffs/lang/render" |
| "github.com/google/puffs/lang/token" |
| ) |
| |
| func compareToPuffsfmt(tm *token.Map, tokens []token.Token, comments []string, src string) error { |
| buf := &bytes.Buffer{} |
| if err := render.Render(buf, tm, tokens, comments); err != nil { |
| return err |
| } |
| got := strings.Split(buf.String(), "\n") |
| want := strings.Split(src, "\n") |
| for line := 1; len(got) != 0 || len(want) != 0; line++ { |
| if len(got) == 0 { |
| w := strings.TrimSpace(want[0]) |
| return fmt.Errorf("difference at line %d:\n%s\n%s", line, "", w) |
| } |
| if len(want) == 0 { |
| g := strings.TrimSpace(got[0]) |
| return fmt.Errorf("difference at line %d:\n%s\n%s", line, g, "") |
| } |
| if g, w := strings.TrimSpace(got[0]), strings.TrimSpace(want[0]); g != w { |
| return fmt.Errorf("difference at line %d:\n%s\n%s", line, g, w) |
| } |
| got = got[1:] |
| want = want[1:] |
| } |
| return nil |
| } |
| |
| func TestCheck(t *testing.T) { |
| const filename = "test.puffs" |
| src := strings.TrimSpace(` |
| pri struct foo( |
| i i32, |
| ) |
| |
| pri func foo.bar()() { |
| var x u8 |
| var y i32 = +2 |
| var z u64[..123] |
| var a[4] u8 |
| var b bool |
| |
| x = 0 |
| x = 1 + (x * 0) |
| y = -y - 1 |
| y = this.i |
| b = not true |
| |
| y = x as i32 |
| |
| var p i32 |
| var q i32[0..8] |
| |
| assert true |
| |
| while:label p == q, |
| pre true, |
| inv true, |
| post p != q, |
| { |
| // Redundant, but shows the labeled jump syntax. |
| continue:label |
| } |
| } |
| `) + "\n" |
| |
| tm := &token.Map{} |
| |
| tokens, comments, err := token.Tokenize(tm, filename, []byte(src)) |
| if err != nil { |
| t.Fatalf("Tokenize: %v", err) |
| } |
| |
| file, err := parse.Parse(tm, filename, tokens) |
| if err != nil { |
| t.Fatalf("Parse: %v", err) |
| } |
| |
| if err := compareToPuffsfmt(tm, tokens, comments, src); err != nil { |
| t.Fatalf("compareToPuffsfmt: %v", err) |
| } |
| |
| c, err := Check(tm, file) |
| if err != nil { |
| t.Fatalf("Check: %v", err) |
| } |
| |
| funcs := c.Funcs() |
| if len(funcs) != 1 { |
| t.Fatalf("Funcs: got %d elements, want 1", len(funcs)) |
| } |
| fooBar := Func{} |
| for _, f := range funcs { |
| fooBar = f |
| break |
| } |
| |
| if got, want := fooBar.QID.String(tm), "foo.bar"; got != want { |
| t.Fatalf("Funcs[0] name: got %q, want %q", got, want) |
| } |
| |
| got := [][2]string(nil) |
| for id, typ := range fooBar.LocalVars { |
| got = append(got, [2]string{ |
| id.String(tm), |
| typ.String(tm), |
| }) |
| } |
| sort.Slice(got, func(i, j int) bool { |
| return got[i][0] < got[j][0] |
| }) |
| |
| want := [][2]string{ |
| {"a", "[4] u8"}, |
| {"b", "bool"}, |
| {"in", "in"}, |
| {"out", "out"}, |
| {"p", "i32"}, |
| {"q", "i32[0..8]"}, |
| {"this", "ptr foo"}, |
| {"x", "u8"}, |
| {"y", "i32"}, |
| {"z", "u64[..123]"}, |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Fatalf("\ngot %v\nwant %v", got, want) |
| } |
| } |
| |
| func TestConstValues(t *testing.T) { |
| const filename = "test.puffs" |
| testCases := map[string]int64{ |
| "var i i32 = 42": 42, |
| |
| "var i i32 = +7": +7, |
| "var i i32 = -7": -7, |
| |
| "var b bool = false": 0, |
| "var b bool = not false": 1, |
| "var b bool = not not false": 0, |
| |
| "var i i32 = 10 + 3": 13, |
| "var i i32 = 10 - 3": 7, |
| "var i i32 = 10 * 3": 30, |
| "var i i32 = 10 / 3": 3, |
| "var i i32 = 10 << 3": 80, |
| "var i i32 = 10 >> 3": 1, |
| "var i i32 = 10 & 3": 2, |
| "var i i32 = 10 &^ 3": 8, |
| "var i i32 = 10 | 3": 11, |
| "var i i32 = 10 ^ 3": 9, |
| |
| "var b bool = 10 != 3": 1, |
| "var b bool = 10 < 3": 0, |
| "var b bool = 10 <= 3": 0, |
| "var b bool = 10 == 3": 0, |
| "var b bool = 10 >= 3": 1, |
| "var b bool = 10 > 3": 1, |
| |
| "var b bool = false and true": 0, |
| "var b bool = false or true": 1, |
| } |
| |
| tm := &token.Map{} |
| for s, wantInt64 := range testCases { |
| src := "pri func foo()() {\n\t" + s + "\n}\n" |
| |
| tokens, _, err := token.Tokenize(tm, filename, []byte(src)) |
| if err != nil { |
| t.Errorf("%q: Tokenize: %v", s, err) |
| continue |
| } |
| |
| file, err := parse.Parse(tm, filename, tokens) |
| if err != nil { |
| t.Errorf("%q: Parse: %v", s, err) |
| continue |
| } |
| |
| c, err := Check(tm, file) |
| if err != nil { |
| t.Errorf("%q: Check: %v", s, err) |
| continue |
| } |
| |
| funcs := c.Funcs() |
| if len(funcs) != 1 { |
| t.Errorf("%q: Funcs: got %d elements, want 1", s, len(funcs)) |
| continue |
| } |
| foo := Func{} |
| for _, f := range funcs { |
| foo = f |
| break |
| } |
| body := foo.Func.Body() |
| if len(body) != 1 { |
| t.Errorf("%q: Body: got %d elements, want 1", s, len(body)) |
| continue |
| } |
| if body[0].Kind() != ast.KVar { |
| t.Errorf("%q: Body[0]: got %s, want %s", s, body[0].Kind(), ast.KVar) |
| continue |
| } |
| |
| got := body[0].Var().Value().ConstValue() |
| want := big.NewInt(wantInt64) |
| if got == nil || want == nil || got.Cmp(want) != 0 { |
| t.Errorf("%q: got %v, want %v", s, got, want) |
| continue |
| } |
| } |
| } |
| |
| func TestBitMask(t *testing.T) { |
| testCases := [][2]uint64{ |
| {0, 0}, |
| {1, 1}, |
| {2, 3}, |
| {3, 3}, |
| {4, 7}, |
| {5, 7}, |
| {50, 63}, |
| {500, 511}, |
| {5000, 8191}, |
| {50000, 65535}, |
| {500000, 524287}, |
| {1<<7 - 2, 1<<7 - 1}, |
| {1<<7 - 1, 1<<7 - 1}, |
| {1<<7 + 0, 1<<8 - 1}, |
| {1<<7 + 1, 1<<8 - 1}, |
| {1<<8 - 2, 1<<8 - 1}, |
| {1<<8 - 1, 1<<8 - 1}, |
| {1<<8 + 0, 1<<9 - 1}, |
| {1<<8 + 1, 1<<9 - 1}, |
| {1<<32 - 2, 1<<32 - 1}, |
| {1<<32 - 1, 1<<32 - 1}, |
| {1<<32 + 0, 1<<33 - 1}, |
| {1<<32 + 1, 1<<33 - 1}, |
| {1<<64 - 2, 1<<64 - 1}, |
| {1<<64 - 1, 1<<64 - 1}, |
| } |
| |
| for _, tc := range testCases { |
| got := bitMask(big.NewInt(0).SetUint64(tc[0]).BitLen()) |
| want := big.NewInt(0).SetUint64(tc[1]) |
| if got.Cmp(want) != 0 { |
| t.Errorf("roundUpToPowerOf2Minus1(%v): got %v, want %v", tc[0], got, tc[1]) |
| } |
| } |
| } |